Merge branch 'master' into tgvoip-api

This commit is contained in:
Ali 2020-01-23 00:06:58 +04:00
commit d3b6e33a4b
224 changed files with 12030 additions and 5533 deletions

6
.gitmodules vendored
View File

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

View File

@ -3,7 +3,7 @@
include Utils.makefile
BUCK_OPTIONS=\
--config custom.appVersion="5.13.1" \
--config custom.appVersion="5.14" \
--config custom.developmentCodeSignIdentity="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \
--config custom.distributionCodeSignIdentity="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \
--config custom.developmentTeam="${DEVELOPMENT_TEAM}" \

View File

@ -3,7 +3,7 @@
@implementation Serialization
- (NSUInteger)currentLayer {
return 108;
return 109;
}
- (id _Nullable)parseMessage:(NSData * _Nullable)data {

View File

@ -32,6 +32,7 @@
<string>merchant.sberbank.test.ph.telegra.Telegraph</string>
<string>merchant.privatbank.test.telergramios</string>
<string>merchant.privatbank.prod.telergram</string>
<string>merchant.telegram.tranzzo.test</string>
</array>
<key>com.apple.developer.pushkit.unrestricted-voip</key>
<true/>

View File

@ -22,6 +22,10 @@
"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll %2$@";
"PUSH_PINNED_POLL" = "%1$@|pinned a poll";
"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz %2$@";
"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz %2$@";
"PUSH_PINNED_QUIZ" = "%1$@|pinned a quiz";
"PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@: %3$@";
"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message";
"PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo";
@ -93,6 +97,7 @@
"PUSH_MESSAGE_GEO" = "%1$@|sent you a map";
"PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location";
"PUSH_MESSAGE_POLL" = "%1$@|sent you a poll";
"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz";
"PUSH_MESSAGE_GIF" = "%1$@|sent you a GIF";
"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
"PUSH_MESSAGE_INVOICE" = "%1$@|sent you an invoice for %2$@";
@ -125,6 +130,7 @@
"PUSH_CHANNEL_MESSAGE_GEO" = "%1$@|posted a map";
"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location";
"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll";
"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz";
"PUSH_CHANNEL_MESSAGE_GIF" = "%1$@|posted a GIF";
"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
"PUSH_CHANNEL_MESSAGE_FWD" = "%1$@|posted a forwarded message";
@ -155,6 +161,7 @@
"PUSH_CHAT_MESSAGE_GEO" = "%2$@|%1$@ sent a map";
"PUSH_CHAT_MESSAGE_GEOLIVE" = "%2$@|%1$@ started sharing their live location";
"PUSH_CHAT_MESSAGE_POLL" = "%2$@|%1$@ sent a poll %3$@ to the group";
"PUSH_CHAT_MESSAGE_QUIZ" = "%2$@|%1$@ sent a quiz %3$@ to the group";
"PUSH_CHAT_MESSAGE_GIF" = "%2$@|%1$@ sent a GIF";
"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@";
"PUSH_CHAT_MESSAGE_INVOICE" = "%2$@|%1$@ sent an invoice for %3$@";
@ -197,6 +204,7 @@
"PUSH_PINNED_GEO" = "%1$@|pinned a map";
"PUSH_PINNED_GEOLIVE" = "%1$@|pinned a live location";
"PUSH_PINNED_POLL" = "|%1$@|pinned a poll %2$@";
"PUSH_PINNED_QUIZ" = "|%1$@|pinned a quiz %2$@";
"PUSH_PINNED_GAME" = "%1$@|pinned a game";
"PUSH_PINNED_INVOICE" = "%1$@|pinned an invoice";
"PUSH_PINNED_GIF" = "%1$@|pinned a GIF";
@ -1927,6 +1935,7 @@
"Notification.PinnedContactMessage" = "%@ pinned a contact";
"Notification.PinnedDeletedMessage" = "%@ pinned deleted message";
"Notification.PinnedPollMessage" = "%@ pinned a poll";
"Notification.PinnedQuizMessage" = "%@ pinned a quiz";
"Message.PinnedTextMessage" = "pinned \"%@\" ";
"Message.PinnedPhotoMessage" = "pinned photo";
@ -1937,7 +1946,6 @@
"Message.PinnedStickerMessage" = "pinned sticker";
"Message.PinnedLocationMessage" = "pinned location";
"Message.PinnedContactMessage" = "pinned contact";
"Message.PinnedPollMessage" = "pinned poll";
"Notification.PinnedMessage" = "pinned message";
@ -3793,6 +3801,7 @@ Unused sets are archived when you add more.";
"MessagePoll.VotedCount_any" = "%@ votes";
"AttachmentMenu.Poll" = "Poll";
"Conversation.PinnedPoll" = "Pinned Poll";
"Conversation.PinnedQuiz" = "Pinned Quiz";
"CreatePoll.Title" = "New Poll";
"CreatePoll.Create" = "Send";
@ -5245,3 +5254,47 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.ContextMenuCancelEditing" = "Cancel Editing";
"Map.NoPlacesNearby" = "There are no known places nearby.\nTry a different location.";
"CreatePoll.QuizTitle" = "New Quiz";
"CreatePoll.QuizOptionsHeader" = "QUIZ ANSWERS";
"CreatePoll.Anonymous" = "Anonymous Voting";
"CreatePoll.MultipleChoice" = "Multiple Choice";
"CreatePoll.MultipleChoiceQuizAlert" = "A quiz has one correct answer.";
"CreatePoll.Quiz" = "Quiz Mode";
"CreatePoll.QuizInfo" = "Polls in Quiz Mode have one correct answer. Users can't revoke their answers.";
"CreatePoll.QuizTip" = "Tap to choose the correct answer";
"MessagePoll.LabelPoll" = "Public Poll";
"MessagePoll.LabelAnonymousQuiz" = "Anonymous Quiz";
"MessagePoll.LabelQuiz" = "Quiz";
"MessagePoll.SubmitVote" = "Vote";
"MessagePoll.ViewResults" = "View Results";
"MessagePoll.QuizNoUsers" = "Nobody answered yet";
"MessagePoll.QuizCount_0" = "%@ answered";
"MessagePoll.QuizCount_1" = "1 answered";
"MessagePoll.QuizCount_2" = "2 answered";
"MessagePoll.QuizCount_3_10" = "%@ answered";
"MessagePoll.QuizCount_many" = "%@ answered";
"MessagePoll.QuizCount_any" = "%@ answered";
"PollResults.Title" = "Poll Results";
"PollResults.Collapse" = "COLLAPSE";
"PollResults.ShowMore_1" = "Show More (%@)";
"PollResults.ShowMore_any" = "Show More (%@)";
"Conversation.StopQuiz" = "Stop Quiz";
"Conversation.StopQuizConfirmationTitle" = "If you stop this quiz now, nobody will be able to submit answers. This action cannot be undone.";
"Conversation.StopQuizConfirmation" = "Stop Quiz";
"Forward.ErrorDisabledForChat" = "Sorry, you can't forward messages to this chat.";
"Forward.ErrorPublicPollDisabledInChannels" = "Sorry, public polls cant be forwarded to channels.";
"Forward.ErrorPublicQuizDisabledInChannels" = "Sorry, public polls cant be forwarded to channels.";
"Map.PlacesInThisArea" = "Places In This Area";
"Appearance.BubbleCornersSetting" = "Message Corners";
"Appearance.BubbleCorners.Title" = "Message Corners";
"Appearance.BubbleCorners.AdjustAdjacent" = "Adjust Adjacent Corners";
"Appearance.BubbleCorners.Apply" = "Set";
"Conversation.LiveLocationYouAndOther" = "**You** and %@";

View File

@ -446,8 +446,8 @@ public protocol SharedAccountContext: class {
func makeComposeController(context: AccountContext) -> ViewController
func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode) -> ChatController
func makeChatMessagePreviewItem(context: AccountContext, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?) -> ListViewItem
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
func makeChatMessagePreviewItem(context: AccountContext, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?) -> ListViewItem
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController?
func makeContactSelectionController(_ params: ContactSelectionControllerParams) -> ContactSelectionController
func makeContactMultiselectionController(_ params: ContactMultiselectionControllerParams) -> ContactMultiselectionController

View File

@ -246,7 +246,7 @@ public enum ChatControllerSubject: Equatable {
public enum ChatControllerPresentationMode: Equatable {
case standard(previewing: Bool)
case overlay
case overlay(NavigationController?)
case inline(NavigationController?)
}

View File

@ -25,6 +25,8 @@ public struct ChatListNodePeersFilter: OptionSet {
public static let excludeDisabled = ChatListNodePeersFilter(rawValue: 1 << 10)
public static let includeSavedMessages = ChatListNodePeersFilter(rawValue: 1 << 11)
public static let excludeChannels = ChatListNodePeersFilter(rawValue: 1 << 12)
}
public final class PeerSelectionControllerParams {
@ -32,12 +34,14 @@ public final class PeerSelectionControllerParams {
public let filter: ChatListNodePeersFilter
public let hasContactSelector: Bool
public let title: String?
public let attemptSelection: ((Peer) -> Void)?
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasContactSelector: Bool = true, title: String? = nil) {
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasContactSelector: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil) {
self.context = context
self.filter = filter
self.hasContactSelector = hasContactSelector
self.title = title
self.attemptSelection = attemptSelection
}
}

View File

@ -44,19 +44,19 @@ private func getCoveringViewSnaphot(window: Window1) -> UIImage? {
context.clear(CGRect(origin: CGPoint(), size: size))
context.scaleBy(x: scale, y: scale)
UIGraphicsPushContext(context)
window.forEachViewController { controller in
window.forEachViewController({ controller in
if let controller = controller as? PasscodeEntryController {
controller.displayNode.alpha = 0.0
}
return true
}
})
window.hostView.containerView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
window.forEachViewController { controller in
window.forEachViewController({ controller in
if let controller = controller as? PasscodeEntryController {
controller.displayNode.alpha = 1.0
}
return true
}
})
UIGraphicsPopContext()
}).flatMap(applyScreenshotEffectToImage)
}
@ -201,6 +201,7 @@ public final class AppLockContextImpl: AppLockContext {
}
}
passcodeController.presentedOverCoveringView = true
passcodeController.isOpaqueWhenInOverlay = true
strongSelf.passcodeController = passcodeController
if let rootViewController = strongSelf.rootController {
if let presentedViewController = rootViewController.presentedViewController as? UIActivityViewController {

View File

@ -0,0 +1,29 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "ArchivedStickerPacksNotice",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/StickerResources:StickerResources",
"//submodules/AlertUI:AlertUI",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/MergeLists:MergeLists",
"//submodules/ItemListUI:ItemListUI",
"//submodules/ItemListStickerPackItem:ItemListStickerPackItem",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -0,0 +1,316 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import ActivityIndicator
import AccountContext
import AlertUI
import PresentationDataUtils
import MergeLists
import ItemListUI
import ItemListStickerPackItem
private struct ArchivedStickersNoticeEntry: Comparable, Identifiable {
let index: Int
let info: StickerPackCollectionInfo
let topItem: StickerPackItem?
let count: String
var stableId: ItemCollectionId {
return info.id
}
static func ==(lhs: ArchivedStickersNoticeEntry, rhs: ArchivedStickersNoticeEntry) -> Bool {
return lhs.index == rhs.index && lhs.info.id == rhs.info.id && lhs.count == rhs.count
}
static func <(lhs: ArchivedStickersNoticeEntry, rhs: ArchivedStickersNoticeEntry) -> Bool {
return lhs.index < rhs.index
}
func item(account: Account, presentationData: PresentationData) -> ListViewItem {
return ItemListStickerPackItem(presentationData: ItemListPresentationData(presentationData), account: account, packInfo: info, itemCount: self.count, topItem: topItem, unread: false, control: .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false), enabled: true, playAnimatedStickers: true, sectionId: 0, action: {
}, setPackIdWithRevealedOptions: { current, previous in
}, addPack: {
}, removePack: {
})
}
}
private struct ArchivedStickersNoticeTransition {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
}
private func preparedTransition(from fromEntries: [ArchivedStickersNoticeEntry], to toEntries: [ArchivedStickersNoticeEntry], account: Account, presentationData: PresentationData) -> ArchivedStickersNoticeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData), directionHint: nil) }
return ArchivedStickersNoticeTransition(deletions: deletions, insertions: insertions, updates: updates)
}
private final class ArchivedStickersNoticeAlertContentNode: AlertContentNode {
private let presentationData: PresentationData
private let archivedStickerPacks: [(StickerPackCollectionInfo, StickerPackItem?)]
private let textNode: ASTextNode
private let listView: ListView
private var enqueuedTransitions: [ArchivedStickersNoticeTransition] = []
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private let disposable = MetaDisposable()
private var validLayout: CGSize?
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(theme: AlertControllerTheme, account: Account, presentationData: PresentationData, archivedStickerPacks: [(StickerPackCollectionInfo, StickerPackItem?)], actions: [TextAlertAction]) {
self.presentationData = presentationData
self.archivedStickerPacks = archivedStickerPacks
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 4
self.listView = ListView()
self.listView.isOpaque = false
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
return TextAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.listView)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
self.actionNodes.last?.actionEnabled = false
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.updateTheme(theme)
var index: Int = 0
var entries: [ArchivedStickersNoticeEntry] = []
for pack in archivedStickerPacks {
entries.append(ArchivedStickersNoticeEntry(index: index, info: pack.0, topItem: pack.1, count: presentationData.strings.StickerPack_StickerCount(pack.0.count)))
index += 1
}
let transition = preparedTransition(from: [], to: entries, account: account, presentationData: presentationData)
self.enqueueTransition(transition)
}
deinit {
self.disposable.dispose()
}
private func enqueueTransition(_ transition: ArchivedStickersNoticeTransition) {
self.enqueuedTransitions.append(transition)
if let _ = self.validLayout {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
}
}
}
private func dequeueTransition() {
guard let layout = self.validLayout, let transition = self.enqueuedTransitions.first else {
return
}
self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
})
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.ArchivedPacksAlert_Title, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
let hadValidLayout = self.validLayout != nil
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let textSize = self.textNode.measure(measureSize)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
origin.y += textSize.height + 16.0
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
var contentWidth = max(textSize.width, minActionsWidth)
contentWidth = max(contentWidth, 234.0)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultWidth = contentWidth + insets.left + insets.right
let listHeight: CGFloat = CGFloat(min(3, self.archivedStickerPacks.count)) * 56.0
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: resultWidth, height: listHeight), insets: UIEdgeInsets(top: -35.0, left: 0.0, bottom: 0.0, right: 0.0), headerInsets: UIEdgeInsets(), scrollIndicatorInsets: UIEdgeInsets(), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: listHeight))
let resultSize = CGSize(width: resultWidth, height: textSize.height + actionsHeight + listHeight + 10.0 + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
if !hadValidLayout {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
}
}
return resultSize
}
}
public func archivedStickerPacksNoticeController(context: AccountContext, archivedStickerPacks: [(StickerPackCollectionInfo, StickerPackItem?)]) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: (() -> Void)?
let disposable = MetaDisposable()
let contentNode = ArchivedStickersNoticeAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), account: context.account, presentationData: presentationData, archivedStickerPacks: archivedStickerPacks, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
dismissImpl?()
})])
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
controller?.theme = AlertControllerTheme(presentationData: presentationData)
})
controller.dismissed = {
presentationDataDisposable.dispose()
disposable.dispose()
}
dismissImpl = { [weak controller, weak contentNode] in
controller?.dismissAnimated()
}
return controller
}

View File

