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"] [submodule "submodules/rlottie/rlottie"]
path = submodules/rlottie/rlottie path = submodules/rlottie/rlottie
url = https://github.com/laktyushin/rlottie.git url=../rlottie.git
[submodule "submodules/libtgvoip/libtgvoip"] [submodule "submodules/libtgvoip/libtgvoip"]
path = submodules/libtgvoip/libtgvoip path = submodules/libtgvoip/libtgvoip
url = https://github.com/peter-iakovlev/libtgvoip.git url = https://github.com/telegramdesktop/libtgvoip.git

View File

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

View File

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

View File

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

View File

@ -22,6 +22,10 @@
"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll %2$@"; "PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll %2$@";
"PUSH_PINNED_POLL" = "%1$@|pinned a poll"; "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_TEXT" = "%2$@|%1$@: %3$@";
"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message"; "PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message";
"PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo"; "PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo";
@ -93,6 +97,7 @@
"PUSH_MESSAGE_GEO" = "%1$@|sent you a map"; "PUSH_MESSAGE_GEO" = "%1$@|sent you a map";
"PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location"; "PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location";
"PUSH_MESSAGE_POLL" = "%1$@|sent you a poll"; "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_GIF" = "%1$@|sent you a GIF";
"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; "PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
"PUSH_MESSAGE_INVOICE" = "%1$@|sent you an invoice for %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_GEO" = "%1$@|posted a map";
"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location"; "PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location";
"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll"; "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_GIF" = "%1$@|posted a GIF";
"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; "PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
"PUSH_CHANNEL_MESSAGE_FWD" = "%1$@|posted a forwarded message"; "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_GEO" = "%2$@|%1$@ sent a map";
"PUSH_CHAT_MESSAGE_GEOLIVE" = "%2$@|%1$@ started sharing their live location"; "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_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_GIF" = "%2$@|%1$@ sent a GIF";
"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@"; "PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@";
"PUSH_CHAT_MESSAGE_INVOICE" = "%2$@|%1$@ sent an invoice for %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_GEO" = "%1$@|pinned a map";
"PUSH_PINNED_GEOLIVE" = "%1$@|pinned a live location"; "PUSH_PINNED_GEOLIVE" = "%1$@|pinned a live location";
"PUSH_PINNED_POLL" = "|%1$@|pinned a poll %2$@"; "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_GAME" = "%1$@|pinned a game";
"PUSH_PINNED_INVOICE" = "%1$@|pinned an invoice"; "PUSH_PINNED_INVOICE" = "%1$@|pinned an invoice";
"PUSH_PINNED_GIF" = "%1$@|pinned a GIF"; "PUSH_PINNED_GIF" = "%1$@|pinned a GIF";
@ -1927,6 +1935,7 @@
"Notification.PinnedContactMessage" = "%@ pinned a contact"; "Notification.PinnedContactMessage" = "%@ pinned a contact";
"Notification.PinnedDeletedMessage" = "%@ pinned deleted message"; "Notification.PinnedDeletedMessage" = "%@ pinned deleted message";
"Notification.PinnedPollMessage" = "%@ pinned a poll"; "Notification.PinnedPollMessage" = "%@ pinned a poll";
"Notification.PinnedQuizMessage" = "%@ pinned a quiz";
"Message.PinnedTextMessage" = "pinned \"%@\" "; "Message.PinnedTextMessage" = "pinned \"%@\" ";
"Message.PinnedPhotoMessage" = "pinned photo"; "Message.PinnedPhotoMessage" = "pinned photo";
@ -1937,7 +1946,6 @@
"Message.PinnedStickerMessage" = "pinned sticker"; "Message.PinnedStickerMessage" = "pinned sticker";
"Message.PinnedLocationMessage" = "pinned location"; "Message.PinnedLocationMessage" = "pinned location";
"Message.PinnedContactMessage" = "pinned contact"; "Message.PinnedContactMessage" = "pinned contact";
"Message.PinnedPollMessage" = "pinned poll";
"Notification.PinnedMessage" = "pinned message"; "Notification.PinnedMessage" = "pinned message";
@ -3793,6 +3801,7 @@ Unused sets are archived when you add more.";
"MessagePoll.VotedCount_any" = "%@ votes"; "MessagePoll.VotedCount_any" = "%@ votes";
"AttachmentMenu.Poll" = "Poll"; "AttachmentMenu.Poll" = "Poll";
"Conversation.PinnedPoll" = "Pinned Poll"; "Conversation.PinnedPoll" = "Pinned Poll";
"Conversation.PinnedQuiz" = "Pinned Quiz";
"CreatePoll.Title" = "New Poll"; "CreatePoll.Title" = "New Poll";
"CreatePoll.Create" = "Send"; "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"; "Conversation.ContextMenuCancelEditing" = "Cancel Editing";
"Map.NoPlacesNearby" = "There are no known places nearby.\nTry a different location."; "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 makeComposeController(context: AccountContext) -> ViewController
func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController 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 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 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, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader 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 makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController?
func makeContactSelectionController(_ params: ContactSelectionControllerParams) -> ContactSelectionController func makeContactSelectionController(_ params: ContactSelectionControllerParams) -> ContactSelectionController
func makeContactMultiselectionController(_ params: ContactMultiselectionControllerParams) -> ContactMultiselectionController func makeContactMultiselectionController(_ params: ContactMultiselectionControllerParams) -> ContactMultiselectionController

View File

@ -246,7 +246,7 @@ public enum ChatControllerSubject: Equatable {
public enum ChatControllerPresentationMode: Equatable { public enum ChatControllerPresentationMode: Equatable {
case standard(previewing: Bool) case standard(previewing: Bool)
case overlay case overlay(NavigationController?)
case inline(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 excludeDisabled = ChatListNodePeersFilter(rawValue: 1 << 10)
public static let includeSavedMessages = ChatListNodePeersFilter(rawValue: 1 << 11) public static let includeSavedMessages = ChatListNodePeersFilter(rawValue: 1 << 11)
public static let excludeChannels = ChatListNodePeersFilter(rawValue: 1 << 12)
} }
public final class PeerSelectionControllerParams { public final class PeerSelectionControllerParams {
@ -32,12 +34,14 @@ public final class PeerSelectionControllerParams {
public let filter: ChatListNodePeersFilter public let filter: ChatListNodePeersFilter
public let hasContactSelector: Bool public let hasContactSelector: Bool
public let title: String? 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.context = context
self.filter = filter self.filter = filter
self.hasContactSelector = hasContactSelector self.hasContactSelector = hasContactSelector
self.title = title 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.clear(CGRect(origin: CGPoint(), size: size))
context.scaleBy(x: scale, y: scale) context.scaleBy(x: scale, y: scale)
UIGraphicsPushContext(context) UIGraphicsPushContext(context)
window.forEachViewController { controller in window.forEachViewController({ controller in
if let controller = controller as? PasscodeEntryController { if let controller = controller as? PasscodeEntryController {
controller.displayNode.alpha = 0.0 controller.displayNode.alpha = 0.0
} }
return true return true
} })
window.hostView.containerView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false) 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 { if let controller = controller as? PasscodeEntryController {
controller.displayNode.alpha = 1.0 controller.displayNode.alpha = 1.0
} }
return true return true
} })
UIGraphicsPopContext() UIGraphicsPopContext()
}).flatMap(applyScreenshotEffectToImage) }).flatMap(applyScreenshotEffectToImage)
} }
@ -201,6 +201,7 @@ public final class AppLockContextImpl: AppLockContext {
} }
} }
passcodeController.presentedOverCoveringView = true passcodeController.presentedOverCoveringView = true
passcodeController.isOpaqueWhenInOverlay = true
strongSelf.passcodeController = passcodeController strongSelf.passcodeController = passcodeController
if let rootViewController = strongSelf.rootController { if let rootViewController = strongSelf.rootController {
if let presentedViewController = rootViewController.presentedViewController as? UIActivityViewController { 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? { private func generateGradientFilledCircleImage(diameter: CGFloat, colors: NSArray) -> UIImage? {
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
@ -167,6 +157,16 @@ public final class AvatarEditOverlayNode: ASDisplayNode {
} }
public final class AvatarNode: 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 { public var font: UIFont {
didSet { didSet {
if oldValue !== font { if oldValue !== font {
@ -318,7 +318,7 @@ public final class AvatarNode: ASDisplayNode {
let parameters: AvatarNodeParameters 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.contents = nil
self.displaySuspended = true self.displaySuspended = true
self.imageReady.set(self.imageNode.ready) self.imageReady.set(self.imageNode.ready)
@ -416,7 +416,7 @@ public final class AvatarNode: ASDisplayNode {
if peerId.namespace == -1 { if peerId.namespace == -1 {
colorIndex = -1 colorIndex = -1
} else { } else {
colorIndex = abs(Int(clamping: accountPeerId.id &+ peerId.id)) colorIndex = abs(Int(clamping: peerId.id))
} }
} else { } else {
colorIndex = -1 colorIndex = -1
@ -456,7 +456,7 @@ public final class AvatarNode: ASDisplayNode {
colorsArray = grayscaleColors colorsArray = grayscaleColors
} }
} else { } else {
colorsArray = gradientColors[colorIndex % gradientColors.count] colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count]
} }
var locations: [CGFloat] = [1.0, 0.0] 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.beginPath()
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
size.height)) size.height))
@ -572,7 +572,7 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont
if colorIndex == -1 { if colorIndex == -1 {
colorsArray = grayscaleColors colorsArray = grayscaleColors
} else { } else {
colorsArray = gradientColors[colorIndex % gradientColors.count] colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count]
} }
var locations: [CGFloat] = [1.0, 0.0] 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.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
context.resetClip()
context.setBlendMode(.normal) context.setBlendMode(.normal)
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1])) 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.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.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) context.translateBy(x: lineOrigin.x, y: lineOrigin.y)
CTLineDraw(line, context) CTLineDraw(line, context)
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y) context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
context.textPosition = textPosition
} }

View File