@ -54,16 +54,6 @@ private class AvatarNodeParameters: NSObject {
}
}
private let gradientColors: [NSArray] = [
[UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
[UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
[UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
[UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
[UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
[UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
]
private func generateGradientFilledCircleImage(diameter: CGFloat, colors: NSArray) -> UIImage? {
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
@ -167,6 +157,16 @@ public final class AvatarEditOverlayNode: ASDisplayNode {
}
public final class AvatarNode: ASDisplayNode {
public static let gradientColors: [NSArray] = [
[UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
[UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
[UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
[UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
[UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
[UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
]
public var font: UIFont {
didSet {
if oldValue !== font {
@ -318,7 +318,7 @@ public final class AvatarNode: ASDisplayNode {
let parameters: AvatarNodeParameters
if let peer = peer, let signal = peerAvatarImage(account: context.account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad) {
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad) {
self.contents = nil
self.displaySuspended = true
self.imageReady.set(self.imageNode.ready)
@ -416,7 +416,7 @@ public final class AvatarNode: ASDisplayNode {
if peerId.namespace == -1 {
colorIndex = -1
} else {
colorIndex = abs(Int(clamping: accountPeerId.id &+ peerId.id))
colorIndex = abs(Int(clamping: peerId.id))
}
} else {
colorIndex = -1
@ -456,7 +456,7 @@ public final class AvatarNode: ASDisplayNode {
colorsArray = grayscaleColors
}
} else {
colorsArray = gradientColors[colorIndex % gradientColors.count]
colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count]
}
var locations: [CGFloat] = [1.0, 0.0]
@ -555,7 +555,7 @@ public final class AvatarNode: ASDisplayNode {
}
}
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], accountPeerId: PeerId, peerId: PeerId) {
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], peerId: PeerId) {
context.beginPath()
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
size.height))
@ -572,7 +572,7 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont
if colorIndex == -1 {
colorsArray = grayscaleColors
} else {
colorsArray = gradientColors[colorIndex % gradientColors.count]
colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count]
}
var locations: [CGFloat] = [1.0, 0.0]
@ -582,6 +582,8 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
context.resetClip()
context.setBlendMode(.normal)
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
@ -597,7 +599,9 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
let textPosition = context.textPosition
context.translateBy(x: lineOrigin.x, y: lineOrigin.y)
CTLineDraw(line, context)
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
context.textPosition = textPosition
}

View File

@ -21,7 +21,7 @@ private let roundCorners = { () -> UIImage in
return image
}()
public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<Data?, NoError>? {
public func peerAvatarImageData(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<Data?, NoError>? {
if let smallProfileImage = representation {
let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad)
let imageData = resourceData
@ -44,7 +44,7 @@ public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: M
subscriber.putCompletion()
})
var fetchedDataDisposable: Disposable?
if let peerReference = PeerReference(peer) {
if let peerReference = peerReference {
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource), statsCategory: .generic).start()
} else if let authorOfMessage = authorOfMessage {
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .messageAuthorAvatar(message: authorOfMessage, resource: smallProfileImage.resource), statsCategory: .generic).start()
@ -64,8 +64,8 @@ public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: M
}
}
public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
if let imageData = peerAvatarImageData(account: account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData
|> mapToSignal { data -> Signal<UIImage?, NoError> in
let generate = deferred { () -> Signal<UIImage?, NoError> in

View File

@ -288,7 +288,7 @@ final class CallListControllerNode: ASDisplayNode {
let showCallsTab = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings])
|> map { sharedData -> Bool in
var value = true
var value = CallListSettings.defaultSettings.showTab
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings] as? CallListSettings {
value = settings.showTab
}

View File

@ -306,7 +306,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked)
}
if case .root = groupId, checkProxy {
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil {
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self {
strongSelf.didShowProxyUnavailableTooltipController = true
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true)
strongSelf.proxyUnavailableTooltipController = tooltipController

View File

@ -244,6 +244,7 @@ final class ChatListControllerNode: ASDisplayNode {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in
self?.requestOpenPeerFromSearch?(peer, dismissSearch)
}, openDisabledPeer: { _ in
}, openRecentPeerOptions: { [weak self] peer in
self?.requestOpenRecentPeerOptions?(peer)
}, openMessage: { [weak self] peer, messageId in

View File

@ -81,7 +81,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
}
}
func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ListViewItem {
func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, disaledPeerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ListViewItem {
switch self {
case let .topPeers(peers, theme, strings):
return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in
@ -133,6 +133,12 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
}
}
if filter.contains(.excludeChannels) {
if let channel = primaryPeer as? TelegramChannel, case .broadcast = channel.info {
enabled = false
}
}
let status: ContactsPeerItemStatus
if let user = primaryPeer as? TelegramUser {
let servicePeer = isServicePeer(primaryPeer)
@ -181,6 +187,10 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
peerSelected(chatPeer)
}
}, disabledAction: { _ in
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
disaledPeerSelected(chatPeer)
}
}, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in
return { node, gesture in
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
@ -501,12 +511,12 @@ public struct ChatListSearchContainerTransition {
}
}
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ChatListSearchContainerRecentTransition {
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, disaledPeerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ChatListSearchContainerRecentTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disaledPeerSelected: disaledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disaledPeerSelected: disaledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
}
@ -615,7 +625,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let filter: ChatListNodePeersFilter
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?) {
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?) {
self.context = context
self.filter = filter
self.dimNode = ASDisplayNode()
@ -837,6 +847,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
if filter.contains(.excludeChannels) {
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
return false
}
}
return true
}
@ -962,6 +978,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openPeer(peer, false)
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
self?.listNode.clearHighlightAnimated(true)
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
self?.view.endEditing(true)
@ -1091,6 +1108,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openPeer(peer, true)
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
self?.recentListNode.clearHighlightAnimated(true)
}, disaledPeerSelected: { peer in
openDisabledPeer(peer)
}, peerContextAction: peerContextAction,
clearRecentlySearchedPeers: {
self?.clearRecentSearch()

View File

@ -755,7 +755,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var currentSecretIconImage: UIImage?
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
let editingOffset: CGFloat
var reorderInset: CGFloat = 0.0
@ -1331,7 +1331,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width + revealOffset - params.rightInset - reorderControlSizeAndApply.0, y: layoutOffset), size: CGSize(width: reorderControlSizeAndApply.0, height: layout.contentSize.height))
if strongSelf.reorderControlNode == nil {
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false)
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
strongSelf.reorderControlNode = reorderControlNode
strongSelf.addSubnode(reorderControlNode)
reorderControlNode.frame = reorderControlFrame
@ -1344,7 +1344,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition.updateAlpha(node: strongSelf.pinnedIconNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.statusNode, alpha: 0.0)
} else if let reorderControlNode = strongSelf.reorderControlNode {
let _ = reorderControlSizeAndApply.1(layout.contentSize.height, false)
let _ = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
transition.updateFrame(node: reorderControlNode, frame: reorderControlFrame)
}
} else if let reorderControlNode = strongSelf.reorderControlNode {

View File

@ -46,6 +46,7 @@ final class ChatListHighlightedLocation {
public final class ChatListNodeInteraction {
let activateSearch: () -> Void
let peerSelected: (Peer) -> Void
let disabledPeerSelected: (Peer) -> Void
let togglePeerSelected: (PeerId) -> Void
let messageSelected: (Peer, Message, Bool) -> Void
let groupSelected: (PeerGroupId) -> Void
@ -62,9 +63,10 @@ public final class ChatListNodeInteraction {
public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation?
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void) {
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void) {
self.activateSearch = activateSearch
self.peerSelected = peerSelected
self.disabledPeerSelected = disabledPeerSelected
self.togglePeerSelected = togglePeerSelected
self.messageSelected = messageSelected
self.groupSelected = groupSelected
@ -202,11 +204,22 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
enabled = false
}
}
if filter.contains(.excludeChannels) {
if let peer = peer.peers[peer.peerId] {
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
enabled = false
}
}
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer)
}
}, disabledAction: { _ in
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}), directionHint: entry.directionHint)
}
case let .HoleEntry(_, theme):
@ -242,10 +255,19 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
enabled = false
}
}
if filter.contains(.excludeChannels) {
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
enabled = false
}
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer)
}
}, disabledAction: { _ in
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}), directionHint: entry.directionHint)
}
case let .HoleEntry(_, theme):
@ -318,6 +340,7 @@ public final class ChatListNode: ListView {
}
public var peerSelected: ((PeerId, Bool, Bool) -> Void)?
public var disabledPeerSelected: ((Peer) -> Void)?
public var groupSelected: ((PeerGroupId) -> Void)?
public var addContact: ((String) -> Void)?
public var activateSearch: (() -> Void)?
@ -409,6 +432,10 @@ public final class ChatListNode: ListView {
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peer.id, true, false)
}
}, disabledPeerSelected: { [weak self] peer in
if let strongSelf = self, let disabledPeerSelected = strongSelf.disabledPeerSelected {
disabledPeerSelected(peer)
}
}, togglePeerSelected: { [weak self] peerId in
self?.updateState { state in
var state = state
@ -584,6 +611,11 @@ public final class ChatListNode: ListView {
}
}
if filter.contains(.excludeChannels) {
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
}
}
if filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) {
if let peer = peer.peers[peer.peerId] {
if !canSendMessagesToPeer(peer) {

File diff suppressed because it is too large Load Diff

View File

@ -170,7 +170,7 @@ class CreatePollOptionActionItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.iconNode.image = updatedIcon
}
if let image = strongSelf.iconNode.image {
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0 - 1.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0 - 3.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
}
if strongSelf.backgroundNode.supernode == nil {

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import CheckNode
struct CreatePollOptionItemEditing {
let editable: Bool
@ -13,27 +14,29 @@ struct CreatePollOptionItemEditing {
}
class CreatePollOptionItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let strings: PresentationStrings
let presentationData: ItemListPresentationData
let id: Int
let placeholder: String
let value: String
let isSelected: Bool?
let maxLength: Int
let editing: CreatePollOptionItemEditing
let sectionId: ItemListSectionId
let setItemIdWithRevealedOptions: (Int?, Int?) -> Void
let updated: (String) -> Void
let updated: (String, Bool) -> Void
let next: (() -> Void)?
let delete: (Bool) -> Void
let focused: () -> Void
let canDelete: Bool
let focused: (Bool) -> Void
let toggleSelected: () -> Void
let tag: ItemListItemTag?
init(theme: PresentationTheme, strings: PresentationStrings, id: Int, placeholder: String, value: String, maxLength: Int, editing: CreatePollOptionItemEditing, sectionId: ItemListSectionId, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, updated: @escaping (String) -> Void, next: (() -> Void)?, delete: @escaping (Bool) -> Void, focused: @escaping () -> Void, tag: ItemListItemTag?) {
self.theme = theme
self.strings = strings
init(presentationData: ItemListPresentationData, id: Int, placeholder: String, value: String, isSelected: Bool?, maxLength: Int, editing: CreatePollOptionItemEditing, sectionId: ItemListSectionId, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, updated: @escaping (String, Bool) -> Void, next: (() -> Void)?, delete: @escaping (Bool) -> Void, canDelete: Bool, focused: @escaping (Bool) -> Void, toggleSelected: @escaping () -> Void, tag: ItemListItemTag?) {
self.presentationData = presentationData
self.id = id
self.placeholder = placeholder
self.value = value
self.isSelected = isSelected
self.maxLength = maxLength
self.editing = editing
self.sectionId = sectionId
@ -41,7 +44,9 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
self.updated = updated
self.next = next
self.delete = delete
self.canDelete = canDelete
self.focused = focused
self.toggleSelected = toggleSelected
self.tag = tag
}
@ -55,7 +60,7 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
return (nil, { _ in apply(.None) })
})
}
}
@ -70,7 +75,7 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
apply(animation)
})
}
}
@ -84,17 +89,19 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(15.0)
class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode, ItemListItemFocusableNode, ASEditableTextNodeDelegate {
private let containerNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private var checkNode: CheckNode?
private let textClippingNode: ASDisplayNode
private let textNode: EditableTextNode
private let measureTextNode: TextNode
private let textLimitNode: TextNode
private let editableControlNode: ItemListEditableControlNode
private let reorderControlNode: ItemListEditableReorderControlNode
private var item: CreatePollOptionItem?
@ -104,7 +111,21 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
return self.item?.tag
}
override var controlsContainer: ASDisplayNode {
return self.containerNode
}
var checkNodeFrame: CGRect? {
guard let _ = self.layoutParams, let checkNode = self.checkNode else {
return nil
}
return checkNode.frame
}
init() {
self.containerNode = ASDisplayNode()
self.containerNode.clipsToBounds = true
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white
@ -117,7 +138,6 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
self.maskNode = ASImageNode()
self.editableControlNode = ItemListEditableControlNode()
self.reorderControlNode = ItemListEditableReorderControlNode()
self.textClippingNode = ASDisplayNode()
@ -131,21 +151,17 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.clipsToBounds = true
self.addSubnode(self.containerNode)
self.textClippingNode.addSubnode(self.textNode)
self.addSubnode(self.textClippingNode)
self.containerNode.addSubnode(self.textClippingNode)
self.addSubnode(self.editableControlNode)
self.addSubnode(self.reorderControlNode)
self.addSubnode(self.textLimitNode)
self.containerNode.addSubnode(self.reorderControlNode)
self.containerNode.addSubnode(self.textLimitNode)
}
self.editableControlNode.tapped = { [weak self] in
if let strongSelf = self {
strongSelf.setRevealOptionsOpened(true, animated: true)
strongSelf.revealOptionsInteractivelyOpened()
}
}
@objc private func checkNodePressed() {
self.item?.toggleSelected()
}
override func didLoad() {
@ -153,7 +169,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
var textColor: UIColor = .black
if let item = self.item {
textColor = item.theme.list.itemPrimaryTextColor
textColor = item.presentationData.theme.list.itemPrimaryTextColor
}
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
self.textNode.clipsToBounds = true
@ -162,7 +178,12 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
}
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
self.item?.focused()
self.item?.focused(true)
}
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
self.internalEditableTextNodeDidUpdateText(editableTextNode, isLosingFocus: true)
self.item?.focused(false)
}
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
@ -177,7 +198,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
if updatedText.count == 1 {
updatedText = ""
}
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
self.textNode.attributedText = updatedAttributedText
self.editableTextNodeDidUpdateText(editableTextNode)
}
@ -192,6 +213,10 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
}
func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.internalEditableTextNodeDidUpdateText(editableTextNode, isLosingFocus: false)
}
private func internalEditableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode, isLosingFocus: Bool) {
if let item = self.item {
let text = self.textNode.attributedText ?? NSAttributedString()
@ -201,15 +226,15 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
hadReturn = true
updatedText = updatedText.replacingOccurrences(of: "\n", with: " ")
}
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
if text.string != updatedAttributedText.string {
self.textNode.attributedText = updatedAttributedText
}
item.updated(updatedText)
item.updated(updatedText, !isLosingFocus && editableTextNode.isFirstResponder())
if hadReturn {
if let next = item.next {
next()
} else {
} else if !isLosingFocus {
editableTextNode.resignFirstResponder()
}
}
@ -220,8 +245,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
self.item?.delete(editableTextNode.isFirstResponder())
}
func asyncLayout() -> (_ item: CreatePollOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
func asyncLayout() -> (_ item: CreatePollOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
let makeTextLimitLayout = TextNode.asyncLayout(self.textLimitNode)
@ -231,32 +255,31 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
return { item, params, neighbors in
var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
}
let controlSizeAndApply = editableControlLayout(item.theme, false)
let reorderSizeAndApply = reorderControlLayout(item.theme)
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
let separatorHeight = UIScreenPixel
let insets = itemListNeighborsGroupedInsets(neighbors)
let leftInset: CGFloat = 60.0 + params.leftInset
let leftInset: CGFloat = params.leftInset + (item.isSelected != nil ? 60.0 : 16.0)
let rightInset: CGFloat = 44.0 + params.rightInset
let textLength = item.value.count
let displayTextLimit = textLength > item.maxLength * 70 / 100
let remainingCount = item.maxLength - textLength
let (textLimitLayout, textLimitApply) = makeTextLimitLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.theme.list.itemDestructiveColor : item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let (textLimitLayout, textLimitApply) = makeTextLimitLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
var measureText = item.value
if measureText.hasSuffix("\n") || measureText.isEmpty {
measureText += "|"
}
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
let attributedText = NSAttributedString(string: item.value, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
let attributedText = NSAttributedString(string: item.value, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.05, cutout: nil, insets: UIEdgeInsets()))
let textTopInset: CGFloat = 11.0
@ -265,21 +288,29 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
let contentSize = CGSize(width: params.width, height: textLayout.size.height + textTopInset + textBottomInset)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(17.0), textColor: item.theme.list.itemPlaceholderTextColor)
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPlaceholderTextColor)
return (layout, { [weak self] in
return (layout, { [weak self] animation in
if let strongSelf = self {
let transition: ContainedViewLayoutTransition
switch animation {
case .System:
transition = .animated(duration: 0.3, curve: .spring)
default:
transition = .immediate
}
strongSelf.item = item
strongSelf.layoutParams = params
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
if strongSelf.isNodeLoaded {
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.theme.list.itemPrimaryTextColor]
strongSelf.textNode.tintColor = item.theme.list.itemAccentColor
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.presentationData.theme.list.itemPrimaryTextColor]
strongSelf.textNode.tintColor = item.presentationData.theme.list.itemAccentColor
}
}
@ -295,7 +326,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
let _ = textApply()
if let currentText = strongSelf.textNode.attributedText {
if currentText.string != attributedText.string {
if currentText.string != attributedText.string || updatedTheme != nil {
strongSelf.textNode.attributedText = attributedText
}
} else {
@ -325,58 +356,93 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
}
strongSelf.textNode.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
strongSelf.textNode.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset, height: textLayout.size.height + 1.0))
let checkSize = CGSize(width: 32.0, height: 32.0)
let checkFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + 11.0, y: floor((layout.contentSize.height - checkSize.height) / 2.0)), size: checkSize)
if let isSelected = item.isSelected {
if let checkNode = strongSelf.checkNode {
transition.updateFrame(node: checkNode, frame: checkFrame)
checkNode.setIsChecked(isSelected, animated: true)
} else {
let checkNode = CheckNode(strokeColor: item.presentationData.theme.list.itemCheckColors.strokeColor, fillColor: item.presentationData.theme.list.itemSwitchColors.positiveColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, style: .plain)
checkNode.addTarget(target: strongSelf, action: #selector(strongSelf.checkNodePressed))
checkNode.setIsChecked(isSelected, animated: false)
strongSelf.checkNode = checkNode
strongSelf.containerNode.addSubnode(checkNode)
checkNode.frame = checkFrame
transition.animatePositionAdditive(node: checkNode, offset: CGPoint(x: -checkFrame.maxX, y: 0.0))
}
} else if let checkNode = strongSelf.checkNode {
strongSelf.checkNode = nil
transition.updateFrame(node: checkNode, frame: checkFrame.offsetBy(dx: -checkFrame.maxX, dy: 0.0), completion: { [weak checkNode] _ in
checkNode?.removeFromSupernode()
})
}
transition.updateFrame(node: strongSelf.textClippingNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height)))
transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset, height: textLayout.size.height + 1.0)))
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
}
let bottomStripeWasHidden = strongSelf.bottomStripeNode.isHidden
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
case .sameSection(false):
bottomStripeInset = leftInset
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.contentSize.width - bottomStripeInset, height: separatorHeight))
if strongSelf.animationForKey("apparentHeight") == nil {
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
let previousX = strongSelf.bottomStripeNode.frame.minX
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
if !bottomStripeWasHidden {
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
}
} else {
let previousX = strongSelf.bottomStripeNode.frame.minX
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: strongSelf.bottomStripeNode.frame.minY), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
if !bottomStripeWasHidden {
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
}
}
let _ = controlSizeAndApply.1(layout.contentSize.height)
let editableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + 6.0 + revealOffset, y: 0.0), size: CGSize(width: controlSizeAndApply.0, height: contentSize.height))
strongSelf.editableControlNode.frame = editableControlFrame
let _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit && layout.contentSize.height <= 44.0)
let _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit, transition)
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width + revealOffset - params.rightInset - reorderSizeAndApply.0, y: 0.0), size: CGSize(width: reorderSizeAndApply.0, height: layout.contentSize.height))
strongSelf.reorderControlNode.frame = reorderControlFrame
strongSelf.reorderControlNode.isHidden = !item.canDelete
let _ = textLimitApply()
strongSelf.textLimitNode.frame = CGRect(origin: CGPoint(x: reorderControlFrame.minX + floor((reorderControlFrame.width - textLimitLayout.size.width) / 2.0) - 4.0 - UIScreenPixel, y: max(floor(reorderControlFrame.midY + 2.0), layout.contentSize.height - 15.0 - textLimitLayout.size.height)), size: textLimitLayout.size)
@ -384,7 +450,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
strongSelf.setRevealOptions((left: [], right: item.canDelete ? [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] : []))
}
})
}
@ -393,18 +459,20 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition)
guard let params = self.layoutParams else {
guard let params = self.layoutParams, let item = self.item else {
return
}
let revealOffset = offset
let leftInset: CGFloat
leftInset = 60.0 + params.leftInset
leftInset = params.leftInset + (item.isSelected != nil ? 60.0 : 16.0)
var controlFrame = self.editableControlNode.frame
controlFrame.origin.x = params.leftInset + 6.0 + revealOffset
transition.updateFrame(node: self.editableControlNode, frame: controlFrame)
if let checkNode = self.checkNode {
var checkNodeFrame = checkNode.frame
checkNodeFrame.origin.x = params.leftInset + 11.0 + revealOffset
transition.updateFrame(node: checkNode, frame: checkNodeFrame)
}
var reorderFrame = self.reorderControlNode.frame
reorderFrame.origin.x = params.width + revealOffset - params.rightInset - reorderFrame.width
@ -436,7 +504,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
}
override func isReorderable(at point: CGPoint) -> Bool {
if self.reorderControlNode.frame.contains(point), !self.isDisplayingRevealedOptions {
if self.reorderControlNode.frame.contains(point), !self.reorderControlNode.isHidden, !self.isDisplayingRevealedOptions {
return true
}
return false
@ -448,5 +516,16 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
var separatorFrame = self.bottomStripeNode.frame
separatorFrame.origin.y = currentValue - UIScreenPixel
self.bottomStripeNode.frame = separatorFrame
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.containerNode.bounds.width, height: currentValue))
let insets = self.insets
let separatorHeight = UIScreenPixel
guard let params = self.layoutParams else {
return
}
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: self.containerNode.bounds.width, height: currentValue + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
}
}

View File

@ -122,6 +122,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
let options: [ItemListPeerItemRevealOption]
let actionIcon: ContactsPeerItemActionIcon
let action: (ContactsPeerItemPeer) -> Void
let disabledAction: ((ContactsPeerItemPeer) -> Void)?
let setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)?
let deletePeer: ((PeerId) -> Void)?
let itemHighlighting: ContactItemHighlighting?
@ -133,7 +134,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
public let header: ListViewItemHeader?
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
self.presentationData = presentationData
self.style = style
self.sectionId = sectionId
@ -150,11 +151,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
self.options = options
self.actionIcon = actionIcon
self.action = action
self.disabledAction = disabledAction
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.deletePeer = deletePeer
self.header = header
self.itemHighlighting = itemHighlighting
self.selectable = enabled
self.selectable = enabled || disabledAction != nil
self.contextAction = contextAction
if let index = index {
@ -245,7 +247,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
}
public func selected(listView: ListView) {
self.action(self.peer)
if self.enabled {
self.action(self.peer)
} else {
listView.clearHighlightAnimated(true)
self.disabledAction?(self.peer)
}
}
static func mergeType(item: ContactsPeerItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {

View File

@ -5,6 +5,8 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
private var validatedGesture = false
private var firstLocation: CGPoint = CGPoint()
public var shouldBegin: ((CGPoint) -> Bool)?
override public init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
@ -21,7 +23,13 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
super.touchesBegan(touches, with: event)
let touch = touches.first!
self.firstLocation = touch.location(in: self.view)
let point = touch.location(in: self.view)
if let shouldBegin = self.shouldBegin, !shouldBegin(point) {
self.state = .failed
return
}
self.firstLocation = point
if let target = self.view?.hitTest(self.firstLocation, with: event) {
if target == self.view {

View File

@ -109,7 +109,6 @@ private func ==(lhs: Tail, rhs: Tail) -> Bool {
}
private var cachedCorners = Atomic<[Corner: DrawingContext]>(value: [:])
private var cachedTails = Atomic<[Tail: DrawingContext]>(value: [:])
private func cornerContext(_ corner: Corner) -> DrawingContext {
let cached: DrawingContext? = cachedCorners.with {
@ -122,20 +121,22 @@ private func cornerContext(_ corner: Corner) -> DrawingContext {
let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true)
context.withContext { c in
c.setBlendMode(.copy)
c.clear(CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius))))
c.setFillColor(UIColor.black.cgColor)
let rect: CGRect
switch corner {
case let .TopLeft(radius):
rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .TopRight(radius):
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .BottomLeft(radius):
rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .BottomRight(radius):
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .TopLeft(radius):
let rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
c.fillEllipse(in: rect)
case let .TopRight(radius):
let rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
c.fillEllipse(in: rect)
case let .BottomLeft(radius):
let rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
c.fillEllipse(in: rect)
case let .BottomRight(radius):
let rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
c.fillEllipse(in: rect)
}
c.fillEllipse(in: rect)
}
let _ = cachedCorners.modify { current in
@ -148,62 +149,6 @@ private func cornerContext(_ corner: Corner) -> DrawingContext {
}
}
private func tailContext(_ tail: Tail) -> DrawingContext {
let cached: DrawingContext? = cachedTails.with {
return $0[tail]
}
if let cached = cached {
return cached
} else {
let context = DrawingContext(size: CGSize(width: CGFloat(tail.radius) + 3.0, height: CGFloat(tail.radius)), clear: true)
context.withContext { c in
c.setBlendMode(.copy)
c.setFillColor(UIColor.black.cgColor)
let rect: CGRect
switch tail {
case let .BottomLeft(radius):
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
c.move(to: CGPoint(x: 3.0, y: 1.0))
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
c.closePath()
c.fillPath()
case let .BottomRight(radius):
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
c.translateBy(x: context.size.width / 2.0, y: context.size.height / 2.0)
c.scaleBy(x: -1.0, y: 1.0)
c.translateBy(x: -context.size.width / 2.0, y: -context.size.height / 2.0)
c.move(to: CGPoint(x: 3.0, y: 1.0))
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
c.closePath()
c.fillPath()
}
c.fillEllipse(in: rect)
}
let _ = cachedTails.modify { current in
var current = current
current[tail] = context
return current
}
return context
}
}
public func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) {
let corners = arguments.corners
let drawingRect = arguments.drawingRect
@ -223,23 +168,24 @@ public func addCorners(_ context: DrawingContext, arguments: TransformImageArgum
let corner = cornerContext(.BottomLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
}
case let .Tail(radius, enabled):
case let .Tail(radius, image):
if radius > CGFloat.ulpOfOne {
if enabled {
let tail = tailContext(.BottomLeft(Int(radius)))
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.minX - 3.0, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
}
context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius))
} else {
let corner = cornerContext(.BottomLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.minX - 4.0, y: 0.0, width: 4.0, height: drawingRect.maxY - 6.0))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 7.0, width: 4.0, height: 7.0))
c.setBlendMode(.destinationIn)
let cornerRect = CGRect(origin: CGPoint(x: drawingRect.minX - 6.0, y: drawingRect.maxY - image.size.height), size: image.size)
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
c.draw(image.cgImage!, in: cornerRect)
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
}
}
}
switch corners.bottomRight {
@ -248,20 +194,22 @@ public func addCorners(_ context: DrawingContext, arguments: TransformImageArgum
let corner = cornerContext(.BottomRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
}
case let .Tail(radius, enabled):
case let .Tail(radius, image):
if radius > CGFloat.ulpOfOne {
if enabled {
let tail = tailContext(.BottomRight(Int(radius)))
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
}
context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
} else {
let corner = cornerContext(.BottomRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 4.0, height: drawingRect.maxY - image.size.height))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 7.0, width: 5.0, height: 7.0))
c.setBlendMode(.destinationIn)
let cornerRect = CGRect(origin: CGPoint(x: drawingRect.maxX - image.size.width + 6.0, y: drawingRect.maxY - image.size.height), size: image.size)
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
c.draw(image.cgImage!, in: cornerRect)
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
}
}
}

View File

@ -8,12 +8,12 @@ private let dispatcher = displayLinkDispatcher
public enum ImageCorner: Equatable {
case Corner(CGFloat)
case Tail(CGFloat, Bool)
case Tail(CGFloat, UIImage)
public var extendedInsets: CGSize {
switch self {
case .Tail:
return CGSize(width: 3.0, height: 0.0)
return CGSize(width: 4.0, height: 0.0)
default:
return CGSize()
}
@ -36,15 +36,6 @@ public enum ImageCorner: Equatable {
return radius
}
}
public func scaledBy(_ scale: CGFloat) -> ImageCorner {
switch self {
case let .Corner(radius):
return .Corner(radius * scale)
case let .Tail(radius, enabled):
return .Tail(radius * scale, enabled)
}
}
}
public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool {
@ -56,8 +47,8 @@ public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool {
default:
return false
}
case let .Tail(lhsRadius, lhsEnabled):
if case let .Tail(rhsRadius, rhsEnabled) = rhs, lhsRadius.isEqual(to: rhsRadius), lhsEnabled == rhsEnabled {
case let .Tail(lhsRadius, lhsImage):
if case let .Tail(rhsRadius, rhsImage) = rhs, lhsRadius.isEqual(to: rhsRadius), lhsImage === rhsImage {
return true
} else {
return false
@ -124,10 +115,6 @@ public struct ImageCorners: Equatable {
public func withRemovedTails() -> ImageCorners {
return ImageCorners(topLeft: self.topLeft.withoutTail, topRight: self.topRight.withoutTail, bottomLeft: self.bottomLeft.withoutTail, bottomRight: self.bottomRight.withoutTail)
}
public func scaledBy(_ scale: CGFloat) -> ImageCorners {
return ImageCorners(topLeft: self.topLeft.scaledBy(scale), topRight: self.topRight.scaledBy(scale), bottomLeft: self.bottomLeft.scaledBy(scale), bottomRight: self.bottomRight.scaledBy(scale))
}
}
public func ==(lhs: ImageCorners, rhs: ImageCorners) -> Bool {

View File

@ -435,6 +435,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
private func endReordering() {
self.itemReorderingTimer?.invalidate()
self.itemReorderingTimer = nil
self.lastReorderingOffset = nil
let f: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
@ -467,11 +471,32 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
private func checkItemReordering() {
if let reorderNode = self.reorderNode, let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self {
guard let verticalTopOffset = reorderNode.currentOffset() else {
return
}
private var itemReorderingTimer: SwiftSignalKit.Timer?
private var lastReorderingOffset: CGFloat?
private func checkItemReordering(force: Bool = false) {
guard let reorderNode = self.reorderNode, let verticalTopOffset = reorderNode.currentOffset() else {
return
}
if let lastReorderingOffset = self.lastReorderingOffset, abs(lastReorderingOffset - verticalTopOffset) < 4.0 && !force {
return
}
self.itemReorderingTimer?.invalidate()
self.itemReorderingTimer = nil
self.lastReorderingOffset = verticalTopOffset
if !force {
self.itemReorderingTimer = SwiftSignalKit.Timer(timeout: 0.025, repeat: false, completion: { [weak self] in
self?.checkItemReordering(force: true)
}, queue: Queue.mainQueue())
self.itemReorderingTimer?.start()
return
}
if let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self {
let verticalOffset = verticalTopOffset
var closestIndex: (Int, CGFloat)?
for i in 0 ..< self.itemNodes.count {
@ -2011,7 +2036,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
}
if node.animationForKey("apparentHeight") == nil || !(node is ListViewTempItemNode) {
node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
if let node = node {
node.animateFrameTransition(progress, currentValue)
}
})
}
node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor())
} else if animated {
@ -2035,8 +2064,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
} else {
if !nodeFrame.size.height.isEqual(to: node.apparentHeight) {
let addAnimation = previousFrame?.height != nodeFrame.size.height
node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
if let node = node {
if let node = node, addAnimation {
node.animateFrameTransition(progress, currentValue)
}
})
@ -3007,21 +3037,25 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let flashing = self.headerItemsAreFlashing()
let addHeader: (_ id: Int64, _ upperBound: CGFloat, _ lowerBound: CGFloat, _ item: ListViewItemHeader, _ hasValidNodes: Bool) -> Void = { id, upperBound, lowerBound, item, hasValidNodes in
func addHeader(id: Int64, upperBound: CGFloat, upperBoundEdge: CGFloat, lowerBound: CGFloat, item: ListViewItemHeader, hasValidNodes: Bool) {
let itemHeaderHeight: CGFloat = item.height
let headerFrame: CGRect
let stickLocationDistanceFactor: CGFloat
let stickLocationDistance: CGFloat
switch item.stickDirection {
case .top:
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
stickLocationDistance = headerFrame.minY - upperBound
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
case .bottom:
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: max(upperBound, min(lowerBound, lowerDisplayBound) - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
stickLocationDistance = lowerBound - headerFrame.maxY
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
case .top:
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
stickLocationDistance = headerFrame.minY - upperBound
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
case .topEdge:
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBoundEdge - itemHeaderHeight), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
stickLocationDistance = headerFrame.minY - upperBoundEdge + itemHeaderHeight
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
case .bottom:
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: max(upperBound, min(lowerBound, lowerDisplayBound) - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
stickLocationDistance = lowerBound - headerFrame.maxY
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
}
visibleHeaderNodes.insert(id)
if let headerNode = self.itemHeaderNodes[id] {
@ -3092,31 +3126,32 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
var previousHeader: (Int64, CGFloat, CGFloat, ListViewItemHeader, Bool)?
var previousHeader: (id: Int64, upperBound: CGFloat, upperBoundEdge: CGFloat, lowerBound: CGFloat, item: ListViewItemHeader, hasValidNodes: Bool)?
for itemNode in self.itemNodes {
let itemFrame = itemNode.apparentFrame
let itemTopInset = itemNode.insets.top
if let itemHeader = itemNode.header() {
if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
if let (previousHeaderId, previousUpperBound, previousUpperBoundEdge, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
if previousHeaderId == itemHeader.id {
previousHeader = (previousHeaderId, previousUpperBound, itemFrame.maxY, previousHeaderItem, hasValidNodes || itemNode.index != nil)
previousHeader = (previousHeaderId, previousUpperBound, previousUpperBoundEdge, itemFrame.maxY, previousHeaderItem, hasValidNodes || itemNode.index != nil)
} else {
addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes)
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, item: previousHeaderItem, hasValidNodes: hasValidNodes)
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.maxY, itemHeader, itemNode.index != nil)
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.minY + itemTopInset, itemFrame.maxY, itemHeader, itemNode.index != nil)
}
} else {
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.maxY, itemHeader, itemNode.index != nil)
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.minY + itemTopInset, itemFrame.maxY, itemHeader, itemNode.index != nil)
}
} else {
if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes)
if let (previousHeaderId, previousUpperBound, previousUpperBoundEdge, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, item: previousHeaderItem, hasValidNodes: hasValidNodes)
}
previousHeader = nil
}
}
if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes)
if let (previousHeaderId, previousUpperBound, previousUpperBoundEdge, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, item: previousHeaderItem, hasValidNodes: hasValidNodes)
}
let currentIds = Set(self.itemHeaderNodes.keys)

View File

@ -4,6 +4,7 @@ import AsyncDisplayKit
public enum ListViewItemHeaderStickDirection {
case top
case topEdge
case bottom
}

View File

@ -564,6 +564,6 @@ open class ListViewItemNode: ASDisplayNode {
}
open func snapshotForReordering() -> UIView? {
return self.view.snapshotContentTree()
return self.view.snapshotContentTree(keepTransform: true)
}
}

View File

@ -131,6 +131,16 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
return false
}
private func checkInteractiveDismissWithControllers() -> Bool {
if let controller = self.container.controllers.last {
if !controller.attemptNavigation({
}) {
return false
}
}
return true
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
@ -147,7 +157,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let progress = translation / self.bounds.width
let velocity = recognizer.velocity(in: self.view).x
if velocity > 1000 || progress > 0.2 {
if (velocity > 1000 || progress > 0.2) && self.checkInteractiveDismissWithControllers() {
self.isDismissed = true
self.horizontalDismissOffset = self.bounds.width
self.dismissProgress = 1.0
@ -243,7 +253,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let duration = Double(min(0.3, velocityFactor))
let transition: ContainedViewLayoutTransition
let dismissProgress: CGFloat
if velocity.y < -0.5 || progress >= 0.5 {
if (velocity.y < -0.5 || progress >= 0.5) && self.checkInteractiveDismissWithControllers() {
dismissProgress = 1.0
targetOffset = 0.0
transition = .animated(duration: duration, curve: .easeInOut)

View File

@ -360,8 +360,8 @@ public func standardTextAlertController(theme: AlertControllerTheme, title: Stri
var dismissImpl: (() -> Void)?
let attributedText: NSAttributedString
if parseMarkdown {
let font = title == nil ? Font.semibold(theme.baseFontSize) : Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
let boldFont = title == nil ? Font.bold(theme.baseFontSize) : Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0))
let font = title == nil ? Font.semibold(theme.baseFontSize * 13.0 / 17.0) : Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
let boldFont = title == nil ? Font.bold(theme.baseFontSize * 13.0 / 17.0) : Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0))
let body = MarkdownAttributeSet(font: font, textColor: theme.primaryColor)
let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.primaryColor)
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)

View File

@ -185,6 +185,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
open func dismissImmediately() {
self.dismissed?(false)
self.controllerNode.hide()
self.presentingViewController?.dismiss(animated: false)
}
}

View File