@ -21,7 +21,7 @@ private let roundCorners = { () -> UIImage in
return image 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 { if let smallProfileImage = representation {
let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad) let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad)
let imageData = resourceData let imageData = resourceData
@ -44,7 +44,7 @@ public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: M
subscriber.putCompletion() subscriber.putCompletion()
}) })
var fetchedDataDisposable: Disposable? 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() fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource), statsCategory: .generic).start()
} else if let authorOfMessage = authorOfMessage { } else if let authorOfMessage = authorOfMessage {
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .messageAuthorAvatar(message: authorOfMessage, resource: smallProfileImage.resource), statsCategory: .generic).start() 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>? { 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, peer: peer, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) { if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData return imageData
|> mapToSignal { data -> Signal<UIImage?, NoError> in |> mapToSignal { data -> Signal<UIImage?, NoError> in
let generate = deferred { () -> 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]) let showCallsTab = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings])
|> map { sharedData -> Bool in |> map { sharedData -> Bool in
var value = true var value = CallListSettings.defaultSettings.showTab
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings] as? CallListSettings { if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings] as? CallListSettings {
value = settings.showTab 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) 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 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 strongSelf.didShowProxyUnavailableTooltipController = true
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true) let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true)
strongSelf.proxyUnavailableTooltipController = tooltipController 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.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in
self?.requestOpenPeerFromSearch?(peer, dismissSearch) self?.requestOpenPeerFromSearch?(peer, dismissSearch)
}, openDisabledPeer: { _ in
}, openRecentPeerOptions: { [weak self] peer in }, openRecentPeerOptions: { [weak self] peer in
self?.requestOpenRecentPeerOptions?(peer) self?.requestOpenRecentPeerOptions?(peer)
}, openMessage: { [weak self] peer, messageId in }, 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 { switch self {
case let .topPeers(peers, theme, strings): case let .topPeers(peers, theme, strings):
return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in 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 let status: ContactsPeerItemStatus
if let user = primaryPeer as? TelegramUser { if let user = primaryPeer as? TelegramUser {
let servicePeer = isServicePeer(primaryPeer) let servicePeer = isServicePeer(primaryPeer)
@ -181,6 +187,10 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
if let chatPeer = peer.peer.peers[peer.peer.peerId] { if let chatPeer = peer.peer.peers[peer.peer.peerId] {
peerSelected(chatPeer) peerSelected(chatPeer)
} }
}, disabledAction: { _ in
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
disaledPeerSelected(chatPeer)
}
}, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in }, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in
return { node, gesture in return { node, gesture in
if let chatPeer = peer.peer.peers[peer.peer.peerId] { 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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } 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 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, 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) return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
} }
@ -615,7 +625,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let filter: ChatListNodePeersFilter 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.context = context
self.filter = filter self.filter = filter
self.dimNode = ASDisplayNode() 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 return true
} }
@ -962,6 +978,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openPeer(peer, false) openPeer(peer, false)
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
self?.listNode.clearHighlightAnimated(true) self?.listNode.clearHighlightAnimated(true)
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in }, togglePeerSelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in }, messageSelected: { [weak self] peer, message, _ in
self?.view.endEditing(true) self?.view.endEditing(true)
@ -1091,6 +1108,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openPeer(peer, true) openPeer(peer, true)
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
self?.recentListNode.clearHighlightAnimated(true) self?.recentListNode.clearHighlightAnimated(true)
}, disaledPeerSelected: { peer in
openDisabledPeer(peer)
}, peerContextAction: peerContextAction, }, peerContextAction: peerContextAction,
clearRecentlySearchedPeers: { clearRecentlySearchedPeers: {
self?.clearRecentSearch() self?.clearRecentSearch()

View File

@ -755,7 +755,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var currentSecretIconImage: UIImage? var currentSecretIconImage: UIImage?
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
let editingOffset: CGFloat let editingOffset: CGFloat
var reorderInset: CGFloat = 0.0 var reorderInset: CGFloat = 0.0
@ -1331,7 +1331,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let reorderControlSizeAndApply = reorderControlSizeAndApply { 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)) 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 { 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.reorderControlNode = reorderControlNode
strongSelf.addSubnode(reorderControlNode) strongSelf.addSubnode(reorderControlNode)
reorderControlNode.frame = reorderControlFrame reorderControlNode.frame = reorderControlFrame
@ -1344,7 +1344,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition.updateAlpha(node: strongSelf.pinnedIconNode, alpha: 0.0) transition.updateAlpha(node: strongSelf.pinnedIconNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.statusNode, alpha: 0.0) transition.updateAlpha(node: strongSelf.statusNode, alpha: 0.0)
} else if let reorderControlNode = strongSelf.reorderControlNode { } 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) transition.updateFrame(node: reorderControlNode, frame: reorderControlFrame)
} }
} else if let reorderControlNode = strongSelf.reorderControlNode { } else if let reorderControlNode = strongSelf.reorderControlNode {

View File

@ -46,6 +46,7 @@ final class ChatListHighlightedLocation {
public final class ChatListNodeInteraction { public final class ChatListNodeInteraction {
let activateSearch: () -> Void let activateSearch: () -> Void
let peerSelected: (Peer) -> Void let peerSelected: (Peer) -> Void
let disabledPeerSelected: (Peer) -> Void
let togglePeerSelected: (PeerId) -> Void let togglePeerSelected: (PeerId) -> Void
let messageSelected: (Peer, Message, Bool) -> Void let messageSelected: (Peer, Message, Bool) -> Void
let groupSelected: (PeerGroupId) -> Void let groupSelected: (PeerGroupId) -> Void
@ -62,9 +63,10 @@ public final class ChatListNodeInteraction {
public var searchTextHighightState: String? public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation? 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.activateSearch = activateSearch
self.peerSelected = peerSelected self.peerSelected = peerSelected
self.disabledPeerSelected = disabledPeerSelected
self.togglePeerSelected = togglePeerSelected self.togglePeerSelected = togglePeerSelected
self.messageSelected = messageSelected self.messageSelected = messageSelected
self.groupSelected = groupSelected self.groupSelected = groupSelected
@ -202,11 +204,22 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
enabled = false 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 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 { if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer) nodeInteraction.peerSelected(chatPeer)
} }
}, disabledAction: { _ in
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}), directionHint: entry.directionHint) }), directionHint: entry.directionHint)
} }
case let .HoleEntry(_, theme): case let .HoleEntry(_, theme):
@ -242,10 +255,19 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
enabled = false 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 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 { if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer) nodeInteraction.peerSelected(chatPeer)
} }
}, disabledAction: { _ in
if let chatPeer = chatPeer {
nodeInteraction.disabledPeerSelected(chatPeer)
}
}), directionHint: entry.directionHint) }), directionHint: entry.directionHint)
} }
case let .HoleEntry(_, theme): case let .HoleEntry(_, theme):
@ -318,6 +340,7 @@ public final class ChatListNode: ListView {
} }
public var peerSelected: ((PeerId, Bool, Bool) -> Void)? public var peerSelected: ((PeerId, Bool, Bool) -> Void)?
public var disabledPeerSelected: ((Peer) -> Void)?
public var groupSelected: ((PeerGroupId) -> Void)? public var groupSelected: ((PeerGroupId) -> Void)?
public var addContact: ((String) -> Void)? public var addContact: ((String) -> Void)?
public var activateSearch: (() -> Void)? public var activateSearch: (() -> Void)?
@ -409,6 +432,10 @@ public final class ChatListNode: ListView {
if let strongSelf = self, let peerSelected = strongSelf.peerSelected { if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peer.id, true, false) 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 }, togglePeerSelected: { [weak self] peerId in
self?.updateState { state in self?.updateState { state in
var state = state 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 filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) {
if let peer = peer.peers[peer.peerId] { if let peer = peer.peers[peer.peerId] {
if !canSendMessagesToPeer(peer) { 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 strongSelf.iconNode.image = updatedIcon
} }
if let image = strongSelf.iconNode.image { 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 { if strongSelf.backgroundNode.supernode == nil {

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import TelegramPresentationData import TelegramPresentationData
import ItemListUI import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import CheckNode
struct CreatePollOptionItemEditing { struct CreatePollOptionItemEditing {
let editable: Bool let editable: Bool
@ -13,27 +14,29 @@ struct CreatePollOptionItemEditing {
} }
class CreatePollOptionItem: ListViewItem, ItemListItem { class CreatePollOptionItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let presentationData: ItemListPresentationData
let strings: PresentationStrings
let id: Int let id: Int
let placeholder: String let placeholder: String
let value: String let value: String
let isSelected: Bool?
let maxLength: Int let maxLength: Int
let editing: CreatePollOptionItemEditing let editing: CreatePollOptionItemEditing
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let setItemIdWithRevealedOptions: (Int?, Int?) -> Void let setItemIdWithRevealedOptions: (Int?, Int?) -> Void
let updated: (String) -> Void let updated: (String, Bool) -> Void
let next: (() -> Void)? let next: (() -> Void)?
let delete: (Bool) -> Void let delete: (Bool) -> Void
let focused: () -> Void let canDelete: Bool
let focused: (Bool) -> Void
let toggleSelected: () -> Void
let tag: ItemListItemTag? 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?) { 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.theme = theme self.presentationData = presentationData
self.strings = strings
self.id = id self.id = id
self.placeholder = placeholder self.placeholder = placeholder
self.value = value self.value = value
self.isSelected = isSelected
self.maxLength = maxLength self.maxLength = maxLength
self.editing = editing self.editing = editing
self.sectionId = sectionId self.sectionId = sectionId
@ -41,7 +44,9 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
self.updated = updated self.updated = updated
self.next = next self.next = next
self.delete = delete self.delete = delete
self.canDelete = canDelete
self.focused = focused self.focused = focused
self.toggleSelected = toggleSelected
self.tag = tag self.tag = tag
} }
@ -55,7 +60,7 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
Queue.mainQueue().async { Queue.mainQueue().async {
completion(node, { 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)) let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async { Queue.mainQueue().async {
completion(layout, { _ in completion(layout, { _ in
apply() apply(animation)
}) })
} }
} }
@ -84,17 +89,19 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(15.0) private let titleFont = Font.regular(15.0)
class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode, ItemListItemFocusableNode, ASEditableTextNodeDelegate { class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode, ItemListItemFocusableNode, ASEditableTextNodeDelegate {
private let containerNode: ASDisplayNode
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode private let maskNode: ASImageNode
private var checkNode: CheckNode?
private let textClippingNode: ASDisplayNode private let textClippingNode: ASDisplayNode
private let textNode: EditableTextNode private let textNode: EditableTextNode
private let measureTextNode: TextNode private let measureTextNode: TextNode
private let textLimitNode: TextNode private let textLimitNode: TextNode
private let editableControlNode: ItemListEditableControlNode
private let reorderControlNode: ItemListEditableReorderControlNode private let reorderControlNode: ItemListEditableReorderControlNode
private var item: CreatePollOptionItem? private var item: CreatePollOptionItem?
@ -104,7 +111,21 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
return self.item?.tag 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() { init() {
self.containerNode = ASDisplayNode()
self.containerNode.clipsToBounds = true
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white self.backgroundNode.backgroundColor = .white
@ -117,7 +138,6 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
self.maskNode = ASImageNode() self.maskNode = ASImageNode()
self.editableControlNode = ItemListEditableControlNode()
self.reorderControlNode = ItemListEditableReorderControlNode() self.reorderControlNode = ItemListEditableReorderControlNode()
self.textClippingNode = ASDisplayNode() self.textClippingNode = ASDisplayNode()
@ -131,21 +151,17 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.clipsToBounds = true self.addSubnode(self.containerNode)
self.textClippingNode.addSubnode(self.textNode) self.textClippingNode.addSubnode(self.textNode)
self.addSubnode(self.textClippingNode) self.containerNode.addSubnode(self.textClippingNode)
self.addSubnode(self.editableControlNode) self.containerNode.addSubnode(self.reorderControlNode)
self.addSubnode(self.reorderControlNode) self.containerNode.addSubnode(self.textLimitNode)
self.addSubnode(self.textLimitNode) }
self.editableControlNode.tapped = { [weak self] in @objc private func checkNodePressed() {
if let strongSelf = self { self.item?.toggleSelected()
strongSelf.setRevealOptionsOpened(true, animated: true)
strongSelf.revealOptionsInteractivelyOpened()
}
}
} }
override func didLoad() { override func didLoad() {
@ -153,7 +169,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
var textColor: UIColor = .black var textColor: UIColor = .black
if let item = self.item { 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.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
self.textNode.clipsToBounds = true self.textNode.clipsToBounds = true
@ -162,7 +178,12 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
} }
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) { 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 { func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
@ -177,7 +198,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
if updatedText.count == 1 { if updatedText.count == 1 {
updatedText = "" 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.textNode.attributedText = updatedAttributedText
self.editableTextNodeDidUpdateText(editableTextNode) self.editableTextNodeDidUpdateText(editableTextNode)
} }
@ -192,6 +213,10 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
} }
func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.internalEditableTextNodeDidUpdateText(editableTextNode, isLosingFocus: false)
}
private func internalEditableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode, isLosingFocus: Bool) {
if let item = self.item { if let item = self.item {
let text = self.textNode.attributedText ?? NSAttributedString() let text = self.textNode.attributedText ?? NSAttributedString()
@ -201,15 +226,15 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
hadReturn = true hadReturn = true
updatedText = updatedText.replacingOccurrences(of: "\n", with: " ") 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 { if text.string != updatedAttributedText.string {
self.textNode.attributedText = updatedAttributedText self.textNode.attributedText = updatedAttributedText
} }
item.updated(updatedText) item.updated(updatedText, !isLosingFocus && editableTextNode.isFirstResponder())
if hadReturn { if hadReturn {
if let next = item.next { if let next = item.next {
next() next()
} else { } else if !isLosingFocus {
editableTextNode.resignFirstResponder() editableTextNode.resignFirstResponder()
} }
} }
@ -220,8 +245,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
self.item?.delete(editableTextNode.isFirstResponder()) self.item?.delete(editableTextNode.isFirstResponder())
} }
func asyncLayout() -> (_ item: CreatePollOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { func asyncLayout() -> (_ item: CreatePollOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode) let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode) let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
let makeTextLimitLayout = TextNode.asyncLayout(self.textLimitNode) let makeTextLimitLayout = TextNode.asyncLayout(self.textLimitNode)
@ -231,32 +255,31 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
return { item, params, neighbors in return { item, params, neighbors in
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme { if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.theme updatedTheme = item.presentationData.theme
} }
let controlSizeAndApply = editableControlLayout(item.theme, false) let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
let reorderSizeAndApply = reorderControlLayout(item.theme)
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let insets = itemListNeighborsGroupedInsets(neighbors) 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 rightInset: CGFloat = 44.0 + params.rightInset
let textLength = item.value.count let textLength = item.value.count
let displayTextLimit = textLength > item.maxLength * 70 / 100 let displayTextLimit = textLength > item.maxLength * 70 / 100
let remainingCount = item.maxLength - textLength 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 var measureText = item.value
if measureText.hasSuffix("\n") || measureText.isEmpty { if measureText.hasSuffix("\n") || measureText.isEmpty {
measureText += "|" measureText += "|"
} }
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black) 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 (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 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 contentSize = CGSize(width: params.width, height: textLayout.size.height + textTopInset + textBottomInset)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) 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 { 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.item = item
strongSelf.layoutParams = params strongSelf.layoutParams = params
if let _ = updatedTheme { if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
if strongSelf.isNodeLoaded { if strongSelf.isNodeLoaded {
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.theme.list.itemPrimaryTextColor] strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.presentationData.theme.list.itemPrimaryTextColor]
strongSelf.textNode.tintColor = item.theme.list.itemAccentColor strongSelf.textNode.tintColor = item.presentationData.theme.list.itemAccentColor
} }
} }
@ -295,7 +326,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
let _ = textApply() let _ = textApply()
if let currentText = strongSelf.textNode.attributedText { if let currentText = strongSelf.textNode.attributedText {
if currentText.string != attributedText.string { if currentText.string != attributedText.string || updatedTheme != nil {
strongSelf.textNode.attributedText = attributedText strongSelf.textNode.attributedText = attributedText
} }
} else { } else {
@ -325,58 +356,93 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText 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)) let checkSize = CGSize(width: 32.0, height: 32.0)
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset, height: textLayout.size.height + 1.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 { if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0)
} }
if strongSelf.topStripeNode.supernode == nil { if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1)
} }
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2)
} }
if strongSelf.maskNode.supernode == nil { 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) let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false var hasTopCorners = false
var hasBottomCorners = false var hasBottomCorners = false
switch neighbors.top { switch neighbors.top {
case .sameSection(false): case .sameSection(false):
strongSelf.topStripeNode.isHidden = true strongSelf.topStripeNode.isHidden = true
default: default:
hasTopCorners = true hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners strongSelf.topStripeNode.isHidden = hasCorners
} }
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
switch neighbors.bottom { switch neighbors.bottom {
case .sameSection(false): case .sameSection(false):
bottomStripeInset = leftInset bottomStripeInset = leftInset
default: strongSelf.bottomStripeNode.isHidden = false
bottomStripeInset = 0.0 default:
hasBottomCorners = true bottomStripeInset = 0.0
strongSelf.bottomStripeNode.isHidden = hasCorners 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.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 _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit, transition)
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 reorderControlFrame = CGRect(origin: CGPoint(x: params.width + revealOffset - params.rightInset - reorderSizeAndApply.0, y: 0.0), size: CGSize(width: reorderSizeAndApply.0, height: layout.contentSize.height)) 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.frame = reorderControlFrame
strongSelf.reorderControlNode.isHidden = !item.canDelete
let _ = textLimitApply() 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) 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.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) { override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition) super.updateRevealOffset(offset: offset, transition: transition)
guard let params = self.layoutParams else { guard let params = self.layoutParams, let item = self.item else {
return return
} }
let revealOffset = offset let revealOffset = offset
let leftInset: CGFloat let leftInset: CGFloat
leftInset = 60.0 + params.leftInset leftInset = params.leftInset + (item.isSelected != nil ? 60.0 : 16.0)
var controlFrame = self.editableControlNode.frame if let checkNode = self.checkNode {
controlFrame.origin.x = params.leftInset + 6.0 + revealOffset var checkNodeFrame = checkNode.frame
transition.updateFrame(node: self.editableControlNode, frame: controlFrame) checkNodeFrame.origin.x = params.leftInset + 11.0 + revealOffset
transition.updateFrame(node: checkNode, frame: checkNodeFrame)
}
var reorderFrame = self.reorderControlNode.frame var reorderFrame = self.reorderControlNode.frame
reorderFrame.origin.x = params.width + revealOffset - params.rightInset - reorderFrame.width 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 { 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 true
} }
return false return false
@ -448,5 +516,16 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
var separatorFrame = self.bottomStripeNode.frame var separatorFrame = self.bottomStripeNode.frame
separatorFrame.origin.y = currentValue - UIScreenPixel separatorFrame.origin.y = currentValue - UIScreenPixel
self.bottomStripeNode.frame = separatorFrame 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 options: [ItemListPeerItemRevealOption]
let actionIcon: ContactsPeerItemActionIcon let actionIcon: ContactsPeerItemActionIcon
let action: (ContactsPeerItemPeer) -> Void let action: (ContactsPeerItemPeer) -> Void
let disabledAction: ((ContactsPeerItemPeer) -> Void)?
let setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? let setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)?
let deletePeer: ((PeerId) -> Void)? let deletePeer: ((PeerId) -> Void)?
let itemHighlighting: ContactItemHighlighting? let itemHighlighting: ContactItemHighlighting?
@ -133,7 +134,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
public let header: ListViewItemHeader? 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.presentationData = presentationData
self.style = style self.style = style
self.sectionId = sectionId self.sectionId = sectionId
@ -150,11 +151,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
self.options = options self.options = options
self.actionIcon = actionIcon self.actionIcon = actionIcon
self.action = action self.action = action
self.disabledAction = disabledAction
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.deletePeer = deletePeer self.deletePeer = deletePeer
self.header = header self.header = header
self.itemHighlighting = itemHighlighting self.itemHighlighting = itemHighlighting
self.selectable = enabled self.selectable = enabled || disabledAction != nil
self.contextAction = contextAction self.contextAction = contextAction
if let index = index { if let index = index {
@ -245,7 +247,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
} }
public func selected(listView: ListView) { 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) { 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 validatedGesture = false
private var firstLocation: CGPoint = CGPoint() private var firstLocation: CGPoint = CGPoint()
public var shouldBegin: ((CGPoint) -> Bool)?
override public init(target: Any?, action: Selector?) { override public init(target: Any?, action: Selector?) {
super.init(target: target, action: action) super.init(target: target, action: action)
@ -21,7 +23,13 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
super.touchesBegan(touches, with: event) super.touchesBegan(touches, with: event)
let touch = touches.first! 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 let target = self.view?.hitTest(self.firstLocation, with: event) {
if target == self.view { 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 cachedCorners = Atomic<[Corner: DrawingContext]>(value: [:])
private var cachedTails = Atomic<[Tail: DrawingContext]>(value: [:])
private func cornerContext(_ corner: Corner) -> DrawingContext { private func cornerContext(_ corner: Corner) -> DrawingContext {
let cached: DrawingContext? = cachedCorners.with { 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) let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true)
context.withContext { c in 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) c.setFillColor(UIColor.black.cgColor)
let rect: CGRect
switch corner { switch corner {
case let .TopLeft(radius): case let .TopLeft(radius):
rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) let rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
case let .TopRight(radius): c.fillEllipse(in: rect)
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) case let .TopRight(radius):
case let .BottomLeft(radius): let rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) c.fillEllipse(in: rect)
case let .BottomRight(radius): case let .BottomLeft(radius):
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) 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 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) { public func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) {
let corners = arguments.corners let corners = arguments.corners
let drawingRect = arguments.drawingRect let drawingRect = arguments.drawingRect
@ -223,23 +168,24 @@ public func addCorners(_ context: DrawingContext, arguments: TransformImageArgum
let corner = cornerContext(.BottomLeft(Int(radius))) let corner = cornerContext(.BottomLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 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 radius > CGFloat.ulpOfOne {
if enabled { let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
let tail = tailContext(.BottomLeft(Int(radius))) context.withContext { c in
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0)) c.clear(CGRect(x: drawingRect.minX - 4.0, y: 0.0, width: 4.0, height: drawingRect.maxY - 6.0))
context.withContext { c in c.setFillColor(color.cgColor)
c.clear(CGRect(x: drawingRect.minX - 3.0, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0)) c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 7.0, width: 4.0, height: 7.0))
c.setFillColor(color.cgColor) c.setBlendMode(.destinationIn)
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0)) 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)
context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius)) c.scaleBy(x: 1.0, y: -1.0)
} else { c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
let corner = cornerContext(.BottomLeft(Int(radius))) c.draw(image.cgImage!, in: cornerRect)
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius)) 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 { switch corners.bottomRight {
@ -248,20 +194,22 @@ public func addCorners(_ context: DrawingContext, arguments: TransformImageArgum
let corner = cornerContext(.BottomRight(Int(radius))) let corner = cornerContext(.BottomRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - 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 radius > CGFloat.ulpOfOne {
if enabled { let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
let tail = tailContext(.BottomRight(Int(radius))) context.withContext { c in
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0)) c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 4.0, height: drawingRect.maxY - image.size.height))
context.withContext { c in c.setFillColor(color.cgColor)
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0)) c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 7.0, width: 5.0, height: 7.0))
c.setFillColor(color.cgColor) c.setBlendMode(.destinationIn)
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0)) 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)
context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius)) c.scaleBy(x: 1.0, y: -1.0)
} else { c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
let corner = cornerContext(.BottomRight(Int(radius))) c.draw(image.cgImage!, in: cornerRect)
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius)) 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 { public enum ImageCorner: Equatable {
case Corner(CGFloat) case Corner(CGFloat)
case Tail(CGFloat, Bool) case Tail(CGFloat, UIImage)
public var extendedInsets: CGSize { public var extendedInsets: CGSize {
switch self { switch self {
case .Tail: case .Tail:
return CGSize(width: 3.0, height: 0.0) return CGSize(width: 4.0, height: 0.0)
default: default:
return CGSize() return CGSize()
} }
@ -36,15 +36,6 @@ public enum ImageCorner: Equatable {
return radius 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 { public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool {
@ -56,8 +47,8 @@ public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool {
default: default:
return false return false
} }
case let .Tail(lhsRadius, lhsEnabled): case let .Tail(lhsRadius, lhsImage):
if case let .Tail(rhsRadius, rhsEnabled) = rhs, lhsRadius.isEqual(to: rhsRadius), lhsEnabled == rhsEnabled { if case let .Tail(rhsRadius, rhsImage) = rhs, lhsRadius.isEqual(to: rhsRadius), lhsImage === rhsImage {
return true return true
} else { } else {
return false return false
@ -124,10 +115,6 @@ public struct ImageCorners: Equatable {
public func withRemovedTails() -> ImageCorners { public func withRemovedTails() -> ImageCorners {
return ImageCorners(topLeft: self.topLeft.withoutTail, topRight: self.topRight.withoutTail, bottomLeft: self.bottomLeft.withoutTail, bottomRight: self.bottomRight.withoutTail) 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 { public func ==(lhs: ImageCorners, rhs: ImageCorners) -> Bool {

View File

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

View File

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

View File

@ -564,6 +564,6 @@ open class ListViewItemNode: ASDisplayNode {
} }
open func snapshotForReordering() -> UIView? { 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 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) { @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state { switch recognizer.state {
case .began: case .began:
@ -147,7 +157,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let progress = translation / self.bounds.width let progress = translation / self.bounds.width
let velocity = recognizer.velocity(in: self.view).x 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.isDismissed = true
self.horizontalDismissOffset = self.bounds.width self.horizontalDismissOffset = self.bounds.width
self.dismissProgress = 1.0 self.dismissProgress = 1.0
@ -243,7 +253,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let duration = Double(min(0.3, velocityFactor)) let duration = Double(min(0.3, velocityFactor))
let transition: ContainedViewLayoutTransition let transition: ContainedViewLayoutTransition
let dismissProgress: CGFloat let dismissProgress: CGFloat
if velocity.y < -0.5 || progress >= 0.5 { if (velocity.y < -0.5 || progress >= 0.5) && self.checkInteractiveDismissWithControllers() {
dismissProgress = 1.0 dismissProgress = 1.0
targetOffset = 0.0 targetOffset = 0.0
transition = .animated(duration: duration, curve: .easeInOut) transition = .animated(duration: duration, curve: .easeInOut)

View File

@ -360,8 +360,8 @@ public func standardTextAlertController(theme: AlertControllerTheme, title: Stri
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let attributedText: NSAttributedString let attributedText: NSAttributedString
if parseMarkdown { if parseMarkdown {
let font = title == nil ? Font.semibold(theme.baseFontSize) : Font.regular(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) : Font.semibold(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 body = MarkdownAttributeSet(font: font, textColor: theme.primaryColor)
let bold = MarkdownAttributeSet(font: boldFont, 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) 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() { open func dismissImmediately() {
self.dismissed?(false) self.dismissed?(false)
self.controllerNode.hide()
self.presentingViewController?.dismiss(animated: false) 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? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let event = event { if let event = event {
var eventIsPresses = false var eventIsPresses = false

View File

@ -337,8 +337,17 @@ public class Window1 {
self?.isInteractionBlocked = value 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.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 self.hostView.present = { [weak self] controller, level, blockInteraction, completion in
@ -1222,10 +1231,12 @@ public class Window1 {
return hidden return hidden
} }
public func forEachViewController(_ f: (ContainableController) -> Bool) { public func forEachViewController(_ f: (ContainableController) -> Bool, excludeNavigationSubControllers: Bool = false) {
if let navigationController = self._rootController as? NavigationController { if let navigationController = self._rootController as? NavigationController {
for case let controller as ContainableController in navigationController.viewControllers { if !excludeNavigationSubControllers {
!f(controller) for case let controller as ContainableController in navigationController.viewControllers {
!f(controller)
}
} }
if let controller = navigationController.topOverlayController { if let controller = navigationController.topOverlayController {
!f(controller) !f(controller)

View File

@ -396,7 +396,7 @@ public class GalleryController: ViewController, StandalonePresentableController
} else { } else {
namespaces = .not(Namespaces.Message.allScheduled) 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 |> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view) let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped) return .single(mapped)

View File

@ -238,7 +238,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
self.statusButtonNode.addSubnode(self.statusNode) 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) self.addSubnode(self.statusButtonNode)

View File

@ -122,6 +122,7 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode {
public final class SecretMediaPreviewController: ViewController { public final class SecretMediaPreviewController: ViewController {
private let context: AccountContext private let context: AccountContext
private let messageId: MessageId
private let _ready = Promise<Bool>() private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> { override public var ready: Promise<Bool> {
@ -150,6 +151,7 @@ public final class SecretMediaPreviewController: ViewController {
public init(context: AccountContext, messageId: MessageId) { public init(context: AccountContext, messageId: MessageId) {
self.context = context self.context = context
self.messageId = messageId
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) 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.statusBar.statusBarStyle = .White
self.disposable.set((context.account.postbox.messageView(messageId) |> deliverOnMainQueue).start(next: { [weak self] view in self.disposable.set((context.account.postbox.messageView(messageId) |> deliverOnMainQueue).start(next: { [weak self] view in
if let strongSelf = self { if let strongSelf = self {
strongSelf.messageView = view strongSelf.messageView = view
@ -178,17 +178,6 @@ public final class SecretMediaPreviewController: ViewController {
return nil 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) { required public init(coder aDecoder: NSCoder) {
@ -348,6 +337,19 @@ public final class SecretMediaPreviewController: ViewController {
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) 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 var nodeAnimatesItself = false
if let centralItemNode = self.controllerNode.pager.centralItemNode(), let message = self.messageView?.message { 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: { let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { peer in }, peerSelected: { peer in
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in }, togglePeerSelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in }, messageSelected: { [weak self] peer, message, _ in
if let strongSelf = self { if let strongSelf = self {

View File

@ -20,9 +20,9 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
let editing: Bool let editing: Bool
let height: ItemListPeerActionItemHeight let height: ItemListPeerActionItemHeight
public let sectionId: ItemListSectionId 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.presentationData = presentationData
self.icon = icon self.icon = icon
self.title = title 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){ public func selected(listView: ListView){
listView.clearHighlightAnimated(true) listView.clearHighlightAnimated(true)
self.action() self.action?()
} }
} }
@ -150,15 +152,15 @@ class ItemListPeerActionItemNode: ListViewItemNode {
updatedTheme = item.presentationData.theme updatedTheme = item.presentationData.theme
} }
let leftInset: CGFloat let leftInset: CGFloat
let vertcalInset: CGFloat let verticalInset: CGFloat
let verticalOffset: CGFloat let verticalOffset: CGFloat
switch item.height { switch item.height {
case .generic: case .generic:
vertcalInset = 11.0 verticalInset = 11.0
verticalOffset = 0.0 verticalOffset = 0.0
leftInset = 59.0 + params.leftInset leftInset = 59.0 + params.leftInset
case .peerList: case .peerList:
vertcalInset = 14.0 verticalInset = 14.0
verticalOffset = 0.0 verticalOffset = 0.0
leftInset = 65.0 + params.leftInset leftInset = 65.0 + params.leftInset
} }
@ -170,7 +172,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let insets = itemListNeighborsGroupedInsets(neighbors) 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 layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size 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)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + 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)) 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 ContextUI
import AccountContext 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 struct ItemListPeerItemEditing: Equatable {
public var editable: Bool public var editable: Bool
public var editing: 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 { public final class ItemListPeerItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
@ -135,8 +332,10 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
let hasTopGroupInset: Bool let hasTopGroupInset: Bool
let noInsets: Bool let noInsets: Bool
public let tag: ItemListItemTag? 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.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
@ -164,12 +363,14 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
self.hasTopGroupInset = hasTopGroupInset self.hasTopGroupInset = hasTopGroupInset
self.noInsets = noInsets self.noInsets = noInsets
self.tag = tag 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) { 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 { async {
let node = ItemListPeerItemNode() 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.contentSize = layout.contentSize
node.insets = layout.insets 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) { 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 { Queue.mainQueue().async {
if let nodeValue = node() as? ItemListPeerItemNode { if let nodeValue = node() as? ItemListPeerItemNode {
@ -193,7 +407,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
} }
async { 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 { Queue.mainQueue().async {
completion(layout, { _ in completion(layout, { _ in
apply(false, animated) apply(false, animated)
@ -232,8 +446,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
private var switchNode: SwitchNode? private var switchNode: SwitchNode?
private var checkNode: ASImageNode? private var checkNode: ASImageNode?
private var shimmerNode: LoadingShimmerNode?
private var absoluteLocation: (CGRect, CGSize)?
private var peerPresenceManager: PeerPresenceStatusManager? private var peerPresenceManager: PeerPresenceStatusManager?
private var layoutParams: (ItemListPeerItem, ListViewItemLayoutParams, ItemListNeighbors)? private var layoutParams: (ItemListPeerItem, ListViewItemLayoutParams, ItemListNeighbors, Bool)?
private var editableControlNode: ItemListEditableControlNode? private var editableControlNode: ItemListEditableControlNode?
@ -303,7 +520,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
if let strongSelf = self, let layoutParams = strongSelf.layoutParams { 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) 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 makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeStatusLayout = TextNode.asyncLayout(self.statusNode) let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
@ -334,7 +551,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let currentHasBadge = self.labelBadgeNode.image != nil let currentHasBadge = self.labelBadgeNode.image != nil
return { item, params, neighbors in return { item, params, neighbors, headerAtTop in
var updateArrowImage: UIImage? var updateArrowImage: UIImage?
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
@ -579,8 +796,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
insets.top = 0.0 insets.top = 0.0
insets.bottom = 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 minHeight: CGFloat = titleLayout.size.height + verticalInset * 2.0
let rawHeight: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + statusLayout.size.height 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 return (layout, { [weak self] synchronousLoad, animated in
if let strongSelf = self { 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.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil strongSelf.containerNode.isGestureEnabled = item.contextAction != nil
@ -829,6 +1049,44 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.peerPresenceManager?.reset(presence: presence) 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.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
strongSelf.setRevealOptions((left: [], right: peerRevealOptions)) strongSelf.setRevealOptions((left: [], right: peerRevealOptions))
@ -940,13 +1198,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
} }
override public func revealOptionsInteractivelyOpened() { override public func revealOptionsInteractivelyOpened() {
if let (item, _, _) = self.layoutParams { if let (item, _, _, _) = self.layoutParams {
item.setPeerIdWithRevealedOptions(item.peer.id, nil) item.setPeerIdWithRevealedOptions(item.peer.id, nil)
} }
} }
override public func revealOptionsInteractivelyClosed() { override public func revealOptionsInteractivelyClosed() {
if let (item, _, _) = self.layoutParams { if let (item, _, _, _) = self.layoutParams {
item.setPeerIdWithRevealedOptions(nil, item.peer.id) item.setPeerIdWithRevealedOptions(nil, item.peer.id)
} }
} }
@ -955,7 +1213,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
self.setRevealOptionsOpened(false, animated: true) self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed() self.revealOptionsInteractivelyClosed()
if let (item, _, _) = self.layoutParams { if let (item, _, _, _) = self.layoutParams {
if let revealOptions = item.revealOptions { if let revealOptions = item.revealOptions {
if option.key >= 0 && option.key < Int32(revealOptions.options.count) { if option.key >= 0 && option.key < Int32(revealOptions.options.count) {
revealOptions.options[Int(option.key)].action() revealOptions.options[Int(option.key)].action()
@ -967,8 +1225,205 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
} }
private func toggleUpdated(_ value: Bool) { private func toggleUpdated(_ value: Bool) {
if let (item, _, _) = self.layoutParams { if let (item, _, _, _) = self.layoutParams {
item.toggleUpdated?(value) 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 let separatorHeight = UIScreenPixel
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)? var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
var editingOffset: CGFloat = 0.0 var editingOffset: CGFloat = 0.0
var reorderInset: CGFloat = 0.0 var reorderInset: CGFloat = 0.0
@ -485,7 +485,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
if let reorderControlSizeAndApply = reorderControlSizeAndApply { if let reorderControlSizeAndApply = reorderControlSizeAndApply {
if strongSelf.reorderControlNode == nil { 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.reorderControlNode = reorderControlNode
strongSelf.addSubnode(reorderControlNode) strongSelf.addSubnode(reorderControlNode)
reorderControlNode.alpha = 0.0 reorderControlNode.alpha = 0.0

View File

@ -56,6 +56,7 @@ public struct ItemListBackButton: Equatable {
public enum ItemListControllerTitle: Equatable { public enum ItemListControllerTitle: Equatable {
case text(String) case text(String)
case textWithSubtitle(String, String)
case sectionControl([String], Int) case sectionControl([String], Int)
} }
@ -197,12 +198,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
public var willScrollToTop: (() -> Void)? 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 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 { didSet {
if self.isNodeLoaded { if self.isNodeLoaded {
(self.displayNode as! ItemListControllerNode).reorderEntry = self.reorderEntry (self.displayNode as! ItemListControllerNode).reorderEntry = self.reorderEntry
@ -287,6 +288,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
strongSelf.title = text strongSelf.title = text
strongSelf.navigationItem.titleView = nil strongSelf.navigationItem.titleView = nil
strongSelf.segmentedTitleView = 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): case let .sectionControl(sections, index):
strongSelf.title = "" strongSelf.title = ""
if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections { if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections {
@ -417,6 +422,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme 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 ?? [] var items = strongSelf.navigationItem.rightBarButtonItems ?? []
for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count { for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count {
if case .activity = strongSelf.rightNavigationButtonTitleAndStyle[i].1 { if case .activity = strongSelf.rightNavigationButtonTitleAndStyle[i].1 {
@ -517,6 +526,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
self.didDisappear?(animated) self.didDisappear?(animated)
} }
public var listInsets: UIEdgeInsets {
return (self.displayNode as! ItemListControllerNode).listNode.insets
}
public func frameForItemNode(_ predicate: (ListViewItemNode) -> Bool) -> CGRect? { public func frameForItemNode(_ predicate: (ListViewItemNode) -> Bool) -> CGRect? {
var result: CGRect? var result: CGRect?
(self.displayNode as! ItemListControllerNode).listNode.forEachItemNode { itemNode in (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 TelegramPresentationData
import MergeLists import MergeLists
public protocol ItemListHeaderItemNode: class {
func updateTheme(theme: PresentationTheme)
}
public typealias ItemListSectionId = Int32 public typealias ItemListSectionId = Int32
public protocol ItemListNodeAnyEntry { public protocol ItemListNodeAnyEntry {
@ -217,7 +221,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
public var contentOffsetChanged: ((ListViewVisibleContentOffset, Bool) -> Void)? public var contentOffsetChanged: ((ListViewVisibleContentOffset, Bool) -> Void)?
public var contentScrollingEnded: ((ListView) -> Bool)? public var contentScrollingEnded: ((ListView) -> Bool)?
public var searchActivated: ((Bool) -> Void)? 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 reorderCompleted: (([ItemListNodeAnyEntry]) -> Void)?
public var requestLayout: ((ContainedViewLayoutTransition) -> Void)? public var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
@ -269,7 +273,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.listNode.reorderItem = { [weak self] fromIndex, toIndex, opaqueTransactionState in self.listNode.reorderItem = { [weak self] fromIndex, toIndex, opaqueTransactionState in
if let strongSelf = self, let reorderEntry = strongSelf.reorderEntry, let mergedEntries = (opaqueTransactionState as? ItemListNodeOpaqueState)?.mergedEntries { 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 { if fromIndex >= 0 && fromIndex < mergedEntries.count && toIndex >= 0 && toIndex < mergedEntries.count {
reorderEntry(fromIndex, toIndex, mergedEntries) return reorderEntry(fromIndex, toIndex, mergedEntries)
} }
} }
return .single(false) return .single(false)
@ -467,6 +471,12 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.rightOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor 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 { if let updateStyle = transition.updateStyle {
@ -501,9 +511,12 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousDrawing)
options.insert(.AnimateAlpha) options.insert(.AnimateAlpha)
} else if transition.crossfade { } else if transition.crossfade {
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing)
options.insert(.AnimateCrossfade) options.insert(.AnimateCrossfade)
} else { } else {
options.insert(.Synchronous) options.insert(.Synchronous)
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousDrawing)
} }
if self.alwaysSynchronous { if self.alwaysSynchronous {

View File

@ -19,7 +19,7 @@ public final class ItemListEditableReorderControlNode: ASDisplayNode {
self.addSubnode(self.iconNode) 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 return { theme in
let image = PresentationResourcesItemList.itemListReorderIndicatorIcon(theme) let image = PresentationResourcesItemList.itemListReorderIndicatorIcon(theme)
@ -31,9 +31,9 @@ public final class ItemListEditableReorderControlNode: ASDisplayNode {
} }
resultNode.iconNode.image = image resultNode.iconNode.image = image
return (40.0, { height, offsetForLabel in return (40.0, { height, offsetForLabel, transition in
if let image = image { 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 return resultNode
}) })

View File

@ -82,6 +82,10 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
super.init(layerBacked: layerBacked, dynamicBounce: dynamicBounce, rotated: rotated, seeThrough: seeThrough) super.init(layerBacked: layerBacked, dynamicBounce: dynamicBounce, rotated: rotated, seeThrough: seeThrough)
} }
open var controlsContainer: ASDisplayNode {
return self
}
override open func didLoad() { override open func didLoad() {
super.didLoad() super.didLoad()
@ -310,7 +314,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
revealNode.updateRevealOffset(offset: 0.0, sideInset: leftInset, transition: .immediate) 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) 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) 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.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.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)) 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.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)) strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - 16.0 - rightInset, height: textLayout.size.height + 1.0))
let _ = limitTextApply() let _ = limitTextApply()
@ -394,6 +396,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
let textBottomInset: CGFloat = 11.0 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.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.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))) 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 { public enum ItemListTextItemText {
case plain(String) case plain(String)
case large(String)
case markdown(String) case markdown(String)
} }
@ -23,13 +24,15 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
let linkAction: ((ItemListTextItemLinkAction) -> Void)? let linkAction: ((ItemListTextItemLinkAction) -> Void)?
let style: ItemListStyle let style: ItemListStyle
public let isAlwaysPlain: Bool = true 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.presentationData = presentationData
self.text = text self.text = text
self.sectionId = sectionId self.sectionId = sectionId
self.linkAction = linkAction self.linkAction = linkAction
self.style = style 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) { 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 titleNode: TextNode
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
private var item: ItemListTextItem? private var item: ItemListTextItem?
public var tag: ItemListItemTag? {
return self.item?.tag
}
public init() { public init() {
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
@ -105,15 +112,19 @@ public class ItemListTextItemNode: ListViewItemNode {
return { item, params, neighbors in return { item, params, neighbors in
let leftInset: CGFloat = 15.0 + params.leftInset 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 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 titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let attributedText: NSAttributedString let attributedText: NSAttributedString
switch item.text { switch item.text {
case let .plain(text): case let .plain(text):
attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.freeTextColor) 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): 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 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) return (TelegramTextAttributes.URL, contents)
@ -123,8 +134,12 @@ public class ItemListTextItemNode: ListViewItemNode {
let contentSize: CGSize let contentSize: CGSize
contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset + verticalInset) var insets = itemListNeighborsGroupedInsets(neighbors)
let 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) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -139,7 +154,7 @@ public class ItemListTextItemNode: ListViewItemNode {
let _ = titleApply() 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; NSInteger _number;
UIColor *_checkColor; UIColor *_checkColor;
CGAffineTransform TGCheckButtonDefaultTransform;
} }
@end @end
@implementation TGCheckButtonView @implementation TGCheckButtonView
static NSMutableDictionary *backgroundImages; + (void)resetCache {
static NSMutableDictionary *fillImages;
static CGAffineTransform TGCheckButtonDefaultTransform;
+ (void)resetCache
{
[backgroundImages removeAllObjects];
[fillImages removeAllObjects];
} }
- (instancetype)initWithStyle:(TGCheckButtonStyle)style { - (instancetype)initWithStyle:(TGCheckButtonStyle)style {
@ -55,15 +50,12 @@ static CGAffineTransform TGCheckButtonDefaultTransform;
self = [super initWithFrame:CGRectMake(0, 0, size.width, size.height)]; self = [super initWithFrame:CGRectMake(0, 0, size.width, size.height)];
if (self != nil) if (self != nil)
{ {
static dispatch_once_t onceToken; CGFloat screenScale = 2.0f;
static CGFloat screenScale = 2.0f;
dispatch_once(&onceToken, ^ TGCheckButtonDefaultTransform = CGAffineTransformMakeRotation(-M_PI_4);
{ NSMutableDictionary *backgroundImages = [[NSMutableDictionary alloc] init];
TGCheckButtonDefaultTransform = CGAffineTransformMakeRotation(-M_PI_4); NSMutableDictionary *fillImages = [[NSMutableDictionary alloc] init];
backgroundImages = [[NSMutableDictionary alloc] init]; screenScale = [UIScreen mainScreen].scale;
fillImages = [[NSMutableDictionary alloc] init];
screenScale = [UIScreen mainScreen].scale;
});
int32_t hex = 0x29c519; 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]; 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)appendVideoPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime;
- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer; - (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer;
- (void)finishRecording; - (void)finishRecording:(void(^)())completed;
- (NSTimeInterval)videoDuration; - (NSTimeInterval)videoDuration;

View File

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

View File

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

View File

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

View File

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

View File

@ -296,7 +296,13 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
})! })!
itemViews.append(locationItem) 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 let pollItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
controller?.dismiss(animated: true) controller?.dismiss(animated: true)
openPoll() openPoll()

View File

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

View File

@ -48,6 +48,13 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent
} }
public func checkPhotoAuthorizationStatus(for intent: TGPhotoAccessIntent, alertDismissCompletion: (() -> Void)!) -> Bool { 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 return true
} }

View File

@ -84,7 +84,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.placesBackgroundNode.isUserInteractionEnabled = true self.placesBackgroundNode.isUserInteractionEnabled = true
self.placesButtonNode = HighlightableButtonNode() 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 = ASImageNode()
self.shadowNode.contentMode = .scaleToFill self.shadowNode.contentMode = .scaleToFill

View File

@ -548,7 +548,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
if foundVenues == nil && !state.searchingVenuesAround { if foundVenues == nil && !state.searchingVenuesAround {
displayingPlacesButton = true displayingPlacesButton = true
} else if let previousLocation = foundVenuesLocation { } 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 { if currentLocation.distance(from: previousLocation) > 300 {
displayingPlacesButton = true displayingPlacesButton = true
} }

View File

@ -181,7 +181,7 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], allUpd
return (removeIndices, insertItems, updatedIndices) 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)]) { 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 removeIndices: [Int] = []
var insertItems: [(Int, T, Int?)] = [] var insertItems: [(Int, T, Int?)] = []
@ -207,6 +207,25 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], isLess
} }
#endif #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 currentList = leftList
var i = 0 var i = 0

View File

@ -1394,7 +1394,7 @@ static int32_t fixedTimeDifferenceValue = 0;
NSArray *currentListeners = [[NSArray alloc] initWithArray:strongSelf->_changeListeners]; NSArray *currentListeners = [[NSArray alloc] initWithArray:strongSelf->_changeListeners];
for (id<MTContextChangeListener> listener in currentListeners) { for (id<MTContextChangeListener> listener in currentListeners) {
if ([listener respondsToSelector:@selector(contextLoggedOut:)]) 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) if ((_mtState & MTProtoStateStopped) == 0)
{ {
[self setMtState:_mtState | MTProtoStateStopped]; [self setMtState:_mtState | MTProtoStateStopped];
[_context removeChangeListener:self];
if (_transport != nil) if (_transport != nil)
{ {
_transport.delegate = nil; _transport.delegate = nil;
@ -2098,6 +2098,9 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
int64_t dataMessageId = 0; int64_t dataMessageId = 0;
bool parseError = false; bool parseError = false;
NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId parseError:&parseError]; NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId parseError:&parseError];
for (MTIncomingMessage *message in parsedMessages) { for (MTIncomingMessage *message in parsedMessages) {
if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { if ([message.body isKindOfClass:[MTRpcResultMessage class]]) {
MTRpcResultMessage *rpcResultMessage = message.body; MTRpcResultMessage *rpcResultMessage = message.body;

View File

@ -160,7 +160,7 @@ final class SetupTwoStepVerificationContentNode: ASDisplayNode, UITextFieldDeleg
let minContentHeight = textHeight + inputHeight let minContentHeight = textHeight + inputHeight
let contentHeight = min(215.0, max(size.height - insets.top - insets.bottom - 40.0, minContentHeight)) 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) 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.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)) 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)) }, transition: .animated(duration: 0.5, curve: .spring))
} }
if case let .enterEmail(enterEmail)? = self.innerState.data.state, case .create = enterEmail.state, enterEmail.email.isEmpty { 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: { 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() continueImpl()
})]), nil) })]), 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 { public func encodeObjectDictionary<K, V: PostboxCoding>(_ value: [K : V], forKey key: StaticString) where K: PostboxCoding {
self.encodeKey(key) self.encodeKey(key)
var t: Int8 = ValueType.ObjectDictionary.rawValue 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 { 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) { 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 var length: Int32 = 0

View File

@ -37,6 +37,17 @@ final class ItemCacheTable: Table {
return key 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 { private func itemIdToAccessIndexKey(id: ItemCacheEntryId) -> ValueBoxKey {
let key = ValueBoxKey(length: 1 + 1 + id.key.length) let key = ValueBoxKey(length: 1 + 1 + id.key.length)
key.setInt8(0, value: ItemCacheSection.accessIndexToItemId.rawValue) 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) 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() { 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) 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 { 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) 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) self.updatedMessageIds.remove(id)
} }
func get(peerId:PeerId) -> [MessageId] { func get(peerId: PeerId) -> [MessageId] {
var ids:[MessageId] = [] var ids:[MessageId] = []
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), keys: { key in 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() 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) 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> { func renderAssociatedMessages(associatedMessageIds: [MessageId], peerTable: PeerTable) -> SimpleDictionary<MessageId, Message> {
var associatedMessages = SimpleDictionary<MessageId, Message>() var associatedMessages = SimpleDictionary<MessageId, Message>()
for messageId in associatedMessageIds { for messageId in associatedMessageIds {

View File

@ -29,13 +29,13 @@ public struct MessageHistoryMessageEntry {
enum MutableMessageHistoryEntry { enum MutableMessageHistoryEntry {
case IntermediateMessageEntry(IntermediateMessage, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?) case IntermediateMessageEntry(IntermediateMessage, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?)
case MessageEntry(MessageHistoryMessageEntry, reloadAssociatedMessages: Bool) case MessageEntry(MessageHistoryMessageEntry, reloadAssociatedMessages: Bool, reloadPeers: Bool)
var index: MessageIndex { var index: MessageIndex {
switch self { switch self {
case let .IntermediateMessageEntry(message, _, _): case let .IntermediateMessageEntry(message, _, _):
return message.index return message.index
case let .MessageEntry(message, _): case let .MessageEntry(message, _, _):
return message.message.index return message.message.index
} }
} }
@ -44,7 +44,7 @@ enum MutableMessageHistoryEntry {
switch self { switch self {
case let .IntermediateMessageEntry(message, _, _): case let .IntermediateMessageEntry(message, _, _):
return message.tags return message.tags
case let .MessageEntry(message, _): case let .MessageEntry(message, _, _):
return message.message.tags return message.message.tags
} }
} }
@ -53,8 +53,8 @@ enum MutableMessageHistoryEntry {
switch self { switch self {
case let .IntermediateMessageEntry(message, _, monthLocation): case let .IntermediateMessageEntry(message, _, monthLocation):
return .IntermediateMessageEntry(message, location, monthLocation) return .IntermediateMessageEntry(message, location, monthLocation)
case let .MessageEntry(message, reloadAssociatedMessages): case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: location, monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages) 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 { switch self {
case let .IntermediateMessageEntry(message, location, _): case let .IntermediateMessageEntry(message, location, _):
return .IntermediateMessageEntry(message, location, monthLocation) return .IntermediateMessageEntry(message, location, monthLocation)
case let .MessageEntry(message, reloadAssociatedMessages): case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: message.location, monthLocation: monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages) 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 { } else {
return self return self
} }
case let .MessageEntry(message, reloadAssociatedMessages): case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
if let location = message.location { if let location = message.location {
if message.message.index > index { 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 { } 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 { } else {
return self return self
@ -107,15 +107,15 @@ enum MutableMessageHistoryEntry {
} else { } else {
return self return self
} }
case let .MessageEntry(message, reloadAssociatedMessages): case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
if let location = message.location { if let location = message.location {
if message.message.index > index { if message.message.index > index {
//assert(location.index > 0) //assert(location.index > 0)
//assert(location.count != 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 { } else {
//assert(location.count != 0) //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 { } else {
return self return self
@ -128,10 +128,10 @@ enum MutableMessageHistoryEntry {
case let .IntermediateMessageEntry(message, location, monthLocation): 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) 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) return .IntermediateMessageEntry(updatedMessage, location, monthLocation)
case let .MessageEntry(value, reloadAssociatedMessages): case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
let message = value.message 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) 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 { switch self {
case let .IntermediateMessageEntry(message, location, monthLocation): case let .IntermediateMessageEntry(message, location, monthLocation):
return [] return []
case let .MessageEntry(value, _): case let .MessageEntry(value, _, _):
return value.message.associatedMessageIds return value.message.associatedMessageIds
} }
} }
@ -258,6 +258,7 @@ final class MutableMessageHistoryView {
let tag: MessageTags? let tag: MessageTags?
let namespaces: MessageIdNamespaces let namespaces: MessageIdNamespaces
private let orderStatistics: MessageHistoryViewOrderStatistics private let orderStatistics: MessageHistoryViewOrderStatistics
private let clipHoles: Bool
private let anchor: HistoryViewInputAnchor private let anchor: HistoryViewInputAnchor
fileprivate var combinedReadStates: MessageHistoryViewReadState? fileprivate var combinedReadStates: MessageHistoryViewReadState?
@ -271,10 +272,11 @@ final class MutableMessageHistoryView {
fileprivate(set) var sampledState: HistoryViewSample 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.anchor = inputAnchor
self.orderStatistics = orderStatistics self.orderStatistics = orderStatistics
self.clipHoles = clipHoles
self.peerIds = peerIds self.peerIds = peerIds
self.combinedReadStates = combinedReadStates self.combinedReadStates = combinedReadStates
self.transientReadStates = transientReadStates self.transientReadStates = transientReadStates
@ -290,12 +292,12 @@ final class MutableMessageHistoryView {
switch sampledState { switch sampledState {
case let .ready(anchor, holes): 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.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: case .loadHole:
break break
} }
} }
self.sampledState = self.state.sample(postbox: postbox) self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
self.render(postbox: postbox) self.render(postbox: postbox)
} }
@ -320,7 +322,7 @@ final class MutableMessageHistoryView {
break break
} }
} }
self.sampledState = self.state.sample(postbox: postbox) self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
} }
func refreshDueToExternalTransaction(postbox: Postbox) -> Bool { func refreshDueToExternalTransaction(postbox: Postbox) -> Bool {
@ -509,7 +511,7 @@ final class MutableMessageHistoryView {
break break
} }
} }
self.sampledState = self.state.sample(postbox: postbox) self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
} }
for operationSet in operations { for operationSet in operations {

View File

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

View File

@ -60,7 +60,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
} }
} }
self.anchor = anchor 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() let _ = self.updateFromView()
} }
@ -132,7 +132,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
case let .peer(id): case let .peer(id):
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) 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() return self.updateFromView()
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) { } else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
var reloadView = false var reloadView = false
@ -160,7 +160,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
case let .peer(id): case let .peer(id):
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) 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() return self.updateFromView()

View File

@ -655,6 +655,11 @@ public final class Transaction {
return self.postbox?.retrieveItemCacheEntry(id: id) 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 { public func operationLogGetNextEntryLocalIndex(peerId: PeerId, tag: PeerOperationLogTag) -> Int32 {
assert(!self.disposed) assert(!self.disposed)
if let postbox = self.postbox { if let postbox = self.postbox {
@ -2065,6 +2070,10 @@ public final class Postbox {
return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable) return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable)
} }
func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
return self.itemCacheTable.removeAll(collectionId: collectionId)
}
fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) { fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) {
self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable) self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable)
} }
@ -2257,7 +2266,7 @@ public final class Postbox {
return peerIds 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 return self.transactionSignal(userInteractive: true, { subscriber, transaction in
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) 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 return self.transactionSignal { subscriber, transaction in
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) 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 return self.transactionSignal { subscriber, transaction in
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) 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 topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
var mainPeerId: PeerId? var mainPeerId: PeerId?
switch peerIds { switch peerIds {
@ -2411,7 +2420,7 @@ public final class Postbox {
readStates = transientReadStates 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 { if let tagMask = tagMask {
return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound)) return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound))
} else { } else {

View File

@ -38,6 +38,13 @@ public struct ValueBoxKey: Equatable, Hashable, CustomStringConvertible, Compara
memcpy(self.memory, buffer.memory, buffer.length) 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) { public func setInt32(_ offset: Int, value: Int32) {
var bigEndianValue = Int32(bigEndian: value) var bigEndianValue = Int32(bigEndian: value)
memcpy(self.memory + offset, &bigEndianValue, 4) memcpy(self.memory + offset, &bigEndianValue, 4)

View File

@ -75,3 +75,50 @@ public func screenCaptureEvents() -> Signal<ScreenCaptureEvent, NoError> {
} }
|> runOn(Queue.mainQueue()) |> 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) 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 { if !prefixSize.width.isZero {
let prefixOffset = prefixSize.width let prefixOffset = prefixSize.width
rect.origin.x += prefixOffset rect.origin.x += prefixOffset
rect.size.width -= prefixOffset rect.size.width -= prefixOffset
} }
rect.size.width = max(rect.size.width, 10.0)
return rect return rect
} }
@ -117,7 +118,7 @@ private class SearchBarTextField: UITextField {
let labelSize = self.placeholderLabel.measure(textRect.size) let labelSize = self.placeholderLabel.measure(textRect.size)
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX, y: textRect.minY + textOffset), size: labelSize) 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) 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) 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? let resolvedValue: CGFloat?
if let value = self.value { if let value = self.value {
if let transition = self.transition { if let transition = self.transition {
transition.valueAt(timestamp: timestamp, actualValue: value) resolvedValue = transition.valueAt(timestamp: timestamp, actualValue: value)
} else { } else {
resolvedValue = value resolvedValue = value
} }
} else { } else {
resolvedValue = nil 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?) { func updateValue(value: CGFloat?) {
@ -351,7 +351,7 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
let timestamp = CACurrentMediaTime() let timestamp = CACurrentMediaTime()
if let value = value, let previousValue = previousValue { if let value = value, let previousValue = previousValue {
if let transition = self.transition { 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 { } else {
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: previousValue) self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: previousValue)
} }
@ -388,7 +388,7 @@ private extension SemanticStatusNodeState {
} }
case let .progress(value, cancelEnabled): case let .progress(value, cancelEnabled):
if let current = current as? SemanticStatusNodeProgressContext, current.displayCancel == cancelEnabled { if let current = current as? SemanticStatusNodeProgressContext, current.displayCancel == cancelEnabled {
current.value = value current.updateValue(value: value)
return current return current
} else { } else {
return SemanticStatusNodeProgressContext(value: value, displayCancel: cancelEnabled) 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): case let .accountHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .account(theme, peer, selected, _): 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) } arguments.updateSettings { $0.withUpdatedAccount(peer.id) }
}, setPeerIdWithRevealedOptions: { _, _ in}, removePeer: { _ in }) }, setPeerIdWithRevealedOptions: { _, _ in}, removePeer: { _ in })
return ItemListTextItem(presentationData: presentationData, text: .plain(""), sectionId: self.section) 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 dismissImpl = { [weak controller] in
controller?.dismiss() 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] let fromEntry = entries[fromIndex]
guard case let .server(_, _, _, fromServer, _, _, _, _) = fromEntry else { guard case let .server(_, _, _, fromServer, _, _, _, _) = fromEntry else {
return return .single(false)
} }
var referenceServer: ProxyServerSettings? var referenceServer: ProxyServerSettings?
var beforeAll = false var beforeAll = false
@ -476,7 +476,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
afterAll = true afterAll = true
} }
let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in return updateProxySettingsInteractively(accountManager: accountManager, { current in
var current = current var current = current
if let index = current.servers.firstIndex(of: fromServer) { if let index = current.servers.firstIndex(of: fromServer) {
current.servers.remove(at: index) current.servers.remove(at: index)
@ -503,7 +503,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
current.servers.append(fromServer) current.servers.append(fromServer)
} }
return current return current
}).start() })
}) })
shareProxyListImpl = { [weak controller] in 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) 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 editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
let editingOffset: CGFloat let editingOffset: CGFloat
var reorderInset: CGFloat = 0.0 var reorderInset: CGFloat = 0.0
@ -341,7 +341,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
if let reorderControlSizeAndApply = reorderControlSizeAndApply { if let reorderControlSizeAndApply = reorderControlSizeAndApply {
if strongSelf.reorderControlNode == nil { 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.reorderControlNode = reorderControlNode
strongSelf.addSubnode(reorderControlNode) strongSelf.addSubnode(reorderControlNode)
reorderControlNode.alpha = 0.0 reorderControlNode.alpha = 0.0

View File

@ -393,6 +393,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
transaction.clearNotices() transaction.clearNotices()
}).start() }).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): case let .reimport(theme):
return ItemListActionItem(presentationData: presentationData, title: "Reimport Application Data", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { 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 strings: PresentationStrings
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let fontSize: PresentationFontSize let fontSize: PresentationFontSize
let chatBubbleCorners: PresentationChatBubbleCorners
let wallpaper: TelegramWallpaper let wallpaper: TelegramWallpaper
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder let nameDisplayOrder: PresentationPersonNameOrder
@ -25,12 +26,13 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
let linkEnabled: Bool let linkEnabled: Bool
let tooltipText: String 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.context = context
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.sectionId = sectionId self.sectionId = sectionId
self.fontSize = fontSize self.fontSize = fontSize
self.chatBubbleCorners = chatBubbleCorners
self.wallpaper = wallpaper self.wallpaper = wallpaper
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder 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 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? var node: ListViewItemNode?
if let current = currentNode { if let current = currentNode {

View File

@ -482,12 +482,19 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
let updateHasTwoStepAuth: () -> Void = { let updateHasTwoStepAuth: () -> Void = {
let signal = twoStepVerificationConfiguration(account: context.account) let signal = twoStepVerificationConfiguration(account: context.account)
|> map { value -> TwoStepVerificationAccessConfiguration? in |> map { value -> TwoStepVerificationAccessConfiguration? in
return TwoStepVerificationAccessConfiguration(configuration: value, password: nil) return TwoStepVerificationAccessConfiguration(configuration: value, password: nil)
} }
|> deliverOnMainQueue |> deliverOnMainQueue
updateTwoStepAuthDisposable.set( updateTwoStepAuthDisposable.set(
signal.start(next: { value in signal.start(next: { value in
twoStepAuthDataValue.set(.single(value)) 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 { private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case forwardsPreviewHeader(PresentationTheme, String) 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 settingHeader(PresentationTheme, String)
case everybody(PresentationTheme, String, Bool) case everybody(PresentationTheme, String, Bool)
case contacts(PresentationTheme, String, Bool) case contacts(PresentationTheme, String, Bool)
@ -194,8 +194,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsPeerName, lhsLinkEnabled, lhsTooltipText): case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, 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 { 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 return true
} else { } else {
return false return false
@ -350,8 +350,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
switch self { switch self {
case let .forwardsPreviewHeader(theme, text): case let .forwardsPreviewHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .forwardsPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, peerName, linkEnabled, 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, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: 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): case let .settingHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .everybody(theme, text, value): case let .everybody(theme, text, value):
@ -591,7 +591,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
linkEnabled = false linkEnabled = false
} }
entries.append(.forwardsPreviewHeader(presentationData.theme, presentationData.strings.Privacy_Forwards_Preview)) 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)) entries.append(.settingHeader(presentationData.theme, settingTitle))

View File

@ -945,7 +945,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
blockedPeers.set(.single(blockedPeersContext)) blockedPeers.set(.single(blockedPeersContext))
}, updatedHasTwoStepAuth: { hasTwoStepAuthValue in }, updatedHasTwoStepAuth: { hasTwoStepAuthValue in
hasTwoStepAuthPromise.set(.single(hasTwoStepAuthValue)) hasTwoStepAuthPromise.set(.single(hasTwoStepAuthValue))
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext)) }, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth))
}) })
}) })
}, openDataAndStorage: { }, openDataAndStorage: {
@ -1474,7 +1474,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
if let primary = primary { if let primary = primary {
let size = CGSize(width: 31.0, height: 31.0) let size = CGSize(width: 31.0, height: 31.0)
let inset: CGFloat = 3.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 return signal
|> map { image -> (UIImage, UIImage)? in |> map { image -> (UIImage, UIImage)? in
if let image = image, let selectedImage = generateImage(size, rotatedContext: { size, context 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 let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: inset, y: inset) 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) })?.withRenderingMode(.alwaysOriginal)
let selectedImage = generateImage(size, rotatedContext: { size, context in let selectedImage = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: inset, y: inset) 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.translateBy(x: -inset, y: -inset)
context.setLineWidth(1.0) context.setLineWidth(1.0)
context.setStrokeColor(primary.2.rootController.tabBar.selectedIconColor.cgColor) 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 presentStickerPackController = { [weak controller] info in
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) 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 return controller

View File

@ -265,7 +265,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
presentStickerPackController = { [weak controller] info in presentStickerPackController = { [weak controller] info in
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) 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 return controller

View File

@ -698,10 +698,10 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
if case .modal = mode { if case .modal = mode {
controller.navigationPresentation = .modal 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] let fromEntry = entries[fromIndex]
guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _) = fromEntry else { guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _) = fromEntry else {
return return .single(false)
} }
var referenceId: ItemCollectionId? var referenceId: ItemCollectionId?
var beforeAll = false var beforeAll = false
@ -731,20 +731,26 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
} }
} }
var previousIndex: Int?
for i in 0 ..< currentIds.count { for i in 0 ..< currentIds.count {
if currentIds[i] == fromPackInfo.id { if currentIds[i] == fromPackInfo.id {
previousIndex = i
currentIds.remove(at: i) currentIds.remove(at: i)
break break
} }
} }
var didReorder = false
if let referenceId = referenceId { if let referenceId = referenceId {
var inserted = false var inserted = false
for i in 0 ..< currentIds.count { for i in 0 ..< currentIds.count {
if currentIds[i] == referenceId { if currentIds[i] == referenceId {
if fromIndex < toIndex { if fromIndex < toIndex {
didReorder = previousIndex != i + 1
currentIds.insert(fromPackInfo.id, at: i + 1) currentIds.insert(fromPackInfo.id, at: i + 1)
} else { } else {
didReorder = previousIndex != i
currentIds.insert(fromPackInfo.id, at: i) currentIds.insert(fromPackInfo.id, at: i)
} }
inserted = true inserted = true
@ -752,15 +758,20 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
} }
} }
if !inserted { if !inserted {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id) currentIds.append(fromPackInfo.id)
} }
} else if beforeAll { } else if beforeAll {
didReorder = previousIndex != 0
currentIds.insert(fromPackInfo.id, at: 0) currentIds.insert(fromPackInfo.id, at: 0)
} else if afterAll { } else if afterAll {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id) currentIds.append(fromPackInfo.id)
} }
temporaryPackOrder.set(.single(currentIds)) temporaryPackOrder.set(.single(currentIds))
return .single(didReorder)
}) })
controller.setReorderCompleted({ (entries: [InstalledStickerPacksEntry]) -> Void in controller.setReorderCompleted({ (entries: [InstalledStickerPacksEntry]) -> Void in
@ -827,7 +838,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
packs.insert(packReference, at: 0) packs.insert(packReference, at: 0)
} }
if let mainStickerPack = mainStickerPack { 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 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var animateInAsReplacement = false var animateInAsReplacement = false
if let navigationController = navigationControllerImpl?() { 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) { private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = [] 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() 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) 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) { 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] = [] var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1) 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: []) 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: []) 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: []) 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 waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA="
let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] 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 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: []) 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: []) 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 let width: CGFloat
if case .regular = layout.metrics.widthClass { 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 slug(PresentationTheme, PresentationStrings, String, String, Bool)
case slugInfo(PresentationTheme, String) case slugInfo(PresentationTheme, String)
case chatPreviewHeader(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 changeColors(PresentationTheme, String)
case uploadTheme(PresentationTheme, String) case uploadTheme(PresentationTheme, String)
case uploadInfo(PresentationTheme, String) case uploadInfo(PresentationTheme, String)
@ -114,8 +114,8 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems): case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, 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 { 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 return true
} else { } else {
return false return false
@ -172,8 +172,8 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .chatPreviewHeader(theme, text): case let .chatPreviewHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, 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, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: 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): case let .changeColors(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.openColors() arguments.openColors()
@ -253,7 +253,7 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat
entries.append(.slugInfo(presentationData.theme, infoText)) entries.append(.slugInfo(presentationData.theme, infoText))
entries.append(.chatPreviewHeader(presentationData.theme, presentationData.strings.EditTheme_Preview.uppercased())) 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)) entries.append(.changeColors(presentationData.theme, presentationData.strings.EditTheme_ChangeColors))
if !hasSettings { if !hasSettings {
@ -551,12 +551,12 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) 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 var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[themeReference.index] = nil 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: { }) |> deliverOnMainQueue).start(completed: {
if !hasCustomFile { if !hasCustomFile {
saveThemeTemplateFile(state.title, themeResource, { 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) 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 var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[themeReference.index] = nil 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: { }) |> deliverOnMainQueue).start(completed: {
if let themeResource = themeResource, !hasCustomFile { if let themeResource = themeResource, !hasCustomFile {
saveThemeTemplateFile(state.title, themeResource, { saveThemeTemplateFile(state.title, themeResource, {

View File

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

View File

@ -226,6 +226,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = theme self.theme = theme
self.wallpaper = self.presentationData.chatWallpaper self.wallpaper = self.presentationData.chatWallpaper
let bubbleCorners = self.presentationData.chatBubbleCorners
self.ready = ready self.ready = ready
@ -498,7 +499,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
updatedTheme = theme 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 { } else {
updatedTheme = nil updatedTheme = nil
} }
@ -765,7 +766,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = [] 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() 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) 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) { 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] = [] var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
@ -878,7 +879,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
sampleMessages.append(message8) sampleMessages.append(message8)
items = sampleMessages.reversed().map { message in 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) { if message.flags.contains(.Incoming) {
self?.updateSection(.accent) self?.updateSection(.accent)
self?.requestSectionUpdate?(.accent) self?.requestSectionUpdate?(.accent)

View File

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

View File

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

View File

@ -350,7 +350,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = [] 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() 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) 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) { 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] = [] var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
@ -484,7 +484,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
sampleMessages.append(message8) sampleMessages.append(message8)
items = sampleMessages.reversed().map { message in 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 let width: CGFloat

View File

@ -40,18 +40,20 @@ class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem {
let strings: PresentationStrings let strings: PresentationStrings
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let fontSize: PresentationFontSize let fontSize: PresentationFontSize
let chatBubbleCorners: PresentationChatBubbleCorners
let wallpaper: TelegramWallpaper let wallpaper: TelegramWallpaper
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder let nameDisplayOrder: PresentationPersonNameOrder
let messageItems: [ChatPreviewMessageItem] 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.context = context
self.theme = theme self.theme = theme
self.componentTheme = componentTheme self.componentTheme = componentTheme
self.strings = strings self.strings = strings
self.sectionId = sectionId self.sectionId = sectionId
self.fontSize = fontSize self.fontSize = fontSize
self.chatBubbleCorners = chatBubbleCorners
self.wallpaper = wallpaper self.wallpaper = wallpaper
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder 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: []) 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] = [] var nodes: [ListViewItemNode] = []

View File

@ -76,6 +76,7 @@ private final class ThemeSettingsControllerArguments {
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
let openAutoNightTheme: () -> Void let openAutoNightTheme: () -> Void
let openTextSize: () -> Void let openTextSize: () -> Void
let openBubbleSettings: () -> Void
let toggleLargeEmoji: (Bool) -> Void let toggleLargeEmoji: (Bool) -> Void
let disableAnimations: (Bool) -> Void let disableAnimations: (Bool) -> Void
let selectAppIcon: (String) -> Void let selectAppIcon: (String) -> Void
@ -83,7 +84,7 @@ private final class ThemeSettingsControllerArguments {
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, 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.context = context
self.selectTheme = selectTheme self.selectTheme = selectTheme
self.selectFontSize = selectFontSize self.selectFontSize = selectFontSize
@ -92,6 +93,7 @@ private final class ThemeSettingsControllerArguments {
self.openAccentColorPicker = openAccentColorPicker self.openAccentColorPicker = openAccentColorPicker
self.openAutoNightTheme = openAutoNightTheme self.openAutoNightTheme = openAutoNightTheme
self.openTextSize = openTextSize self.openTextSize = openTextSize
self.openBubbleSettings = openBubbleSettings
self.toggleLargeEmoji = toggleLargeEmoji self.toggleLargeEmoji = toggleLargeEmoji
self.disableAnimations = disableAnimations self.disableAnimations = disableAnimations
self.selectAppIcon = selectAppIcon self.selectAppIcon = selectAppIcon
@ -131,11 +133,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case themeListHeader(PresentationTheme, String) case themeListHeader(PresentationTheme, String)
case fontSizeHeader(PresentationTheme, String) case fontSizeHeader(PresentationTheme, String)
case fontSize(PresentationTheme, PresentationFontSize) 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 wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?) case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?)
case autoNightTheme(PresentationTheme, String, String) case autoNightTheme(PresentationTheme, String, String)
case textSize(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 themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
case iconHeader(PresentationTheme, String) case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?) case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
@ -150,7 +153,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsControllerSection.chatPreview.rawValue return ThemeSettingsControllerSection.chatPreview.rawValue
case .fontSizeHeader, .fontSize: case .fontSizeHeader, .fontSize:
return ThemeSettingsControllerSection.fontSize.rawValue return ThemeSettingsControllerSection.fontSize.rawValue
case .wallpaper, .autoNightTheme, .textSize: case .wallpaper, .autoNightTheme, .textSize, .bubbleSettings:
return ThemeSettingsControllerSection.background.rawValue return ThemeSettingsControllerSection.background.rawValue
case .iconHeader, .iconItem: case .iconHeader, .iconItem:
return ThemeSettingsControllerSection.icon.rawValue return ThemeSettingsControllerSection.icon.rawValue
@ -175,29 +178,31 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 6 return 6
case .textSize: case .textSize:
return 7 return 7
case .fontSizeHeader: case .bubbleSettings:
return 8 return 8
case .fontSize: case .fontSizeHeader:
return 9 return 9
case .iconHeader: case .fontSize:
return 10 return 10
case .iconItem: case .iconHeader:
return 11 return 11
case .otherHeader: case .iconItem:
return 12 return 12
case .largeEmoji: case .otherHeader:
return 13 return 13
case .animations: case .largeEmoji:
return 14 return 14
case .animationsInfo: case .animations:
return 15 return 15
case .animationsInfo:
return 16
} }
} }
static func ==(lhs: ThemeSettingsControllerEntry, rhs: ThemeSettingsControllerEntry) -> Bool { static func ==(lhs: ThemeSettingsControllerEntry, rhs: ThemeSettingsControllerEntry) -> Bool {
switch lhs { switch lhs {
case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems): case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, 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 { 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 return true
} else { } else {
return false return false
@ -226,6 +231,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false 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): case let .themeListHeader(lhsTheme, lhsText):
if case let .themeListHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .themeListHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -302,8 +313,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
arguments.selectFontSize(value) arguments.selectFontSize(value)
}, tag: ThemeSettingsEntryTag.fontSize) }, tag: ThemeSettingsEntryTag.fontSize)
case let .chatPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, 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, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: 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): case let .wallpaper(theme, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings() 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: { return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openTextSize() 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): case let .themeListHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _): case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
@ -429,7 +444,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
let strings = presentationData.strings let strings = presentationData.strings
let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased() let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased()
entries.append(.themeListHeader(presentationData.theme, title)) 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 let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
if case let .cloud(theme) = reference { 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(.textSize(presentationData.theme, strings.Appearance_TextSizeSetting, textSizeValue))
entries.append(.bubbleSettings(presentationData.theme, strings.Appearance_BubbleCornersSetting, ""))
if !availableAppIcons.isEmpty { if !availableAppIcons.isEmpty {
entries.append(.iconHeader(presentationData.theme, strings.Appearance_AppIcon.uppercased())) 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 let settings = (view.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
pushControllerImpl?(TextSizeSelectionController(context: context, presentationThemeSettings: settings)) 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 }, toggleLargeEmoji: { largeEmoji in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedLargeEmoji(largeEmoji) 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 previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
let newTheme: PresentationThemeReference let newTheme: PresentationThemeReference
if let previousThemeIndex = previousThemeIndex { 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 { } else {
newTheme = .builtin(.nightAccent) 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 previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
let newTheme: PresentationThemeReference let newTheme: PresentationThemeReference
if let previousThemeIndex = previousThemeIndex { 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 { } else {
if settings.baseTheme == .night { if settings.baseTheme == .night {
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue)) selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
@ -1014,7 +1039,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
defaultThemes.append(contentsOf: [.builtin(.night), .builtin(.nightAccent)]) 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 var availableThemes = defaultThemes
if defaultThemes.first(where: { $0.index == themeReference.index }) == nil && cloudThemes.first(where: { $0.index == themeReference.index }) == nil { 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 baseThemeIndex: Int64?
var updatedThemeBaseIndex: Int64? var updatedThemeBaseIndex: Int64?
if case let .cloud(info) = 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))
if let settings = info.theme.settings { if let settings = info.theme.settings {
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
updatedThemeBaseIndex = baseThemeIndex 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() }).start()
presentCrossfadeControllerImpl?(true) presentCrossfadeControllerImpl?(true)

View File

@ -16,17 +16,19 @@ class ThemeSettingsFontSizeItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let fontSize: PresentationFontSize let fontSize: PresentationFontSize
let disableLeadingInset: Bool let disableLeadingInset: Bool
let displayIcons: Bool
let force: Bool let force: Bool
let enabled: Bool let enabled: Bool
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let updated: (PresentationFontSize) -> Void let updated: (PresentationFontSize) -> Void
let tag: ItemListItemTag? 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.theme = theme
self.fontSize = fontSize self.fontSize = fontSize
self.enabled = enabled self.enabled = enabled
self.disableLeadingInset = disableLeadingInset self.disableLeadingInset = disableLeadingInset
self.displayIcons = displayIcons
self.force = force self.force = force
self.sectionId = sectionId self.sectionId = sectionId
self.updated = updated self.updated = updated
@ -164,7 +166,9 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
sliderView.knobImage = generateKnobImage() 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) self.view.insertSubview(sliderView, belowSubview: self.disabledOverlayNode.view)
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) 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.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 { if let sliderView = strongSelf.sliderView {
sliderView.isUserInteractionEnabled = item.enabled sliderView.isUserInteractionEnabled = item.enabled
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
@ -302,7 +309,8 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
sliderView.value = value 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 insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
let crossfade: Bool 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 { 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 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) } 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 { private func ensureThemeVisible(listNode: ListView, themeReference: PresentationThemeReference, animated: Bool) -> Bool {
@ -512,10 +513,13 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
if self.initialized && transition.crossfade { if self.initialized && transition.crossfade {
options.insert(.AnimateCrossfade) options.insert(.AnimateCrossfade)
} }
options.insert(.Synchronous)
var scrollToItem: ListViewScrollToItem? var scrollToItem: ListViewScrollToItem?
if !self.initialized { 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) scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
self.initialized = true self.initialized = true
} }

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