@ -126,6 +126,10 @@ final class TooltipControllerNode: ASDisplayNode {
})
}
func hide() {
self.containerNode.alpha = 0.0
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let event = event {
var eventIsPresses = false

View File

@ -337,8 +337,17 @@ public class Window1 {
self?.isInteractionBlocked = value
}
let updateOpaqueOverlays: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf._rootController?.displayNode.accessibilityElementsHidden = strongSelf.presentationContext.hasOpaqueOverlay || strongSelf.topPresentationContext.hasOpaqueOverlay
}
self.presentationContext.updateHasOpaqueOverlay = { [weak self] value in
self?._rootController?.displayNode.accessibilityElementsHidden = value
updateOpaqueOverlays()
}
self.topPresentationContext.updateHasOpaqueOverlay = { [weak self] value in
updateOpaqueOverlays()
}
self.hostView.present = { [weak self] controller, level, blockInteraction, completion in
@ -1222,10 +1231,12 @@ public class Window1 {
return hidden
}
public func forEachViewController(_ f: (ContainableController) -> Bool) {
public func forEachViewController(_ f: (ContainableController) -> Bool, excludeNavigationSubControllers: Bool = false) {
if let navigationController = self._rootController as? NavigationController {
for case let controller as ContainableController in navigationController.viewControllers {
!f(controller)
if !excludeNavigationSubControllers {
for case let controller as ContainableController in navigationController.viewControllers {
!f(controller)
}
}
if let controller = navigationController.topOverlayController {
!f(controller)

View File

@ -396,7 +396,7 @@ public class GalleryController: ViewController, StandalonePresentableController
} else {
namespaces = .not(Namespaces.Message.allScheduled)
}
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)

View File

@ -238,7 +238,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
self.statusButtonNode.addSubnode(self.statusNode)
self.statusButtonNode.addTarget(self, action: #selector(statusButtonPressed), forControlEvents: .touchUpInside)
self.statusButtonNode.addTarget(self, action: #selector(self.statusButtonPressed), forControlEvents: .touchUpInside)
self.addSubnode(self.statusButtonNode)

View File

@ -122,6 +122,7 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode {
public final class SecretMediaPreviewController: ViewController {
private let context: AccountContext
private let messageId: MessageId
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
@ -150,6 +151,7 @@ public final class SecretMediaPreviewController: ViewController {
public init(context: AccountContext, messageId: MessageId) {
self.context = context
self.messageId = messageId
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
@ -159,8 +161,6 @@ public final class SecretMediaPreviewController: ViewController {
self.statusBar.statusBarStyle = .White
self.disposable.set((context.account.postbox.messageView(messageId) |> deliverOnMainQueue).start(next: { [weak self] view in
if let strongSelf = self {
strongSelf.messageView = view
@ -178,17 +178,6 @@ public final class SecretMediaPreviewController: ViewController {
return nil
}
})
self.screenCaptureEventsDisposable = (screenCaptureEvents()
|> deliverOnMainQueue).start(next: { [weak self] _ in
if let strongSelf = self, strongSelf.traceVisibility() {
if messageId.peerId.namespace == Namespaces.Peer.CloudUser {
let _ = enqueueMessages(account: context.account, peerId: messageId.peerId, messages: [.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.historyScreenshot)), replyToMessageId: nil, localGroupingKey: nil)]).start()
} else if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
let _ = addSecretChatMessageScreenshot(account: context.account, peerId: messageId.peerId).start()
}
}
})
}
required public init(coder aDecoder: NSCoder) {
@ -348,6 +337,19 @@ public final class SecretMediaPreviewController: ViewController {
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.screenCaptureEventsDisposable == nil {
self.screenCaptureEventsDisposable = (screenCaptureEvents()
|> deliverOnMainQueue).start(next: { [weak self] _ in
if let strongSelf = self, strongSelf.traceVisibility() {
if strongSelf.messageId.peerId.namespace == Namespaces.Peer.CloudUser {
let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.messageId.peerId, messages: [.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.historyScreenshot)), replyToMessageId: nil, localGroupingKey: nil)]).start()
} else if strongSelf.messageId.peerId.namespace == Namespaces.Peer.SecretChat {
let _ = addSecretChatMessageScreenshot(account: strongSelf.context.account, peerId: strongSelf.messageId.peerId).start()
}
}
})
}
var nodeAnimatesItself = false
if let centralItemNode = self.controllerNode.pager.centralItemNode(), let message = self.messageView?.message {

View File

@ -49,6 +49,7 @@ public final class HashtagSearchController: TelegramBaseController {
}
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { peer in
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
if let strongSelf = self {

View File

@ -20,9 +20,9 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
let editing: Bool
let height: ItemListPeerActionItemHeight
public let sectionId: ItemListSectionId
let action: () -> Void
let action: (() -> Void)?
public init(presentationData: ItemListPresentationData, icon: UIImage?, title: String, alwaysPlain: Bool = false, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, editing: Bool, action: @escaping () -> Void) {
public init(presentationData: ItemListPresentationData, icon: UIImage?, title: String, alwaysPlain: Bool = false, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, editing: Bool, action: (() -> Void)?) {
self.presentationData = presentationData
self.icon = icon
self.title = title
@ -79,11 +79,13 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
}
}
public var selectable: Bool = true
public var selectable: Bool {
return self.action != nil
}
public func selected(listView: ListView){
listView.clearHighlightAnimated(true)
self.action()
self.action?()
}
}
@ -150,15 +152,15 @@ class ItemListPeerActionItemNode: ListViewItemNode {
updatedTheme = item.presentationData.theme
}
let leftInset: CGFloat
let vertcalInset: CGFloat
let verticalInset: CGFloat
let verticalOffset: CGFloat
switch item.height {
case .generic:
vertcalInset = 11.0
verticalInset = 11.0
verticalOffset = 0.0
leftInset = 59.0 + params.leftInset
case .peerList:
vertcalInset = 14.0
verticalInset = 14.0
verticalOffset = 0.0
leftInset = 65.0 + params.leftInset
}
@ -170,7 +172,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
let separatorHeight = UIScreenPixel
let insets = itemListNeighborsGroupedInsets(neighbors)
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + vertcalInset * 2.0)
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
@ -247,7 +249,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: vertcalInset + verticalOffset), size: titleLayout.size))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
}

View File

@ -16,6 +16,195 @@ import PeerPresenceStatusManager
import ContextUI
import AccountContext
private final class ShimmerEffectNode: ASDisplayNode {
private var currentBackgroundColor: UIColor?
private var currentForegroundColor: UIColor?
private let imageNodeContainer: ASDisplayNode
private let imageNode: ASImageNode
private var absoluteLocation: (CGRect, CGSize)?
private var isCurrentlyInHierarchy = false
private var shouldBeAnimating = false
override init() {
self.imageNodeContainer = ASDisplayNode()
self.imageNodeContainer.isLayerBacked = true
self.imageNode = ASImageNode()
self.imageNode.isLayerBacked = true
self.imageNode.displaysAsynchronously = false
self.imageNode.displayWithoutProcessing = true
self.imageNode.contentMode = .scaleToFill
super.init()
self.isLayerBacked = true
self.clipsToBounds = true
self.imageNodeContainer.addSubnode(self.imageNode)
self.addSubnode(self.imageNodeContainer)
}
override func didEnterHierarchy() {
super.didEnterHierarchy()
self.isCurrentlyInHierarchy = true
self.updateAnimation()
}
override func didExitHierarchy() {
super.didExitHierarchy()
self.isCurrentlyInHierarchy = false
self.updateAnimation()
}
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
return
}
self.currentBackgroundColor = backgroundColor
self.currentForegroundColor = foregroundColor
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
context.setFillColor(backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: CGPoint(), size: size))
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
let peakColor = foregroundColor.cgColor
var locations: [CGFloat] = [0.0, 0.5, 1.0]
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
})
}
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
return
}
let sizeUpdated = self.absoluteLocation?.1 != containerSize
let frameUpdated = self.absoluteLocation?.0 != rect
self.absoluteLocation = (rect, containerSize)
if sizeUpdated {
if self.shouldBeAnimating {
self.imageNode.layer.removeAnimation(forKey: "shimmer")
self.addImageAnimation()
}
}
if frameUpdated {
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
}
}
private func updateAnimation() {
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
if shouldBeAnimating != self.shouldBeAnimating {
self.shouldBeAnimating = shouldBeAnimating
if shouldBeAnimating {
self.addImageAnimation()
} else {
self.imageNode.layer.removeAnimation(forKey: "shimmer")
}
}
}
private func addImageAnimation() {
guard let containerSize = self.absoluteLocation?.1 else {
return
}
let gradientHeight: CGFloat = 250.0
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
animation.repeatCount = Float.infinity
animation.beginTime = 1.0
self.imageNode.layer.add(animation, forKey: "shimmer")
}
}
private final class LoadingShimmerNode: ASDisplayNode {
enum Shape: Equatable {
case circle(CGRect)
case roundedRectLine(startPoint: CGPoint, width: CGFloat, diameter: CGFloat)
}
private let backgroundNode: ASDisplayNode
private let effectNode: ShimmerEffectNode
private let foregroundNode: ASImageNode
private var currentShapes: [Shape] = []
private var currentBackgroundColor: UIColor?
private var currentForegroundColor: UIColor?
private var currentShimmeringColor: UIColor?
private var currentSize = CGSize()
override init() {
self.backgroundNode = ASDisplayNode()
self.effectNode = ShimmerEffectNode()
self.foregroundNode = ASImageNode()
self.foregroundNode.displaysAsynchronously = false
self.foregroundNode.displayWithoutProcessing = true
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.effectNode)
self.addSubnode(self.foregroundNode)
}
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
}
func update(backgroundColor: UIColor, foregroundColor: UIColor, shimmeringColor: UIColor, shapes: [Shape], size: CGSize) {
if self.currentShapes == shapes, let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor), self.currentSize == size {
return
}
self.currentBackgroundColor = backgroundColor
self.currentForegroundColor = foregroundColor
self.currentShimmeringColor = shimmeringColor
self.currentShapes = shapes
self.currentSize = size
self.backgroundNode.backgroundColor = foregroundColor
self.effectNode.update(backgroundColor: foregroundColor, foregroundColor: shimmeringColor)
self.foregroundNode.image = generateImage(size, rotatedContext: { size, context in
context.setFillColor(backgroundColor.cgColor)
context.setBlendMode(.copy)
context.fill(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.clear.cgColor)
for shape in shapes {
switch shape {
case let .circle(frame):
context.fillEllipse(in: frame)
case let .roundedRectLine(startPoint, width, diameter):
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
}
}
})
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.foregroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
}
}
public struct ItemListPeerItemEditing: Equatable {
public var editable: Bool
public var editing: Bool
@ -107,6 +296,14 @@ public struct ItemListPeerItemRevealOptions {
}
}
public struct ItemListPeerItemShimmering {
public var alternationIndex: Int
public init(alternationIndex: Int) {
self.alternationIndex = alternationIndex
}
}
public final class ItemListPeerItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let dateTimeFormat: PresentationDateTimeFormat
@ -135,8 +332,10 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
let hasTopGroupInset: Bool
let noInsets: Bool
public let tag: ItemListItemTag?
let header: ListViewItemHeader?
let shimmering: ItemListPeerItemShimmering?
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, tag: ItemListItemTag? = nil) {
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil) {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@ -164,12 +363,14 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
self.hasTopGroupInset = hasTopGroupInset
self.noInsets = noInsets
self.tag = tag
self.header = header
self.shimmering = shimmering
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ItemListPeerItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), self.getHeaderAtTop(top: previousItem, bottom: nextItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
@ -182,6 +383,19 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
}
}
private func getHeaderAtTop(top: ListViewItem?, bottom: ListViewItem?) -> Bool {
var headerAtTop = false
if let top = top as? ItemListPeerItem, top.header != nil {
if top.header?.id != self.header?.id {
headerAtTop = true
}
} else if self.header != nil {
headerAtTop = true
}
return headerAtTop
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ItemListPeerItemNode {
@ -193,7 +407,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
}
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), self.getHeaderAtTop(top: previousItem, bottom: nextItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply(false, animated)
@ -232,8 +446,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
private var switchNode: SwitchNode?
private var checkNode: ASImageNode?
private var shimmerNode: LoadingShimmerNode?
private var absoluteLocation: (CGRect, CGSize)?
private var peerPresenceManager: PeerPresenceStatusManager?
private var layoutParams: (ItemListPeerItem, ListViewItemLayoutParams, ItemListNeighbors)?
private var layoutParams: (ItemListPeerItem, ListViewItemLayoutParams, ItemListNeighbors, Bool)?
private var editableControlNode: ItemListEditableControlNode?
@ -303,7 +520,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2)
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2, layoutParams.3)
apply(false, true)
}
})
@ -317,7 +534,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
}
}
public func asyncLayout() -> (_ item: ItemListPeerItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
public func asyncLayout() -> (_ item: ItemListPeerItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ headerAtTop: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
@ -334,7 +551,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let currentHasBadge = self.labelBadgeNode.image != nil
return { item, params, neighbors in
return { item, params, neighbors, headerAtTop in
var updateArrowImage: UIImage?
var updatedTheme: PresentationTheme?
@ -579,8 +796,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
insets.top = 0.0
insets.bottom = 0.0
}
if headerAtTop, let header = item.header {
insets.top += header.height + 18.0
}
let titleSpacing: CGFloat = 1.0
let titleSpacing: CGFloat = statusLayout.size.height == 0.0 ? 0.0 : 1.0
let minHeight: CGFloat = titleLayout.size.height + verticalInset * 2.0
let rawHeight: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + statusLayout.size.height
@ -602,7 +822,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
return (layout, { [weak self] synchronousLoad, animated in
if let strongSelf = self {
strongSelf.layoutParams = (item, params, neighbors)
strongSelf.layoutParams = (item, params, neighbors, headerAtTop)
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil
@ -829,6 +1049,44 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.peerPresenceManager?.reset(presence: presence)
}
if let shimmering = item.shimmering {
strongSelf.avatarNode.isHidden = true
strongSelf.titleNode.isHidden = true
let shimmerNode: LoadingShimmerNode
if let current = strongSelf.shimmerNode {
shimmerNode = current
} else {
shimmerNode = LoadingShimmerNode()
strongSelf.shimmerNode = shimmerNode
strongSelf.insertSubnode(shimmerNode, aboveSubnode: strongSelf.backgroundNode)
}
shimmerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
if let (rect, size) = strongSelf.absoluteLocation {
shimmerNode.updateAbsoluteRect(rect, within: size)
}
var shapes: [LoadingShimmerNode.Shape] = []
shapes.append(.circle(strongSelf.avatarNode.frame))
let possibleLines: [[CGFloat]] = [
[50.0, 40.0],
[70.0, 45.0]
]
let titleFrame = strongSelf.titleNode.frame
let lineDiameter: CGFloat = 10.0
var lineStart = titleFrame.minX
for lineWidth in possibleLines[shimmering.alternationIndex % possibleLines.count] {
shapes.append(.roundedRectLine(startPoint: CGPoint(x: lineStart, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: lineWidth, diameter: lineDiameter))
lineStart += lineWidth + lineDiameter
}
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize)
} else if let shimmerNode = strongSelf.shimmerNode {
strongSelf.avatarNode.isHidden = false
strongSelf.titleNode.isHidden = false
strongSelf.shimmerNode = nil
shimmerNode.removeFromSupernode()
}
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
strongSelf.setRevealOptions((left: [], right: peerRevealOptions))
@ -940,13 +1198,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
}
override public func revealOptionsInteractivelyOpened() {
if let (item, _, _) = self.layoutParams {
if let (item, _, _, _) = self.layoutParams {
item.setPeerIdWithRevealedOptions(item.peer.id, nil)
}
}
override public func revealOptionsInteractivelyClosed() {
if let (item, _, _) = self.layoutParams {
if let (item, _, _, _) = self.layoutParams {
item.setPeerIdWithRevealedOptions(nil, item.peer.id)
}
}
@ -955,7 +1213,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed()
if let (item, _, _) = self.layoutParams {
if let (item, _, _, _) = self.layoutParams {
if let revealOptions = item.revealOptions {
if option.key >= 0 && option.key < Int32(revealOptions.options.count) {
revealOptions.options[Int(option.key)].action()
@ -967,8 +1225,205 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
}
private func toggleUpdated(_ value: Bool) {
if let (item, _, _) = self.layoutParams {
if let (item, _, _, _) = self.layoutParams {
item.toggleUpdated?(value)
}
}
override public func header() -> ListViewItemHeader? {
return self.layoutParams?.0.header
}
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
var rect = rect
rect.origin.y += self.insets.top
self.absoluteLocation = (rect, containerSize)
if let shimmerNode = self.shimmerNode {
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
}
}
}
public final class ItemListPeerItemHeader: ListViewItemHeader {
public let id: Int64
public let text: String
public let additionalText: String
public let stickDirection: ListViewItemHeaderStickDirection = .topEdge
public let theme: PresentationTheme
public let strings: PresentationStrings
public let actionTitle: String?
public let action: (() -> Void)?
public let height: CGFloat = 28.0
public init(theme: PresentationTheme, strings: PresentationStrings, text: String, additionalText: String, actionTitle: String? = nil, id: Int64, action: (() -> Void)? = nil) {
self.text = text
self.additionalText = additionalText
self.id = id
self.theme = theme
self.strings = strings
self.actionTitle = actionTitle
self.action = action
}
public func node() -> ListViewItemHeaderNode {
return ItemListPeerItemHeaderNode(theme: self.theme, strings: self.strings, text: self.text, additionalText: self.additionalText, actionTitle: self.actionTitle, action: self.action)
}
public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
(node as? ItemListPeerItemHeaderNode)?.update(text: self.text, additionalText: self.additionalText, actionTitle: self.actionTitle, action: self.action)
}
}
public final class ItemListPeerItemHeaderNode: ListViewItemHeaderNode, ItemListHeaderItemNode {
private var theme: PresentationTheme
private var strings: PresentationStrings
private var actionTitle: String?
private var action: (() -> Void)?
private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)?
private let backgroundNode: ASDisplayNode
private let snappedBackgroundNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private let textNode: ImmediateTextNode
private let additionalTextNode: ImmediateTextNode
private let actionTextNode: ImmediateTextNode
private let actionButton: HighlightableButtonNode
private var stickDistanceFactor: CGFloat?
public init(theme: PresentationTheme, strings: PresentationStrings, text: String, additionalText: String, actionTitle: String?, action: (() -> Void)?) {
self.theme = theme
self.strings = strings
self.actionTitle = actionTitle
self.action = action
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = theme.list.blocksBackgroundColor
self.snappedBackgroundNode = ASDisplayNode()
self.snappedBackgroundNode.backgroundColor = theme.rootController.navigationBar.backgroundColor
self.snappedBackgroundNode.alpha = 0.0
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = theme.list.itemBlocksSeparatorColor
self.separatorNode.alpha = 0.0
let titleFont = Font.regular(13.0)
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 1
self.textNode.attributedText = NSAttributedString(string: text, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
self.additionalTextNode = ImmediateTextNode()
self.additionalTextNode.displaysAsynchronously = false
self.additionalTextNode.maximumNumberOfLines = 1
self.additionalTextNode.attributedText = NSAttributedString(string: additionalText, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
self.actionTextNode = ImmediateTextNode()
self.actionTextNode.displaysAsynchronously = false
self.actionTextNode.maximumNumberOfLines = 1
self.actionTextNode.attributedText = NSAttributedString(string: actionTitle ?? "", font: titleFont, textColor: action == nil ? theme.list.sectionHeaderTextColor : theme.list.itemAccentColor)
self.actionButton = HighlightableButtonNode()
self.actionButton.isUserInteractionEnabled = self.action != nil
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.snappedBackgroundNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.textNode)
self.addSubnode(self.additionalTextNode)
self.addSubnode(self.actionTextNode)
self.addSubnode(self.actionButton)
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
self.actionButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.actionTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.actionTextNode.alpha = 0.4
} else {
strongSelf.actionTextNode.alpha = 1.0
strongSelf.actionTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
@objc private func actionButtonPressed() {
self.action?()
}
public func updateTheme(theme: PresentationTheme) {
self.theme = theme
self.backgroundNode.backgroundColor = theme.list.blocksBackgroundColor
self.snappedBackgroundNode.backgroundColor = theme.rootController.navigationBar.backgroundColor
self.separatorNode.backgroundColor = theme.list.itemBlocksSeparatorColor
let titleFont = Font.regular(13.0)
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: titleFont, textColor: theme.list.sectionHeaderTextColor)
self.additionalTextNode.attributedText = NSAttributedString(string: self.additionalTextNode.attributedText?.string ?? "", font: titleFont, textColor: theme.list.sectionHeaderTextColor)
self.actionTextNode.attributedText = NSAttributedString(string: self.actionTextNode.attributedText?.string ?? "", font: titleFont, textColor: theme.list.sectionHeaderTextColor)
}
public func update(text: String, additionalText: String, actionTitle: String?, action: (() -> Void)?) {
self.actionTitle = actionTitle
self.action = action
let titleFont = Font.regular(13.0)
self.textNode.attributedText = NSAttributedString(string: text, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
self.additionalTextNode.attributedText = NSAttributedString(string: additionalText, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
self.actionTextNode.attributedText = NSAttributedString(string: actionTitle ?? "", font: titleFont, textColor: action == nil ? theme.list.sectionHeaderTextColor : theme.list.itemAccentColor)
self.actionButton.isUserInteractionEnabled = self.action != nil
if let (size, leftInset, rightInset) = self.validLayout {
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
}
}
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.validLayout = (size, leftInset, rightInset)
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.snappedBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel))
let sideInset: CGFloat = 15.0 + leftInset
let actionTextSize = self.actionTextNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height))
let additionalTextSize = self.additionalTextNode.updateLayout(CGSize(width: size.width - sideInset * 2.0 - actionTextSize.width - 8.0, height: size.height))
let textSize = self.textNode.updateLayout(CGSize(width: max(1.0, size.width - sideInset * 2.0 - actionTextSize.width - 8.0 - additionalTextSize.width), height: size.height))
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 7.0), size: textSize)
self.textNode.frame = textFrame
self.additionalTextNode.frame = CGRect(origin: CGPoint(x: textFrame.maxX, y: 7.0), size: additionalTextSize)
self.actionTextNode.frame = CGRect(origin: CGPoint(x: size.width - sideInset - actionTextSize.width, y: 7.0), size: actionTextSize)
self.actionButton.frame = CGRect(origin: CGPoint(x: size.width - sideInset - actionTextSize.width, y: 0.0), size: CGSize(width: actionTextSize.width, height: size.height))
}
override public func animateRemoved(duration: Double) {
self.alpha = 0.0
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: true)
}
override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
if self.stickDistanceFactor == factor {
return
}
self.stickDistanceFactor = factor
if let (size, leftInset, _) = self.validLayout {
if leftInset.isZero {
transition.updateAlpha(node: self.separatorNode, alpha: 1.0)
transition.updateAlpha(node: self.snappedBackgroundNode, alpha: (1.0 - factor) * 0.0 + factor * 1.0)
} else {
let distance = factor * size.height
let alpha = abs(distance) / 16.0
transition.updateAlpha(node: self.separatorNode, alpha: max(0.0, min(1.0, alpha)))
transition.updateAlpha(node: self.snappedBackgroundNode, alpha: 0.0)
}
}
}
}

View File

@ -324,7 +324,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
let separatorHeight = UIScreenPixel
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
var editingOffset: CGFloat = 0.0
var reorderInset: CGFloat = 0.0
@ -485,7 +485,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
if strongSelf.reorderControlNode == nil {
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false)
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
strongSelf.reorderControlNode = reorderControlNode
strongSelf.addSubnode(reorderControlNode)
reorderControlNode.alpha = 0.0

View File

@ -56,6 +56,7 @@ public struct ItemListBackButton: Equatable {
public enum ItemListControllerTitle: Equatable {
case text(String)
case textWithSubtitle(String, String)
case sectionControl([String], Int)
}
@ -197,12 +198,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
public var willScrollToTop: (() -> Void)?
public func setReorderEntry<T: ItemListNodeEntry>(_ f: @escaping (Int, Int, [T]) -> Void) {
public func setReorderEntry<T: ItemListNodeEntry>(_ f: @escaping (Int, Int, [T]) -> Signal<Bool, NoError>) {
self.reorderEntry = { a, b, list in
f(a, b, list.map { $0 as! T })
return f(a, b, list.map { $0 as! T })
}
}
private var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Void)? {
private var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Signal<Bool, NoError>)? {
didSet {
if self.isNodeLoaded {
(self.displayNode as! ItemListControllerNode).reorderEntry = self.reorderEntry
@ -287,6 +288,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
strongSelf.title = text
strongSelf.navigationItem.titleView = nil
strongSelf.segmentedTitleView = nil
case let .textWithSubtitle(title, subtitle):
strongSelf.title = ""
strongSelf.navigationItem.titleView = ItemListTextWithSubtitleTitleView(theme: controllerState.presentationData.theme, title: title, subtitle: subtitle)
strongSelf.segmentedTitleView = nil
case let .sectionControl(sections, index):
strongSelf.title = ""
if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections {
@ -417,6 +422,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme
if let titleView = strongSelf.navigationItem.titleView as? ItemListTextWithSubtitleTitleView {
titleView.updateTheme(theme: controllerState.presentationData.theme)
}
var items = strongSelf.navigationItem.rightBarButtonItems ?? []
for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count {
if case .activity = strongSelf.rightNavigationButtonTitleAndStyle[i].1 {
@ -517,6 +526,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
self.didDisappear?(animated)
}
public var listInsets: UIEdgeInsets {
return (self.displayNode as! ItemListControllerNode).listNode.insets
}
public func frameForItemNode(_ predicate: (ListViewItemNode) -> Bool) -> CGRect? {
var result: CGRect?
(self.displayNode as! ItemListControllerNode).listNode.forEachItemNode { itemNode in
@ -598,3 +611,68 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
})]
}
}
private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitleView {
private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
private var validLayout: (CGSize, CGRect)?
init(theme: PresentationTheme, title: String, subtitle: String) {
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.maximumNumberOfLines = 1
self.titleNode.isOpaque = false
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode = ImmediateTextNode()
self.subtitleNode.displaysAsynchronously = false
self.subtitleNode.maximumNumberOfLines = 1
self.subtitleNode.isOpaque = false
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
super.init(frame: CGRect())
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateTheme(theme: PresentationTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
override func layoutSubviews() {
super.layoutSubviews()
if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
}
}
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, clearBounds)
let titleSize = self.titleNode.updateLayout(size)
let subtitleSize = self.subtitleNode.updateLayout(size)
let spacing: CGFloat = 0.0
let contentHeight = titleSize.height + spacing + subtitleSize.height
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - contentHeight) / 2.0)), size: titleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + spacing), size: subtitleSize)
self.titleNode.frame = titleFrame
self.subtitleNode.frame = subtitleFrame
}
func animateLayoutTransition() {
}
}

View File

@ -8,6 +8,10 @@ import SyncCore
import TelegramPresentationData
import MergeLists
public protocol ItemListHeaderItemNode: class {
func updateTheme(theme: PresentationTheme)
}
public typealias ItemListSectionId = Int32
public protocol ItemListNodeAnyEntry {
@ -217,7 +221,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
public var contentOffsetChanged: ((ListViewVisibleContentOffset, Bool) -> Void)?
public var contentScrollingEnded: ((ListView) -> Bool)?
public var searchActivated: ((Bool) -> Void)?
public var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Void)?
public var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Signal<Bool, NoError>)?
public var reorderCompleted: (([ItemListNodeAnyEntry]) -> Void)?
public var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
@ -269,7 +273,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.listNode.reorderItem = { [weak self] fromIndex, toIndex, opaqueTransactionState in
if let strongSelf = self, let reorderEntry = strongSelf.reorderEntry, let mergedEntries = (opaqueTransactionState as? ItemListNodeOpaqueState)?.mergedEntries {
if fromIndex >= 0 && fromIndex < mergedEntries.count && toIndex >= 0 && toIndex < mergedEntries.count {
reorderEntry(fromIndex, toIndex, mergedEntries)
return reorderEntry(fromIndex, toIndex, mergedEntries)
}
}
return .single(false)
@ -467,6 +471,12 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.rightOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor
}
}
self.listNode.forEachItemHeaderNode({ itemHeaderNode in
if let itemHeaderNode = itemHeaderNode as? ItemListHeaderItemNode {
itemHeaderNode.updateTheme(theme: transition.theme)
}
})
}
if let updateStyle = transition.updateStyle {
@ -501,9 +511,12 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
options.insert(.PreferSynchronousDrawing)
options.insert(.AnimateAlpha)
} else if transition.crossfade {
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing)
options.insert(.AnimateCrossfade)
} else {
options.insert(.Synchronous)
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing)
}
if self.alwaysSynchronous {

View File

@ -19,7 +19,7 @@ public final class ItemListEditableReorderControlNode: ASDisplayNode {
self.addSubnode(self.iconNode)
}
public static func asyncLayout(_ node: ItemListEditableReorderControlNode?) -> (_ theme: PresentationTheme) -> (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode) {
public static func asyncLayout(_ node: ItemListEditableReorderControlNode?) -> (_ theme: PresentationTheme) -> (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode) {
return { theme in
let image = PresentationResourcesItemList.itemListReorderIndicatorIcon(theme)
@ -31,9 +31,9 @@ public final class ItemListEditableReorderControlNode: ASDisplayNode {
}
resultNode.iconNode.image = image
return (40.0, { height, offsetForLabel in
return (40.0, { height, offsetForLabel, transition in
if let image = image {
resultNode.iconNode.frame = CGRect(origin: CGPoint(x: 7.0, y: floor((height - image.size.height) / 2.0) - (offsetForLabel ? 6.0 : 0.0)), size: image.size)
transition.updateFrame(node: resultNode.iconNode, frame: CGRect(origin: CGPoint(x: 7.0, y: floor((height - image.size.height) / 2.0) - (offsetForLabel ? 6.0 : 0.0)), size: image.size))
}
return resultNode
})

View File

@ -82,6 +82,10 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
super.init(layerBacked: layerBacked, dynamicBounce: dynamicBounce, rotated: rotated, seeThrough: seeThrough)
}
open var controlsContainer: ASDisplayNode {
return self
}
override open func didLoad() {
super.didLoad()
@ -310,7 +314,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
revealNode.updateRevealOffset(offset: 0.0, sideInset: leftInset, transition: .immediate)
}
self.addSubnode(revealNode)
self.controlsContainer.addSubnode(revealNode)
}
}
@ -332,7 +336,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
revealNode.updateRevealOffset(offset: 0.0, sideInset: -rightInset, transition: .immediate)
}
self.addSubnode(revealNode)
self.controlsContainer.addSubnode(revealNode)
}
}
@ -492,4 +496,8 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
}
self.hapticFeedback?.impact(.medium)
}
override open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
super.animateFrameTransition(progress, currentValue)
}
}

View File

@ -325,8 +325,6 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
@ -336,7 +334,11 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
strongSelf.textNode.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
if strongSelf.animationForKey("apparentHeight") == nil {
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
}
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - 16.0 - rightInset, height: textLayout.size.height + 1.0))
let _ = limitTextApply()
@ -394,6 +396,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
let textBottomInset: CGFloat = 11.0
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: self.bottomStripeNode.frame.minX, y: contentSize.height), size: CGSize(width: self.bottomStripeNode.frame.size.width, height: separatorHeight))
self.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: max(0.0, params.width - leftInset - params.rightInset), height: max(0.0, contentSize.height - textTopInset - textBottomInset)))

View File

@ -9,6 +9,7 @@ import Markdown
public enum ItemListTextItemText {
case plain(String)
case large(String)
case markdown(String)
}
@ -23,13 +24,15 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
let linkAction: ((ItemListTextItemLinkAction) -> Void)?
let style: ItemListStyle
public let isAlwaysPlain: Bool = true
public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks) {
public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks, tag: ItemListItemTag? = nil) {
self.presentationData = presentationData
self.text = text
self.sectionId = sectionId
self.linkAction = linkAction
self.style = style
self.tag = tag
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -69,12 +72,16 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
}
}
public class ItemListTextItemNode: ListViewItemNode {
public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
private let titleNode: TextNode
private let activateArea: AccessibilityAreaNode
private var item: ItemListTextItem?
public var tag: ItemListItemTag? {
return self.item?.tag
}
public init() {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
@ -105,15 +112,19 @@ public class ItemListTextItemNode: ListViewItemNode {
return { item, params, neighbors in
let leftInset: CGFloat = 15.0 + params.leftInset
let verticalInset: CGFloat = 7.0
let topInset: CGFloat = 7.0
var bottomInset: CGFloat = 7.0
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize))
let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let attributedText: NSAttributedString
switch item.text {
case let .plain(text):
attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.freeTextColor)
case let .large(text):
attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
case let .markdown(text):
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
@ -123,8 +134,12 @@ public class ItemListTextItemNode: ListViewItemNode {
let contentSize: CGSize
contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset + verticalInset)
let insets = itemListNeighborsGroupedInsets(neighbors)
var insets = itemListNeighborsGroupedInsets(neighbors)
if case .large = item.text {
insets.top = 14.0
bottomInset = -6.0
}
contentSize = CGSize(width: params.width, height: titleLayout.size.height + topInset + bottomInset)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -139,7 +154,7 @@ public class ItemListTextItemNode: ListViewItemNode {
let _ = titleApply()
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)
}
})
}

View File

@ -24,19 +24,14 @@
NSInteger _number;
UIColor *_checkColor;
CGAffineTransform TGCheckButtonDefaultTransform;
}
@end
@implementation TGCheckButtonView
static NSMutableDictionary *backgroundImages;
static NSMutableDictionary *fillImages;
static CGAffineTransform TGCheckButtonDefaultTransform;
+ (void)resetCache
{
[backgroundImages removeAllObjects];
[fillImages removeAllObjects];
+ (void)resetCache {
}
- (instancetype)initWithStyle:(TGCheckButtonStyle)style {
@ -55,15 +50,12 @@ static CGAffineTransform TGCheckButtonDefaultTransform;
self = [super initWithFrame:CGRectMake(0, 0, size.width, size.height)];
if (self != nil)
{
static dispatch_once_t onceToken;
static CGFloat screenScale = 2.0f;
dispatch_once(&onceToken, ^
{
TGCheckButtonDefaultTransform = CGAffineTransformMakeRotation(-M_PI_4);
backgroundImages = [[NSMutableDictionary alloc] init];
fillImages = [[NSMutableDictionary alloc] init];
screenScale = [UIScreen mainScreen].scale;
});
CGFloat screenScale = 2.0f;
TGCheckButtonDefaultTransform = CGAffineTransformMakeRotation(-M_PI_4);
NSMutableDictionary *backgroundImages = [[NSMutableDictionary alloc] init];
NSMutableDictionary *fillImages = [[NSMutableDictionary alloc] init];
screenScale = [UIScreen mainScreen].scale;
int32_t hex = 0x29c519;
UIColor *greenColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f];

View File

@ -18,7 +18,7 @@
- (void)appendVideoPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime;
- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer;
- (void)finishRecording;
- (void)finishRecording:(void(^)())completed;
- (NSTimeInterval)videoDuration;

View File

@ -105,7 +105,7 @@ typedef enum {
if (_status != TGMovieRecorderStatusIdle)
return;
[self transitionToStatus:TGMovieRecorderStatusPreparingToRecord error:nil];
[self transitionToStatus:TGMovieRecorderStatusPreparingToRecord error:nil completed:nil];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^
@ -138,9 +138,9 @@ typedef enum {
@synchronized (self)
{
if (error || !succeed)
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:nil];
else
[self transitionToStatus:TGMovieRecorderStatusRecording error:nil];
[self transitionToStatus:TGMovieRecorderStatusRecording error:nil completed:nil];
}
}
} );
@ -169,8 +169,9 @@ typedef enum {
[self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
}
- (void)finishRecording
- (void)finishRecording:(void(^)())completed
{
printf("finishRecording %d\n", _status);
@synchronized (self)
{
bool shouldFinishRecording = false;
@ -190,9 +191,10 @@ typedef enum {
}
if (shouldFinishRecording)
[self transitionToStatus:TGMovieRecorderStatusFinishingWaiting error:nil];
else
[self transitionToStatus:TGMovieRecorderStatusFinishingWaiting error:nil completed:completed];
else {
return;
}
}
dispatch_async(_writingQueue, ^
@ -201,10 +203,14 @@ typedef enum {
{
@synchronized (self)
{
if (_status != TGMovieRecorderStatusFinishingWaiting)
if (_status != TGMovieRecorderStatusFinishingWaiting) {
if (completed) {
completed();
}
return;
}
[self transitionToStatus:TGMovieRecorderStatusFinishingCommiting error:nil];
[self transitionToStatus:TGMovieRecorderStatusFinishingCommiting error:nil completed:nil];
}
[_assetWriter finishWritingWithCompletionHandler:^
@ -213,9 +219,9 @@ typedef enum {
{
NSError *error = _assetWriter.error;
if (error)
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:completed];
else
[self transitionToStatus:TGMovieRecorderStatusFinished error:nil];
[self transitionToStatus:TGMovieRecorderStatusFinished error:nil completed:completed];
}
}];
}
@ -340,7 +346,7 @@ typedef enum {
NSError *error = _assetWriter.error;
@synchronized (self)
{
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:nil];
}
}
}
@ -349,8 +355,10 @@ typedef enum {
});
}
- (void)transitionToStatus:(TGMovieRecorderStatus)newStatus error:(NSError *)error
- (void)transitionToStatus:(TGMovieRecorderStatus)newStatus error:(NSError *)error completed:(void(^)())completed
{
printf("recorder transitionToStatus %d\n", newStatus);
bool shouldNotifyDelegate = false;
if (newStatus != _status)
@ -389,6 +397,7 @@ typedef enum {
break;
case TGMovieRecorderStatusFinished:
printf("TGMovieRecorderStatusFinished _delegate == nil = %d\n", (int)(_delegate == nil));
[_delegate movieRecorderDidFinishRecording:self];
break;
@ -399,9 +408,16 @@ typedef enum {
default:
break;
}
if (completed) {
completed();
}
}
});
}
} else {
if (completed) {
completed();
}
}
}
- (bool)setupAssetWriterAudioInputWithSourceFormatDescription:(CMFormatDescriptionRef)audioFormatDescription settings:(NSDictionary *)audioSettings

View File

@ -21,7 +21,7 @@
- (void)stopRunning;
- (void)startRecording:(NSURL *)url preset:(TGMediaVideoConversionPreset)preset liveUpload:(bool)liveUpload;
- (void)stopRecording;
- (void)stopRecording:(void (^)())completed;
- (CGAffineTransform)transformForOrientation:(AVCaptureVideoOrientation)orientation;

View File

@ -111,6 +111,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
- (void)dealloc
{
printf("Camera pipeline dealloc\n");
[self destroyCaptureSession];
}
@ -134,7 +135,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
{
_running = false;
[self stopRecording];
[self stopRecording:^{}];
[_captureSession stopRunning];
[self captureSessionDidStopRunning];
@ -285,7 +286,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
- (void)captureSessionDidStopRunning
{
[self stopRecording];
[self stopRecording:^{}];
[self destroyVideoPipeline];
}
@ -684,20 +685,29 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
[recorder prepareToRecord];
}
- (void)stopRecording
- (void)stopRecording:(void (^)())completed
{
[[TGVideoCameraPipeline cameraQueue] dispatch:^
{
@synchronized (self)
{
if (_recordingStatus != TGVideoCameraRecordingStatusRecording)
if (_recordingStatus != TGVideoCameraRecordingStatusRecording) {
if (completed) {
completed();
}
return;
}
[self transitionToRecordingStatus:TGVideoCameraRecordingStatusStoppingRecording error:nil];
}
_resultDuration = _recorder.videoDuration;
[_recorder finishRecording];
[_recorder finishRecording:^{
__unused __auto_type description = [self description];
if (completed) {
completed();
}
}];
}];
}
@ -734,6 +744,8 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
- (void)movieRecorderDidFinishRecording:(TGVideoCameraMovieRecorder *)__unused recorder
{
printf("movieRecorderDidFinishRecording\n");
@synchronized (self)
{
if (_recordingStatus != TGVideoCameraRecordingStatusStoppingRecording)
@ -750,6 +762,8 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
- (void)transitionToRecordingStatus:(TGVideoCameraRecordingStatus)newStatus error:(NSError *)error
{
printf("transitionToRecordingStatus %d\n", newStatus);
TGVideoCameraRecordingStatus oldStatus = _recordingStatus;
_recordingStatus = newStatus;
@ -763,12 +777,16 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
}
else
{
__strong id<TGVideoCameraPipelineDelegate> delegate = _delegate;
if ((oldStatus == TGVideoCameraRecordingStatusStartingRecording) && (newStatus == TGVideoCameraRecordingStatusRecording))
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingDidStart:self]; };
delegateCallbackBlock = ^{ [delegate capturePipelineRecordingDidStart:self]; };
else if ((oldStatus == TGVideoCameraRecordingStatusRecording) && (newStatus == TGVideoCameraRecordingStatusStoppingRecording))
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingWillStop:self]; };
delegateCallbackBlock = ^{ [delegate capturePipelineRecordingWillStop:self]; };
else if ((oldStatus == TGVideoCameraRecordingStatusStoppingRecording) && (newStatus == TGVideoCameraRecordingStatusIdle))
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingDidStop:self duration:_resultDuration liveUploadData:_liveUploadData thumbnailImage:_recordingThumbnail thumbnails:_thumbnails]; };
delegateCallbackBlock = ^{
printf("transitionToRecordingStatus delegateCallbackBlock _delegate == nil = %d\n", (int)(delegate == nil));
[delegate capturePipelineRecordingDidStop:self duration:_resultDuration liveUploadData:_liveUploadData thumbnailImage:_recordingThumbnail thumbnails:_thumbnails];
};
}
if (delegateCallbackBlock != nil)

View File

@ -201,6 +201,7 @@ typedef enum
- (void)dealloc
{
printf("Video controller dealloc\n");
[_thumbnailsDisposable dispose];
[[NSNotificationCenter defaultCenter] removeObserver:_didEnterBackgroundObserver];
[_activityDisposable dispose];
@ -649,9 +650,11 @@ typedef enum
return;
[_activityDisposable dispose];
[self stopRecording];
[self dismiss:false];
[self stopRecording:^{
TGDispatchOnMainThread(^{
[self dismiss:false];
});
}];
}
- (void)buttonInteractionUpdate:(CGPoint)value
@ -684,7 +687,7 @@ typedef enum
_switchButton.userInteractionEnabled = false;
[_activityDisposable dispose];
[self stopRecording];
[self stopRecording:^{}];
return true;
}
@ -939,9 +942,9 @@ typedef enum
[self startRecordingTimer];
}
- (void)stopRecording
- (void)stopRecording:(void (^)())completed
{
[_capturePipeline stopRecording];
[_capturePipeline stopRecording:completed];
[_buttonHandler ignoreEventsFor:1.0f andDisable:true];
}

View File

@ -296,7 +296,13 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
})!
itemViews.append(locationItem)
if (peer is TelegramGroup || peer is TelegramChannel) && canSendMessagesToPeer(peer) && canSendPolls {
var peerSupportsPolls = false
if peer is TelegramGroup || peer is TelegramChannel {
peerSupportsPolls = true
} else if let user = peer as? TelegramUser, let _ = user.botInfo {
peerSupportsPolls = true
}
if peerSupportsPolls && canSendMessagesToPeer(peer) && canSendPolls {
let pollItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
controller?.dismiss(animated: true)
openPoll()

View File

@ -173,6 +173,9 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
}
public func currentlyInSplitView() -> Bool {
if let controller = self.controller as? LegacyController, let validLayout = controller.validLayout {
return validLayout.isNonExclusive
}
return false
}

View File

@ -48,6 +48,13 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent
}
public func checkPhotoAuthorizationStatus(for intent: TGPhotoAccessIntent, alertDismissCompletion: (() -> Void)!) -> Bool {
if let context = self.context {
DeviceAccess.authorizeAccess(to: .mediaLibrary(.send), presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
if !value {
alertDismissCompletion?()
}
})
}
return true
}

View File

@ -84,7 +84,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.placesBackgroundNode.isUserInteractionEnabled = true
self.placesButtonNode = HighlightableButtonNode()
self.placesButtonNode.setTitle("Places In This Area", with: Font.regular(17.0), with: presentationData.theme.rootController.navigationBar.buttonColor, for: .normal)
self.placesButtonNode.setTitle(presentationData.strings.Map_PlacesInThisArea, with: Font.regular(17.0), with: presentationData.theme.rootController.navigationBar.buttonColor, for: .normal)
self.shadowNode = ASImageNode()
self.shadowNode.contentMode = .scaleToFill

View File

@ -548,7 +548,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
if foundVenues == nil && !state.searchingVenuesAround {
displayingPlacesButton = true
} else if let previousLocation = foundVenuesLocation {
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
if currentLocation.distance(from: previousLocation) > 300 {
displayingPlacesButton = true
}

View File

@ -181,7 +181,7 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], allUpd
return (removeIndices, insertItems, updatedIndices)
}
@inlinable
//@inlinable
public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], isLess: (T, T) -> Bool, isEqual: (T, T) -> Bool, getId: (T) -> AnyHashable, allUpdated: Bool = false) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) {
var removeIndices: [Int] = []
var insertItems: [(Int, T, Int?)] = []
@ -207,6 +207,25 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], isLess
}
#endif
var leftStableIds: [AnyHashable] = []
var rightStableIds: [AnyHashable] = []
for item in leftList {
leftStableIds.append(getId(item))
}
for item in rightList {
rightStableIds.append(getId(item))
}
if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds {
/*var i = 0
var j = 0
while true {
if getId(leftList[i]) != getId(rightList[i]) {
}
}*/
print("order changed")
}
var currentList = leftList
var i = 0

View File

@ -1394,7 +1394,7 @@ static int32_t fixedTimeDifferenceValue = 0;
NSArray *currentListeners = [[NSArray alloc] initWithArray:strongSelf->_changeListeners];
for (id<MTContextChangeListener> listener in currentListeners) {
if ([listener respondsToSelector:@selector(contextLoggedOut:)])
[listener contextLoggedOut:self];
[listener contextLoggedOut:strongSelf];
}
}
}];

View File

@ -235,7 +235,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
if ((_mtState & MTProtoStateStopped) == 0)
{
[self setMtState:_mtState | MTProtoStateStopped];
[_context removeChangeListener:self];
if (_transport != nil)
{
_transport.delegate = nil;
@ -2098,6 +2098,9 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
int64_t dataMessageId = 0;
bool parseError = false;
NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId parseError:&parseError];
for (MTIncomingMessage *message in parsedMessages) {
if ([message.body isKindOfClass:[MTRpcResultMessage class]]) {
MTRpcResultMessage *rpcResultMessage = message.body;

View File

@ -160,7 +160,7 @@ final class SetupTwoStepVerificationContentNode: ASDisplayNode, UITextFieldDeleg
let minContentHeight = textHeight + inputHeight
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 contentOrigin = max(56.0, 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.subtitleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize))

View File

@ -698,7 +698,6 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
}, transition: .animated(duration: 0.5, curve: .spring))
}
if case let .enterEmail(enterEmail)? = self.innerState.data.state, case .create = enterEmail.state, enterEmail.email.isEmpty {
self.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: {
continueImpl()
})]), nil)

View File

@ -484,6 +484,22 @@ public final class PostboxEncoder {
}
}
public func encodeDataArray(_ value: [Data], forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.BytesArray.rawValue
self.buffer.write(&type, offset: 0, length: 1)
var length: Int32 = Int32(value.count)
self.buffer.write(&length, offset: 0, length: 4)
for object in value {
var length: Int32 = Int32(object.count)
self.buffer.write(&length, offset: 0, length: 4)
object.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
self.buffer.write(bytes, offset: 0, length: Int(length))
}
}
}
public func encodeObjectDictionary<K, V: PostboxCoding>(_ value: [K : V], forKey key: StaticString) where K: PostboxCoding {
self.encodeKey(key)
var t: Int8 = ValueType.ObjectDictionary.rawValue
@ -1173,6 +1189,31 @@ public final class PostboxDecoder {
}
}
public func decodeOptionalDataArrayForKey(_ key: StaticString) -> [Data]? {
if PostboxDecoder.positionOnKey(self.buffer.memory, offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .BytesArray) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.offset, 4)
self.offset += 4
var array: [Data] = []
array.reserveCapacity(Int(length))
var i: Int32 = 0
while i < length {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.offset, 4)
array.append(Data(bytes: self.buffer.memory.advanced(by: self.offset + 4), count: Int(length)))
self.offset += 4 + Int(length)
i += 1
}
return array
} else {
return nil
}
}
public func decodeObjectArrayForKey<T>(_ key: StaticString) -> [T] where T: PostboxCoding {
if PostboxDecoder.positionOnKey(self.buffer.memory, offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .ObjectArray) {
var length: Int32 = 0

View File

@ -37,6 +37,17 @@ final class ItemCacheTable: Table {
return key
}
private func lowerBound(collectionId: ItemCacheCollectionId) -> ValueBoxKey {
let key = ValueBoxKey(length: 1 + 1)
key.setInt8(0, value: ItemCacheSection.items.rawValue)
key.setInt8(1, value: collectionId)
return key
}
private func upperBound(collectionId: ItemCacheCollectionId) -> ValueBoxKey {
return self.lowerBound(collectionId: collectionId).successor
}
private func itemIdToAccessIndexKey(id: ItemCacheEntryId) -> ValueBoxKey {
let key = ValueBoxKey(length: 1 + 1 + id.key.length)
key.setInt8(0, value: ItemCacheSection.accessIndexToItemId.rawValue)
@ -72,6 +83,10 @@ final class ItemCacheTable: Table {
self.valueBox.remove(self.table, key: self.itemKey(id: id), secure: false)
}
func removeAll(collectionId: ItemCacheCollectionId) {
self.valueBox.removeRange(self.table, start: self.lowerBound(collectionId: collectionId), end: self.upperBound(collectionId: collectionId))
}
override func clearMemoryCache() {
}

View File

@ -525,6 +525,10 @@ public final class Message {
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
}
public func withUpdatedPeers(_ peers: SimpleDictionary<PeerId, Peer>) -> Message {
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
}
public func withUpdatedFlags(_ flags: MessageFlags) -> Message {
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
}

View File

@ -42,7 +42,7 @@ final class MessageHistoryFailedTable: Table {
self.updatedMessageIds.remove(id)
}
func get(peerId:PeerId) -> [MessageId] {
func get(peerId: PeerId) -> [MessageId] {
var ids:[MessageId] = []
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), keys: { key in
@ -72,4 +72,3 @@ final class MessageHistoryFailedTable: Table {
self.updatedPeerIds.removeAll()
}
}

View File

@ -2275,6 +2275,42 @@ final class MessageHistoryTable: Table {
return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds)
}
func renderMessagePeers(_ message: Message, peerTable: PeerTable) -> Message {
var author: Peer?
var peers = SimpleDictionary<PeerId, Peer>()
if let authorId = message.author?.id {
author = peerTable.get(authorId)
}
if let chatPeer = peerTable.get(message.id.peerId) {
peers[chatPeer.id] = chatPeer
if let associatedPeerId = chatPeer.associatedPeerId {
if let peer = peerTable.get(associatedPeerId) {
peers[peer.id] = peer
}
}
}
for media in message.media {
for peerId in media.peerIds {
if let peer = peerTable.get(peerId) {
peers[peer.id] = peer
}
}
}
for attribute in message.attributes {
for peerId in attribute.associatedPeerIds {
if let peer = peerTable.get(peerId) {
peers[peer.id] = peer
}
}
}
return message.withUpdatedPeers(peers)
}
func renderAssociatedMessages(associatedMessageIds: [MessageId], peerTable: PeerTable) -> SimpleDictionary<MessageId, Message> {
var associatedMessages = SimpleDictionary<MessageId, Message>()
for messageId in associatedMessageIds {

View File

@ -29,13 +29,13 @@ public struct MessageHistoryMessageEntry {
enum MutableMessageHistoryEntry {
case IntermediateMessageEntry(IntermediateMessage, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?)
case MessageEntry(MessageHistoryMessageEntry, reloadAssociatedMessages: Bool)
case MessageEntry(MessageHistoryMessageEntry, reloadAssociatedMessages: Bool, reloadPeers: Bool)
var index: MessageIndex {
switch self {
case let .IntermediateMessageEntry(message, _, _):
return message.index
case let .MessageEntry(message, _):
case let .MessageEntry(message, _, _):
return message.message.index
}
}
@ -44,7 +44,7 @@ enum MutableMessageHistoryEntry {
switch self {
case let .IntermediateMessageEntry(message, _, _):
return message.tags
case let .MessageEntry(message, _):
case let .MessageEntry(message, _, _):
return message.message.tags
}
}
@ -53,8 +53,8 @@ enum MutableMessageHistoryEntry {
switch self {
case let .IntermediateMessageEntry(message, _, monthLocation):
return .IntermediateMessageEntry(message, location, monthLocation)
case let .MessageEntry(message, reloadAssociatedMessages):
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: location, monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: location, monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
@ -62,8 +62,8 @@ enum MutableMessageHistoryEntry {
switch self {
case let .IntermediateMessageEntry(message, location, _):
return .IntermediateMessageEntry(message, location, monthLocation)
case let .MessageEntry(message, reloadAssociatedMessages):
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: message.location, monthLocation: monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: message.location, monthLocation: monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
@ -79,12 +79,12 @@ enum MutableMessageHistoryEntry {
} else {
return self
}
case let .MessageEntry(message, reloadAssociatedMessages):
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
if let location = message.location {
if message.message.index > index {
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
} else {
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
} else {
return self
@ -107,15 +107,15 @@ enum MutableMessageHistoryEntry {
} else {
return self
}
case let .MessageEntry(message, reloadAssociatedMessages):
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
if let location = message.location {
if message.message.index > index {
//assert(location.index > 0)
//assert(location.count != 0)
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
} else {
//assert(location.count != 0)
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
} else {
return self
@ -128,10 +128,10 @@ enum MutableMessageHistoryEntry {
case let .IntermediateMessageEntry(message, location, monthLocation):
let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia)
return .IntermediateMessageEntry(updatedMessage, location, monthLocation)
case let .MessageEntry(value, reloadAssociatedMessages):
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
let message = value.message
let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds)
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
@ -139,7 +139,7 @@ enum MutableMessageHistoryEntry {
switch self {
case let .IntermediateMessageEntry(message, location, monthLocation):
return []
case let .MessageEntry(value, _):
case let .MessageEntry(value, _, _):
return value.message.associatedMessageIds
}
}
@ -258,6 +258,7 @@ final class MutableMessageHistoryView {
let tag: MessageTags?
let namespaces: MessageIdNamespaces
private let orderStatistics: MessageHistoryViewOrderStatistics
private let clipHoles: Bool
private let anchor: HistoryViewInputAnchor
fileprivate var combinedReadStates: MessageHistoryViewReadState?
@ -271,10 +272,11 @@ final class MutableMessageHistoryView {
fileprivate(set) var sampledState: HistoryViewSample
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
self.anchor = inputAnchor
self.orderStatistics = orderStatistics
self.clipHoles = clipHoles
self.peerIds = peerIds
self.combinedReadStates = combinedReadStates
self.transientReadStates = transientReadStates
@ -290,12 +292,12 @@ final class MutableMessageHistoryView {
switch sampledState {
case let .ready(anchor, holes):
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
self.sampledState = self.state.sample(postbox: postbox)
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
case .loadHole:
break
}
}
self.sampledState = self.state.sample(postbox: postbox)
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
self.render(postbox: postbox)
}
@ -320,7 +322,7 @@ final class MutableMessageHistoryView {
break
}
}
self.sampledState = self.state.sample(postbox: postbox)
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
}
func refreshDueToExternalTransaction(postbox: Postbox) -> Bool {
@ -509,7 +511,7 @@ final class MutableMessageHistoryView {
break
}
}
self.sampledState = self.state.sample(postbox: postbox)
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
}
for operationSet in operations {

View File

@ -874,10 +874,10 @@ final class HistoryViewLoadedState {
let currentLocation = nextLocation
nextLocation = nextLocation.successor
switch entry {
case let .IntermediateMessageEntry(message, _, monthLocation):
return .IntermediateMessageEntry(message, currentLocation, monthLocation)
case let .MessageEntry(entry, reloadAssociatedMessages):
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: currentLocation, monthLocation: entry.monthLocation, attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
case let .IntermediateMessageEntry(message, _, monthLocation):
return .IntermediateMessageEntry(message, currentLocation, monthLocation)
case let .MessageEntry(entry, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: currentLocation, monthLocation: entry.monthLocation, attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
}
@ -900,8 +900,8 @@ final class HistoryViewLoadedState {
switch entry {
case let .IntermediateMessageEntry(message, location, _):
return .IntermediateMessageEntry(message, location, MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth)))
case let .MessageEntry(entry, reloadAssociatedMessages):
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: entry.location, monthLocation: MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth)), attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
case let .MessageEntry(entry, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: entry.location, monthLocation: MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth)), attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
}
@ -960,8 +960,8 @@ final class HistoryViewLoadedState {
switch entry {
case let .IntermediateMessageEntry(message, location, monthLocation):
return .IntermediateMessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation)
case let .MessageEntry(messageEntry, reloadAssociatedMessages):
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedGroupInfo(groupInfo), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedGroupInfo(groupInfo), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
return nil
@ -983,8 +983,8 @@ final class HistoryViewLoadedState {
switch entry {
case let .IntermediateMessageEntry(message, location, monthLocation):
return .IntermediateMessageEntry(message.withUpdatedEmbeddedMedia(buffer), location, monthLocation)
case let .MessageEntry(messageEntry, reloadAssociatedMessages):
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message, location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message, location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
})
}
@ -994,8 +994,9 @@ final class HistoryViewLoadedState {
for space in self.orderedEntriesBySpace.keys {
let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in
switch entry {
case let .MessageEntry(value, reloadAssociatedMessages):
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
let message = value.message
var reloadPeers = reloadPeers
var rebuild = false
for media in message.media {
@ -1010,6 +1011,9 @@ final class HistoryViewLoadedState {
for media in message.media {
if let mediaId = media.id, let updated = updatedMedia[mediaId] {
if let updated = updated {
if media.peerIds != updated.peerIds {
reloadPeers = true
}
messageMedia.append(updated)
}
} else {
@ -1017,7 +1021,7 @@ final class HistoryViewLoadedState {
}
}
let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds)
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
case .IntermediateMessageEntry:
break
@ -1046,9 +1050,9 @@ final class HistoryViewLoadedState {
switch current {
case .IntermediateMessageEntry:
return current
case let .MessageEntry(messageEntry, _):
case let .MessageEntry(messageEntry, _, reloadPeers):
updated = true
return .MessageEntry(messageEntry, reloadAssociatedMessages: true)
return .MessageEntry(messageEntry, reloadAssociatedMessages: true, reloadPeers: reloadPeers)
}
})
}
@ -1109,11 +1113,11 @@ final class HistoryViewLoadedState {
switch current {
case .IntermediateMessageEntry:
return current
case let .MessageEntry(messageEntry, reloadAssociatedMessages):
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
updated = true
if let associatedMessages = messageEntry.message.associatedMessages.filteredOut(keysIn: [index.id]) {
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedAssociatedMessages(associatedMessages), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedAssociatedMessages(associatedMessages), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
} else {
return current
}
@ -1130,7 +1134,7 @@ final class HistoryViewLoadedState {
return updated
}
func completeAndSample(postbox: Postbox) -> HistoryViewLoadedSample {
func completeAndSample(postbox: Postbox, clipHoles: Bool) -> HistoryViewLoadedSample {
if !self.spacesWithRemovals.isEmpty {
for space in self.spacesWithRemovals {
self.fillSpace(space: space, postbox: postbox)
@ -1161,7 +1165,7 @@ final class HistoryViewLoadedState {
entry = self.orderedEntriesBySpace[space]!.higherThanAnchor[index]
}
if !clipRanges.isEmpty {
if clipHoles && !clipRanges.isEmpty {
let entryIndex = entry.index
for range in clipRanges {
if range.contains(entryIndex) {
@ -1177,14 +1181,22 @@ final class HistoryViewLoadedState {
}
switch entry {
case let .MessageEntry(value, reloadAssociatedMessages):
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
var updatedMessage = value.message
if reloadAssociatedMessages {
let associatedMessages = postbox.messageHistoryTable.renderAssociatedMessages(associatedMessageIds: value.message.associatedMessageIds, peerTable: postbox.peerTable)
let updatedValue = MessageHistoryMessageEntry(message: value.message.withUpdatedAssociatedMessages(associatedMessages), location: value.location, monthLocation: value.monthLocation, attributes: value.attributes)
updatedMessage = value.message.withUpdatedAssociatedMessages(associatedMessages)
}
if reloadPeers {
updatedMessage = postbox.messageHistoryTable.renderMessagePeers(updatedMessage, peerTable: postbox.peerTable)
}
if value.message !== updatedMessage {
let updatedValue = MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes)
if directionIndex == 0 {
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false))
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false, reloadPeers: false))
} else {
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false))
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false, reloadPeers: false))
}
result.append(updatedValue)
} else {
@ -1198,9 +1210,9 @@ final class HistoryViewLoadedState {
}
let entry = MessageHistoryMessageEntry(message: renderedMessage, location: location, monthLocation: monthLocation, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: authorIsContact))
if directionIndex == 0 {
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false))
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false, reloadPeers: false))
} else {
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false))
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false, reloadPeers: false))
}
result.append(entry)
}
@ -1361,12 +1373,12 @@ enum HistoryViewState {
}
}
func sample(postbox: Postbox) -> HistoryViewSample {
func sample(postbox: Postbox, clipHoles: Bool) -> HistoryViewSample {
switch self {
case let .loading(loadingState):
return .loading(loadingState.checkAndSample(postbox: postbox))
case let .loaded(loadedState):
return .loaded(loadedState.completeAndSample(postbox: postbox))
case let .loading(loadingState):
return .loading(loadingState.checkAndSample(postbox: postbox))
case let .loaded(loadedState):
return .loaded(loadedState.completeAndSample(postbox: postbox, clipHoles: clipHoles))
}
}
}

View File

@ -60,7 +60,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
}
}
self.anchor = anchor
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
let _ = self.updateFromView()
}
@ -132,7 +132,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
case let .peer(id):
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil)
}
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
return self.updateFromView()
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
var reloadView = false
@ -160,7 +160,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
case let .peer(id):
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil)
}
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
}
return self.updateFromView()

View File

@ -655,6 +655,11 @@ public final class Transaction {
return self.postbox?.retrieveItemCacheEntry(id: id)
}
public func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
assert(!self.disposed)
self.postbox?.clearItemCacheCollection(collectionId: collectionId)
}
public func operationLogGetNextEntryLocalIndex(peerId: PeerId, tag: PeerOperationLogTag) -> Int32 {
assert(!self.disposed)
if let postbox = self.postbox {
@ -2065,6 +2070,10 @@ public final class Postbox {
return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable)
}
func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
return self.itemCacheTable.removeAll(collectionId: collectionId)
}
fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) {
self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable)
}
@ -2257,7 +2266,7 @@ public final class Postbox {
return peerIds
}
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
return self.transactionSignal(userInteractive: true, { subscriber, transaction in
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
@ -2303,26 +2312,26 @@ public final class Postbox {
}
}
}
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
})
}
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
return self.transactionSignal { subscriber, transaction in
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
}
}
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
return self.transactionSignal { subscriber, transaction in
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
}
}
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
var mainPeerId: PeerId?
switch peerIds {
@ -2411,7 +2420,7 @@ public final class Postbox {
readStates = transientReadStates
}
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, clipHoles: clipHoles, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
if let tagMask = tagMask {
return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound))
} else {

View File

@ -38,6 +38,13 @@ public struct ValueBoxKey: Equatable, Hashable, CustomStringConvertible, Compara
memcpy(self.memory, buffer.memory, buffer.length)
}
public func setData(_ offset: Int, value: Data) {
let valueLength = value.count
value.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
memcpy(self.memory + offset, bytes, valueLength)
}
}
public func setInt32(_ offset: Int, value: Int32) {
var bigEndianValue = Int32(bigEndian: value)
memcpy(self.memory + offset, &bigEndianValue, 4)

View File

@ -75,3 +75,50 @@ public func screenCaptureEvents() -> Signal<ScreenCaptureEvent, NoError> {
}
|> runOn(Queue.mainQueue())
}
public final class ScreenCaptureDetectionManager {
private var observer: NSObjectProtocol?
private var screenRecordingDisposable: Disposable?
private var screenRecordingCheckTimer: SwiftSignalKit.Timer?
public init(check: @escaping () -> Bool) {
self.observer = NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: .main, using: { [weak self] _ in
guard let strongSelf = self else {
return
}
check()
})
self.screenRecordingDisposable = screenRecordingActive().start(next: { [weak self] value in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if value {
if strongSelf.screenRecordingCheckTimer == nil {
strongSelf.screenRecordingCheckTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: {
guard let strongSelf = self else {
return
}
if check() {
strongSelf.screenRecordingCheckTimer?.invalidate()
strongSelf.screenRecordingCheckTimer = nil
}
}, queue: Queue.mainQueue())
strongSelf.screenRecordingCheckTimer?.start()
}
} else if strongSelf.screenRecordingCheckTimer != nil {
strongSelf.screenRecordingCheckTimer?.invalidate()
strongSelf.screenRecordingCheckTimer = nil
}
}
})
}
deinit {
NotificationCenter.default.removeObserver(self.observer)
self.screenRecordingDisposable?.dispose()
self.screenRecordingCheckTimer?.invalidate()
self.screenRecordingCheckTimer = nil
}
}

View File

@ -87,12 +87,13 @@ private class SearchBarTextField: UITextField {
}
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
let prefixSize = self.prefixLabel.measure(bounds.size)
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
if !prefixSize.width.isZero {
let prefixOffset = prefixSize.width
rect.origin.x += prefixOffset
rect.size.width -= prefixOffset
}
rect.size.width = max(rect.size.width, 10.0)
return rect
}
@ -117,7 +118,7 @@ private class SearchBarTextField: UITextField {
let labelSize = self.placeholderLabel.measure(textRect.size)
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX, y: textRect.minY + textOffset), size: labelSize)
let prefixSize = self.prefixLabel.measure(bounds.size)
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + textOffset), size: prefixSize)
}

View File

@ -334,14 +334,14 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
let resolvedValue: CGFloat?
if let value = self.value {
if let transition = self.transition {
transition.valueAt(timestamp: timestamp, actualValue: value)
resolvedValue = transition.valueAt(timestamp: timestamp, actualValue: value)
} else {
resolvedValue = value
}
} else {
resolvedValue = nil
}
return DrawingState(transitionFraction: transitionFraction, value: self.value, displayCancel: self.displayCancel, timestamp: timestamp)
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, displayCancel: self.displayCancel, timestamp: timestamp)
}
func updateValue(value: CGFloat?) {
@ -351,7 +351,7 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
let timestamp = CACurrentMediaTime()
if let value = value, let previousValue = previousValue {
if let transition = self.transition {
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: transition.valueAt(timestamp: timestamp, actualValue: value))
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: transition.valueAt(timestamp: timestamp, actualValue: previousValue))
} else {
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: previousValue)
}
@ -388,7 +388,7 @@ private extension SemanticStatusNodeState {
}
case let .progress(value, cancelEnabled):
if let current = current as? SemanticStatusNodeProgressContext, current.displayCancel == cancelEnabled {
current.value = value
current.updateValue(value: value)
return current
} else {
return SemanticStatusNodeProgressContext(value: value, displayCancel: cancelEnabled)

View File

@ -0,0 +1,542 @@
import Foundation
import UIKit
import Display
import Postbox
import SwiftSignalKit
import AsyncDisplayKit
import TelegramCore
import SyncCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import ChatListUI
import WallpaperResources
import LegacyComponents
import ItemListUI
private func generateMaskImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let gradientColors = [color.withAlphaComponent(0.0).cgColor, color.cgColor, color.cgColor] as CFArray
var locations: [CGFloat] = [0.0, 0.75, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 80.0), options: CGGradientDrawingOptions())
})
}
private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let context: AccountContext
private var presentationThemeSettings: PresentationThemeSettings
private var presentationData: PresentationData
private let referenceTimestamp: Int32
private let scrollNode: ASScrollNode
private let maskNode: ASImageNode
private let chatBackgroundNode: WallpaperBackgroundNode
private let messagesContainerNode: ASDisplayNode
private var dateHeaderNode: ListViewItemHeaderNode?
private var messageNodes: [ListViewItemNode]?
private let toolbarNode: BubbleSettingsToolbarNode
private var validLayout: (ContainerViewLayout, CGFloat)?
init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings, dismiss: @escaping () -> Void, apply: @escaping (PresentationChatBubbleSettings) -> Void) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationThemeSettings = presentationThemeSettings
let calendar = Calendar(identifier: .gregorian)
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: Date())
components.hour = 13
components.minute = 0
components.second = 0
self.referenceTimestamp = Int32(calendar.date(from: components)?.timeIntervalSince1970 ?? 0.0)
self.scrollNode = ASScrollNode()
self.chatBackgroundNode = WallpaperBackgroundNode()
self.chatBackgroundNode.displaysAsynchronously = false
self.messagesContainerNode = ASDisplayNode()
self.messagesContainerNode.clipsToBounds = true
self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false)
self.chatBackgroundNode.motionEnabled = self.presentationData.chatWallpaper.settings?.motion ?? false
if case .gradient = self.presentationData.chatWallpaper {
self.chatBackgroundNode.imageContentMode = .scaleToFill
}
self.toolbarNode = BubbleSettingsToolbarNode(presentationThemeSettings: self.presentationThemeSettings, presentationData: self.presentationData)
self.maskNode = ASImageNode()
self.maskNode.displaysAsynchronously = false
self.maskNode.displayWithoutProcessing = true
self.maskNode.contentMode = .scaleToFill
super.init()
self.setViewBlock({
return UITracingLayerView()
})
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.maskNode.image = generateMaskImage(color: self.presentationData.theme.chatList.backgroundColor)
self.addSubnode(self.scrollNode)
self.addSubnode(self.toolbarNode)
self.scrollNode.addSubnode(self.chatBackgroundNode)
self.scrollNode.addSubnode(self.messagesContainerNode)
self.toolbarNode.cancel = {
dismiss()
}
var dismissed = false
self.toolbarNode.done = { [weak self] in
guard let strongSelf = self else {
return
}
if !dismissed {
dismissed = true
apply(strongSelf.presentationThemeSettings.chatBubbleSettings)
}
}
self.toolbarNode.updateMergeBubbleCorners = { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.presentationThemeSettings.chatBubbleSettings.mergeBubbleCorners = value
strongSelf.updatePresentationThemeSettings(strongSelf.presentationThemeSettings)
}
self.toolbarNode.updateCornerRadius = { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.presentationThemeSettings.chatBubbleSettings.mainRadius = Int32(value)
strongSelf.presentationThemeSettings.chatBubbleSettings.auxiliaryRadius = Int32(value / 2)
strongSelf.updatePresentationThemeSettings(strongSelf.presentationThemeSettings)
}
}
override func didLoad() {
super.didLoad()
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.isPagingEnabled = true
self.scrollNode.view.delegate = self
self.scrollNode.view.alwaysBounceHorizontal = false
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
func animateIn(completion: (() -> Void)? = nil) {
if let (layout, _) = self.validLayout, case .compact = layout.metrics.widthClass {
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) {
if let (layout, _) = self.validLayout, case .compact = layout.metrics.widthClass {
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: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in
completion?()
})
} else {
completion?()
}
}
private func updateMessagesLayout(layout: ContainerViewLayout, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
let otherPeerId = self.context.account.peerId
var peers = SimpleDictionary<PeerId, Peer>()
var messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message1, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message2, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA="
let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))]
let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes)
let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil))
let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message4, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
let width: CGFloat
if case .regular = layout.metrics.widthClass {
width = layout.size.width
} else {
width = layout.size.width
}
let params = ListViewItemLayoutParams(width: width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height)
if let messageNodes = self.messageNodes {
for i in 0 ..< items.count {
let itemNode = messageNodes[i]
items[i].updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
})
}
} else {
var messageNodes: [ListViewItemNode] = []
for i in 0 ..< items.count {
var itemNode: ListViewItemNode?
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
itemNode = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode!.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
itemNode!.isUserInteractionEnabled = false
messageNodes.append(itemNode!)
self.messagesContainerNode.addSubnode(itemNode!)
}
self.messageNodes = messageNodes
}
var bottomOffset: CGFloat = 9.0 + bottomInset
if let messageNodes = self.messageNodes {
for itemNode in messageNodes {
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: itemNode.frame.size))
bottomOffset += itemNode.frame.height
itemNode.updateFrame(itemNode.frame, within: layout.size)
}
}
let dateHeaderNode: ListViewItemHeaderNode
if let currentDateHeaderNode = self.dateHeaderNode {
dateHeaderNode = currentDateHeaderNode
headerItem.updateNode(dateHeaderNode, previous: nil, next: headerItem)
} else {
dateHeaderNode = headerItem.node()
dateHeaderNode.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
self.messagesContainerNode.addSubnode(dateHeaderNode)
self.dateHeaderNode = dateHeaderNode
}
transition.updateFrame(node: dateHeaderNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: CGSize(width: layout.size.width, height: headerItem.height)))
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
}
func updatePresentationThemeSettings(_ presentationThemeSettings: PresentationThemeSettings) {
let chatBubbleCorners = PresentationChatBubbleCorners(mainRadius: CGFloat(presentationThemeSettings.chatBubbleSettings.mainRadius), auxiliaryRadius: CGFloat(presentationThemeSettings.chatBubbleSettings.auxiliaryRadius), mergeBubbleCorners: presentationThemeSettings.chatBubbleSettings.mergeBubbleCorners)
self.presentationData = self.presentationData.withChatBubbleCorners(chatBubbleCorners)
self.toolbarNode.updatePresentationData(presentationData: self.presentationData)
self.toolbarNode.updatePresentationThemeSettings(presentationThemeSettings: self.presentationThemeSettings)
if let (layout, navigationBarHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.recursivelyEnsureDisplaySynchronously(true)
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
let bounds = CGRect(origin: CGPoint(), size: layout.size)
self.scrollNode.frame = bounds
let toolbarHeight = self.toolbarNode.updateLayout(width: layout.size.width, bottomInset: layout.intrinsicInsets.bottom, layout: layout, transition: transition)
var chatFrame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)
let bottomInset: CGFloat
chatFrame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)
self.scrollNode.view.contentSize = CGSize(width: bounds.width, height: bounds.height)
bottomInset = 37.0
self.chatBackgroundNode.frame = chatFrame
self.chatBackgroundNode.updateLayout(size: chatFrame.size, transition: transition)
self.messagesContainerNode.frame = chatFrame
transition.updateFrame(node: self.toolbarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight + layout.intrinsicInsets.bottom)))
self.updateMessagesLayout(layout: layout, bottomInset: toolbarHeight + bottomInset, transition: transition)
transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - toolbarHeight - 80.0, width: bounds.width, height: 80.0))
}
}
final class BubbleSettingsController: ViewController {
private let context: AccountContext
private var controllerNode: BubbleSettingsControllerNode {
return self.displayNode as! BubbleSettingsControllerNode
}
private var didPlayPresentationAnimation = false
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var presentationThemeSettings: PresentationThemeSettings
private var presentationThemeSettingsDisposable: Disposable?
private var disposable: Disposable?
private var applyDisposable = MetaDisposable()
public init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationThemeSettings = presentationThemeSettings
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings))
self.blocksBackgroundWhenInOverlay = true
self.navigationPresentation = .modal
self.navigationItem.title = self.presentationData.strings.Appearance_BubbleCorners_Title
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
strongSelf.presentationData = presentationData
}
})
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
self.presentationThemeSettingsDisposable?.dispose()
self.disposable?.dispose()
self.applyDisposable.dispose()
}
override public 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 public func loadDisplayNode() {
super.loadDisplayNode()
self.displayNode = BubbleSettingsControllerNode(context: self.context, presentationThemeSettings: self.presentationThemeSettings, dismiss: { [weak self] in
if let strongSelf = self {
strongSelf.dismiss()
}
}, apply: { [weak self] chatBubbleSettings in
if let strongSelf = self {
strongSelf.apply(chatBubbleSettings: chatBubbleSettings)
}
})
self.displayNodeDidLoad()
}
private func apply(chatBubbleSettings: PresentationChatBubbleSettings) {
let _ = (updatePresentationThemeSettingsInteractively(accountManager: self.context.sharedContext.accountManager, { current in
var current = current
current.chatBubbleSettings = chatBubbleSettings
return current
})
|> deliverOnMainQueue).start(completed: { [weak self] in
self?.dismiss()
})
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
}
}
private enum TextSelectionCustomMode {
case list
case chat
}
private final class BubbleSettingsToolbarNode: ASDisplayNode {
private var presentationThemeSettings: PresentationThemeSettings
private var presentationData: PresentationData
private let cancelButton = HighlightableButtonNode()
private let doneButton = HighlightableButtonNode()
private let separatorNode = ASDisplayNode()
private let topSeparatorNode = ASDisplayNode()
private var switchItemNode: ItemListSwitchItemNode
private var cornerRadiusItemNode: BubbleSettingsRadiusItemNode
private(set) var customMode: TextSelectionCustomMode = .chat
var cancel: (() -> Void)?
var done: (() -> Void)?
var updateMergeBubbleCorners: ((Bool) -> Void)?
var updateCornerRadius: ((Int32) -> Void)?
init(presentationThemeSettings: PresentationThemeSettings, presentationData: PresentationData) {
self.presentationThemeSettings = presentationThemeSettings
self.presentationData = presentationData
self.switchItemNode = ItemListSwitchItemNode(type: .regular)
self.cornerRadiusItemNode = BubbleSettingsRadiusItemNode()
super.init()
self.addSubnode(self.switchItemNode)
self.addSubnode(self.cornerRadiusItemNode)
self.addSubnode(self.cancelButton)
self.addSubnode(self.doneButton)
self.addSubnode(self.separatorNode)
self.addSubnode(self.topSeparatorNode)
self.updatePresentationData(presentationData: self.presentationData)
self.cancelButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.cancelButton.backgroundColor = strongSelf.presentationData.theme.list.itemHighlightedBackgroundColor
} else {
UIView.animate(withDuration: 0.3, animations: {
strongSelf.cancelButton.backgroundColor = .clear
})
}
}
}
self.doneButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.doneButton.backgroundColor = strongSelf.presentationData.theme.list.itemHighlightedBackgroundColor
} else {
UIView.animate(withDuration: 0.3, animations: {
strongSelf.doneButton.backgroundColor = .clear
})
}
}
}
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside)
}
func setDoneEnabled(_ enabled: Bool) {
self.doneButton.alpha = enabled ? 1.0 : 0.4
self.doneButton.isUserInteractionEnabled = enabled
}
func setCustomMode(_ customMode: TextSelectionCustomMode) {
self.customMode = customMode
}
func updatePresentationData(presentationData: PresentationData) {
self.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
self.separatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor
self.topSeparatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor
self.cancelButton.setTitle(presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: presentationData.theme.list.itemPrimaryTextColor, for: [])
self.doneButton.setTitle(presentationData.strings.Wallpaper_Set, with: Font.regular(17.0), with: presentationData.theme.list.itemPrimaryTextColor, for: [])
}
func updatePresentationThemeSettings(presentationThemeSettings: PresentationThemeSettings) {
self.presentationThemeSettings = presentationThemeSettings
}
func updateLayout(width: CGFloat, bottomInset: CGFloat, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
var contentHeight: CGFloat = 0.0
let switchItem = ItemListSwitchItem(presentationData: ItemListPresentationData(self.presentationData), title: self.presentationData.strings.Appearance_BubbleCorners_AdjustAdjacent, value: self.presentationThemeSettings.chatBubbleSettings.mergeBubbleCorners, disableLeadingInset: true, sectionId: 0, style: .blocks, updated: { [weak self] value in
self?.updateMergeBubbleCorners?(value)
})
let cornerRadiusItem = BubbleSettingsRadiusItem(theme: self.presentationData.theme, value: Int(self.presentationData.chatBubbleCorners.mainRadius), enabled: true, disableLeadingInset: false, displayIcons: false, force: false, sectionId: 0, updated: { [weak self] value in
self?.updateCornerRadius?(Int32(max(8, min(16, value))))
})
/*switchItem.updateNode(async: { f in
f()
}, node: {
return self.switchItemNode
}, params: ListViewItemLayoutParams(width: width, leftInset: layout.intrinsicInsets.left, rightInset: layout.intrinsicInsets.right, availableHeight: 1000.0), previousItem: nil, nextItem: cornerRadiusItem, animation: .None, completion: { layout, apply in
self.switchItemNode.contentSize = layout.contentSize
self.switchItemNode.insets = layout.insets
transition.updateFrame(node: self.switchItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: layout.contentSize))
contentHeight += layout.contentSize.height
apply(ListViewItemApply(isOnScreen: true))
})*/
cornerRadiusItem.updateNode(async: { f in
f()
}, node: {
return self.cornerRadiusItemNode
}, params: ListViewItemLayoutParams(width: width, leftInset: layout.intrinsicInsets.left, rightInset: layout.intrinsicInsets.right, availableHeight: 1000.0), previousItem: switchItem, nextItem: nil, animation: .None, completion: { layout, apply in
self.cornerRadiusItemNode.contentSize = layout.contentSize
self.cornerRadiusItemNode.insets = layout.insets
transition.updateFrame(node: self.cornerRadiusItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: layout.contentSize))
contentHeight += layout.contentSize.height
apply(ListViewItemApply(isOnScreen: true))
})
self.cancelButton.frame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: floor(width / 2.0), height: 49.0))
self.doneButton.frame = CGRect(origin: CGPoint(x: floor(width / 2.0), y: contentHeight), size: CGSize(width: width - floor(width / 2.0), height: 49.0))
contentHeight += 49.0
self.topSeparatorNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: UIScreenPixel))
let resultHeight = contentHeight + bottomInset
self.separatorNode.frame = CGRect(origin: CGPoint(x: floor(width / 2.0), y: self.cancelButton.frame.minY), size: CGSize(width: UIScreenPixel, height: resultHeight - self.cancelButton.frame.minY))
return resultHeight
}
@objc func cancelPressed() {
self.cancel?()
}
@objc func donePressed() {
self.doneButton.isUserInteractionEnabled = false
self.done?()
}
}

View File

@ -187,7 +187,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
case let .accountHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .account(theme, peer, selected, _):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: false), revealOptions: nil, switchValue: ItemListPeerItemSwitch(value: selected, style: .check), enabled: true, selectable: true, sectionId: self.section, action: {
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context.sharedContext.makeTempAccountContext(account: arguments.context.account), peer: peer, height: .generic, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: false), revealOptions: nil, switchValue: ItemListPeerItemSwitch(value: selected, style: .check), enabled: true, selectable: true, sectionId: self.section, action: {
arguments.updateSettings { $0.withUpdatedAccount(peer.id) }
}, setPeerIdWithRevealedOptions: { _, _ in}, removePeer: { _ in })
return ItemListTextItem(presentationData: presentationData, text: .plain(""), sectionId: self.section)

View File

@ -453,10 +453,10 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
dismissImpl = { [weak controller] in
controller?.dismiss()
}
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ProxySettingsControllerEntry]) -> Void in
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ProxySettingsControllerEntry]) -> Signal<Bool, NoError> in
let fromEntry = entries[fromIndex]
guard case let .server(_, _, _, fromServer, _, _, _, _) = fromEntry else {
return
return .single(false)
}
var referenceServer: ProxyServerSettings?
var beforeAll = false
@ -476,7 +476,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
afterAll = true
}
let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in
return updateProxySettingsInteractively(accountManager: accountManager, { current in
var current = current
if let index = current.servers.firstIndex(of: fromServer) {
current.servers.remove(at: index)
@ -503,7 +503,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
current.servers.append(fromServer)
}
return current
}).start()
})
})
shareProxyListImpl = { [weak controller] in

View File

@ -241,7 +241,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
let statusAttributedString = NSAttributedString(string: item.label, font: statusFont, textColor: item.labelAccent ? item.theme.list.itemAccentColor : item.theme.list.itemSecondaryTextColor)
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
let editingOffset: CGFloat
var reorderInset: CGFloat = 0.0
@ -341,7 +341,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
if strongSelf.reorderControlNode == nil {
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false)
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
strongSelf.reorderControlNode = reorderControlNode
strongSelf.addSubnode(reorderControlNode)
reorderControlNode.alpha = 0.0

View File

@ -393,6 +393,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
transaction.clearNotices()
}).start()
if let context = arguments.context {
let _ = (context.account.postbox.transaction { transaction -> Void in
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedPollResults)
}).start()
}
})
case let .reimport(theme):
return ItemListActionItem(presentationData: presentationData, title: "Reimport Application Data", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {

View File

@ -18,6 +18,7 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
let strings: PresentationStrings
let sectionId: ItemListSectionId
let fontSize: PresentationFontSize
let chatBubbleCorners: PresentationChatBubbleCorners
let wallpaper: TelegramWallpaper
let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder
@ -25,12 +26,13 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
let linkEnabled: Bool
let tooltipText: String
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peerName: String, linkEnabled: Bool, tooltipText: String) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peerName: String, linkEnabled: Bool, tooltipText: String) {
self.context = context
self.theme = theme
self.strings = strings
self.sectionId = sectionId
self.fontSize = fontSize
self.chatBubbleCorners = chatBubbleCorners
self.wallpaper = wallpaper
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@ -157,7 +159,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName)
let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)
let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)
var node: ListViewItemNode?
if let current = currentNode {

View File

@ -482,12 +482,19 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
let updateHasTwoStepAuth: () -> Void = {
let signal = twoStepVerificationConfiguration(account: context.account)
|> map { value -> TwoStepVerificationAccessConfiguration? in
return TwoStepVerificationAccessConfiguration(configuration: value, password: nil)
return TwoStepVerificationAccessConfiguration(configuration: value, password: nil)
}
|> deliverOnMainQueue
updateTwoStepAuthDisposable.set(
signal.start(next: { value in
twoStepAuthDataValue.set(.single(value))
if let value = value {
if case .set = value {
updatedHasTwoStepAuth?(true)
} else {
updatedHasTwoStepAuth?(false)
}
}
})
)
}

View File

@ -87,7 +87,7 @@ private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings
private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case forwardsPreviewHeader(PresentationTheme, String)
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String)
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String)
case settingHeader(PresentationTheme, String)
case everybody(PresentationTheme, String, Bool)
case contacts(PresentationTheme, String, Bool)
@ -194,8 +194,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsPeerName, lhsLinkEnabled, lhsTooltipText):
if case let .forwardsPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsPeerName, rhsLinkEnabled, rhsTooltipText) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsPeerName == rhsPeerName, lhsLinkEnabled == rhsLinkEnabled, lhsTooltipText == rhsTooltipText {
case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsPeerName, lhsLinkEnabled, lhsTooltipText):
if case let .forwardsPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsChatBubbleCorners, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsPeerName, rhsLinkEnabled, rhsTooltipText) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsChatBubbleCorners == rhsChatBubbleCorners, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsPeerName == rhsPeerName, lhsLinkEnabled == rhsLinkEnabled, lhsTooltipText == rhsTooltipText {
return true
} else {
return false
@ -350,8 +350,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
switch self {
case let .forwardsPreviewHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .forwardsPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, peerName, linkEnabled, tooltipText):
return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText)
case let .forwardsPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, peerName, linkEnabled, tooltipText):
return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText)
case let .settingHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .everybody(theme, text, value):
@ -591,7 +591,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
linkEnabled = false
}
entries.append(.forwardsPreviewHeader(presentationData.theme, presentationData.strings.Privacy_Forwards_Preview))
entries.append(.forwardsPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peerName, linkEnabled, tootipText))
entries.append(.forwardsPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peerName, linkEnabled, tootipText))
}
entries.append(.settingHeader(presentationData.theme, settingTitle))

View File

@ -945,7 +945,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
blockedPeers.set(.single(blockedPeersContext))
}, updatedHasTwoStepAuth: { hasTwoStepAuthValue in
hasTwoStepAuthPromise.set(.single(hasTwoStepAuthValue))
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext))
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth))
})
})
}, openDataAndStorage: {
@ -1474,7 +1474,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
if let primary = primary {
let size = CGSize(width: 31.0, height: 31.0)
let inset: CGFloat = 3.0
if let signal = peerAvatarImage(account: primary.0, peer: primary.1, authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) {
if let signal = peerAvatarImage(account: primary.0, peerReference: PeerReference(primary.1), authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) {
return signal
|> map { image -> (UIImage, UIImage)? in
if let image = image, let selectedImage = generateImage(size, rotatedContext: { size, context in
@ -1497,13 +1497,13 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: inset, y: inset)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, accountPeerId: primary.1.id, peerId: primary.1.id)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, peerId: primary.1.id)
})?.withRenderingMode(.alwaysOriginal)
let selectedImage = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: inset, y: inset)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, accountPeerId: primary.1.id, peerId: primary.1.id)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, peerId: primary.1.id)
context.translateBy(x: -inset, y: -inset)
context.setLineWidth(1.0)
context.setStrokeColor(primary.2.rootController.tabBar.selectedIconColor.cgColor)

View File

@ -453,7 +453,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
}
presentStickerPackController = { [weak controller] info in
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
}
return controller

View File

@ -265,7 +265,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
presentStickerPackController = { [weak controller] info in
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
}
return controller

View File

@ -698,10 +698,10 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
if case .modal = mode {
controller.navigationPresentation = .modal
}
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [InstalledStickerPacksEntry]) -> Void in
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [InstalledStickerPacksEntry]) -> Signal<Bool, NoError> in
let fromEntry = entries[fromIndex]
guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _) = fromEntry else {
return
return .single(false)
}
var referenceId: ItemCollectionId?
var beforeAll = false
@ -731,20 +731,26 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
}
}
var previousIndex: Int?
for i in 0 ..< currentIds.count {
if currentIds[i] == fromPackInfo.id {
previousIndex = i
currentIds.remove(at: i)
break
}
}
var didReorder = false
if let referenceId = referenceId {
var inserted = false
for i in 0 ..< currentIds.count {
if currentIds[i] == referenceId {
if fromIndex < toIndex {
didReorder = previousIndex != i + 1
currentIds.insert(fromPackInfo.id, at: i + 1)
} else {
didReorder = previousIndex != i
currentIds.insert(fromPackInfo.id, at: i)
}
inserted = true
@ -752,15 +758,20 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
}
}
if !inserted {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id)
}
} else if beforeAll {
didReorder = previousIndex != 0
currentIds.insert(fromPackInfo.id, at: 0)
} else if afterAll {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id)
}
temporaryPackOrder.set(.single(currentIds))
return .single(didReorder)
})
controller.setReorderCompleted({ (entries: [InstalledStickerPacksEntry]) -> Void in
@ -827,7 +838,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
packs.insert(packReference, at: 0)
}
if let mainStickerPack = mainStickerPack {
presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: mainStickerPack, stickerPacks: packs, parentNavigationController: controller?.navigationController as? NavigationController, actionPerformed: { info, items, action in
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: mainStickerPack, stickerPacks: packs, parentNavigationController: controller?.navigationController as? NavigationController, actionPerformed: { info, items, action in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var animateInAsReplacement = false
if let navigationController = navigationControllerImpl?() {

View File

@ -213,7 +213,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
gesture?.cancel()
})
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
@ -303,7 +303,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
}
private func updateMessagesLayout(layout: ContainerViewLayout, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
@ -317,20 +317,20 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message1, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message1, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message2, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message2, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA="
let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))]
let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes)
let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil))
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil))
let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message4, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message4, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
let width: CGFloat
if case .regular = layout.metrics.widthClass {

View File

@ -0,0 +1,302 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import SyncCore
import TelegramPresentationData
import TelegramUIPreferences
import LegacyComponents
import ItemListUI
import PresentationDataUtils
import AppBundle
class BubbleSettingsRadiusItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let value: Int
let disableLeadingInset: Bool
let displayIcons: Bool
let force: Bool
let enabled: Bool
let sectionId: ItemListSectionId
let updated: (Int) -> Void
let tag: ItemListItemTag?
init(theme: PresentationTheme, value: Int, enabled: Bool = true, disableLeadingInset: Bool = false, displayIcons: Bool = true, force: Bool = false, sectionId: ItemListSectionId, updated: @escaping (Int) -> Void, tag: ItemListItemTag? = nil) {
self.theme = theme
self.value = value
self.enabled = enabled
self.disableLeadingInset = disableLeadingInset
self.displayIcons = displayIcons
self.force = force
self.sectionId = sectionId
self.updated = updated
self.tag = tag
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = BubbleSettingsRadiusItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? BubbleSettingsRadiusItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
}
private func generateKnobImage() -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(width: 0.0, height: -1.0), blur: 3.5, color: UIColor(white: 0.0, alpha: 0.25).cgColor)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)))
})
}
class BubbleSettingsRadiusItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private var sliderView: TGPhotoEditorSliderView?
private let leftIconNode: ASImageNode
private let rightIconNode: ASImageNode
private let disabledOverlayNode: ASDisplayNode
private var item: BubbleSettingsRadiusItem?
private var layoutParams: ListViewItemLayoutParams?
var tag: ItemListItemTag? {
return self.item?.tag
}
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.leftIconNode = ASImageNode()
self.leftIconNode.displaysAsynchronously = false
self.leftIconNode.displayWithoutProcessing = true
self.rightIconNode = ASImageNode()
self.rightIconNode.displaysAsynchronously = false
self.rightIconNode.displayWithoutProcessing = true
self.disabledOverlayNode = ASDisplayNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.leftIconNode)
self.addSubnode(self.rightIconNode)
self.addSubnode(self.disabledOverlayNode)
}
override func didLoad() {
super.didLoad()
let sliderView = TGPhotoEditorSliderView()
sliderView.enablePanHandling = true
sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 1.0
sliderView.lineSize = 2.0
sliderView.dotSize = 5.0
sliderView.minimumValue = 0.0
sliderView.maximumValue = 4.0
sliderView.startValue = 0.0
sliderView.positionsCount = 5
sliderView.disablesInteractiveTransitionGestureRecognizer = true
if let item = self.item, let params = self.layoutParams {
sliderView.isUserInteractionEnabled = item.enabled
sliderView.value = CGFloat((item.value - 8) / 2)
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
sliderView.knobImage = generateKnobImage()
let sliderInset: CGFloat = item.displayIcons ? 38.0 : 16.0
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
}
self.view.insertSubview(sliderView, belowSubview: self.disabledOverlayNode.view)
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
self.sliderView = sliderView
}
func asyncLayout() -> (_ item: BubbleSettingsRadiusItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
return { item, params, neighbors in
var updatedLeftIcon: UIImage?
var updatedRightIcon: UIImage?
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
updatedLeftIcon = generateTintedImage(image: UIImage(bundleImageName: "Instant View/SettingsFontMinIcon"), color: item.theme.list.itemPrimaryTextColor)
updatedRightIcon = generateTintedImage(image: UIImage(bundleImageName: "Instant View/SettingsFontMaxIcon"), color: item.theme.list.itemPrimaryTextColor)
}
let contentSize: CGSize
var insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
contentSize = CGSize(width: params.width, height: 60.0)
insets = itemListNeighborsGroupedInsets(neighbors)
if item.disableLeadingInset {
insets.top = 0.0
insets.bottom = 0.0
}
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
return (layout, { [weak self] in
if let strongSelf = self {
let firstTime = strongSelf.item == nil || item.force
strongSelf.item = item
strongSelf.layoutParams = params
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.disabledOverlayNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4)
strongSelf.disabledOverlayNode.isHidden = item.enabled
strongSelf.disabledOverlayNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: 44.0))
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = params.leftInset + 16.0
bottomStripeOffset = -separatorHeight
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
if let updatedLeftIcon = updatedLeftIcon {
strongSelf.leftIconNode.image = updatedLeftIcon
}
if let image = strongSelf.leftIconNode.image {
strongSelf.leftIconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 25.0), size: CGSize(width: image.size.width, height: image.size.height))
}
if let updatedRightIcon = updatedRightIcon {
strongSelf.rightIconNode.image = updatedRightIcon
}
if let image = strongSelf.rightIconNode.image {
strongSelf.rightIconNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 14.0 - image.size.width, y: 21.0), size: CGSize(width: image.size.width, height: image.size.height))
}
strongSelf.leftIconNode.isHidden = !item.displayIcons
strongSelf.rightIconNode.isHidden = !item.displayIcons
if let sliderView = strongSelf.sliderView {
sliderView.isUserInteractionEnabled = item.enabled
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
if themeUpdated {
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor
sliderView.knobImage = generateKnobImage()
}
let value: CGFloat = CGFloat((item.value - 8) / 2)
if firstTime {
sliderView.value = value
}
let sliderInset: CGFloat = item.displayIcons ? 38.0 : 16.0
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
}
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
@objc func sliderValueChanged() {
guard let sliderView = self.sliderView else {
return
}
let value = Int(sliderView.value) * 2 + 8
self.item?.updated(value)
}
}

View File

@ -53,7 +53,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
case slug(PresentationTheme, PresentationStrings, String, String, Bool)
case slugInfo(PresentationTheme, String)
case chatPreviewHeader(PresentationTheme, String)
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case changeColors(PresentationTheme, String)
case uploadTheme(PresentationTheme, String)
case uploadInfo(PresentationTheme, String)
@ -114,8 +114,8 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsChatBubbleCorners, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsChatBubbleCorners == rhsChatBubbleCorners, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
return true
} else {
return false
@ -172,8 +172,8 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .chatPreviewHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, items):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
case let .changeColors(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.openColors()
@ -253,7 +253,7 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat
entries.append(.slugInfo(presentationData.theme, infoText))
entries.append(.chatPreviewHeader(presentationData.theme, presentationData.strings.EditTheme_Preview.uppercased()))
entries.append(.chatPreview(presentationData.theme, previewTheme, previewTheme.chat.defaultWallpaper, presentationData.chatFontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (previewIncomingReplyName, previewIncomingReplyText), text: previewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: previewOutgoingText)]))
entries.append(.chatPreview(presentationData.theme, previewTheme, previewTheme.chat.defaultWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (previewIncomingReplyName, previewIncomingReplyText), text: previewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: previewOutgoingText)]))
entries.append(.changeColors(presentationData.theme, presentationData.strings.EditTheme_ChangeColors))
if !hasSettings {
@ -551,12 +551,12 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
}
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id))
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[themeReference.index] = nil
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}) |> deliverOnMainQueue).start(completed: {
if !hasCustomFile {
saveThemeTemplateFile(state.title, themeResource, {
@ -585,12 +585,12 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
}
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id))
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[themeReference.index] = nil
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}) |> deliverOnMainQueue).start(completed: {
if let themeResource = themeResource, !hasCustomFile {
saveThemeTemplateFile(state.title, themeResource, {

View File

@ -260,7 +260,7 @@ final class ThemeAccentColorController: ViewController {
if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper, creatorAccountId: context.account.id))
var updatedTheme = current.theme
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
@ -276,7 +276,7 @@ final class ThemeAccentColorController: ViewController {
var themeSpecificAccentColors = current.themeSpecificAccentColors
themeSpecificAccentColors[baseThemeReference.index] = PresentationThemeAccentColor(themeIndex: themeReference.index)
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})
|> castError(CreateThemeError.self)
} else {
@ -289,7 +289,7 @@ final class ThemeAccentColorController: ViewController {
if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper, creatorAccountId: context.account.id))
var updatedTheme = current.theme
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
@ -305,7 +305,7 @@ final class ThemeAccentColorController: ViewController {
var themeSpecificAccentColors = current.themeSpecificAccentColors
themeSpecificAccentColors[baseThemeReference.index] = PresentationThemeAccentColor(themeIndex: themeReference.index)
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})
|> castError(CreateThemeError.self)
} else {
@ -443,8 +443,7 @@ final class ThemeAccentColorController: ViewController {
var defaultPatternWallpaper: TelegramWallpaper?
for wallpaper in wallpapers {
//JqSUrO0-mFIBAAAAWwTvLzoWGQI, 25
if case let .file(file) = wallpaper, file.slug == "-Xc-np9y2VMCAAAARKr0yNNPYW0" {
if case let .file(file) = wallpaper, file.slug == "JqSUrO0-mFIBAAAAWwTvLzoWGQI" {
defaultPatternWallpaper = wallpaper
break
}

View File

@ -226,6 +226,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = theme
self.wallpaper = self.presentationData.chatWallpaper
let bubbleCorners = self.presentationData.chatBubbleCorners
self.ready = ready
@ -498,7 +499,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
updatedTheme = theme
}
let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: updatedTheme!, wallpaper: wallpaper)
let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: updatedTheme!, wallpaper: wallpaper, bubbleCorners: bubbleCorners)
} else {
updatedTheme = nil
}
@ -765,7 +766,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
gesture?.cancel()
})
let chatListPresentationData = ChatListPresentationData(theme: self.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
@ -836,7 +837,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}
private func updateMessagesLayout(layout: ContainerViewLayout, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
@ -878,7 +879,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
sampleMessages.append(message8)
items = sampleMessages.reversed().map { message in
let item = self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: { [weak self] message in
let item = self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: { [weak self] message in
if message.flags.contains(.Incoming) {
self?.updateSection(.accent)
self?.requestSectionUpdate?(.accent)

View File

@ -550,7 +550,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|> mapToSignal { resolvedWallpaper -> Signal<Void, NoError> in
var updatedTheme = theme
if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
}
updateSettings { settings in
@ -572,7 +572,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
let defaultThemes: [PresentationThemeReference] = [.builtin(.night), .builtin(.nightAccent)]
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }
var availableThemes = defaultThemes
availableThemes.append(contentsOf: cloudThemes)
@ -587,6 +587,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
}
let controller = ItemListController(context: context, state: signal)
controller.alwaysSynchronous = true
presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root))
}

View File

@ -234,9 +234,9 @@ public final class ThemePreviewController: ViewController {
|> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in
if let theme = theme {
if case let .file(file) = wallpaper, file.id != 0 {
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)))
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? context.account.id : nil)))
} else {
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)))
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
}
} else {
return .complete()
@ -313,7 +313,7 @@ public final class ThemePreviewController: ViewController {
}
if case let .result(theme) = result, let file = theme.file {
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper)), true))
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: theme.isCreator ? context.account.id : nil)), true))
} else {
return .complete()
}
@ -332,7 +332,7 @@ public final class ThemePreviewController: ViewController {
}
if case let .result(updatedTheme) = result, let file = updatedTheme.file {
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
return .single((.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper)), true))
return .single((.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: updatedTheme.isCreator ? context.account.id : nil)), true))
} else {
return .complete()
}

View File

@ -350,7 +350,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
gesture?.cancel()
})
let chatListPresentationData = ChatListPresentationData(theme: self.previewTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
@ -442,7 +442,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
private func updateMessagesLayout(layout: ContainerViewLayout, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
@ -484,7 +484,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
sampleMessages.append(message8)
items = sampleMessages.reversed().map { message in
self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: nil, clickThroughMessage: nil)
self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: nil, clickThroughMessage: nil)
}
let width: CGFloat

View File

@ -40,18 +40,20 @@ class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem {
let strings: PresentationStrings
let sectionId: ItemListSectionId
let fontSize: PresentationFontSize
let chatBubbleCorners: PresentationChatBubbleCorners
let wallpaper: TelegramWallpaper
let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder
let messageItems: [ChatPreviewMessageItem]
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [ChatPreviewMessageItem]) {
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [ChatPreviewMessageItem]) {
self.context = context
self.theme = theme
self.componentTheme = componentTheme
self.strings = strings
self.sectionId = sectionId
self.fontSize = fontSize
self.chatBubbleCorners = chatBubbleCorners
self.wallpaper = wallpaper
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@ -163,7 +165,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
}
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: message, theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: message, theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
}
var nodes: [ListViewItemNode] = []

View File

@ -76,6 +76,7 @@ private final class ThemeSettingsControllerArguments {
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
let openAutoNightTheme: () -> Void
let openTextSize: () -> Void
let openBubbleSettings: () -> Void
let toggleLargeEmoji: (Bool) -> Void
let disableAnimations: (Bool) -> Void
let selectAppIcon: (String) -> Void
@ -83,7 +84,7 @@ private final class ThemeSettingsControllerArguments {
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context
self.selectTheme = selectTheme
self.selectFontSize = selectFontSize
@ -92,6 +93,7 @@ private final class ThemeSettingsControllerArguments {
self.openAccentColorPicker = openAccentColorPicker
self.openAutoNightTheme = openAutoNightTheme
self.openTextSize = openTextSize
self.openBubbleSettings = openBubbleSettings
self.toggleLargeEmoji = toggleLargeEmoji
self.disableAnimations = disableAnimations
self.selectAppIcon = selectAppIcon
@ -131,11 +133,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case themeListHeader(PresentationTheme, String)
case fontSizeHeader(PresentationTheme, String)
case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?)
case autoNightTheme(PresentationTheme, String, String)
case textSize(PresentationTheme, String, String)
case bubbleSettings(PresentationTheme, String, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
@ -150,7 +153,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsControllerSection.chatPreview.rawValue
case .fontSizeHeader, .fontSize:
return ThemeSettingsControllerSection.fontSize.rawValue
case .wallpaper, .autoNightTheme, .textSize:
case .wallpaper, .autoNightTheme, .textSize, .bubbleSettings:
return ThemeSettingsControllerSection.background.rawValue
case .iconHeader, .iconItem:
return ThemeSettingsControllerSection.icon.rawValue
@ -175,29 +178,31 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 6
case .textSize:
return 7
case .fontSizeHeader:
case .bubbleSettings:
return 8
case .fontSize:
case .fontSizeHeader:
return 9
case .iconHeader:
case .fontSize:
return 10
case .iconItem:
case .iconHeader:
return 11
case .otherHeader:
case .iconItem:
return 12
case .largeEmoji:
case .otherHeader:
return 13
case .animations:
case .largeEmoji:
return 14
case .animationsInfo:
case .animations:
return 15
case .animationsInfo:
return 16
}
}
static func ==(lhs: ThemeSettingsControllerEntry, rhs: ThemeSettingsControllerEntry) -> Bool {
switch lhs {
case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
if case let .chatPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
if case let .chatPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsChatBubbleCorners, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsChatBubbleCorners == rhsChatBubbleCorners, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
return true
} else {
return false
@ -226,6 +231,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .bubbleSettings(lhsTheme, lhsText, lhsValue):
if case let .bubbleSettings(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .themeListHeader(lhsTheme, lhsText):
if case let .themeListHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -302,8 +313,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
arguments.selectFontSize(value)
}, tag: ThemeSettingsEntryTag.fontSize)
case let .chatPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, items):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
case let .wallpaper(theme, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings()
@ -387,6 +398,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openTextSize()
})
case let .bubbleSettings(theme, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openBubbleSettings()
})
case let .themeListHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
@ -429,7 +444,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
let strings = presentationData.strings
let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased()
entries.append(.themeListHeader(presentationData.theme, title))
entries.append(.chatPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)]))
entries.append(.chatPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)]))
let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
if case let .cloud(theme) = reference {
@ -497,6 +512,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
}
}
entries.append(.textSize(presentationData.theme, strings.Appearance_TextSizeSetting, textSizeValue))
entries.append(.bubbleSettings(presentationData.theme, strings.Appearance_BubbleCornersSetting, ""))
if !availableAppIcons.isEmpty {
entries.append(.iconHeader(presentationData.theme, strings.Appearance_AppIcon.uppercased()))
@ -570,6 +586,13 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let settings = (view.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
pushControllerImpl?(TextSizeSelectionController(context: context, presentationThemeSettings: settings))
})
}, openBubbleSettings: {
let _ = (context.sharedContext.accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.presentationThemeSettings]))
|> take(1)
|> deliverOnMainQueue).start(next: { view in
let settings = (view.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
pushControllerImpl?(BubbleSettingsController(context: context, presentationThemeSettings: settings))
})
}, toggleLargeEmoji: { largeEmoji in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedLargeEmoji(largeEmoji)
@ -729,7 +752,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
let newTheme: PresentationThemeReference
if let previousThemeIndex = previousThemeIndex {
newTheme = .cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil))
let theme = themes[themes.index(before: previousThemeIndex.base)]
newTheme = .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil))
} else {
newTheme = .builtin(.nightAccent)
}
@ -953,7 +977,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
let newTheme: PresentationThemeReference
if let previousThemeIndex = previousThemeIndex {
selectThemeImpl?(.cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil)))
let theme = themes[themes.index(before: previousThemeIndex.base)]
selectThemeImpl?(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
} else {
if settings.baseTheme == .night {
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
@ -1014,7 +1039,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
defaultThemes.append(contentsOf: [.builtin(.night), .builtin(.nightAccent)])
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }.filter { !removedThemeIndexes.contains($0.index) }
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }.filter { !removedThemeIndexes.contains($0.index) }
var availableThemes = defaultThemes
if defaultThemes.first(where: { $0.index == themeReference.index }) == nil && cloudThemes.first(where: { $0.index == themeReference.index }) == nil {
@ -1158,7 +1183,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
var baseThemeIndex: Int64?
var updatedThemeBaseIndex: Int64?
if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
if let settings = info.theme.settings {
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
updatedThemeBaseIndex = baseThemeIndex
@ -1270,7 +1295,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
}
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}).start()
presentCrossfadeControllerImpl?(true)

View File

@ -16,17 +16,19 @@ class ThemeSettingsFontSizeItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let fontSize: PresentationFontSize
let disableLeadingInset: Bool
let displayIcons: Bool
let force: Bool
let enabled: Bool
let sectionId: ItemListSectionId
let updated: (PresentationFontSize) -> Void
let tag: ItemListItemTag?
init(theme: PresentationTheme, fontSize: PresentationFontSize, enabled: Bool = true, disableLeadingInset: Bool = false, force: Bool = false, sectionId: ItemListSectionId, updated: @escaping (PresentationFontSize) -> Void, tag: ItemListItemTag? = nil) {
init(theme: PresentationTheme, fontSize: PresentationFontSize, enabled: Bool = true, disableLeadingInset: Bool = false, displayIcons: Bool = true, force: Bool = false, sectionId: ItemListSectionId, updated: @escaping (PresentationFontSize) -> Void, tag: ItemListItemTag? = nil) {
self.theme = theme
self.fontSize = fontSize
self.enabled = enabled
self.disableLeadingInset = disableLeadingInset
self.displayIcons = displayIcons
self.force = force
self.sectionId = sectionId
self.updated = updated
@ -164,7 +166,9 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
sliderView.knobImage = generateKnobImage()
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 38.0, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 38.0 * 2.0, height: 44.0))
let sliderInset: CGFloat = item.displayIcons ? 38.0 : 16.0
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
}
self.view.insertSubview(sliderView, belowSubview: self.disabledOverlayNode.view)
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
@ -271,6 +275,9 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.rightIconNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 14.0 - image.size.width, y: 21.0), size: CGSize(width: image.size.width, height: image.size.height))
}
strongSelf.leftIconNode.isHidden = !item.displayIcons
strongSelf.rightIconNode.isHidden = !item.displayIcons
if let sliderView = strongSelf.sliderView {
sliderView.isUserInteractionEnabled = item.enabled
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
@ -302,7 +309,8 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
sliderView.value = value
}
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 38.0, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 38.0 * 2.0, height: 44.0))
let sliderInset: CGFloat = item.displayIcons ? 38.0 : 16.0
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
}
}
})

View File

@ -415,6 +415,7 @@ private struct ThemeSettingsThemeItemNodeTransition {
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
let crossfade: Bool
let entries: [ThemeSettingsThemeEntry]
}
private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeSettingsThemeEntry], to toEntries: [ThemeSettingsThemeEntry], crossfade: Bool) -> ThemeSettingsThemeItemNodeTransition {
@ -424,7 +425,7 @@ private func preparedTransition(context: AccountContext, action: @escaping (Pres
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: .Down) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: nil) }
return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade)
return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade, entries: toEntries)
}
private func ensureThemeVisible(listNode: ListView, themeReference: PresentationThemeReference, animated: Bool) -> Bool {
@ -512,10 +513,13 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
if self.initialized && transition.crossfade {
options.insert(.AnimateCrossfade)
}
options.insert(.Synchronous)
var scrollToItem: ListViewScrollToItem?
if !self.initialized {
if let index = item.themes.firstIndex(where: { $0.index == item.currentTheme.index }) {
if let index = transition.entries.firstIndex(where: { entry in
return entry.theme.index == item.currentTheme.index
}) {
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
self.initialized = true
}

Some files were not shown because too many files have changed in this diff Show More