mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into tgvoip-api
This commit is contained in:
commit
d3b6e33a4b
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,7 +1,7 @@
|
||||
|
||||
[submodule "submodules/rlottie/rlottie"]
|
||||
path = submodules/rlottie/rlottie
|
||||
url = https://github.com/laktyushin/rlottie.git
|
||||
url=../rlottie.git
|
||||
[submodule "submodules/libtgvoip/libtgvoip"]
|
||||
path = submodules/libtgvoip/libtgvoip
|
||||
url = https://github.com/peter-iakovlev/libtgvoip.git
|
||||
path = submodules/libtgvoip/libtgvoip
|
||||
url = https://github.com/telegramdesktop/libtgvoip.git
|
2
Makefile
2
Makefile
@ -3,7 +3,7 @@
|
||||
include Utils.makefile
|
||||
|
||||
BUCK_OPTIONS=\
|
||||
--config custom.appVersion="5.13.1" \
|
||||
--config custom.appVersion="5.14" \
|
||||
--config custom.developmentCodeSignIdentity="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \
|
||||
--config custom.distributionCodeSignIdentity="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \
|
||||
--config custom.developmentTeam="${DEVELOPMENT_TEAM}" \
|
||||
|
@ -3,7 +3,7 @@
|
||||
@implementation Serialization
|
||||
|
||||
- (NSUInteger)currentLayer {
|
||||
return 108;
|
||||
return 109;
|
||||
}
|
||||
|
||||
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
|
||||
|
@ -32,6 +32,7 @@
|
||||
<string>merchant.sberbank.test.ph.telegra.Telegraph</string>
|
||||
<string>merchant.privatbank.test.telergramios</string>
|
||||
<string>merchant.privatbank.prod.telergram</string>
|
||||
<string>merchant.telegram.tranzzo.test</string>
|
||||
</array>
|
||||
<key>com.apple.developer.pushkit.unrestricted-voip</key>
|
||||
<true/>
|
||||
|
@ -22,6 +22,10 @@
|
||||
"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll %2$@";
|
||||
"PUSH_PINNED_POLL" = "%1$@|pinned a poll";
|
||||
|
||||
"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz %2$@";
|
||||
"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz %2$@";
|
||||
"PUSH_PINNED_QUIZ" = "%1$@|pinned a quiz";
|
||||
|
||||
"PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@: %3$@";
|
||||
"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message";
|
||||
"PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo";
|
||||
@ -93,6 +97,7 @@
|
||||
"PUSH_MESSAGE_GEO" = "%1$@|sent you a map";
|
||||
"PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location";
|
||||
"PUSH_MESSAGE_POLL" = "%1$@|sent you a poll";
|
||||
"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz";
|
||||
"PUSH_MESSAGE_GIF" = "%1$@|sent you a GIF";
|
||||
"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
|
||||
"PUSH_MESSAGE_INVOICE" = "%1$@|sent you an invoice for %2$@";
|
||||
@ -125,6 +130,7 @@
|
||||
"PUSH_CHANNEL_MESSAGE_GEO" = "%1$@|posted a map";
|
||||
"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location";
|
||||
"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll";
|
||||
"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz";
|
||||
"PUSH_CHANNEL_MESSAGE_GIF" = "%1$@|posted a GIF";
|
||||
"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
|
||||
"PUSH_CHANNEL_MESSAGE_FWD" = "%1$@|posted a forwarded message";
|
||||
@ -155,6 +161,7 @@
|
||||
"PUSH_CHAT_MESSAGE_GEO" = "%2$@|%1$@ sent a map";
|
||||
"PUSH_CHAT_MESSAGE_GEOLIVE" = "%2$@|%1$@ started sharing their live location";
|
||||
"PUSH_CHAT_MESSAGE_POLL" = "%2$@|%1$@ sent a poll %3$@ to the group";
|
||||
"PUSH_CHAT_MESSAGE_QUIZ" = "%2$@|%1$@ sent a quiz %3$@ to the group";
|
||||
"PUSH_CHAT_MESSAGE_GIF" = "%2$@|%1$@ sent a GIF";
|
||||
"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@";
|
||||
"PUSH_CHAT_MESSAGE_INVOICE" = "%2$@|%1$@ sent an invoice for %3$@";
|
||||
@ -197,6 +204,7 @@
|
||||
"PUSH_PINNED_GEO" = "%1$@|pinned a map";
|
||||
"PUSH_PINNED_GEOLIVE" = "%1$@|pinned a live location";
|
||||
"PUSH_PINNED_POLL" = "|%1$@|pinned a poll %2$@";
|
||||
"PUSH_PINNED_QUIZ" = "|%1$@|pinned a quiz %2$@";
|
||||
"PUSH_PINNED_GAME" = "%1$@|pinned a game";
|
||||
"PUSH_PINNED_INVOICE" = "%1$@|pinned an invoice";
|
||||
"PUSH_PINNED_GIF" = "%1$@|pinned a GIF";
|
||||
@ -1927,6 +1935,7 @@
|
||||
"Notification.PinnedContactMessage" = "%@ pinned a contact";
|
||||
"Notification.PinnedDeletedMessage" = "%@ pinned deleted message";
|
||||
"Notification.PinnedPollMessage" = "%@ pinned a poll";
|
||||
"Notification.PinnedQuizMessage" = "%@ pinned a quiz";
|
||||
|
||||
"Message.PinnedTextMessage" = "pinned \"%@\" ";
|
||||
"Message.PinnedPhotoMessage" = "pinned photo";
|
||||
@ -1937,7 +1946,6 @@
|
||||
"Message.PinnedStickerMessage" = "pinned sticker";
|
||||
"Message.PinnedLocationMessage" = "pinned location";
|
||||
"Message.PinnedContactMessage" = "pinned contact";
|
||||
"Message.PinnedPollMessage" = "pinned poll";
|
||||
|
||||
"Notification.PinnedMessage" = "pinned message";
|
||||
|
||||
@ -3793,6 +3801,7 @@ Unused sets are archived when you add more.";
|
||||
"MessagePoll.VotedCount_any" = "%@ votes";
|
||||
"AttachmentMenu.Poll" = "Poll";
|
||||
"Conversation.PinnedPoll" = "Pinned Poll";
|
||||
"Conversation.PinnedQuiz" = "Pinned Quiz";
|
||||
|
||||
"CreatePoll.Title" = "New Poll";
|
||||
"CreatePoll.Create" = "Send";
|
||||
@ -5245,3 +5254,47 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Conversation.ContextMenuCancelEditing" = "Cancel Editing";
|
||||
|
||||
"Map.NoPlacesNearby" = "There are no known places nearby.\nTry a different location.";
|
||||
|
||||
"CreatePoll.QuizTitle" = "New Quiz";
|
||||
"CreatePoll.QuizOptionsHeader" = "QUIZ ANSWERS";
|
||||
"CreatePoll.Anonymous" = "Anonymous Voting";
|
||||
"CreatePoll.MultipleChoice" = "Multiple Choice";
|
||||
"CreatePoll.MultipleChoiceQuizAlert" = "A quiz has one correct answer.";
|
||||
"CreatePoll.Quiz" = "Quiz Mode";
|
||||
"CreatePoll.QuizInfo" = "Polls in Quiz Mode have one correct answer. Users can't revoke their answers.";
|
||||
"CreatePoll.QuizTip" = "Tap to choose the correct answer";
|
||||
|
||||
"MessagePoll.LabelPoll" = "Public Poll";
|
||||
"MessagePoll.LabelAnonymousQuiz" = "Anonymous Quiz";
|
||||
"MessagePoll.LabelQuiz" = "Quiz";
|
||||
"MessagePoll.SubmitVote" = "Vote";
|
||||
"MessagePoll.ViewResults" = "View Results";
|
||||
"MessagePoll.QuizNoUsers" = "Nobody answered yet";
|
||||
"MessagePoll.QuizCount_0" = "%@ answered";
|
||||
"MessagePoll.QuizCount_1" = "1 answered";
|
||||
"MessagePoll.QuizCount_2" = "2 answered";
|
||||
"MessagePoll.QuizCount_3_10" = "%@ answered";
|
||||
"MessagePoll.QuizCount_many" = "%@ answered";
|
||||
"MessagePoll.QuizCount_any" = "%@ answered";
|
||||
|
||||
"PollResults.Title" = "Poll Results";
|
||||
"PollResults.Collapse" = "COLLAPSE";
|
||||
"PollResults.ShowMore_1" = "Show More (%@)";
|
||||
"PollResults.ShowMore_any" = "Show More (%@)";
|
||||
|
||||
"Conversation.StopQuiz" = "Stop Quiz";
|
||||
"Conversation.StopQuizConfirmationTitle" = "If you stop this quiz now, nobody will be able to submit answers. This action cannot be undone.";
|
||||
"Conversation.StopQuizConfirmation" = "Stop Quiz";
|
||||
|
||||
"Forward.ErrorDisabledForChat" = "Sorry, you can't forward messages to this chat.";
|
||||
"Forward.ErrorPublicPollDisabledInChannels" = "Sorry, public polls can’t be forwarded to channels.";
|
||||
"Forward.ErrorPublicQuizDisabledInChannels" = "Sorry, public polls can’t 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 %@";
|
||||
|
@ -446,8 +446,8 @@ public protocol SharedAccountContext: class {
|
||||
func makeComposeController(context: AccountContext) -> ViewController
|
||||
func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
|
||||
func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode) -> ChatController
|
||||
func makeChatMessagePreviewItem(context: AccountContext, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?) -> ListViewItem
|
||||
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
|
||||
func makeChatMessagePreviewItem(context: AccountContext, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?) -> ListViewItem
|
||||
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
|
||||
func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController?
|
||||
func makeContactSelectionController(_ params: ContactSelectionControllerParams) -> ContactSelectionController
|
||||
func makeContactMultiselectionController(_ params: ContactMultiselectionControllerParams) -> ContactMultiselectionController
|
||||
|
@ -246,7 +246,7 @@ public enum ChatControllerSubject: Equatable {
|
||||
|
||||
public enum ChatControllerPresentationMode: Equatable {
|
||||
case standard(previewing: Bool)
|
||||
case overlay
|
||||
case overlay(NavigationController?)
|
||||
case inline(NavigationController?)
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,8 @@ public struct ChatListNodePeersFilter: OptionSet {
|
||||
|
||||
public static let excludeDisabled = ChatListNodePeersFilter(rawValue: 1 << 10)
|
||||
public static let includeSavedMessages = ChatListNodePeersFilter(rawValue: 1 << 11)
|
||||
|
||||
public static let excludeChannels = ChatListNodePeersFilter(rawValue: 1 << 12)
|
||||
}
|
||||
|
||||
public final class PeerSelectionControllerParams {
|
||||
@ -32,12 +34,14 @@ public final class PeerSelectionControllerParams {
|
||||
public let filter: ChatListNodePeersFilter
|
||||
public let hasContactSelector: Bool
|
||||
public let title: String?
|
||||
public let attemptSelection: ((Peer) -> Void)?
|
||||
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasContactSelector: Bool = true, title: String? = nil) {
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasContactSelector: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.filter = filter
|
||||
self.hasContactSelector = hasContactSelector
|
||||
self.title = title
|
||||
self.attemptSelection = attemptSelection
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,19 +44,19 @@ private func getCoveringViewSnaphot(window: Window1) -> UIImage? {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.scaleBy(x: scale, y: scale)
|
||||
UIGraphicsPushContext(context)
|
||||
window.forEachViewController { controller in
|
||||
window.forEachViewController({ controller in
|
||||
if let controller = controller as? PasscodeEntryController {
|
||||
controller.displayNode.alpha = 0.0
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
window.hostView.containerView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
|
||||
window.forEachViewController { controller in
|
||||
window.forEachViewController({ controller in
|
||||
if let controller = controller as? PasscodeEntryController {
|
||||
controller.displayNode.alpha = 1.0
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
UIGraphicsPopContext()
|
||||
}).flatMap(applyScreenshotEffectToImage)
|
||||
}
|
||||
@ -201,6 +201,7 @@ public final class AppLockContextImpl: AppLockContext {
|
||||
}
|
||||
}
|
||||
passcodeController.presentedOverCoveringView = true
|
||||
passcodeController.isOpaqueWhenInOverlay = true
|
||||
strongSelf.passcodeController = passcodeController
|
||||
if let rootViewController = strongSelf.rootController {
|
||||
if let presentedViewController = rootViewController.presentedViewController as? UIActivityViewController {
|
||||
|
29
submodules/ArchivedStickerPacksNotice/BUCK
Normal file
29
submodules/ArchivedStickerPacksNotice/BUCK
Normal 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",
|
||||
],
|
||||
)
|
@ -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
|
||||
}
|
@ -54,16 +54,6 @@ private class AvatarNodeParameters: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private let gradientColors: [NSArray] = [
|
||||
[UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
|
||||
[UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
|
||||
[UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
|
||||
[UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
|
||||
[UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
|
||||
[UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
|
||||
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
||||
]
|
||||
|
||||
private func generateGradientFilledCircleImage(diameter: CGFloat, colors: NSArray) -> UIImage? {
|
||||
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
@ -167,6 +157,16 @@ public final class AvatarEditOverlayNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public final class AvatarNode: ASDisplayNode {
|
||||
public static let gradientColors: [NSArray] = [
|
||||
[UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
|
||||
[UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
|
||||
[UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
|
||||
[UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
|
||||
[UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
|
||||
[UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
|
||||
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
||||
]
|
||||
|
||||
public var font: UIFont {
|
||||
didSet {
|
||||
if oldValue !== font {
|
||||
@ -318,7 +318,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
|
||||
let parameters: AvatarNodeParameters
|
||||
|
||||
if let peer = peer, let signal = peerAvatarImage(account: context.account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad) {
|
||||
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad) {
|
||||
self.contents = nil
|
||||
self.displaySuspended = true
|
||||
self.imageReady.set(self.imageNode.ready)
|
||||
@ -416,7 +416,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
if peerId.namespace == -1 {
|
||||
colorIndex = -1
|
||||
} else {
|
||||
colorIndex = abs(Int(clamping: accountPeerId.id &+ peerId.id))
|
||||
colorIndex = abs(Int(clamping: peerId.id))
|
||||
}
|
||||
} else {
|
||||
colorIndex = -1
|
||||
@ -456,7 +456,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
colorsArray = grayscaleColors
|
||||
}
|
||||
} else {
|
||||
colorsArray = gradientColors[colorIndex % gradientColors.count]
|
||||
colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count]
|
||||
}
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
@ -555,7 +555,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], accountPeerId: PeerId, peerId: PeerId) {
|
||||
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], peerId: PeerId) {
|
||||
context.beginPath()
|
||||
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
|
||||
size.height))
|
||||
@ -572,7 +572,7 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont
|
||||
if colorIndex == -1 {
|
||||
colorsArray = grayscaleColors
|
||||
} else {
|
||||
colorsArray = gradientColors[colorIndex % gradientColors.count]
|
||||
colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count]
|
||||
}
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
@ -582,6 +582,8 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
context.resetClip()
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
|
||||
@ -597,7 +599,9 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
let textPosition = context.textPosition
|
||||
context.translateBy(x: lineOrigin.x, y: lineOrigin.y)
|
||||
CTLineDraw(line, context)
|
||||
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
||||
context.textPosition = textPosition
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ private let roundCorners = { () -> UIImage in
|
||||
return image
|
||||
}()
|
||||
|
||||
public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<Data?, NoError>? {
|
||||
public func peerAvatarImageData(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<Data?, NoError>? {
|
||||
if let smallProfileImage = representation {
|
||||
let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad)
|
||||
let imageData = resourceData
|
||||
@ -44,7 +44,7 @@ public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: M
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
var fetchedDataDisposable: Disposable?
|
||||
if let peerReference = PeerReference(peer) {
|
||||
if let peerReference = peerReference {
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
} else if let authorOfMessage = authorOfMessage {
|
||||
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .messageAuthorAvatar(message: authorOfMessage, resource: smallProfileImage.resource), statsCategory: .generic).start()
|
||||
@ -64,8 +64,8 @@ public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: M
|
||||
}
|
||||
}
|
||||
|
||||
public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
|
||||
if let imageData = peerAvatarImageData(account: account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
||||
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
|
||||
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
||||
return imageData
|
||||
|> mapToSignal { data -> Signal<UIImage?, NoError> in
|
||||
let generate = deferred { () -> Signal<UIImage?, NoError> in
|
||||
|
@ -288,7 +288,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
|
||||
let showCallsTab = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings])
|
||||
|> map { sharedData -> Bool in
|
||||
var value = true
|
||||
var value = CallListSettings.defaultSettings.showTab
|
||||
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings] as? CallListSettings {
|
||||
value = settings.showTab
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked)
|
||||
}
|
||||
if case .root = groupId, checkProxy {
|
||||
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil {
|
||||
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self {
|
||||
strongSelf.didShowProxyUnavailableTooltipController = true
|
||||
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true)
|
||||
strongSelf.proxyUnavailableTooltipController = tooltipController
|
||||
|
@ -244,6 +244,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in
|
||||
self?.requestOpenPeerFromSearch?(peer, dismissSearch)
|
||||
}, openDisabledPeer: { _ in
|
||||
}, openRecentPeerOptions: { [weak self] peer in
|
||||
self?.requestOpenRecentPeerOptions?(peer)
|
||||
}, openMessage: { [weak self] peer, messageId in
|
||||
|
@ -81,7 +81,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ListViewItem {
|
||||
func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, disaledPeerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ListViewItem {
|
||||
switch self {
|
||||
case let .topPeers(peers, theme, strings):
|
||||
return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in
|
||||
@ -133,6 +133,12 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let channel = primaryPeer as? TelegramChannel, case .broadcast = channel.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
let status: ContactsPeerItemStatus
|
||||
if let user = primaryPeer as? TelegramUser {
|
||||
let servicePeer = isServicePeer(primaryPeer)
|
||||
@ -181,6 +187,10 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
|
||||
peerSelected(chatPeer)
|
||||
}
|
||||
}, disabledAction: { _ in
|
||||
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
|
||||
disaledPeerSelected(chatPeer)
|
||||
}
|
||||
}, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in
|
||||
return { node, gesture in
|
||||
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
|
||||
@ -501,12 +511,12 @@ public struct ChatListSearchContainerTransition {
|
||||
}
|
||||
}
|
||||
|
||||
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ChatListSearchContainerRecentTransition {
|
||||
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, disaledPeerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ChatListSearchContainerRecentTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disaledPeerSelected: disaledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, peerSelected: peerSelected, disaledPeerSelected: disaledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
|
||||
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
@ -615,7 +625,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
private let filter: ChatListNodePeersFilter
|
||||
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?) {
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?) {
|
||||
self.context = context
|
||||
self.filter = filter
|
||||
self.dimNode = ASDisplayNode()
|
||||
@ -837,6 +847,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
}
|
||||
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -962,6 +978,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
openPeer(peer, false)
|
||||
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}, disabledPeerSelected: { _ in
|
||||
}, togglePeerSelected: { _ in
|
||||
}, messageSelected: { [weak self] peer, message, _ in
|
||||
self?.view.endEditing(true)
|
||||
@ -1091,6 +1108,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
openPeer(peer, true)
|
||||
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
|
||||
self?.recentListNode.clearHighlightAnimated(true)
|
||||
}, disaledPeerSelected: { peer in
|
||||
openDisabledPeer(peer)
|
||||
}, peerContextAction: peerContextAction,
|
||||
clearRecentlySearchedPeers: {
|
||||
self?.clearRecentSearch()
|
||||
|
@ -755,7 +755,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var currentSecretIconImage: UIImage?
|
||||
|
||||
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||
|
||||
let editingOffset: CGFloat
|
||||
var reorderInset: CGFloat = 0.0
|
||||
@ -1331,7 +1331,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
|
||||
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width + revealOffset - params.rightInset - reorderControlSizeAndApply.0, y: layoutOffset), size: CGSize(width: reorderControlSizeAndApply.0, height: layout.contentSize.height))
|
||||
if strongSelf.reorderControlNode == nil {
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false)
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
|
||||
strongSelf.reorderControlNode = reorderControlNode
|
||||
strongSelf.addSubnode(reorderControlNode)
|
||||
reorderControlNode.frame = reorderControlFrame
|
||||
@ -1344,7 +1344,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateAlpha(node: strongSelf.pinnedIconNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: strongSelf.statusNode, alpha: 0.0)
|
||||
} else if let reorderControlNode = strongSelf.reorderControlNode {
|
||||
let _ = reorderControlSizeAndApply.1(layout.contentSize.height, false)
|
||||
let _ = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
|
||||
transition.updateFrame(node: reorderControlNode, frame: reorderControlFrame)
|
||||
}
|
||||
} else if let reorderControlNode = strongSelf.reorderControlNode {
|
||||
|
@ -46,6 +46,7 @@ final class ChatListHighlightedLocation {
|
||||
public final class ChatListNodeInteraction {
|
||||
let activateSearch: () -> Void
|
||||
let peerSelected: (Peer) -> Void
|
||||
let disabledPeerSelected: (Peer) -> Void
|
||||
let togglePeerSelected: (PeerId) -> Void
|
||||
let messageSelected: (Peer, Message, Bool) -> Void
|
||||
let groupSelected: (PeerGroupId) -> Void
|
||||
@ -62,9 +63,10 @@ public final class ChatListNodeInteraction {
|
||||
public var searchTextHighightState: String?
|
||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||
|
||||
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
self.activateSearch = activateSearch
|
||||
self.peerSelected = peerSelected
|
||||
self.disabledPeerSelected = disabledPeerSelected
|
||||
self.togglePeerSelected = togglePeerSelected
|
||||
self.messageSelected = messageSelected
|
||||
self.groupSelected = groupSelected
|
||||
@ -202,11 +204,22 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.peerSelected(chatPeer)
|
||||
}
|
||||
}, disabledAction: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.disabledPeerSelected(chatPeer)
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
}
|
||||
case let .HoleEntry(_, theme):
|
||||
@ -242,10 +255,19 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.peerSelected(chatPeer)
|
||||
}
|
||||
}, disabledAction: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.disabledPeerSelected(chatPeer)
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
}
|
||||
case let .HoleEntry(_, theme):
|
||||
@ -318,6 +340,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
public var peerSelected: ((PeerId, Bool, Bool) -> Void)?
|
||||
public var disabledPeerSelected: ((Peer) -> Void)?
|
||||
public var groupSelected: ((PeerGroupId) -> Void)?
|
||||
public var addContact: ((String) -> Void)?
|
||||
public var activateSearch: (() -> Void)?
|
||||
@ -409,6 +432,10 @@ public final class ChatListNode: ListView {
|
||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||
peerSelected(peer.id, true, false)
|
||||
}
|
||||
}, disabledPeerSelected: { [weak self] peer in
|
||||
if let strongSelf = self, let disabledPeerSelected = strongSelf.disabledPeerSelected {
|
||||
disabledPeerSelected(peer)
|
||||
}
|
||||
}, togglePeerSelected: { [weak self] peerId in
|
||||
self?.updateState { state in
|
||||
var state = state
|
||||
@ -584,6 +611,11 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
|
||||
}
|
||||
}
|
||||
|
||||
if filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -170,7 +170,7 @@ class CreatePollOptionActionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.iconNode.image = updatedIcon
|
||||
}
|
||||
if let image = strongSelf.iconNode.image {
|
||||
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0 - 1.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
|
||||
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0 - 3.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
|
@ -6,6 +6,7 @@ import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import CheckNode
|
||||
|
||||
struct CreatePollOptionItemEditing {
|
||||
let editable: Bool
|
||||
@ -13,27 +14,29 @@ struct CreatePollOptionItemEditing {
|
||||
}
|
||||
|
||||
class CreatePollOptionItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let presentationData: ItemListPresentationData
|
||||
let id: Int
|
||||
let placeholder: String
|
||||
let value: String
|
||||
let isSelected: Bool?
|
||||
let maxLength: Int
|
||||
let editing: CreatePollOptionItemEditing
|
||||
let sectionId: ItemListSectionId
|
||||
let setItemIdWithRevealedOptions: (Int?, Int?) -> Void
|
||||
let updated: (String) -> Void
|
||||
let updated: (String, Bool) -> Void
|
||||
let next: (() -> Void)?
|
||||
let delete: (Bool) -> Void
|
||||
let focused: () -> Void
|
||||
let canDelete: Bool
|
||||
let focused: (Bool) -> Void
|
||||
let toggleSelected: () -> Void
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, id: Int, placeholder: String, value: String, maxLength: Int, editing: CreatePollOptionItemEditing, sectionId: ItemListSectionId, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, updated: @escaping (String) -> Void, next: (() -> Void)?, delete: @escaping (Bool) -> Void, focused: @escaping () -> Void, tag: ItemListItemTag?) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
init(presentationData: ItemListPresentationData, id: Int, placeholder: String, value: String, isSelected: Bool?, maxLength: Int, editing: CreatePollOptionItemEditing, sectionId: ItemListSectionId, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, updated: @escaping (String, Bool) -> Void, next: (() -> Void)?, delete: @escaping (Bool) -> Void, canDelete: Bool, focused: @escaping (Bool) -> Void, toggleSelected: @escaping () -> Void, tag: ItemListItemTag?) {
|
||||
self.presentationData = presentationData
|
||||
self.id = id
|
||||
self.placeholder = placeholder
|
||||
self.value = value
|
||||
self.isSelected = isSelected
|
||||
self.maxLength = maxLength
|
||||
self.editing = editing
|
||||
self.sectionId = sectionId
|
||||
@ -41,7 +44,9 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
|
||||
self.updated = updated
|
||||
self.next = next
|
||||
self.delete = delete
|
||||
self.canDelete = canDelete
|
||||
self.focused = focused
|
||||
self.toggleSelected = toggleSelected
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
@ -55,7 +60,7 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
return (nil, { _ in apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -70,7 +75,7 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
apply(animation)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -84,17 +89,19 @@ class CreatePollOptionItem: ListViewItem, ItemListItem {
|
||||
private let titleFont = Font.regular(15.0)
|
||||
|
||||
class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode, ItemListItemFocusableNode, ASEditableTextNodeDelegate {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private var checkNode: CheckNode?
|
||||
|
||||
private let textClippingNode: ASDisplayNode
|
||||
private let textNode: EditableTextNode
|
||||
private let measureTextNode: TextNode
|
||||
|
||||
private let textLimitNode: TextNode
|
||||
private let editableControlNode: ItemListEditableControlNode
|
||||
private let reorderControlNode: ItemListEditableReorderControlNode
|
||||
|
||||
private var item: CreatePollOptionItem?
|
||||
@ -104,7 +111,21 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
override var controlsContainer: ASDisplayNode {
|
||||
return self.containerNode
|
||||
}
|
||||
|
||||
var checkNodeFrame: CGRect? {
|
||||
guard let _ = self.layoutParams, let checkNode = self.checkNode else {
|
||||
return nil
|
||||
}
|
||||
return checkNode.frame
|
||||
}
|
||||
|
||||
init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.clipsToBounds = true
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
@ -117,7 +138,6 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.editableControlNode = ItemListEditableControlNode()
|
||||
self.reorderControlNode = ItemListEditableReorderControlNode()
|
||||
|
||||
self.textClippingNode = ASDisplayNode()
|
||||
@ -131,21 +151,17 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.textClippingNode.addSubnode(self.textNode)
|
||||
self.addSubnode(self.textClippingNode)
|
||||
self.containerNode.addSubnode(self.textClippingNode)
|
||||
|
||||
self.addSubnode(self.editableControlNode)
|
||||
self.addSubnode(self.reorderControlNode)
|
||||
self.addSubnode(self.textLimitNode)
|
||||
self.containerNode.addSubnode(self.reorderControlNode)
|
||||
self.containerNode.addSubnode(self.textLimitNode)
|
||||
}
|
||||
|
||||
self.editableControlNode.tapped = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.setRevealOptionsOpened(true, animated: true)
|
||||
strongSelf.revealOptionsInteractivelyOpened()
|
||||
}
|
||||
}
|
||||
@objc private func checkNodePressed() {
|
||||
self.item?.toggleSelected()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -153,7 +169,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
|
||||
var textColor: UIColor = .black
|
||||
if let item = self.item {
|
||||
textColor = item.theme.list.itemPrimaryTextColor
|
||||
textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||
}
|
||||
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
||||
self.textNode.clipsToBounds = true
|
||||
@ -162,7 +178,12 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
}
|
||||
|
||||
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.item?.focused()
|
||||
self.item?.focused(true)
|
||||
}
|
||||
|
||||
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.internalEditableTextNodeDidUpdateText(editableTextNode, isLosingFocus: true)
|
||||
self.item?.focused(false)
|
||||
}
|
||||
|
||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
@ -177,7 +198,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
if updatedText.count == 1 {
|
||||
updatedText = ""
|
||||
}
|
||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
|
||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.textNode.attributedText = updatedAttributedText
|
||||
self.editableTextNodeDidUpdateText(editableTextNode)
|
||||
}
|
||||
@ -192,6 +213,10 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
}
|
||||
|
||||
func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
self.internalEditableTextNodeDidUpdateText(editableTextNode, isLosingFocus: false)
|
||||
}
|
||||
|
||||
private func internalEditableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode, isLosingFocus: Bool) {
|
||||
if let item = self.item {
|
||||
let text = self.textNode.attributedText ?? NSAttributedString()
|
||||
|
||||
@ -201,15 +226,15 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
hadReturn = true
|
||||
updatedText = updatedText.replacingOccurrences(of: "\n", with: " ")
|
||||
}
|
||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
|
||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
if text.string != updatedAttributedText.string {
|
||||
self.textNode.attributedText = updatedAttributedText
|
||||
}
|
||||
item.updated(updatedText)
|
||||
item.updated(updatedText, !isLosingFocus && editableTextNode.isFirstResponder())
|
||||
if hadReturn {
|
||||
if let next = item.next {
|
||||
next()
|
||||
} else {
|
||||
} else if !isLosingFocus {
|
||||
editableTextNode.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
@ -220,8 +245,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
self.item?.delete(editableTextNode.isFirstResponder())
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: CreatePollOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
func asyncLayout() -> (_ item: CreatePollOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
|
||||
let makeTextLimitLayout = TextNode.asyncLayout(self.textLimitNode)
|
||||
@ -231,32 +255,31 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
|
||||
let controlSizeAndApply = editableControlLayout(item.theme, false)
|
||||
let reorderSizeAndApply = reorderControlLayout(item.theme)
|
||||
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
|
||||
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
||||
let leftInset: CGFloat = 60.0 + params.leftInset
|
||||
let leftInset: CGFloat = params.leftInset + (item.isSelected != nil ? 60.0 : 16.0)
|
||||
let rightInset: CGFloat = 44.0 + params.rightInset
|
||||
|
||||
let textLength = item.value.count
|
||||
let displayTextLimit = textLength > item.maxLength * 70 / 100
|
||||
let remainingCount = item.maxLength - textLength
|
||||
|
||||
let (textLimitLayout, textLimitApply) = makeTextLimitLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.theme.list.itemDestructiveColor : item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (textLimitLayout, textLimitApply) = makeTextLimitLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var measureText = item.value
|
||||
if measureText.hasSuffix("\n") || measureText.isEmpty {
|
||||
measureText += "|"
|
||||
}
|
||||
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
|
||||
let attributedText = NSAttributedString(string: item.value, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
|
||||
let attributedText = NSAttributedString(string: item.value, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.05, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let textTopInset: CGFloat = 11.0
|
||||
@ -265,21 +288,29 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
let contentSize = CGSize(width: params.width, height: textLayout.size.height + textTopInset + textBottomInset)
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(17.0), textColor: item.theme.list.itemPlaceholderTextColor)
|
||||
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPlaceholderTextColor)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
return (layout, { [weak self] animation in
|
||||
if let strongSelf = self {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
switch animation {
|
||||
case .System:
|
||||
transition = .animated(duration: 0.3, curve: .spring)
|
||||
default:
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutParams = params
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
if strongSelf.isNodeLoaded {
|
||||
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.theme.list.itemPrimaryTextColor]
|
||||
strongSelf.textNode.tintColor = item.theme.list.itemAccentColor
|
||||
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.presentationData.theme.list.itemPrimaryTextColor]
|
||||
strongSelf.textNode.tintColor = item.presentationData.theme.list.itemAccentColor
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +326,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
|
||||
let _ = textApply()
|
||||
if let currentText = strongSelf.textNode.attributedText {
|
||||
if currentText.string != attributedText.string {
|
||||
if currentText.string != attributedText.string || updatedTheme != nil {
|
||||
strongSelf.textNode.attributedText = attributedText
|
||||
}
|
||||
} else {
|
||||
@ -325,58 +356,93 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
|
||||
}
|
||||
|
||||
strongSelf.textNode.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
strongSelf.textNode.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
||||
|
||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset, height: textLayout.size.height + 1.0))
|
||||
let checkSize = CGSize(width: 32.0, height: 32.0)
|
||||
let checkFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + 11.0, y: floor((layout.contentSize.height - checkSize.height) / 2.0)), size: checkSize)
|
||||
if let isSelected = item.isSelected {
|
||||
if let checkNode = strongSelf.checkNode {
|
||||
transition.updateFrame(node: checkNode, frame: checkFrame)
|
||||
checkNode.setIsChecked(isSelected, animated: true)
|
||||
} else {
|
||||
let checkNode = CheckNode(strokeColor: item.presentationData.theme.list.itemCheckColors.strokeColor, fillColor: item.presentationData.theme.list.itemSwitchColors.positiveColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, style: .plain)
|
||||
checkNode.addTarget(target: strongSelf, action: #selector(strongSelf.checkNodePressed))
|
||||
checkNode.setIsChecked(isSelected, animated: false)
|
||||
strongSelf.checkNode = checkNode
|
||||
strongSelf.containerNode.addSubnode(checkNode)
|
||||
checkNode.frame = checkFrame
|
||||
transition.animatePositionAdditive(node: checkNode, offset: CGPoint(x: -checkFrame.maxX, y: 0.0))
|
||||
}
|
||||
} else if let checkNode = strongSelf.checkNode {
|
||||
strongSelf.checkNode = nil
|
||||
transition.updateFrame(node: checkNode, frame: checkFrame.offsetBy(dx: -checkFrame.maxX, dy: 0.0), completion: { [weak checkNode] _ in
|
||||
checkNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.textClippingNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height)))
|
||||
transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset, height: textLayout.size.height + 1.0)))
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
let bottomStripeWasHidden = strongSelf.bottomStripeNode.isHidden
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.contentSize.width - bottomStripeInset, height: separatorHeight))
|
||||
if strongSelf.animationForKey("apparentHeight") == nil {
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
let previousX = strongSelf.bottomStripeNode.frame.minX
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
|
||||
if !bottomStripeWasHidden {
|
||||
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
|
||||
}
|
||||
} else {
|
||||
let previousX = strongSelf.bottomStripeNode.frame.minX
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: strongSelf.bottomStripeNode.frame.minY), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
|
||||
if !bottomStripeWasHidden {
|
||||
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
let _ = controlSizeAndApply.1(layout.contentSize.height)
|
||||
let editableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + 6.0 + revealOffset, y: 0.0), size: CGSize(width: controlSizeAndApply.0, height: contentSize.height))
|
||||
strongSelf.editableControlNode.frame = editableControlFrame
|
||||
|
||||
let _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit && layout.contentSize.height <= 44.0)
|
||||
let _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit, transition)
|
||||
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width + revealOffset - params.rightInset - reorderSizeAndApply.0, y: 0.0), size: CGSize(width: reorderSizeAndApply.0, height: layout.contentSize.height))
|
||||
strongSelf.reorderControlNode.frame = reorderControlFrame
|
||||
strongSelf.reorderControlNode.isHidden = !item.canDelete
|
||||
|
||||
let _ = textLimitApply()
|
||||
strongSelf.textLimitNode.frame = CGRect(origin: CGPoint(x: reorderControlFrame.minX + floor((reorderControlFrame.width - textLimitLayout.size.width) / 2.0) - 4.0 - UIScreenPixel, y: max(floor(reorderControlFrame.midY + 2.0), layout.contentSize.height - 15.0 - textLimitLayout.size.height)), size: textLimitLayout.size)
|
||||
@ -384,7 +450,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
|
||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||
|
||||
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
|
||||
strongSelf.setRevealOptions((left: [], right: item.canDelete ? [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] : []))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -393,18 +459,20 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.updateRevealOffset(offset: offset, transition: transition)
|
||||
|
||||
guard let params = self.layoutParams else {
|
||||
guard let params = self.layoutParams, let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
let revealOffset = offset
|
||||
|
||||
let leftInset: CGFloat
|
||||
leftInset = 60.0 + params.leftInset
|
||||
leftInset = params.leftInset + (item.isSelected != nil ? 60.0 : 16.0)
|
||||
|
||||
var controlFrame = self.editableControlNode.frame
|
||||
controlFrame.origin.x = params.leftInset + 6.0 + revealOffset
|
||||
transition.updateFrame(node: self.editableControlNode, frame: controlFrame)
|
||||
if let checkNode = self.checkNode {
|
||||
var checkNodeFrame = checkNode.frame
|
||||
checkNodeFrame.origin.x = params.leftInset + 11.0 + revealOffset
|
||||
transition.updateFrame(node: checkNode, frame: checkNodeFrame)
|
||||
}
|
||||
|
||||
var reorderFrame = self.reorderControlNode.frame
|
||||
reorderFrame.origin.x = params.width + revealOffset - params.rightInset - reorderFrame.width
|
||||
@ -436,7 +504,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
}
|
||||
|
||||
override func isReorderable(at point: CGPoint) -> Bool {
|
||||
if self.reorderControlNode.frame.contains(point), !self.isDisplayingRevealedOptions {
|
||||
if self.reorderControlNode.frame.contains(point), !self.reorderControlNode.isHidden, !self.isDisplayingRevealedOptions {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -448,5 +516,16 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
var separatorFrame = self.bottomStripeNode.frame
|
||||
separatorFrame.origin.y = currentValue - UIScreenPixel
|
||||
self.bottomStripeNode.frame = separatorFrame
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.containerNode.bounds.width, height: currentValue))
|
||||
|
||||
let insets = self.insets
|
||||
let separatorHeight = UIScreenPixel
|
||||
guard let params = self.layoutParams else {
|
||||
return
|
||||
}
|
||||
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: self.containerNode.bounds.width, height: currentValue + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
let options: [ItemListPeerItemRevealOption]
|
||||
let actionIcon: ContactsPeerItemActionIcon
|
||||
let action: (ContactsPeerItemPeer) -> Void
|
||||
let disabledAction: ((ContactsPeerItemPeer) -> Void)?
|
||||
let setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)?
|
||||
let deletePeer: ((PeerId) -> Void)?
|
||||
let itemHighlighting: ContactItemHighlighting?
|
||||
@ -133,7 +134,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
|
||||
public let header: ListViewItemHeader?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.style = style
|
||||
self.sectionId = sectionId
|
||||
@ -150,11 +151,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
self.options = options
|
||||
self.actionIcon = actionIcon
|
||||
self.action = action
|
||||
self.disabledAction = disabledAction
|
||||
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||
self.deletePeer = deletePeer
|
||||
self.header = header
|
||||
self.itemHighlighting = itemHighlighting
|
||||
self.selectable = enabled
|
||||
self.selectable = enabled || disabledAction != nil
|
||||
self.contextAction = contextAction
|
||||
|
||||
if let index = index {
|
||||
@ -245,7 +247,12 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
}
|
||||
|
||||
public func selected(listView: ListView) {
|
||||
self.action(self.peer)
|
||||
if self.enabled {
|
||||
self.action(self.peer)
|
||||
} else {
|
||||
listView.clearHighlightAnimated(true)
|
||||
self.disabledAction?(self.peer)
|
||||
}
|
||||
}
|
||||
|
||||
static func mergeType(item: ContactsPeerItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {
|
||||
|
@ -5,6 +5,8 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
private var validatedGesture = false
|
||||
private var firstLocation: CGPoint = CGPoint()
|
||||
|
||||
public var shouldBegin: ((CGPoint) -> Bool)?
|
||||
|
||||
override public init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
|
||||
@ -21,7 +23,13 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
let touch = touches.first!
|
||||
self.firstLocation = touch.location(in: self.view)
|
||||
let point = touch.location(in: self.view)
|
||||
if let shouldBegin = self.shouldBegin, !shouldBegin(point) {
|
||||
self.state = .failed
|
||||
return
|
||||
}
|
||||
|
||||
self.firstLocation = point
|
||||
|
||||
if let target = self.view?.hitTest(self.firstLocation, with: event) {
|
||||
if target == self.view {
|
||||
|
@ -109,7 +109,6 @@ private func ==(lhs: Tail, rhs: Tail) -> Bool {
|
||||
}
|
||||
|
||||
private var cachedCorners = Atomic<[Corner: DrawingContext]>(value: [:])
|
||||
private var cachedTails = Atomic<[Tail: DrawingContext]>(value: [:])
|
||||
|
||||
private func cornerContext(_ corner: Corner) -> DrawingContext {
|
||||
let cached: DrawingContext? = cachedCorners.with {
|
||||
@ -122,20 +121,22 @@ private func cornerContext(_ corner: Corner) -> DrawingContext {
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true)
|
||||
|
||||
context.withContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
c.clear(CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius))))
|
||||
c.setFillColor(UIColor.black.cgColor)
|
||||
let rect: CGRect
|
||||
switch corner {
|
||||
case let .TopLeft(radius):
|
||||
rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
case let .TopRight(radius):
|
||||
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
case let .BottomLeft(radius):
|
||||
rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
case let .BottomRight(radius):
|
||||
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
case let .TopLeft(radius):
|
||||
let rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
|
||||
c.fillEllipse(in: rect)
|
||||
case let .TopRight(radius):
|
||||
let rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
|
||||
c.fillEllipse(in: rect)
|
||||
case let .BottomLeft(radius):
|
||||
let rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
|
||||
c.fillEllipse(in: rect)
|
||||
case let .BottomRight(radius):
|
||||
let rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius * 2), height: CGFloat(radius * 2)))
|
||||
c.fillEllipse(in: rect)
|
||||
}
|
||||
c.fillEllipse(in: rect)
|
||||
}
|
||||
|
||||
let _ = cachedCorners.modify { current in
|
||||
@ -148,62 +149,6 @@ private func cornerContext(_ corner: Corner) -> DrawingContext {
|
||||
}
|
||||
}
|
||||
|
||||
private func tailContext(_ tail: Tail) -> DrawingContext {
|
||||
let cached: DrawingContext? = cachedTails.with {
|
||||
return $0[tail]
|
||||
}
|
||||
|
||||
if let cached = cached {
|
||||
return cached
|
||||
} else {
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(tail.radius) + 3.0, height: CGFloat(tail.radius)), clear: true)
|
||||
|
||||
context.withContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
c.setFillColor(UIColor.black.cgColor)
|
||||
let rect: CGRect
|
||||
switch tail {
|
||||
case let .BottomLeft(radius):
|
||||
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
|
||||
c.move(to: CGPoint(x: 3.0, y: 1.0))
|
||||
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
|
||||
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
|
||||
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
|
||||
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
|
||||
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
|
||||
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
|
||||
c.closePath()
|
||||
c.fillPath()
|
||||
case let .BottomRight(radius):
|
||||
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
|
||||
c.translateBy(x: context.size.width / 2.0, y: context.size.height / 2.0)
|
||||
c.scaleBy(x: -1.0, y: 1.0)
|
||||
c.translateBy(x: -context.size.width / 2.0, y: -context.size.height / 2.0)
|
||||
|
||||
c.move(to: CGPoint(x: 3.0, y: 1.0))
|
||||
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
|
||||
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
|
||||
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
|
||||
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
|
||||
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
|
||||
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
|
||||
c.closePath()
|
||||
c.fillPath()
|
||||
}
|
||||
c.fillEllipse(in: rect)
|
||||
}
|
||||
|
||||
let _ = cachedTails.modify { current in
|
||||
var current = current
|
||||
current[tail] = context
|
||||
return current
|
||||
}
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
public func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) {
|
||||
let corners = arguments.corners
|
||||
let drawingRect = arguments.drawingRect
|
||||
@ -223,23 +168,24 @@ public func addCorners(_ context: DrawingContext, arguments: TransformImageArgum
|
||||
let corner = cornerContext(.BottomLeft(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
|
||||
}
|
||||
case let .Tail(radius, enabled):
|
||||
case let .Tail(radius, image):
|
||||
if radius > CGFloat.ulpOfOne {
|
||||
if enabled {
|
||||
let tail = tailContext(.BottomLeft(Int(radius)))
|
||||
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
|
||||
context.withContext { c in
|
||||
c.clear(CGRect(x: drawingRect.minX - 3.0, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
|
||||
c.setFillColor(color.cgColor)
|
||||
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
|
||||
}
|
||||
context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius))
|
||||
} else {
|
||||
let corner = cornerContext(.BottomLeft(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
|
||||
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
|
||||
context.withContext { c in
|
||||
c.clear(CGRect(x: drawingRect.minX - 4.0, y: 0.0, width: 4.0, height: drawingRect.maxY - 6.0))
|
||||
c.setFillColor(color.cgColor)
|
||||
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 7.0, width: 4.0, height: 7.0))
|
||||
c.setBlendMode(.destinationIn)
|
||||
let cornerRect = CGRect(origin: CGPoint(x: drawingRect.minX - 6.0, y: drawingRect.maxY - image.size.height), size: image.size)
|
||||
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
|
||||
c.draw(image.cgImage!, in: cornerRect)
|
||||
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch corners.bottomRight {
|
||||
@ -248,20 +194,22 @@ public func addCorners(_ context: DrawingContext, arguments: TransformImageArgum
|
||||
let corner = cornerContext(.BottomRight(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
|
||||
}
|
||||
case let .Tail(radius, enabled):
|
||||
case let .Tail(radius, image):
|
||||
if radius > CGFloat.ulpOfOne {
|
||||
if enabled {
|
||||
let tail = tailContext(.BottomRight(Int(radius)))
|
||||
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
|
||||
context.withContext { c in
|
||||
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
|
||||
c.setFillColor(color.cgColor)
|
||||
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
|
||||
}
|
||||
context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
|
||||
} else {
|
||||
let corner = cornerContext(.BottomRight(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
|
||||
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
|
||||
context.withContext { c in
|
||||
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 4.0, height: drawingRect.maxY - image.size.height))
|
||||
c.setFillColor(color.cgColor)
|
||||
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 7.0, width: 5.0, height: 7.0))
|
||||
c.setBlendMode(.destinationIn)
|
||||
let cornerRect = CGRect(origin: CGPoint(x: drawingRect.maxX - image.size.width + 6.0, y: drawingRect.maxY - image.size.height), size: image.size)
|
||||
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
|
||||
c.draw(image.cgImage!, in: cornerRect)
|
||||
c.translateBy(x: cornerRect.midX, y: cornerRect.midY)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -cornerRect.midX, y: -cornerRect.midY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ private let dispatcher = displayLinkDispatcher
|
||||
|
||||
public enum ImageCorner: Equatable {
|
||||
case Corner(CGFloat)
|
||||
case Tail(CGFloat, Bool)
|
||||
case Tail(CGFloat, UIImage)
|
||||
|
||||
public var extendedInsets: CGSize {
|
||||
switch self {
|
||||
case .Tail:
|
||||
return CGSize(width: 3.0, height: 0.0)
|
||||
return CGSize(width: 4.0, height: 0.0)
|
||||
default:
|
||||
return CGSize()
|
||||
}
|
||||
@ -36,15 +36,6 @@ public enum ImageCorner: Equatable {
|
||||
return radius
|
||||
}
|
||||
}
|
||||
|
||||
public func scaledBy(_ scale: CGFloat) -> ImageCorner {
|
||||
switch self {
|
||||
case let .Corner(radius):
|
||||
return .Corner(radius * scale)
|
||||
case let .Tail(radius, enabled):
|
||||
return .Tail(radius * scale, enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool {
|
||||
@ -56,8 +47,8 @@ public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let .Tail(lhsRadius, lhsEnabled):
|
||||
if case let .Tail(rhsRadius, rhsEnabled) = rhs, lhsRadius.isEqual(to: rhsRadius), lhsEnabled == rhsEnabled {
|
||||
case let .Tail(lhsRadius, lhsImage):
|
||||
if case let .Tail(rhsRadius, rhsImage) = rhs, lhsRadius.isEqual(to: rhsRadius), lhsImage === rhsImage {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -124,10 +115,6 @@ public struct ImageCorners: Equatable {
|
||||
public func withRemovedTails() -> ImageCorners {
|
||||
return ImageCorners(topLeft: self.topLeft.withoutTail, topRight: self.topRight.withoutTail, bottomLeft: self.bottomLeft.withoutTail, bottomRight: self.bottomRight.withoutTail)
|
||||
}
|
||||
|
||||
public func scaledBy(_ scale: CGFloat) -> ImageCorners {
|
||||
return ImageCorners(topLeft: self.topLeft.scaledBy(scale), topRight: self.topRight.scaledBy(scale), bottomLeft: self.bottomLeft.scaledBy(scale), bottomRight: self.bottomRight.scaledBy(scale))
|
||||
}
|
||||
}
|
||||
|
||||
public func ==(lhs: ImageCorners, rhs: ImageCorners) -> Bool {
|
||||
|
@ -435,6 +435,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
|
||||
private func endReordering() {
|
||||
self.itemReorderingTimer?.invalidate()
|
||||
self.itemReorderingTimer = nil
|
||||
self.lastReorderingOffset = nil
|
||||
|
||||
let f: () -> Void = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -467,11 +471,32 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
}
|
||||
|
||||
private func checkItemReordering() {
|
||||
if let reorderNode = self.reorderNode, let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self {
|
||||
guard let verticalTopOffset = reorderNode.currentOffset() else {
|
||||
return
|
||||
}
|
||||
private var itemReorderingTimer: SwiftSignalKit.Timer?
|
||||
private var lastReorderingOffset: CGFloat?
|
||||
|
||||
private func checkItemReordering(force: Bool = false) {
|
||||
guard let reorderNode = self.reorderNode, let verticalTopOffset = reorderNode.currentOffset() else {
|
||||
return
|
||||
}
|
||||
|
||||
if let lastReorderingOffset = self.lastReorderingOffset, abs(lastReorderingOffset - verticalTopOffset) < 4.0 && !force {
|
||||
return
|
||||
}
|
||||
|
||||
self.itemReorderingTimer?.invalidate()
|
||||
self.itemReorderingTimer = nil
|
||||
|
||||
self.lastReorderingOffset = verticalTopOffset
|
||||
|
||||
if !force {
|
||||
self.itemReorderingTimer = SwiftSignalKit.Timer(timeout: 0.025, repeat: false, completion: { [weak self] in
|
||||
self?.checkItemReordering(force: true)
|
||||
}, queue: Queue.mainQueue())
|
||||
self.itemReorderingTimer?.start()
|
||||
return
|
||||
}
|
||||
|
||||
if let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self {
|
||||
let verticalOffset = verticalTopOffset
|
||||
var closestIndex: (Int, CGFloat)?
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
@ -2011,7 +2036,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
}
|
||||
if node.animationForKey("apparentHeight") == nil || !(node is ListViewTempItemNode) {
|
||||
node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
|
||||
if let node = node {
|
||||
node.animateFrameTransition(progress, currentValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor())
|
||||
} else if animated {
|
||||
@ -2035,8 +2064,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
} else {
|
||||
if !nodeFrame.size.height.isEqual(to: node.apparentHeight) {
|
||||
let addAnimation = previousFrame?.height != nodeFrame.size.height
|
||||
node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
|
||||
if let node = node {
|
||||
if let node = node, addAnimation {
|
||||
node.animateFrameTransition(progress, currentValue)
|
||||
}
|
||||
})
|
||||
@ -3007,21 +3037,25 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
|
||||
let flashing = self.headerItemsAreFlashing()
|
||||
|
||||
let addHeader: (_ id: Int64, _ upperBound: CGFloat, _ lowerBound: CGFloat, _ item: ListViewItemHeader, _ hasValidNodes: Bool) -> Void = { id, upperBound, lowerBound, item, hasValidNodes in
|
||||
func addHeader(id: Int64, upperBound: CGFloat, upperBoundEdge: CGFloat, lowerBound: CGFloat, item: ListViewItemHeader, hasValidNodes: Bool) {
|
||||
let itemHeaderHeight: CGFloat = item.height
|
||||
|
||||
let headerFrame: CGRect
|
||||
let stickLocationDistanceFactor: CGFloat
|
||||
let stickLocationDistance: CGFloat
|
||||
switch item.stickDirection {
|
||||
case .top:
|
||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||
stickLocationDistance = headerFrame.minY - upperBound
|
||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||
case .bottom:
|
||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: max(upperBound, min(lowerBound, lowerDisplayBound) - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||
stickLocationDistance = lowerBound - headerFrame.maxY
|
||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||
case .top:
|
||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||
stickLocationDistance = headerFrame.minY - upperBound
|
||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||
case .topEdge:
|
||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBoundEdge - itemHeaderHeight), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||
stickLocationDistance = headerFrame.minY - upperBoundEdge + itemHeaderHeight
|
||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||
case .bottom:
|
||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: max(upperBound, min(lowerBound, lowerDisplayBound) - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||
stickLocationDistance = lowerBound - headerFrame.maxY
|
||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||
}
|
||||
visibleHeaderNodes.insert(id)
|
||||
if let headerNode = self.itemHeaderNodes[id] {
|
||||
@ -3092,31 +3126,32 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
}
|
||||
|
||||
var previousHeader: (Int64, CGFloat, CGFloat, ListViewItemHeader, Bool)?
|
||||
var previousHeader: (id: Int64, upperBound: CGFloat, upperBoundEdge: CGFloat, lowerBound: CGFloat, item: ListViewItemHeader, hasValidNodes: Bool)?
|
||||
for itemNode in self.itemNodes {
|
||||
let itemFrame = itemNode.apparentFrame
|
||||
let itemTopInset = itemNode.insets.top
|
||||
if let itemHeader = itemNode.header() {
|
||||
if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
|
||||
if let (previousHeaderId, previousUpperBound, previousUpperBoundEdge, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
|
||||
if previousHeaderId == itemHeader.id {
|
||||
previousHeader = (previousHeaderId, previousUpperBound, itemFrame.maxY, previousHeaderItem, hasValidNodes || itemNode.index != nil)
|
||||
previousHeader = (previousHeaderId, previousUpperBound, previousUpperBoundEdge, itemFrame.maxY, previousHeaderItem, hasValidNodes || itemNode.index != nil)
|
||||
} else {
|
||||
addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes)
|
||||
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, item: previousHeaderItem, hasValidNodes: hasValidNodes)
|
||||
|
||||
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.maxY, itemHeader, itemNode.index != nil)
|
||||
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.minY + itemTopInset, itemFrame.maxY, itemHeader, itemNode.index != nil)
|
||||
}
|
||||
} else {
|
||||
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.maxY, itemHeader, itemNode.index != nil)
|
||||
previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.minY + itemTopInset, itemFrame.maxY, itemHeader, itemNode.index != nil)
|
||||
}
|
||||
} else {
|
||||
if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
|
||||
addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes)
|
||||
if let (previousHeaderId, previousUpperBound, previousUpperBoundEdge, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
|
||||
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, item: previousHeaderItem, hasValidNodes: hasValidNodes)
|
||||
}
|
||||
previousHeader = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
|
||||
addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes)
|
||||
if let (previousHeaderId, previousUpperBound, previousUpperBoundEdge, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader {
|
||||
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, item: previousHeaderItem, hasValidNodes: hasValidNodes)
|
||||
}
|
||||
|
||||
let currentIds = Set(self.itemHeaderNodes.keys)
|
||||
|
@ -4,6 +4,7 @@ import AsyncDisplayKit
|
||||
|
||||
public enum ListViewItemHeaderStickDirection {
|
||||
case top
|
||||
case topEdge
|
||||
case bottom
|
||||
}
|
||||
|
||||
|
@ -564,6 +564,6 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
open func snapshotForReordering() -> UIView? {
|
||||
return self.view.snapshotContentTree()
|
||||
return self.view.snapshotContentTree(keepTransform: true)
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +131,16 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
return false
|
||||
}
|
||||
|
||||
private func checkInteractiveDismissWithControllers() -> Bool {
|
||||
if let controller = self.container.controllers.last {
|
||||
if !controller.attemptNavigation({
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
@ -147,7 +157,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
let progress = translation / self.bounds.width
|
||||
let velocity = recognizer.velocity(in: self.view).x
|
||||
|
||||
if velocity > 1000 || progress > 0.2 {
|
||||
if (velocity > 1000 || progress > 0.2) && self.checkInteractiveDismissWithControllers() {
|
||||
self.isDismissed = true
|
||||
self.horizontalDismissOffset = self.bounds.width
|
||||
self.dismissProgress = 1.0
|
||||
@ -243,7 +253,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
let duration = Double(min(0.3, velocityFactor))
|
||||
let transition: ContainedViewLayoutTransition
|
||||
let dismissProgress: CGFloat
|
||||
if velocity.y < -0.5 || progress >= 0.5 {
|
||||
if (velocity.y < -0.5 || progress >= 0.5) && self.checkInteractiveDismissWithControllers() {
|
||||
dismissProgress = 1.0
|
||||
targetOffset = 0.0
|
||||
transition = .animated(duration: duration, curve: .easeInOut)
|
||||
|
@ -360,8 +360,8 @@ public func standardTextAlertController(theme: AlertControllerTheme, title: Stri
|
||||
var dismissImpl: (() -> Void)?
|
||||
let attributedText: NSAttributedString
|
||||
if parseMarkdown {
|
||||
let font = title == nil ? Font.semibold(theme.baseFontSize) : Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
let boldFont = title == nil ? Font.bold(theme.baseFontSize) : Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
let font = title == nil ? Font.semibold(theme.baseFontSize * 13.0 / 17.0) : Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
let boldFont = title == nil ? Font.bold(theme.baseFontSize * 13.0 / 17.0) : Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
let body = MarkdownAttributeSet(font: font, textColor: theme.primaryColor)
|
||||
let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.primaryColor)
|
||||
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)
|
||||
|
@ -185,6 +185,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
|
||||
open func dismissImmediately() {
|
||||
self.dismissed?(false)
|
||||
self.controllerNode.hide()
|
||||
self.presentingViewController?.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,10 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
func hide() {
|
||||
self.containerNode.alpha = 0.0
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let event = event {
|
||||
var eventIsPresses = false
|
||||
|
@ -337,8 +337,17 @@ public class Window1 {
|
||||
self?.isInteractionBlocked = value
|
||||
}
|
||||
|
||||
let updateOpaqueOverlays: () -> Void = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf._rootController?.displayNode.accessibilityElementsHidden = strongSelf.presentationContext.hasOpaqueOverlay || strongSelf.topPresentationContext.hasOpaqueOverlay
|
||||
}
|
||||
self.presentationContext.updateHasOpaqueOverlay = { [weak self] value in
|
||||
self?._rootController?.displayNode.accessibilityElementsHidden = value
|
||||
updateOpaqueOverlays()
|
||||
}
|
||||
self.topPresentationContext.updateHasOpaqueOverlay = { [weak self] value in
|
||||
updateOpaqueOverlays()
|
||||
}
|
||||
|
||||
self.hostView.present = { [weak self] controller, level, blockInteraction, completion in
|
||||
@ -1222,10 +1231,12 @@ public class Window1 {
|
||||
return hidden
|
||||
}
|
||||
|
||||
public func forEachViewController(_ f: (ContainableController) -> Bool) {
|
||||
public func forEachViewController(_ f: (ContainableController) -> Bool, excludeNavigationSubControllers: Bool = false) {
|
||||
if let navigationController = self._rootController as? NavigationController {
|
||||
for case let controller as ContainableController in navigationController.viewControllers {
|
||||
!f(controller)
|
||||
if !excludeNavigationSubControllers {
|
||||
for case let controller as ContainableController in navigationController.viewControllers {
|
||||
!f(controller)
|
||||
}
|
||||
}
|
||||
if let controller = navigationController.topOverlayController {
|
||||
!f(controller)
|
||||
|
@ -396,7 +396,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
} else {
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
}
|
||||
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
|
@ -238,7 +238,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
self.statusButtonNode.addSubnode(self.statusNode)
|
||||
self.statusButtonNode.addTarget(self, action: #selector(statusButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.statusButtonNode.addTarget(self, action: #selector(self.statusButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.addSubnode(self.statusButtonNode)
|
||||
|
||||
|
@ -122,6 +122,7 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode {
|
||||
|
||||
public final class SecretMediaPreviewController: ViewController {
|
||||
private let context: AccountContext
|
||||
private let messageId: MessageId
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
@ -150,6 +151,7 @@ public final class SecretMediaPreviewController: ViewController {
|
||||
|
||||
public init(context: AccountContext, messageId: MessageId) {
|
||||
self.context = context
|
||||
self.messageId = messageId
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
|
||||
@ -159,8 +161,6 @@ public final class SecretMediaPreviewController: ViewController {
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
|
||||
|
||||
|
||||
self.disposable.set((context.account.postbox.messageView(messageId) |> deliverOnMainQueue).start(next: { [weak self] view in
|
||||
if let strongSelf = self {
|
||||
strongSelf.messageView = view
|
||||
@ -178,17 +178,6 @@ public final class SecretMediaPreviewController: ViewController {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
self.screenCaptureEventsDisposable = (screenCaptureEvents()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let strongSelf = self, strongSelf.traceVisibility() {
|
||||
if messageId.peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
let _ = enqueueMessages(account: context.account, peerId: messageId.peerId, messages: [.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.historyScreenshot)), replyToMessageId: nil, localGroupingKey: nil)]).start()
|
||||
} else if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
let _ = addSecretChatMessageScreenshot(account: context.account, peerId: messageId.peerId).start()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -348,6 +337,19 @@ public final class SecretMediaPreviewController: ViewController {
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if self.screenCaptureEventsDisposable == nil {
|
||||
self.screenCaptureEventsDisposable = (screenCaptureEvents()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let strongSelf = self, strongSelf.traceVisibility() {
|
||||
if strongSelf.messageId.peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.messageId.peerId, messages: [.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.historyScreenshot)), replyToMessageId: nil, localGroupingKey: nil)]).start()
|
||||
} else if strongSelf.messageId.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
let _ = addSecretChatMessageScreenshot(account: strongSelf.context.account, peerId: strongSelf.messageId.peerId).start()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var nodeAnimatesItself = false
|
||||
|
||||
if let centralItemNode = self.controllerNode.pager.centralItemNode(), let message = self.messageView?.message {
|
||||
|
@ -49,6 +49,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
}
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {
|
||||
}, peerSelected: { peer in
|
||||
}, disabledPeerSelected: { _ in
|
||||
}, togglePeerSelected: { _ in
|
||||
}, messageSelected: { [weak self] peer, message, _ in
|
||||
if let strongSelf = self {
|
||||
|
@ -20,9 +20,9 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||
let editing: Bool
|
||||
let height: ItemListPeerActionItemHeight
|
||||
public let sectionId: ItemListSectionId
|
||||
let action: () -> Void
|
||||
let action: (() -> Void)?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage?, title: String, alwaysPlain: Bool = false, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, editing: Bool, action: @escaping () -> Void) {
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage?, title: String, alwaysPlain: Bool = false, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, editing: Bool, action: (() -> Void)?) {
|
||||
self.presentationData = presentationData
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
@ -79,11 +79,13 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
public var selectable: Bool = true
|
||||
public var selectable: Bool {
|
||||
return self.action != nil
|
||||
}
|
||||
|
||||
public func selected(listView: ListView){
|
||||
listView.clearHighlightAnimated(true)
|
||||
self.action()
|
||||
self.action?()
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,15 +152,15 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
let leftInset: CGFloat
|
||||
let vertcalInset: CGFloat
|
||||
let verticalInset: CGFloat
|
||||
let verticalOffset: CGFloat
|
||||
switch item.height {
|
||||
case .generic:
|
||||
vertcalInset = 11.0
|
||||
verticalInset = 11.0
|
||||
verticalOffset = 0.0
|
||||
leftInset = 59.0 + params.leftInset
|
||||
case .peerList:
|
||||
vertcalInset = 14.0
|
||||
verticalInset = 14.0
|
||||
verticalOffset = 0.0
|
||||
leftInset = 65.0 + params.leftInset
|
||||
}
|
||||
@ -170,7 +172,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + vertcalInset * 2.0)
|
||||
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
@ -247,7 +249,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: vertcalInset + verticalOffset), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size))
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||
}
|
||||
|
@ -16,6 +16,195 @@ import PeerPresenceStatusManager
|
||||
import ContextUI
|
||||
import AccountContext
|
||||
|
||||
private final class ShimmerEffectNode: ASDisplayNode {
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private let imageNodeContainer: ASDisplayNode
|
||||
private let imageNode: ASImageNode
|
||||
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
private var isCurrentlyInHierarchy = false
|
||||
private var shouldBeAnimating = false
|
||||
|
||||
override init() {
|
||||
self.imageNodeContainer = ASDisplayNode()
|
||||
self.imageNodeContainer.isLayerBacked = true
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.imageNode.displayWithoutProcessing = true
|
||||
self.imageNode.contentMode = .scaleToFill
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.imageNodeContainer.addSubnode(self.imageNode)
|
||||
self.addSubnode(self.imageNodeContainer)
|
||||
}
|
||||
|
||||
override func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = true
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = false
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
||||
return
|
||||
}
|
||||
self.currentBackgroundColor = backgroundColor
|
||||
self.currentForegroundColor = foregroundColor
|
||||
|
||||
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foregroundColor.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||
return
|
||||
}
|
||||
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
||||
let frameUpdated = self.absoluteLocation?.0 != rect
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
|
||||
if sizeUpdated {
|
||||
if self.shouldBeAnimating {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
self.addImageAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
if frameUpdated {
|
||||
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAnimation() {
|
||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
||||
if shouldBeAnimating != self.shouldBeAnimating {
|
||||
self.shouldBeAnimating = shouldBeAnimating
|
||||
if shouldBeAnimating {
|
||||
self.addImageAnimation()
|
||||
} else {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addImageAnimation() {
|
||||
guard let containerSize = self.absoluteLocation?.1 else {
|
||||
return
|
||||
}
|
||||
let gradientHeight: CGFloat = 250.0
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoadingShimmerNode: ASDisplayNode {
|
||||
enum Shape: Equatable {
|
||||
case circle(CGRect)
|
||||
case roundedRectLine(startPoint: CGPoint, width: CGFloat, diameter: CGFloat)
|
||||
}
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let effectNode: ShimmerEffectNode
|
||||
private let foregroundNode: ASImageNode
|
||||
|
||||
private var currentShapes: [Shape] = []
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private var currentShimmeringColor: UIColor?
|
||||
private var currentSize = CGSize()
|
||||
|
||||
override init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
|
||||
self.effectNode = ShimmerEffectNode()
|
||||
|
||||
self.foregroundNode = ASImageNode()
|
||||
self.foregroundNode.displaysAsynchronously = false
|
||||
self.foregroundNode.displayWithoutProcessing = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.effectNode)
|
||||
self.addSubnode(self.foregroundNode)
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
|
||||
func update(backgroundColor: UIColor, foregroundColor: UIColor, shimmeringColor: UIColor, shapes: [Shape], size: CGSize) {
|
||||
if self.currentShapes == shapes, let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor), self.currentSize == size {
|
||||
return
|
||||
}
|
||||
|
||||
self.currentBackgroundColor = backgroundColor
|
||||
self.currentForegroundColor = foregroundColor
|
||||
self.currentShimmeringColor = shimmeringColor
|
||||
self.currentShapes = shapes
|
||||
self.currentSize = size
|
||||
|
||||
self.backgroundNode.backgroundColor = foregroundColor
|
||||
|
||||
self.effectNode.update(backgroundColor: foregroundColor, foregroundColor: shimmeringColor)
|
||||
|
||||
self.foregroundNode.image = generateImage(size, rotatedContext: { size, context in
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
for shape in shapes {
|
||||
switch shape {
|
||||
case let .circle(frame):
|
||||
context.fillEllipse(in: frame)
|
||||
case let .roundedRectLine(startPoint, width, diameter):
|
||||
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.foregroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ItemListPeerItemEditing: Equatable {
|
||||
public var editable: Bool
|
||||
public var editing: Bool
|
||||
@ -107,6 +296,14 @@ public struct ItemListPeerItemRevealOptions {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ItemListPeerItemShimmering {
|
||||
public var alternationIndex: Int
|
||||
|
||||
public init(alternationIndex: Int) {
|
||||
self.alternationIndex = alternationIndex
|
||||
}
|
||||
}
|
||||
|
||||
public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||
let presentationData: ItemListPresentationData
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
@ -135,8 +332,10 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||
let hasTopGroupInset: Bool
|
||||
let noInsets: Bool
|
||||
public let tag: ItemListItemTag?
|
||||
let header: ListViewItemHeader?
|
||||
let shimmering: ItemListPeerItemShimmering?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, tag: ItemListItemTag? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -164,12 +363,14 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||
self.hasTopGroupInset = hasTopGroupInset
|
||||
self.noInsets = noInsets
|
||||
self.tag = tag
|
||||
self.header = header
|
||||
self.shimmering = shimmering
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = ItemListPeerItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), self.getHeaderAtTop(top: previousItem, bottom: nextItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
@ -182,6 +383,19 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
private func getHeaderAtTop(top: ListViewItem?, bottom: ListViewItem?) -> Bool {
|
||||
var headerAtTop = false
|
||||
if let top = top as? ItemListPeerItem, top.header != nil {
|
||||
if top.header?.id != self.header?.id {
|
||||
headerAtTop = true
|
||||
}
|
||||
} else if self.header != nil {
|
||||
headerAtTop = true
|
||||
}
|
||||
|
||||
return headerAtTop
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListPeerItemNode {
|
||||
@ -193,7 +407,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), self.getHeaderAtTop(top: previousItem, bottom: nextItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply(false, animated)
|
||||
@ -232,8 +446,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
private var switchNode: SwitchNode?
|
||||
private var checkNode: ASImageNode?
|
||||
|
||||
private var shimmerNode: LoadingShimmerNode?
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
|
||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||
private var layoutParams: (ItemListPeerItem, ListViewItemLayoutParams, ItemListNeighbors)?
|
||||
private var layoutParams: (ItemListPeerItem, ListViewItemLayoutParams, ItemListNeighbors, Bool)?
|
||||
|
||||
private var editableControlNode: ItemListEditableControlNode?
|
||||
|
||||
@ -303,7 +520,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
|
||||
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
|
||||
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2)
|
||||
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2, layoutParams.3)
|
||||
apply(false, true)
|
||||
}
|
||||
})
|
||||
@ -317,7 +534,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
}
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListPeerItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
||||
public func asyncLayout() -> (_ item: ItemListPeerItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ headerAtTop: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
@ -334,7 +551,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
|
||||
let currentHasBadge = self.labelBadgeNode.image != nil
|
||||
|
||||
return { item, params, neighbors in
|
||||
return { item, params, neighbors, headerAtTop in
|
||||
var updateArrowImage: UIImage?
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
@ -579,8 +796,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
insets.top = 0.0
|
||||
insets.bottom = 0.0
|
||||
}
|
||||
if headerAtTop, let header = item.header {
|
||||
insets.top += header.height + 18.0
|
||||
}
|
||||
|
||||
let titleSpacing: CGFloat = 1.0
|
||||
let titleSpacing: CGFloat = statusLayout.size.height == 0.0 ? 0.0 : 1.0
|
||||
|
||||
let minHeight: CGFloat = titleLayout.size.height + verticalInset * 2.0
|
||||
let rawHeight: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + statusLayout.size.height
|
||||
@ -602,7 +822,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
|
||||
return (layout, { [weak self] synchronousLoad, animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.layoutParams = (item, params, neighbors)
|
||||
strongSelf.layoutParams = (item, params, neighbors, headerAtTop)
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil
|
||||
@ -829,6 +1049,44 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
strongSelf.peerPresenceManager?.reset(presence: presence)
|
||||
}
|
||||
|
||||
if let shimmering = item.shimmering {
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
strongSelf.titleNode.isHidden = true
|
||||
|
||||
let shimmerNode: LoadingShimmerNode
|
||||
if let current = strongSelf.shimmerNode {
|
||||
shimmerNode = current
|
||||
} else {
|
||||
shimmerNode = LoadingShimmerNode()
|
||||
strongSelf.shimmerNode = shimmerNode
|
||||
strongSelf.insertSubnode(shimmerNode, aboveSubnode: strongSelf.backgroundNode)
|
||||
}
|
||||
shimmerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
if let (rect, size) = strongSelf.absoluteLocation {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: size)
|
||||
}
|
||||
var shapes: [LoadingShimmerNode.Shape] = []
|
||||
shapes.append(.circle(strongSelf.avatarNode.frame))
|
||||
let possibleLines: [[CGFloat]] = [
|
||||
[50.0, 40.0],
|
||||
[70.0, 45.0]
|
||||
]
|
||||
let titleFrame = strongSelf.titleNode.frame
|
||||
let lineDiameter: CGFloat = 10.0
|
||||
var lineStart = titleFrame.minX
|
||||
for lineWidth in possibleLines[shimmering.alternationIndex % possibleLines.count] {
|
||||
shapes.append(.roundedRectLine(startPoint: CGPoint(x: lineStart, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: lineWidth, diameter: lineDiameter))
|
||||
lineStart += lineWidth + lineDiameter
|
||||
}
|
||||
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize)
|
||||
} else if let shimmerNode = strongSelf.shimmerNode {
|
||||
strongSelf.avatarNode.isHidden = false
|
||||
strongSelf.titleNode.isHidden = false
|
||||
|
||||
strongSelf.shimmerNode = nil
|
||||
shimmerNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||
|
||||
strongSelf.setRevealOptions((left: [], right: peerRevealOptions))
|
||||
@ -940,13 +1198,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
}
|
||||
|
||||
override public func revealOptionsInteractivelyOpened() {
|
||||
if let (item, _, _) = self.layoutParams {
|
||||
if let (item, _, _, _) = self.layoutParams {
|
||||
item.setPeerIdWithRevealedOptions(item.peer.id, nil)
|
||||
}
|
||||
}
|
||||
|
||||
override public func revealOptionsInteractivelyClosed() {
|
||||
if let (item, _, _) = self.layoutParams {
|
||||
if let (item, _, _, _) = self.layoutParams {
|
||||
item.setPeerIdWithRevealedOptions(nil, item.peer.id)
|
||||
}
|
||||
}
|
||||
@ -955,7 +1213,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
self.setRevealOptionsOpened(false, animated: true)
|
||||
self.revealOptionsInteractivelyClosed()
|
||||
|
||||
if let (item, _, _) = self.layoutParams {
|
||||
if let (item, _, _, _) = self.layoutParams {
|
||||
if let revealOptions = item.revealOptions {
|
||||
if option.key >= 0 && option.key < Int32(revealOptions.options.count) {
|
||||
revealOptions.options[Int(option.key)].action()
|
||||
@ -967,8 +1225,205 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
}
|
||||
|
||||
private func toggleUpdated(_ value: Bool) {
|
||||
if let (item, _, _) = self.layoutParams {
|
||||
if let (item, _, _, _) = self.layoutParams {
|
||||
item.toggleUpdated?(value)
|
||||
}
|
||||
}
|
||||
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
return self.layoutParams?.0.header
|
||||
}
|
||||
|
||||
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
var rect = rect
|
||||
rect.origin.y += self.insets.top
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
if let shimmerNode = self.shimmerNode {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class ItemListPeerItemHeader: ListViewItemHeader {
|
||||
public let id: Int64
|
||||
public let text: String
|
||||
public let additionalText: String
|
||||
public let stickDirection: ListViewItemHeaderStickDirection = .topEdge
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let actionTitle: String?
|
||||
public let action: (() -> Void)?
|
||||
|
||||
public let height: CGFloat = 28.0
|
||||
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, text: String, additionalText: String, actionTitle: String? = nil, id: Int64, action: (() -> Void)? = nil) {
|
||||
self.text = text
|
||||
self.additionalText = additionalText
|
||||
self.id = id
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.actionTitle = actionTitle
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public func node() -> ListViewItemHeaderNode {
|
||||
return ItemListPeerItemHeaderNode(theme: self.theme, strings: self.strings, text: self.text, additionalText: self.additionalText, actionTitle: self.actionTitle, action: self.action)
|
||||
}
|
||||
|
||||
public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
|
||||
(node as? ItemListPeerItemHeaderNode)?.update(text: self.text, additionalText: self.additionalText, actionTitle: self.actionTitle, action: self.action)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ItemListPeerItemHeaderNode: ListViewItemHeaderNode, ItemListHeaderItemNode {
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
private var actionTitle: String?
|
||||
private var action: (() -> Void)?
|
||||
|
||||
private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)?
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let snappedBackgroundNode: ASDisplayNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let additionalTextNode: ImmediateTextNode
|
||||
private let actionTextNode: ImmediateTextNode
|
||||
private let actionButton: HighlightableButtonNode
|
||||
|
||||
private var stickDistanceFactor: CGFloat?
|
||||
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, text: String, additionalText: String, actionTitle: String?, action: (() -> Void)?) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.actionTitle = actionTitle
|
||||
self.action = action
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = theme.list.blocksBackgroundColor
|
||||
|
||||
self.snappedBackgroundNode = ASDisplayNode()
|
||||
self.snappedBackgroundNode.backgroundColor = theme.rootController.navigationBar.backgroundColor
|
||||
self.snappedBackgroundNode.alpha = 0.0
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.backgroundColor = theme.list.itemBlocksSeparatorColor
|
||||
self.separatorNode.alpha = 0.0
|
||||
|
||||
let titleFont = Font.regular(13.0)
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
|
||||
|
||||
self.additionalTextNode = ImmediateTextNode()
|
||||
self.additionalTextNode.displaysAsynchronously = false
|
||||
self.additionalTextNode.maximumNumberOfLines = 1
|
||||
self.additionalTextNode.attributedText = NSAttributedString(string: additionalText, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
|
||||
|
||||
self.actionTextNode = ImmediateTextNode()
|
||||
self.actionTextNode.displaysAsynchronously = false
|
||||
self.actionTextNode.maximumNumberOfLines = 1
|
||||
self.actionTextNode.attributedText = NSAttributedString(string: actionTitle ?? "", font: titleFont, textColor: action == nil ? theme.list.sectionHeaderTextColor : theme.list.itemAccentColor)
|
||||
|
||||
self.actionButton = HighlightableButtonNode()
|
||||
self.actionButton.isUserInteractionEnabled = self.action != nil
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.snappedBackgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.additionalTextNode)
|
||||
self.addSubnode(self.actionTextNode)
|
||||
self.addSubnode(self.actionButton)
|
||||
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.actionButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.actionTextNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.actionTextNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.actionTextNode.alpha = 1.0
|
||||
strongSelf.actionTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func actionButtonPressed() {
|
||||
self.action?()
|
||||
}
|
||||
|
||||
public func updateTheme(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode.backgroundColor = theme.list.blocksBackgroundColor
|
||||
self.snappedBackgroundNode.backgroundColor = theme.rootController.navigationBar.backgroundColor
|
||||
self.separatorNode.backgroundColor = theme.list.itemBlocksSeparatorColor
|
||||
|
||||
let titleFont = Font.regular(13.0)
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: titleFont, textColor: theme.list.sectionHeaderTextColor)
|
||||
self.additionalTextNode.attributedText = NSAttributedString(string: self.additionalTextNode.attributedText?.string ?? "", font: titleFont, textColor: theme.list.sectionHeaderTextColor)
|
||||
self.actionTextNode.attributedText = NSAttributedString(string: self.actionTextNode.attributedText?.string ?? "", font: titleFont, textColor: theme.list.sectionHeaderTextColor)
|
||||
}
|
||||
|
||||
public func update(text: String, additionalText: String, actionTitle: String?, action: (() -> Void)?) {
|
||||
self.actionTitle = actionTitle
|
||||
self.action = action
|
||||
let titleFont = Font.regular(13.0)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
|
||||
self.additionalTextNode.attributedText = NSAttributedString(string: additionalText, font: titleFont, textColor: theme.list.sectionHeaderTextColor)
|
||||
self.actionTextNode.attributedText = NSAttributedString(string: actionTitle ?? "", font: titleFont, textColor: action == nil ? theme.list.sectionHeaderTextColor : theme.list.itemAccentColor)
|
||||
self.actionButton.isUserInteractionEnabled = self.action != nil
|
||||
if let (size, leftInset, rightInset) = self.validLayout {
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
|
||||
}
|
||||
}
|
||||
|
||||
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
|
||||
self.validLayout = (size, leftInset, rightInset)
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.snappedBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel))
|
||||
|
||||
let sideInset: CGFloat = 15.0 + leftInset
|
||||
|
||||
let actionTextSize = self.actionTextNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height))
|
||||
let additionalTextSize = self.additionalTextNode.updateLayout(CGSize(width: size.width - sideInset * 2.0 - actionTextSize.width - 8.0, height: size.height))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: max(1.0, size.width - sideInset * 2.0 - actionTextSize.width - 8.0 - additionalTextSize.width), height: size.height))
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 7.0), size: textSize)
|
||||
self.textNode.frame = textFrame
|
||||
self.additionalTextNode.frame = CGRect(origin: CGPoint(x: textFrame.maxX, y: 7.0), size: additionalTextSize)
|
||||
self.actionTextNode.frame = CGRect(origin: CGPoint(x: size.width - sideInset - actionTextSize.width, y: 7.0), size: actionTextSize)
|
||||
self.actionButton.frame = CGRect(origin: CGPoint(x: size.width - sideInset - actionTextSize.width, y: 0.0), size: CGSize(width: actionTextSize.width, height: size.height))
|
||||
}
|
||||
|
||||
override public func animateRemoved(duration: Double) {
|
||||
self.alpha = 0.0
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: true)
|
||||
}
|
||||
|
||||
override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.stickDistanceFactor == factor {
|
||||
return
|
||||
}
|
||||
self.stickDistanceFactor = factor
|
||||
if let (size, leftInset, _) = self.validLayout {
|
||||
if leftInset.isZero {
|
||||
transition.updateAlpha(node: self.separatorNode, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.snappedBackgroundNode, alpha: (1.0 - factor) * 0.0 + factor * 1.0)
|
||||
} else {
|
||||
let distance = factor * size.height
|
||||
let alpha = abs(distance) / 16.0
|
||||
transition.updateAlpha(node: self.separatorNode, alpha: max(0.0, min(1.0, alpha)))
|
||||
transition.updateAlpha(node: self.snappedBackgroundNode, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||
|
||||
var editingOffset: CGFloat = 0.0
|
||||
var reorderInset: CGFloat = 0.0
|
||||
@ -485,7 +485,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
|
||||
if strongSelf.reorderControlNode == nil {
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false)
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
|
||||
strongSelf.reorderControlNode = reorderControlNode
|
||||
strongSelf.addSubnode(reorderControlNode)
|
||||
reorderControlNode.alpha = 0.0
|
||||
|
@ -56,6 +56,7 @@ public struct ItemListBackButton: Equatable {
|
||||
|
||||
public enum ItemListControllerTitle: Equatable {
|
||||
case text(String)
|
||||
case textWithSubtitle(String, String)
|
||||
case sectionControl([String], Int)
|
||||
}
|
||||
|
||||
@ -197,12 +198,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
|
||||
public var willScrollToTop: (() -> Void)?
|
||||
|
||||
public func setReorderEntry<T: ItemListNodeEntry>(_ f: @escaping (Int, Int, [T]) -> Void) {
|
||||
public func setReorderEntry<T: ItemListNodeEntry>(_ f: @escaping (Int, Int, [T]) -> Signal<Bool, NoError>) {
|
||||
self.reorderEntry = { a, b, list in
|
||||
f(a, b, list.map { $0 as! T })
|
||||
return f(a, b, list.map { $0 as! T })
|
||||
}
|
||||
}
|
||||
private var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Void)? {
|
||||
private var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Signal<Bool, NoError>)? {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
(self.displayNode as! ItemListControllerNode).reorderEntry = self.reorderEntry
|
||||
@ -287,6 +288,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
strongSelf.title = text
|
||||
strongSelf.navigationItem.titleView = nil
|
||||
strongSelf.segmentedTitleView = nil
|
||||
case let .textWithSubtitle(title, subtitle):
|
||||
strongSelf.title = ""
|
||||
strongSelf.navigationItem.titleView = ItemListTextWithSubtitleTitleView(theme: controllerState.presentationData.theme, title: title, subtitle: subtitle)
|
||||
strongSelf.segmentedTitleView = nil
|
||||
case let .sectionControl(sections, index):
|
||||
strongSelf.title = ""
|
||||
if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections {
|
||||
@ -417,6 +422,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
|
||||
strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme
|
||||
|
||||
if let titleView = strongSelf.navigationItem.titleView as? ItemListTextWithSubtitleTitleView {
|
||||
titleView.updateTheme(theme: controllerState.presentationData.theme)
|
||||
}
|
||||
|
||||
var items = strongSelf.navigationItem.rightBarButtonItems ?? []
|
||||
for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count {
|
||||
if case .activity = strongSelf.rightNavigationButtonTitleAndStyle[i].1 {
|
||||
@ -517,6 +526,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
self.didDisappear?(animated)
|
||||
}
|
||||
|
||||
public var listInsets: UIEdgeInsets {
|
||||
return (self.displayNode as! ItemListControllerNode).listNode.insets
|
||||
}
|
||||
|
||||
public func frameForItemNode(_ predicate: (ListViewItemNode) -> Bool) -> CGRect? {
|
||||
var result: CGRect?
|
||||
(self.displayNode as! ItemListControllerNode).listNode.forEachItemNode { itemNode in
|
||||
@ -598,3 +611,68 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
})]
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitleView {
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let subtitleNode: ImmediateTextNode
|
||||
|
||||
private var validLayout: (CGSize, CGRect)?
|
||||
|
||||
init(theme: PresentationTheme, title: String, subtitle: String) {
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
self.titleNode.isOpaque = false
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
|
||||
|
||||
self.subtitleNode = ImmediateTextNode()
|
||||
self.subtitleNode.displaysAsynchronously = false
|
||||
self.subtitleNode.maximumNumberOfLines = 1
|
||||
self.subtitleNode.isOpaque = false
|
||||
|
||||
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateTheme(theme: PresentationTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
|
||||
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
|
||||
if let (size, clearBounds) = self.validLayout {
|
||||
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
if let (size, clearBounds) = self.validLayout {
|
||||
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, clearBounds)
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(size)
|
||||
let subtitleSize = self.subtitleNode.updateLayout(size)
|
||||
let spacing: CGFloat = 0.0
|
||||
let contentHeight = titleSize.height + spacing + subtitleSize.height
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - contentHeight) / 2.0)), size: titleSize)
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + spacing), size: subtitleSize)
|
||||
|
||||
self.titleNode.frame = titleFrame
|
||||
self.subtitleNode.frame = subtitleFrame
|
||||
}
|
||||
|
||||
func animateLayoutTransition() {
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,10 @@ import SyncCore
|
||||
import TelegramPresentationData
|
||||
import MergeLists
|
||||
|
||||
public protocol ItemListHeaderItemNode: class {
|
||||
func updateTheme(theme: PresentationTheme)
|
||||
}
|
||||
|
||||
public typealias ItemListSectionId = Int32
|
||||
|
||||
public protocol ItemListNodeAnyEntry {
|
||||
@ -217,7 +221,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
public var contentOffsetChanged: ((ListViewVisibleContentOffset, Bool) -> Void)?
|
||||
public var contentScrollingEnded: ((ListView) -> Bool)?
|
||||
public var searchActivated: ((Bool) -> Void)?
|
||||
public var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Void)?
|
||||
public var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Signal<Bool, NoError>)?
|
||||
public var reorderCompleted: (([ItemListNodeAnyEntry]) -> Void)?
|
||||
public var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
@ -269,7 +273,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.listNode.reorderItem = { [weak self] fromIndex, toIndex, opaqueTransactionState in
|
||||
if let strongSelf = self, let reorderEntry = strongSelf.reorderEntry, let mergedEntries = (opaqueTransactionState as? ItemListNodeOpaqueState)?.mergedEntries {
|
||||
if fromIndex >= 0 && fromIndex < mergedEntries.count && toIndex >= 0 && toIndex < mergedEntries.count {
|
||||
reorderEntry(fromIndex, toIndex, mergedEntries)
|
||||
return reorderEntry(fromIndex, toIndex, mergedEntries)
|
||||
}
|
||||
}
|
||||
return .single(false)
|
||||
@ -467,6 +471,12 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.rightOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.forEachItemHeaderNode({ itemHeaderNode in
|
||||
if let itemHeaderNode = itemHeaderNode as? ItemListHeaderItemNode {
|
||||
itemHeaderNode.updateTheme(theme: transition.theme)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if let updateStyle = transition.updateStyle {
|
||||
@ -501,9 +511,12 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
options.insert(.AnimateAlpha)
|
||||
} else if transition.crossfade {
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
options.insert(.AnimateCrossfade)
|
||||
} else {
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
}
|
||||
if self.alwaysSynchronous {
|
||||
|
@ -19,7 +19,7 @@ public final class ItemListEditableReorderControlNode: ASDisplayNode {
|
||||
self.addSubnode(self.iconNode)
|
||||
}
|
||||
|
||||
public static func asyncLayout(_ node: ItemListEditableReorderControlNode?) -> (_ theme: PresentationTheme) -> (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode) {
|
||||
public static func asyncLayout(_ node: ItemListEditableReorderControlNode?) -> (_ theme: PresentationTheme) -> (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode) {
|
||||
return { theme in
|
||||
let image = PresentationResourcesItemList.itemListReorderIndicatorIcon(theme)
|
||||
|
||||
@ -31,9 +31,9 @@ public final class ItemListEditableReorderControlNode: ASDisplayNode {
|
||||
}
|
||||
resultNode.iconNode.image = image
|
||||
|
||||
return (40.0, { height, offsetForLabel in
|
||||
return (40.0, { height, offsetForLabel, transition in
|
||||
if let image = image {
|
||||
resultNode.iconNode.frame = CGRect(origin: CGPoint(x: 7.0, y: floor((height - image.size.height) / 2.0) - (offsetForLabel ? 6.0 : 0.0)), size: image.size)
|
||||
transition.updateFrame(node: resultNode.iconNode, frame: CGRect(origin: CGPoint(x: 7.0, y: floor((height - image.size.height) / 2.0) - (offsetForLabel ? 6.0 : 0.0)), size: image.size))
|
||||
}
|
||||
return resultNode
|
||||
})
|
||||
|
@ -82,6 +82,10 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
|
||||
super.init(layerBacked: layerBacked, dynamicBounce: dynamicBounce, rotated: rotated, seeThrough: seeThrough)
|
||||
}
|
||||
|
||||
open var controlsContainer: ASDisplayNode {
|
||||
return self
|
||||
}
|
||||
|
||||
override open func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
@ -310,7 +314,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
|
||||
revealNode.updateRevealOffset(offset: 0.0, sideInset: leftInset, transition: .immediate)
|
||||
}
|
||||
|
||||
self.addSubnode(revealNode)
|
||||
self.controlsContainer.addSubnode(revealNode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,7 +336,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
|
||||
revealNode.updateRevealOffset(offset: 0.0, sideInset: -rightInset, transition: .immediate)
|
||||
}
|
||||
|
||||
self.addSubnode(revealNode)
|
||||
self.controlsContainer.addSubnode(revealNode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -492,4 +496,8 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
|
||||
}
|
||||
self.hapticFeedback?.impact(.medium)
|
||||
}
|
||||
|
||||
override open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
||||
super.animateFrameTransition(progress, currentValue)
|
||||
}
|
||||
}
|
||||
|
@ -325,8 +325,6 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
@ -336,7 +334,11 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
||||
|
||||
strongSelf.textNode.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
||||
|
||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
|
||||
if strongSelf.animationForKey("apparentHeight") == nil {
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
|
||||
}
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - 16.0 - rightInset, height: textLayout.size.height + 1.0))
|
||||
|
||||
let _ = limitTextApply()
|
||||
@ -394,6 +396,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
||||
let textBottomInset: CGFloat = 11.0
|
||||
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: self.bottomStripeNode.frame.minX, y: contentSize.height), size: CGSize(width: self.bottomStripeNode.frame.size.width, height: separatorHeight))
|
||||
|
||||
self.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: max(0.0, params.width - leftInset - params.rightInset), height: max(0.0, contentSize.height - textTopInset - textBottomInset)))
|
||||
|
@ -9,6 +9,7 @@ import Markdown
|
||||
|
||||
public enum ItemListTextItemText {
|
||||
case plain(String)
|
||||
case large(String)
|
||||
case markdown(String)
|
||||
}
|
||||
|
||||
@ -23,13 +24,15 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
|
||||
let linkAction: ((ItemListTextItemLinkAction) -> Void)?
|
||||
let style: ItemListStyle
|
||||
public let isAlwaysPlain: Bool = true
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks) {
|
||||
public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks, tag: ItemListItemTag? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.text = text
|
||||
self.sectionId = sectionId
|
||||
self.linkAction = linkAction
|
||||
self.style = style
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
@ -69,12 +72,16 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemListTextItemNode: ListViewItemNode {
|
||||
public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let titleNode: TextNode
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: ItemListTextItem?
|
||||
|
||||
public var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
@ -105,15 +112,19 @@ public class ItemListTextItemNode: ListViewItemNode {
|
||||
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||
let verticalInset: CGFloat = 7.0
|
||||
let topInset: CGFloat = 7.0
|
||||
var bottomInset: CGFloat = 7.0
|
||||
|
||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
|
||||
let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize))
|
||||
let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize)
|
||||
|
||||
let attributedText: NSAttributedString
|
||||
switch item.text {
|
||||
case let .plain(text):
|
||||
attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.freeTextColor)
|
||||
case let .large(text):
|
||||
attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
case let .markdown(text):
|
||||
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
@ -123,8 +134,12 @@ public class ItemListTextItemNode: ListViewItemNode {
|
||||
|
||||
let contentSize: CGSize
|
||||
|
||||
contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset + verticalInset)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
var insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
if case .large = item.text {
|
||||
insets.top = 14.0
|
||||
bottomInset = -6.0
|
||||
}
|
||||
contentSize = CGSize(width: params.width, height: titleLayout.size.height + topInset + bottomInset)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
@ -139,7 +154,7 @@ public class ItemListTextItemNode: ListViewItemNode {
|
||||
|
||||
let _ = titleApply()
|
||||
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -24,19 +24,14 @@
|
||||
NSInteger _number;
|
||||
|
||||
UIColor *_checkColor;
|
||||
|
||||
CGAffineTransform TGCheckButtonDefaultTransform;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGCheckButtonView
|
||||
|
||||
static NSMutableDictionary *backgroundImages;
|
||||
static NSMutableDictionary *fillImages;
|
||||
static CGAffineTransform TGCheckButtonDefaultTransform;
|
||||
|
||||
+ (void)resetCache
|
||||
{
|
||||
[backgroundImages removeAllObjects];
|
||||
[fillImages removeAllObjects];
|
||||
+ (void)resetCache {
|
||||
}
|
||||
|
||||
- (instancetype)initWithStyle:(TGCheckButtonStyle)style {
|
||||
@ -55,15 +50,12 @@ static CGAffineTransform TGCheckButtonDefaultTransform;
|
||||
self = [super initWithFrame:CGRectMake(0, 0, size.width, size.height)];
|
||||
if (self != nil)
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static CGFloat screenScale = 2.0f;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
TGCheckButtonDefaultTransform = CGAffineTransformMakeRotation(-M_PI_4);
|
||||
backgroundImages = [[NSMutableDictionary alloc] init];
|
||||
fillImages = [[NSMutableDictionary alloc] init];
|
||||
screenScale = [UIScreen mainScreen].scale;
|
||||
});
|
||||
CGFloat screenScale = 2.0f;
|
||||
|
||||
TGCheckButtonDefaultTransform = CGAffineTransformMakeRotation(-M_PI_4);
|
||||
NSMutableDictionary *backgroundImages = [[NSMutableDictionary alloc] init];
|
||||
NSMutableDictionary *fillImages = [[NSMutableDictionary alloc] init];
|
||||
screenScale = [UIScreen mainScreen].scale;
|
||||
|
||||
int32_t hex = 0x29c519;
|
||||
UIColor *greenColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f];
|
||||
|
@ -18,7 +18,7 @@
|
||||
- (void)appendVideoPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime;
|
||||
- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer;
|
||||
|
||||
- (void)finishRecording;
|
||||
- (void)finishRecording:(void(^)())completed;
|
||||
|
||||
- (NSTimeInterval)videoDuration;
|
||||
|
||||
|
@ -105,7 +105,7 @@ typedef enum {
|
||||
if (_status != TGMovieRecorderStatusIdle)
|
||||
return;
|
||||
|
||||
[self transitionToStatus:TGMovieRecorderStatusPreparingToRecord error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusPreparingToRecord error:nil completed:nil];
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^
|
||||
@ -138,9 +138,9 @@ typedef enum {
|
||||
@synchronized (self)
|
||||
{
|
||||
if (error || !succeed)
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:nil];
|
||||
else
|
||||
[self transitionToStatus:TGMovieRecorderStatusRecording error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusRecording error:nil completed:nil];
|
||||
}
|
||||
}
|
||||
} );
|
||||
@ -169,8 +169,9 @@ typedef enum {
|
||||
[self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
|
||||
}
|
||||
|
||||
- (void)finishRecording
|
||||
- (void)finishRecording:(void(^)())completed
|
||||
{
|
||||
printf("finishRecording %d\n", _status);
|
||||
@synchronized (self)
|
||||
{
|
||||
bool shouldFinishRecording = false;
|
||||
@ -190,9 +191,10 @@ typedef enum {
|
||||
}
|
||||
|
||||
if (shouldFinishRecording)
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingWaiting error:nil];
|
||||
else
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingWaiting error:nil completed:completed];
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(_writingQueue, ^
|
||||
@ -201,10 +203,14 @@ typedef enum {
|
||||
{
|
||||
@synchronized (self)
|
||||
{
|
||||
if (_status != TGMovieRecorderStatusFinishingWaiting)
|
||||
if (_status != TGMovieRecorderStatusFinishingWaiting) {
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingCommiting error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingCommiting error:nil completed:nil];
|
||||
}
|
||||
|
||||
[_assetWriter finishWritingWithCompletionHandler:^
|
||||
@ -213,9 +219,9 @@ typedef enum {
|
||||
{
|
||||
NSError *error = _assetWriter.error;
|
||||
if (error)
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:completed];
|
||||
else
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinished error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinished error:nil completed:completed];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@ -340,7 +346,7 @@ typedef enum {
|
||||
NSError *error = _assetWriter.error;
|
||||
@synchronized (self)
|
||||
{
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -349,8 +355,10 @@ typedef enum {
|
||||
});
|
||||
}
|
||||
|
||||
- (void)transitionToStatus:(TGMovieRecorderStatus)newStatus error:(NSError *)error
|
||||
- (void)transitionToStatus:(TGMovieRecorderStatus)newStatus error:(NSError *)error completed:(void(^)())completed
|
||||
{
|
||||
printf("recorder transitionToStatus %d\n", newStatus);
|
||||
|
||||
bool shouldNotifyDelegate = false;
|
||||
|
||||
if (newStatus != _status)
|
||||
@ -389,6 +397,7 @@ typedef enum {
|
||||
break;
|
||||
|
||||
case TGMovieRecorderStatusFinished:
|
||||
printf("TGMovieRecorderStatusFinished _delegate == nil = %d\n", (int)(_delegate == nil));
|
||||
[_delegate movieRecorderDidFinishRecording:self];
|
||||
break;
|
||||
|
||||
@ -399,9 +408,16 @@ typedef enum {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)setupAssetWriterAudioInputWithSourceFormatDescription:(CMFormatDescriptionRef)audioFormatDescription settings:(NSDictionary *)audioSettings
|
||||
|
@ -21,7 +21,7 @@
|
||||
- (void)stopRunning;
|
||||
|
||||
- (void)startRecording:(NSURL *)url preset:(TGMediaVideoConversionPreset)preset liveUpload:(bool)liveUpload;
|
||||
- (void)stopRecording;
|
||||
- (void)stopRecording:(void (^)())completed;
|
||||
|
||||
- (CGAffineTransform)transformForOrientation:(AVCaptureVideoOrientation)orientation;
|
||||
|
||||
|
@ -111,6 +111,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
printf("Camera pipeline dealloc\n");
|
||||
[self destroyCaptureSession];
|
||||
}
|
||||
|
||||
@ -134,7 +135,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
{
|
||||
_running = false;
|
||||
|
||||
[self stopRecording];
|
||||
[self stopRecording:^{}];
|
||||
|
||||
[_captureSession stopRunning];
|
||||
[self captureSessionDidStopRunning];
|
||||
@ -285,7 +286,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)captureSessionDidStopRunning
|
||||
{
|
||||
[self stopRecording];
|
||||
[self stopRecording:^{}];
|
||||
[self destroyVideoPipeline];
|
||||
}
|
||||
|
||||
@ -684,20 +685,29 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
[recorder prepareToRecord];
|
||||
}
|
||||
|
||||
- (void)stopRecording
|
||||
- (void)stopRecording:(void (^)())completed
|
||||
{
|
||||
[[TGVideoCameraPipeline cameraQueue] dispatch:^
|
||||
{
|
||||
@synchronized (self)
|
||||
{
|
||||
if (_recordingStatus != TGVideoCameraRecordingStatusRecording)
|
||||
if (_recordingStatus != TGVideoCameraRecordingStatusRecording) {
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self transitionToRecordingStatus:TGVideoCameraRecordingStatusStoppingRecording error:nil];
|
||||
}
|
||||
|
||||
_resultDuration = _recorder.videoDuration;
|
||||
[_recorder finishRecording];
|
||||
[_recorder finishRecording:^{
|
||||
__unused __auto_type description = [self description];
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@ -734,6 +744,8 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)movieRecorderDidFinishRecording:(TGVideoCameraMovieRecorder *)__unused recorder
|
||||
{
|
||||
printf("movieRecorderDidFinishRecording\n");
|
||||
|
||||
@synchronized (self)
|
||||
{
|
||||
if (_recordingStatus != TGVideoCameraRecordingStatusStoppingRecording)
|
||||
@ -750,6 +762,8 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)transitionToRecordingStatus:(TGVideoCameraRecordingStatus)newStatus error:(NSError *)error
|
||||
{
|
||||
printf("transitionToRecordingStatus %d\n", newStatus);
|
||||
|
||||
TGVideoCameraRecordingStatus oldStatus = _recordingStatus;
|
||||
_recordingStatus = newStatus;
|
||||
|
||||
@ -763,12 +777,16 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
__strong id<TGVideoCameraPipelineDelegate> delegate = _delegate;
|
||||
if ((oldStatus == TGVideoCameraRecordingStatusStartingRecording) && (newStatus == TGVideoCameraRecordingStatusRecording))
|
||||
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingDidStart:self]; };
|
||||
delegateCallbackBlock = ^{ [delegate capturePipelineRecordingDidStart:self]; };
|
||||
else if ((oldStatus == TGVideoCameraRecordingStatusRecording) && (newStatus == TGVideoCameraRecordingStatusStoppingRecording))
|
||||
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingWillStop:self]; };
|
||||
delegateCallbackBlock = ^{ [delegate capturePipelineRecordingWillStop:self]; };
|
||||
else if ((oldStatus == TGVideoCameraRecordingStatusStoppingRecording) && (newStatus == TGVideoCameraRecordingStatusIdle))
|
||||
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingDidStop:self duration:_resultDuration liveUploadData:_liveUploadData thumbnailImage:_recordingThumbnail thumbnails:_thumbnails]; };
|
||||
delegateCallbackBlock = ^{
|
||||
printf("transitionToRecordingStatus delegateCallbackBlock _delegate == nil = %d\n", (int)(delegate == nil));
|
||||
[delegate capturePipelineRecordingDidStop:self duration:_resultDuration liveUploadData:_liveUploadData thumbnailImage:_recordingThumbnail thumbnails:_thumbnails];
|
||||
};
|
||||
}
|
||||
|
||||
if (delegateCallbackBlock != nil)
|
||||
|
@ -201,6 +201,7 @@ typedef enum
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
printf("Video controller dealloc\n");
|
||||
[_thumbnailsDisposable dispose];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_didEnterBackgroundObserver];
|
||||
[_activityDisposable dispose];
|
||||
@ -649,9 +650,11 @@ typedef enum
|
||||
return;
|
||||
|
||||
[_activityDisposable dispose];
|
||||
[self stopRecording];
|
||||
|
||||
[self dismiss:false];
|
||||
[self stopRecording:^{
|
||||
TGDispatchOnMainThread(^{
|
||||
[self dismiss:false];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)buttonInteractionUpdate:(CGPoint)value
|
||||
@ -684,7 +687,7 @@ typedef enum
|
||||
_switchButton.userInteractionEnabled = false;
|
||||
|
||||
[_activityDisposable dispose];
|
||||
[self stopRecording];
|
||||
[self stopRecording:^{}];
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -939,9 +942,9 @@ typedef enum
|
||||
[self startRecordingTimer];
|
||||
}
|
||||
|
||||
- (void)stopRecording
|
||||
- (void)stopRecording:(void (^)())completed
|
||||
{
|
||||
[_capturePipeline stopRecording];
|
||||
[_capturePipeline stopRecording:completed];
|
||||
[_buttonHandler ignoreEventsFor:1.0f andDisable:true];
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,13 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
|
||||
})!
|
||||
itemViews.append(locationItem)
|
||||
|
||||
if (peer is TelegramGroup || peer is TelegramChannel) && canSendMessagesToPeer(peer) && canSendPolls {
|
||||
var peerSupportsPolls = false
|
||||
if peer is TelegramGroup || peer is TelegramChannel {
|
||||
peerSupportsPolls = true
|
||||
} else if let user = peer as? TelegramUser, let _ = user.botInfo {
|
||||
peerSupportsPolls = true
|
||||
}
|
||||
if peerSupportsPolls && canSendMessagesToPeer(peer) && canSendPolls {
|
||||
let pollItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
openPoll()
|
||||
|
@ -173,6 +173,9 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
|
||||
}
|
||||
|
||||
public func currentlyInSplitView() -> Bool {
|
||||
if let controller = self.controller as? LegacyController, let validLayout = controller.validLayout {
|
||||
return validLayout.isNonExclusive
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,13 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent
|
||||
}
|
||||
|
||||
public func checkPhotoAuthorizationStatus(for intent: TGPhotoAccessIntent, alertDismissCompletion: (() -> Void)!) -> Bool {
|
||||
if let context = self.context {
|
||||
DeviceAccess.authorizeAccess(to: .mediaLibrary(.send), presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
|
||||
if !value {
|
||||
alertDismissCompletion?()
|
||||
}
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
||||
self.placesBackgroundNode.isUserInteractionEnabled = true
|
||||
|
||||
self.placesButtonNode = HighlightableButtonNode()
|
||||
self.placesButtonNode.setTitle("Places In This Area", with: Font.regular(17.0), with: presentationData.theme.rootController.navigationBar.buttonColor, for: .normal)
|
||||
self.placesButtonNode.setTitle(presentationData.strings.Map_PlacesInThisArea, with: Font.regular(17.0), with: presentationData.theme.rootController.navigationBar.buttonColor, for: .normal)
|
||||
|
||||
self.shadowNode = ASImageNode()
|
||||
self.shadowNode.contentMode = .scaleToFill
|
||||
|
@ -548,7 +548,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
||||
if foundVenues == nil && !state.searchingVenuesAround {
|
||||
displayingPlacesButton = true
|
||||
} else if let previousLocation = foundVenuesLocation {
|
||||
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
if currentLocation.distance(from: previousLocation) > 300 {
|
||||
displayingPlacesButton = true
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], allUpd
|
||||
return (removeIndices, insertItems, updatedIndices)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
//@inlinable
|
||||
public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], isLess: (T, T) -> Bool, isEqual: (T, T) -> Bool, getId: (T) -> AnyHashable, allUpdated: Bool = false) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) {
|
||||
var removeIndices: [Int] = []
|
||||
var insertItems: [(Int, T, Int?)] = []
|
||||
@ -207,6 +207,25 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], isLess
|
||||
}
|
||||
#endif
|
||||
|
||||
var leftStableIds: [AnyHashable] = []
|
||||
var rightStableIds: [AnyHashable] = []
|
||||
for item in leftList {
|
||||
leftStableIds.append(getId(item))
|
||||
}
|
||||
for item in rightList {
|
||||
rightStableIds.append(getId(item))
|
||||
}
|
||||
if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds {
|
||||
/*var i = 0
|
||||
var j = 0
|
||||
while true {
|
||||
if getId(leftList[i]) != getId(rightList[i]) {
|
||||
|
||||
}
|
||||
}*/
|
||||
print("order changed")
|
||||
}
|
||||
|
||||
var currentList = leftList
|
||||
|
||||
var i = 0
|
||||
|
@ -1394,7 +1394,7 @@ static int32_t fixedTimeDifferenceValue = 0;
|
||||
NSArray *currentListeners = [[NSArray alloc] initWithArray:strongSelf->_changeListeners];
|
||||
for (id<MTContextChangeListener> listener in currentListeners) {
|
||||
if ([listener respondsToSelector:@selector(contextLoggedOut:)])
|
||||
[listener contextLoggedOut:self];
|
||||
[listener contextLoggedOut:strongSelf];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
@ -235,7 +235,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
if ((_mtState & MTProtoStateStopped) == 0)
|
||||
{
|
||||
[self setMtState:_mtState | MTProtoStateStopped];
|
||||
|
||||
[_context removeChangeListener:self];
|
||||
if (_transport != nil)
|
||||
{
|
||||
_transport.delegate = nil;
|
||||
@ -2098,6 +2098,9 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
|
||||
int64_t dataMessageId = 0;
|
||||
bool parseError = false;
|
||||
NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId parseError:&parseError];
|
||||
|
||||
|
||||
|
||||
for (MTIncomingMessage *message in parsedMessages) {
|
||||
if ([message.body isKindOfClass:[MTRpcResultMessage class]]) {
|
||||
MTRpcResultMessage *rpcResultMessage = message.body;
|
||||
|
@ -160,7 +160,7 @@ final class SetupTwoStepVerificationContentNode: ASDisplayNode, UITextFieldDeleg
|
||||
|
||||
let minContentHeight = textHeight + inputHeight
|
||||
let contentHeight = min(215.0, max(size.height - insets.top - insets.bottom - 40.0, minContentHeight))
|
||||
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0)
|
||||
let contentOrigin = max(56.0, insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentOrigin), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize))
|
||||
|
@ -698,7 +698,6 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
|
||||
}, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
if case let .enterEmail(enterEmail)? = self.innerState.data.state, case .create = enterEmail.state, enterEmail.email.isEmpty {
|
||||
|
||||
self.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: {
|
||||
continueImpl()
|
||||
})]), nil)
|
||||
|
@ -484,6 +484,22 @@ public final class PostboxEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
public func encodeDataArray(_ value: [Data], forKey key: StaticString) {
|
||||
self.encodeKey(key)
|
||||
var type: Int8 = ValueType.BytesArray.rawValue
|
||||
self.buffer.write(&type, offset: 0, length: 1)
|
||||
var length: Int32 = Int32(value.count)
|
||||
self.buffer.write(&length, offset: 0, length: 4)
|
||||
|
||||
for object in value {
|
||||
var length: Int32 = Int32(object.count)
|
||||
self.buffer.write(&length, offset: 0, length: 4)
|
||||
object.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
self.buffer.write(bytes, offset: 0, length: Int(length))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func encodeObjectDictionary<K, V: PostboxCoding>(_ value: [K : V], forKey key: StaticString) where K: PostboxCoding {
|
||||
self.encodeKey(key)
|
||||
var t: Int8 = ValueType.ObjectDictionary.rawValue
|
||||
@ -1173,6 +1189,31 @@ public final class PostboxDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
public func decodeOptionalDataArrayForKey(_ key: StaticString) -> [Data]? {
|
||||
if PostboxDecoder.positionOnKey(self.buffer.memory, offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .BytesArray) {
|
||||
var length: Int32 = 0
|
||||
memcpy(&length, self.buffer.memory + self.offset, 4)
|
||||
self.offset += 4
|
||||
|
||||
var array: [Data] = []
|
||||
array.reserveCapacity(Int(length))
|
||||
|
||||
var i: Int32 = 0
|
||||
while i < length {
|
||||
var length: Int32 = 0
|
||||
memcpy(&length, self.buffer.memory + self.offset, 4)
|
||||
array.append(Data(bytes: self.buffer.memory.advanced(by: self.offset + 4), count: Int(length)))
|
||||
self.offset += 4 + Int(length)
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
return array
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func decodeObjectArrayForKey<T>(_ key: StaticString) -> [T] where T: PostboxCoding {
|
||||
if PostboxDecoder.positionOnKey(self.buffer.memory, offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .ObjectArray) {
|
||||
var length: Int32 = 0
|
||||
|
@ -37,6 +37,17 @@ final class ItemCacheTable: Table {
|
||||
return key
|
||||
}
|
||||
|
||||
private func lowerBound(collectionId: ItemCacheCollectionId) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 1 + 1)
|
||||
key.setInt8(0, value: ItemCacheSection.items.rawValue)
|
||||
key.setInt8(1, value: collectionId)
|
||||
return key
|
||||
}
|
||||
|
||||
private func upperBound(collectionId: ItemCacheCollectionId) -> ValueBoxKey {
|
||||
return self.lowerBound(collectionId: collectionId).successor
|
||||
}
|
||||
|
||||
private func itemIdToAccessIndexKey(id: ItemCacheEntryId) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 1 + 1 + id.key.length)
|
||||
key.setInt8(0, value: ItemCacheSection.accessIndexToItemId.rawValue)
|
||||
@ -72,6 +83,10 @@ final class ItemCacheTable: Table {
|
||||
self.valueBox.remove(self.table, key: self.itemKey(id: id), secure: false)
|
||||
}
|
||||
|
||||
func removeAll(collectionId: ItemCacheCollectionId) {
|
||||
self.valueBox.removeRange(self.table, start: self.lowerBound(collectionId: collectionId), end: self.upperBound(collectionId: collectionId))
|
||||
}
|
||||
|
||||
override func clearMemoryCache() {
|
||||
|
||||
}
|
||||
|
@ -525,6 +525,10 @@ public final class Message {
|
||||
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
|
||||
}
|
||||
|
||||
public func withUpdatedPeers(_ peers: SimpleDictionary<PeerId, Peer>) -> Message {
|
||||
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
|
||||
}
|
||||
|
||||
public func withUpdatedFlags(_ flags: MessageFlags) -> Message {
|
||||
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ final class MessageHistoryFailedTable: Table {
|
||||
self.updatedMessageIds.remove(id)
|
||||
}
|
||||
|
||||
func get(peerId:PeerId) -> [MessageId] {
|
||||
func get(peerId: PeerId) -> [MessageId] {
|
||||
var ids:[MessageId] = []
|
||||
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), keys: { key in
|
||||
|
||||
@ -72,4 +72,3 @@ final class MessageHistoryFailedTable: Table {
|
||||
self.updatedPeerIds.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2275,6 +2275,42 @@ final class MessageHistoryTable: Table {
|
||||
return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds)
|
||||
}
|
||||
|
||||
func renderMessagePeers(_ message: Message, peerTable: PeerTable) -> Message {
|
||||
var author: Peer?
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
if let authorId = message.author?.id {
|
||||
author = peerTable.get(authorId)
|
||||
}
|
||||
|
||||
if let chatPeer = peerTable.get(message.id.peerId) {
|
||||
peers[chatPeer.id] = chatPeer
|
||||
|
||||
if let associatedPeerId = chatPeer.associatedPeerId {
|
||||
if let peer = peerTable.get(associatedPeerId) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for media in message.media {
|
||||
for peerId in media.peerIds {
|
||||
if let peer = peerTable.get(peerId) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attribute in message.attributes {
|
||||
for peerId in attribute.associatedPeerIds {
|
||||
if let peer = peerTable.get(peerId) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return message.withUpdatedPeers(peers)
|
||||
}
|
||||
|
||||
func renderAssociatedMessages(associatedMessageIds: [MessageId], peerTable: PeerTable) -> SimpleDictionary<MessageId, Message> {
|
||||
var associatedMessages = SimpleDictionary<MessageId, Message>()
|
||||
for messageId in associatedMessageIds {
|
||||
|
@ -29,13 +29,13 @@ public struct MessageHistoryMessageEntry {
|
||||
|
||||
enum MutableMessageHistoryEntry {
|
||||
case IntermediateMessageEntry(IntermediateMessage, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?)
|
||||
case MessageEntry(MessageHistoryMessageEntry, reloadAssociatedMessages: Bool)
|
||||
case MessageEntry(MessageHistoryMessageEntry, reloadAssociatedMessages: Bool, reloadPeers: Bool)
|
||||
|
||||
var index: MessageIndex {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, _, _):
|
||||
return message.index
|
||||
case let .MessageEntry(message, _):
|
||||
case let .MessageEntry(message, _, _):
|
||||
return message.message.index
|
||||
}
|
||||
}
|
||||
@ -44,7 +44,7 @@ enum MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, _, _):
|
||||
return message.tags
|
||||
case let .MessageEntry(message, _):
|
||||
case let .MessageEntry(message, _, _):
|
||||
return message.message.tags
|
||||
}
|
||||
}
|
||||
@ -53,8 +53,8 @@ enum MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, _, monthLocation):
|
||||
return .IntermediateMessageEntry(message, location, monthLocation)
|
||||
case let .MessageEntry(message, reloadAssociatedMessages):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: location, monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: location, monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,8 +62,8 @@ enum MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, location, _):
|
||||
return .IntermediateMessageEntry(message, location, monthLocation)
|
||||
case let .MessageEntry(message, reloadAssociatedMessages):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: message.location, monthLocation: monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: message.location, monthLocation: monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,12 +79,12 @@ enum MutableMessageHistoryEntry {
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
case let .MessageEntry(message, reloadAssociatedMessages):
|
||||
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
|
||||
if let location = message.location {
|
||||
if message.message.index > index {
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
} else {
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
} else {
|
||||
return self
|
||||
@ -107,15 +107,15 @@ enum MutableMessageHistoryEntry {
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
case let .MessageEntry(message, reloadAssociatedMessages):
|
||||
case let .MessageEntry(message, reloadAssociatedMessages, reloadPeers):
|
||||
if let location = message.location {
|
||||
if message.message.index > index {
|
||||
//assert(location.index > 0)
|
||||
//assert(location.count != 0)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
} else {
|
||||
//assert(location.count != 0)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
} else {
|
||||
return self
|
||||
@ -128,10 +128,10 @@ enum MutableMessageHistoryEntry {
|
||||
case let .IntermediateMessageEntry(message, location, monthLocation):
|
||||
let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia)
|
||||
return .IntermediateMessageEntry(updatedMessage, location, monthLocation)
|
||||
case let .MessageEntry(value, reloadAssociatedMessages):
|
||||
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
|
||||
let message = value.message
|
||||
let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ enum MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, location, monthLocation):
|
||||
return []
|
||||
case let .MessageEntry(value, _):
|
||||
case let .MessageEntry(value, _, _):
|
||||
return value.message.associatedMessageIds
|
||||
}
|
||||
}
|
||||
@ -258,6 +258,7 @@ final class MutableMessageHistoryView {
|
||||
let tag: MessageTags?
|
||||
let namespaces: MessageIdNamespaces
|
||||
private let orderStatistics: MessageHistoryViewOrderStatistics
|
||||
private let clipHoles: Bool
|
||||
private let anchor: HistoryViewInputAnchor
|
||||
|
||||
fileprivate var combinedReadStates: MessageHistoryViewReadState?
|
||||
@ -271,10 +272,11 @@ final class MutableMessageHistoryView {
|
||||
|
||||
fileprivate(set) var sampledState: HistoryViewSample
|
||||
|
||||
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
|
||||
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
|
||||
self.anchor = inputAnchor
|
||||
|
||||
self.orderStatistics = orderStatistics
|
||||
self.clipHoles = clipHoles
|
||||
self.peerIds = peerIds
|
||||
self.combinedReadStates = combinedReadStates
|
||||
self.transientReadStates = transientReadStates
|
||||
@ -290,12 +292,12 @@ final class MutableMessageHistoryView {
|
||||
switch sampledState {
|
||||
case let .ready(anchor, holes):
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
|
||||
|
||||
self.render(postbox: postbox)
|
||||
}
|
||||
@ -320,7 +322,7 @@ final class MutableMessageHistoryView {
|
||||
break
|
||||
}
|
||||
}
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
|
||||
}
|
||||
|
||||
func refreshDueToExternalTransaction(postbox: Postbox) -> Bool {
|
||||
@ -509,7 +511,7 @@ final class MutableMessageHistoryView {
|
||||
break
|
||||
}
|
||||
}
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
|
||||
}
|
||||
|
||||
for operationSet in operations {
|
||||
|
@ -874,10 +874,10 @@ final class HistoryViewLoadedState {
|
||||
let currentLocation = nextLocation
|
||||
nextLocation = nextLocation.successor
|
||||
switch entry {
|
||||
case let .IntermediateMessageEntry(message, _, monthLocation):
|
||||
return .IntermediateMessageEntry(message, currentLocation, monthLocation)
|
||||
case let .MessageEntry(entry, reloadAssociatedMessages):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: currentLocation, monthLocation: entry.monthLocation, attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
case let .IntermediateMessageEntry(message, _, monthLocation):
|
||||
return .IntermediateMessageEntry(message, currentLocation, monthLocation)
|
||||
case let .MessageEntry(entry, reloadAssociatedMessages, reloadPeers):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: currentLocation, monthLocation: entry.monthLocation, attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -900,8 +900,8 @@ final class HistoryViewLoadedState {
|
||||
switch entry {
|
||||
case let .IntermediateMessageEntry(message, location, _):
|
||||
return .IntermediateMessageEntry(message, location, MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth)))
|
||||
case let .MessageEntry(entry, reloadAssociatedMessages):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: entry.location, monthLocation: MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth)), attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
case let .MessageEntry(entry, reloadAssociatedMessages, reloadPeers):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: entry.location, monthLocation: MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth)), attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -960,8 +960,8 @@ final class HistoryViewLoadedState {
|
||||
switch entry {
|
||||
case let .IntermediateMessageEntry(message, location, monthLocation):
|
||||
return .IntermediateMessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation)
|
||||
case let .MessageEntry(messageEntry, reloadAssociatedMessages):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedGroupInfo(groupInfo), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedGroupInfo(groupInfo), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -983,8 +983,8 @@ final class HistoryViewLoadedState {
|
||||
switch entry {
|
||||
case let .IntermediateMessageEntry(message, location, monthLocation):
|
||||
return .IntermediateMessageEntry(message.withUpdatedEmbeddedMedia(buffer), location, monthLocation)
|
||||
case let .MessageEntry(messageEntry, reloadAssociatedMessages):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message, location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message, location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -994,8 +994,9 @@ final class HistoryViewLoadedState {
|
||||
for space in self.orderedEntriesBySpace.keys {
|
||||
let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in
|
||||
switch entry {
|
||||
case let .MessageEntry(value, reloadAssociatedMessages):
|
||||
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
|
||||
let message = value.message
|
||||
var reloadPeers = reloadPeers
|
||||
|
||||
var rebuild = false
|
||||
for media in message.media {
|
||||
@ -1010,6 +1011,9 @@ final class HistoryViewLoadedState {
|
||||
for media in message.media {
|
||||
if let mediaId = media.id, let updated = updatedMedia[mediaId] {
|
||||
if let updated = updated {
|
||||
if media.peerIds != updated.peerIds {
|
||||
reloadPeers = true
|
||||
}
|
||||
messageMedia.append(updated)
|
||||
}
|
||||
} else {
|
||||
@ -1017,7 +1021,7 @@ final class HistoryViewLoadedState {
|
||||
}
|
||||
}
|
||||
let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
}
|
||||
case .IntermediateMessageEntry:
|
||||
break
|
||||
@ -1046,9 +1050,9 @@ final class HistoryViewLoadedState {
|
||||
switch current {
|
||||
case .IntermediateMessageEntry:
|
||||
return current
|
||||
case let .MessageEntry(messageEntry, _):
|
||||
case let .MessageEntry(messageEntry, _, reloadPeers):
|
||||
updated = true
|
||||
return .MessageEntry(messageEntry, reloadAssociatedMessages: true)
|
||||
return .MessageEntry(messageEntry, reloadAssociatedMessages: true, reloadPeers: reloadPeers)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1109,11 +1113,11 @@ final class HistoryViewLoadedState {
|
||||
switch current {
|
||||
case .IntermediateMessageEntry:
|
||||
return current
|
||||
case let .MessageEntry(messageEntry, reloadAssociatedMessages):
|
||||
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
|
||||
updated = true
|
||||
|
||||
if let associatedMessages = messageEntry.message.associatedMessages.filteredOut(keysIn: [index.id]) {
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedAssociatedMessages(associatedMessages), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedAssociatedMessages(associatedMessages), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
@ -1130,7 +1134,7 @@ final class HistoryViewLoadedState {
|
||||
return updated
|
||||
}
|
||||
|
||||
func completeAndSample(postbox: Postbox) -> HistoryViewLoadedSample {
|
||||
func completeAndSample(postbox: Postbox, clipHoles: Bool) -> HistoryViewLoadedSample {
|
||||
if !self.spacesWithRemovals.isEmpty {
|
||||
for space in self.spacesWithRemovals {
|
||||
self.fillSpace(space: space, postbox: postbox)
|
||||
@ -1161,7 +1165,7 @@ final class HistoryViewLoadedState {
|
||||
entry = self.orderedEntriesBySpace[space]!.higherThanAnchor[index]
|
||||
}
|
||||
|
||||
if !clipRanges.isEmpty {
|
||||
if clipHoles && !clipRanges.isEmpty {
|
||||
let entryIndex = entry.index
|
||||
for range in clipRanges {
|
||||
if range.contains(entryIndex) {
|
||||
@ -1177,14 +1181,22 @@ final class HistoryViewLoadedState {
|
||||
}
|
||||
|
||||
switch entry {
|
||||
case let .MessageEntry(value, reloadAssociatedMessages):
|
||||
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
|
||||
var updatedMessage = value.message
|
||||
if reloadAssociatedMessages {
|
||||
let associatedMessages = postbox.messageHistoryTable.renderAssociatedMessages(associatedMessageIds: value.message.associatedMessageIds, peerTable: postbox.peerTable)
|
||||
let updatedValue = MessageHistoryMessageEntry(message: value.message.withUpdatedAssociatedMessages(associatedMessages), location: value.location, monthLocation: value.monthLocation, attributes: value.attributes)
|
||||
updatedMessage = value.message.withUpdatedAssociatedMessages(associatedMessages)
|
||||
}
|
||||
if reloadPeers {
|
||||
updatedMessage = postbox.messageHistoryTable.renderMessagePeers(updatedMessage, peerTable: postbox.peerTable)
|
||||
}
|
||||
|
||||
if value.message !== updatedMessage {
|
||||
let updatedValue = MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes)
|
||||
if directionIndex == 0 {
|
||||
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false))
|
||||
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false, reloadPeers: false))
|
||||
} else {
|
||||
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false))
|
||||
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false, reloadPeers: false))
|
||||
}
|
||||
result.append(updatedValue)
|
||||
} else {
|
||||
@ -1198,9 +1210,9 @@ final class HistoryViewLoadedState {
|
||||
}
|
||||
let entry = MessageHistoryMessageEntry(message: renderedMessage, location: location, monthLocation: monthLocation, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: authorIsContact))
|
||||
if directionIndex == 0 {
|
||||
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false))
|
||||
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false, reloadPeers: false))
|
||||
} else {
|
||||
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false))
|
||||
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false, reloadPeers: false))
|
||||
}
|
||||
result.append(entry)
|
||||
}
|
||||
@ -1361,12 +1373,12 @@ enum HistoryViewState {
|
||||
}
|
||||
}
|
||||
|
||||
func sample(postbox: Postbox) -> HistoryViewSample {
|
||||
func sample(postbox: Postbox, clipHoles: Bool) -> HistoryViewSample {
|
||||
switch self {
|
||||
case let .loading(loadingState):
|
||||
return .loading(loadingState.checkAndSample(postbox: postbox))
|
||||
case let .loaded(loadedState):
|
||||
return .loaded(loadedState.completeAndSample(postbox: postbox))
|
||||
case let .loading(loadingState):
|
||||
return .loading(loadingState.checkAndSample(postbox: postbox))
|
||||
case let .loaded(loadedState):
|
||||
return .loaded(loadedState.completeAndSample(postbox: postbox, clipHoles: clipHoles))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
||||
}
|
||||
}
|
||||
self.anchor = anchor
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
let _ = self.updateFromView()
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
||||
case let .peer(id):
|
||||
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil)
|
||||
}
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
return self.updateFromView()
|
||||
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
|
||||
var reloadView = false
|
||||
@ -160,7 +160,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
||||
case let .peer(id):
|
||||
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil)
|
||||
}
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
}
|
||||
|
||||
return self.updateFromView()
|
||||
|
@ -655,6 +655,11 @@ public final class Transaction {
|
||||
return self.postbox?.retrieveItemCacheEntry(id: id)
|
||||
}
|
||||
|
||||
public func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
|
||||
assert(!self.disposed)
|
||||
self.postbox?.clearItemCacheCollection(collectionId: collectionId)
|
||||
}
|
||||
|
||||
public func operationLogGetNextEntryLocalIndex(peerId: PeerId, tag: PeerOperationLogTag) -> Int32 {
|
||||
assert(!self.disposed)
|
||||
if let postbox = self.postbox {
|
||||
@ -2065,6 +2070,10 @@ public final class Postbox {
|
||||
return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable)
|
||||
}
|
||||
|
||||
func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
|
||||
return self.itemCacheTable.removeAll(collectionId: collectionId)
|
||||
}
|
||||
|
||||
fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) {
|
||||
self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable)
|
||||
}
|
||||
@ -2257,7 +2266,7 @@ public final class Postbox {
|
||||
return peerIds
|
||||
}
|
||||
|
||||
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
return self.transactionSignal(userInteractive: true, { subscriber, transaction in
|
||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||
|
||||
@ -2303,26 +2312,26 @@ public final class Postbox {
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
})
|
||||
}
|
||||
|
||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
return self.transactionSignal { subscriber, transaction in
|
||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
return self.transactionSignal { subscriber, transaction in
|
||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
|
||||
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
||||
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
||||
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
|
||||
var mainPeerId: PeerId?
|
||||
switch peerIds {
|
||||
@ -2411,7 +2420,7 @@ public final class Postbox {
|
||||
readStates = transientReadStates
|
||||
}
|
||||
|
||||
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
|
||||
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, clipHoles: clipHoles, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
|
||||
if let tagMask = tagMask {
|
||||
return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound))
|
||||
} else {
|
||||
|
@ -38,6 +38,13 @@ public struct ValueBoxKey: Equatable, Hashable, CustomStringConvertible, Compara
|
||||
memcpy(self.memory, buffer.memory, buffer.length)
|
||||
}
|
||||
|
||||
public func setData(_ offset: Int, value: Data) {
|
||||
let valueLength = value.count
|
||||
value.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(self.memory + offset, bytes, valueLength)
|
||||
}
|
||||
}
|
||||
|
||||
public func setInt32(_ offset: Int, value: Int32) {
|
||||
var bigEndianValue = Int32(bigEndian: value)
|
||||
memcpy(self.memory + offset, &bigEndianValue, 4)
|
||||
|
@ -75,3 +75,50 @@ public func screenCaptureEvents() -> Signal<ScreenCaptureEvent, NoError> {
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
}
|
||||
|
||||
public final class ScreenCaptureDetectionManager {
|
||||
private var observer: NSObjectProtocol?
|
||||
private var screenRecordingDisposable: Disposable?
|
||||
private var screenRecordingCheckTimer: SwiftSignalKit.Timer?
|
||||
|
||||
public init(check: @escaping () -> Bool) {
|
||||
self.observer = NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: .main, using: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
check()
|
||||
})
|
||||
|
||||
self.screenRecordingDisposable = screenRecordingActive().start(next: { [weak self] value in
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if value {
|
||||
if strongSelf.screenRecordingCheckTimer == nil {
|
||||
strongSelf.screenRecordingCheckTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if check() {
|
||||
strongSelf.screenRecordingCheckTimer?.invalidate()
|
||||
strongSelf.screenRecordingCheckTimer = nil
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
strongSelf.screenRecordingCheckTimer?.start()
|
||||
}
|
||||
} else if strongSelf.screenRecordingCheckTimer != nil {
|
||||
strongSelf.screenRecordingCheckTimer?.invalidate()
|
||||
strongSelf.screenRecordingCheckTimer = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self.observer)
|
||||
self.screenRecordingDisposable?.dispose()
|
||||
self.screenRecordingCheckTimer?.invalidate()
|
||||
self.screenRecordingCheckTimer = nil
|
||||
}
|
||||
}
|
||||
|
@ -87,12 +87,13 @@ private class SearchBarTextField: UITextField {
|
||||
}
|
||||
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(bounds.size)
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
if !prefixSize.width.isZero {
|
||||
let prefixOffset = prefixSize.width
|
||||
rect.origin.x += prefixOffset
|
||||
rect.size.width -= prefixOffset
|
||||
}
|
||||
rect.size.width = max(rect.size.width, 10.0)
|
||||
return rect
|
||||
}
|
||||
|
||||
@ -117,7 +118,7 @@ private class SearchBarTextField: UITextField {
|
||||
let labelSize = self.placeholderLabel.measure(textRect.size)
|
||||
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX, y: textRect.minY + textOffset), size: labelSize)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(bounds.size)
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + textOffset), size: prefixSize)
|
||||
}
|
||||
|
@ -334,14 +334,14 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
|
||||
let resolvedValue: CGFloat?
|
||||
if let value = self.value {
|
||||
if let transition = self.transition {
|
||||
transition.valueAt(timestamp: timestamp, actualValue: value)
|
||||
resolvedValue = transition.valueAt(timestamp: timestamp, actualValue: value)
|
||||
} else {
|
||||
resolvedValue = value
|
||||
}
|
||||
} else {
|
||||
resolvedValue = nil
|
||||
}
|
||||
return DrawingState(transitionFraction: transitionFraction, value: self.value, displayCancel: self.displayCancel, timestamp: timestamp)
|
||||
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, displayCancel: self.displayCancel, timestamp: timestamp)
|
||||
}
|
||||
|
||||
func updateValue(value: CGFloat?) {
|
||||
@ -351,7 +351,7 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
|
||||
let timestamp = CACurrentMediaTime()
|
||||
if let value = value, let previousValue = previousValue {
|
||||
if let transition = self.transition {
|
||||
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: transition.valueAt(timestamp: timestamp, actualValue: value))
|
||||
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: transition.valueAt(timestamp: timestamp, actualValue: previousValue))
|
||||
} else {
|
||||
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: previousValue)
|
||||
}
|
||||
@ -388,7 +388,7 @@ private extension SemanticStatusNodeState {
|
||||
}
|
||||
case let .progress(value, cancelEnabled):
|
||||
if let current = current as? SemanticStatusNodeProgressContext, current.displayCancel == cancelEnabled {
|
||||
current.value = value
|
||||
current.updateValue(value: value)
|
||||
return current
|
||||
} else {
|
||||
return SemanticStatusNodeProgressContext(value: value, displayCancel: cancelEnabled)
|
||||
|
@ -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?()
|
||||
}
|
||||
}
|
@ -187,7 +187,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
|
||||
case let .accountHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .account(theme, peer, selected, _):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: false), revealOptions: nil, switchValue: ItemListPeerItemSwitch(value: selected, style: .check), enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context.sharedContext.makeTempAccountContext(account: arguments.context.account), peer: peer, height: .generic, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: false), revealOptions: nil, switchValue: ItemListPeerItemSwitch(value: selected, style: .check), enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
arguments.updateSettings { $0.withUpdatedAccount(peer.id) }
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in}, removePeer: { _ in })
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(""), sectionId: self.section)
|
||||
|
@ -453,10 +453,10 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ProxySettingsControllerEntry]) -> Void in
|
||||
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ProxySettingsControllerEntry]) -> Signal<Bool, NoError> in
|
||||
let fromEntry = entries[fromIndex]
|
||||
guard case let .server(_, _, _, fromServer, _, _, _, _) = fromEntry else {
|
||||
return
|
||||
return .single(false)
|
||||
}
|
||||
var referenceServer: ProxyServerSettings?
|
||||
var beforeAll = false
|
||||
@ -476,7 +476,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
|
||||
afterAll = true
|
||||
}
|
||||
|
||||
let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in
|
||||
return updateProxySettingsInteractively(accountManager: accountManager, { current in
|
||||
var current = current
|
||||
if let index = current.servers.firstIndex(of: fromServer) {
|
||||
current.servers.remove(at: index)
|
||||
@ -503,7 +503,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
|
||||
current.servers.append(fromServer)
|
||||
}
|
||||
return current
|
||||
}).start()
|
||||
})
|
||||
})
|
||||
|
||||
shareProxyListImpl = { [weak controller] in
|
||||
|
@ -241,7 +241,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
|
||||
let statusAttributedString = NSAttributedString(string: item.label, font: statusFont, textColor: item.labelAccent ? item.theme.list.itemAccentColor : item.theme.list.itemSecondaryTextColor)
|
||||
|
||||
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool) -> ItemListEditableReorderControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||
|
||||
let editingOffset: CGFloat
|
||||
var reorderInset: CGFloat = 0.0
|
||||
@ -341,7 +341,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
|
||||
if strongSelf.reorderControlNode == nil {
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false)
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
|
||||
strongSelf.reorderControlNode = reorderControlNode
|
||||
strongSelf.addSubnode(reorderControlNode)
|
||||
reorderControlNode.alpha = 0.0
|
||||
|
@ -393,6 +393,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.clearNotices()
|
||||
}).start()
|
||||
if let context = arguments.context {
|
||||
let _ = (context.account.postbox.transaction { transaction -> Void in
|
||||
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedPollResults)
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
case let .reimport(theme):
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reimport Application Data", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
|
@ -18,6 +18,7 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
|
||||
let strings: PresentationStrings
|
||||
let sectionId: ItemListSectionId
|
||||
let fontSize: PresentationFontSize
|
||||
let chatBubbleCorners: PresentationChatBubbleCorners
|
||||
let wallpaper: TelegramWallpaper
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
@ -25,12 +26,13 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
|
||||
let linkEnabled: Bool
|
||||
let tooltipText: String
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peerName: String, linkEnabled: Bool, tooltipText: String) {
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peerName: String, linkEnabled: Bool, tooltipText: String) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.sectionId = sectionId
|
||||
self.fontSize = fontSize
|
||||
self.chatBubbleCorners = chatBubbleCorners
|
||||
self.wallpaper = wallpaper
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -157,7 +159,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
|
||||
|
||||
let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName)
|
||||
|
||||
let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)
|
||||
let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)
|
||||
|
||||
var node: ListViewItemNode?
|
||||
if let current = currentNode {
|
||||
|
@ -482,12 +482,19 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
let updateHasTwoStepAuth: () -> Void = {
|
||||
let signal = twoStepVerificationConfiguration(account: context.account)
|
||||
|> map { value -> TwoStepVerificationAccessConfiguration? in
|
||||
return TwoStepVerificationAccessConfiguration(configuration: value, password: nil)
|
||||
return TwoStepVerificationAccessConfiguration(configuration: value, password: nil)
|
||||
}
|
||||
|> deliverOnMainQueue
|
||||
updateTwoStepAuthDisposable.set(
|
||||
signal.start(next: { value in
|
||||
twoStepAuthDataValue.set(.single(value))
|
||||
if let value = value {
|
||||
if case .set = value {
|
||||
updatedHasTwoStepAuth?(true)
|
||||
} else {
|
||||
updatedHasTwoStepAuth?(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings
|
||||
|
||||
private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
case forwardsPreviewHeader(PresentationTheme, String)
|
||||
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String)
|
||||
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String)
|
||||
case settingHeader(PresentationTheme, String)
|
||||
case everybody(PresentationTheme, String, Bool)
|
||||
case contacts(PresentationTheme, String, Bool)
|
||||
@ -194,8 +194,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsPeerName, lhsLinkEnabled, lhsTooltipText):
|
||||
if case let .forwardsPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsPeerName, rhsLinkEnabled, rhsTooltipText) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsPeerName == rhsPeerName, lhsLinkEnabled == rhsLinkEnabled, lhsTooltipText == rhsTooltipText {
|
||||
case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsPeerName, lhsLinkEnabled, lhsTooltipText):
|
||||
if case let .forwardsPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsChatBubbleCorners, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsPeerName, rhsLinkEnabled, rhsTooltipText) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsChatBubbleCorners == rhsChatBubbleCorners, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsPeerName == rhsPeerName, lhsLinkEnabled == rhsLinkEnabled, lhsTooltipText == rhsTooltipText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -350,8 +350,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case let .forwardsPreviewHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
||||
case let .forwardsPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, peerName, linkEnabled, tooltipText):
|
||||
return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText)
|
||||
case let .forwardsPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, peerName, linkEnabled, tooltipText):
|
||||
return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText)
|
||||
case let .settingHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
||||
case let .everybody(theme, text, value):
|
||||
@ -591,7 +591,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
||||
linkEnabled = false
|
||||
}
|
||||
entries.append(.forwardsPreviewHeader(presentationData.theme, presentationData.strings.Privacy_Forwards_Preview))
|
||||
entries.append(.forwardsPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peerName, linkEnabled, tootipText))
|
||||
entries.append(.forwardsPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peerName, linkEnabled, tootipText))
|
||||
}
|
||||
|
||||
entries.append(.settingHeader(presentationData.theme, settingTitle))
|
||||
|
@ -945,7 +945,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
blockedPeers.set(.single(blockedPeersContext))
|
||||
}, updatedHasTwoStepAuth: { hasTwoStepAuthValue in
|
||||
hasTwoStepAuthPromise.set(.single(hasTwoStepAuthValue))
|
||||
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext))
|
||||
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth))
|
||||
})
|
||||
})
|
||||
}, openDataAndStorage: {
|
||||
@ -1474,7 +1474,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
if let primary = primary {
|
||||
let size = CGSize(width: 31.0, height: 31.0)
|
||||
let inset: CGFloat = 3.0
|
||||
if let signal = peerAvatarImage(account: primary.0, peer: primary.1, authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) {
|
||||
if let signal = peerAvatarImage(account: primary.0, peerReference: PeerReference(primary.1), authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) {
|
||||
return signal
|
||||
|> map { image -> (UIImage, UIImage)? in
|
||||
if let image = image, let selectedImage = generateImage(size, rotatedContext: { size, context in
|
||||
@ -1497,13 +1497,13 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
let image = generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.translateBy(x: inset, y: inset)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, accountPeerId: primary.1.id, peerId: primary.1.id)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, peerId: primary.1.id)
|
||||
})?.withRenderingMode(.alwaysOriginal)
|
||||
|
||||
let selectedImage = generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.translateBy(x: inset, y: inset)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, accountPeerId: primary.1.id, peerId: primary.1.id)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: primary.1.displayLetters, peerId: primary.1.id)
|
||||
context.translateBy(x: -inset, y: -inset)
|
||||
context.setLineWidth(1.0)
|
||||
context.setStrokeColor(primary.2.rootController.tabBar.selectedIconColor.cgColor)
|
||||
|
@ -453,7 +453,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
|
||||
}
|
||||
presentStickerPackController = { [weak controller] info in
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
|
||||
}
|
||||
|
||||
return controller
|
||||
|
@ -265,7 +265,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
|
||||
|
||||
presentStickerPackController = { [weak controller] info in
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)
|
||||
}
|
||||
|
||||
return controller
|
||||
|
@ -698,10 +698,10 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
||||
if case .modal = mode {
|
||||
controller.navigationPresentation = .modal
|
||||
}
|
||||
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [InstalledStickerPacksEntry]) -> Void in
|
||||
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [InstalledStickerPacksEntry]) -> Signal<Bool, NoError> in
|
||||
let fromEntry = entries[fromIndex]
|
||||
guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _) = fromEntry else {
|
||||
return
|
||||
return .single(false)
|
||||
}
|
||||
var referenceId: ItemCollectionId?
|
||||
var beforeAll = false
|
||||
@ -731,20 +731,26 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
||||
}
|
||||
}
|
||||
|
||||
var previousIndex: Int?
|
||||
for i in 0 ..< currentIds.count {
|
||||
if currentIds[i] == fromPackInfo.id {
|
||||
previousIndex = i
|
||||
currentIds.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var didReorder = false
|
||||
|
||||
if let referenceId = referenceId {
|
||||
var inserted = false
|
||||
for i in 0 ..< currentIds.count {
|
||||
if currentIds[i] == referenceId {
|
||||
if fromIndex < toIndex {
|
||||
didReorder = previousIndex != i + 1
|
||||
currentIds.insert(fromPackInfo.id, at: i + 1)
|
||||
} else {
|
||||
didReorder = previousIndex != i
|
||||
currentIds.insert(fromPackInfo.id, at: i)
|
||||
}
|
||||
inserted = true
|
||||
@ -752,15 +758,20 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
||||
}
|
||||
}
|
||||
if !inserted {
|
||||
didReorder = previousIndex != currentIds.count
|
||||
currentIds.append(fromPackInfo.id)
|
||||
}
|
||||
} else if beforeAll {
|
||||
didReorder = previousIndex != 0
|
||||
currentIds.insert(fromPackInfo.id, at: 0)
|
||||
} else if afterAll {
|
||||
didReorder = previousIndex != currentIds.count
|
||||
currentIds.append(fromPackInfo.id)
|
||||
}
|
||||
|
||||
temporaryPackOrder.set(.single(currentIds))
|
||||
|
||||
return .single(didReorder)
|
||||
})
|
||||
|
||||
controller.setReorderCompleted({ (entries: [InstalledStickerPacksEntry]) -> Void in
|
||||
@ -827,7 +838,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
||||
packs.insert(packReference, at: 0)
|
||||
}
|
||||
if let mainStickerPack = mainStickerPack {
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: mainStickerPack, stickerPacks: packs, parentNavigationController: controller?.navigationController as? NavigationController, actionPerformed: { info, items, action in
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: mainStickerPack, stickerPacks: packs, parentNavigationController: controller?.navigationController as? NavigationController, actionPerformed: { info, items, action in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = navigationControllerImpl?() {
|
||||
|
@ -213,7 +213,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
||||
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var items: [ChatListItem] = []
|
||||
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
|
||||
gesture?.cancel()
|
||||
})
|
||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
@ -303,7 +303,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
||||
}
|
||||
|
||||
private func updateMessagesLayout(layout: ContainerViewLayout, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
|
||||
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
|
||||
|
||||
var items: [ListViewItem] = []
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
|
||||
@ -317,20 +317,20 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
||||
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||
|
||||
let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message1, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message1, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
|
||||
let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message2, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message2, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
|
||||
let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA="
|
||||
let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))]
|
||||
let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes)
|
||||
|
||||
let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil))
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil))
|
||||
|
||||
let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message4, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message4, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
|
||||
let width: CGFloat
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -53,7 +53,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
|
||||
case slug(PresentationTheme, PresentationStrings, String, String, Bool)
|
||||
case slugInfo(PresentationTheme, String)
|
||||
case chatPreviewHeader(PresentationTheme, String)
|
||||
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
|
||||
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
|
||||
case changeColors(PresentationTheme, String)
|
||||
case uploadTheme(PresentationTheme, String)
|
||||
case uploadInfo(PresentationTheme, String)
|
||||
@ -114,8 +114,8 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
|
||||
if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
|
||||
case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
|
||||
if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsChatBubbleCorners, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsChatBubbleCorners == rhsChatBubbleCorners, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -172,8 +172,8 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .chatPreviewHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, items):
|
||||
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
|
||||
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items):
|
||||
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
|
||||
case let .changeColors(theme, text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openColors()
|
||||
@ -253,7 +253,7 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat
|
||||
entries.append(.slugInfo(presentationData.theme, infoText))
|
||||
|
||||
entries.append(.chatPreviewHeader(presentationData.theme, presentationData.strings.EditTheme_Preview.uppercased()))
|
||||
entries.append(.chatPreview(presentationData.theme, previewTheme, previewTheme.chat.defaultWallpaper, presentationData.chatFontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (previewIncomingReplyName, previewIncomingReplyText), text: previewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: previewOutgoingText)]))
|
||||
entries.append(.chatPreview(presentationData.theme, previewTheme, previewTheme.chat.defaultWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (previewIncomingReplyName, previewIncomingReplyText), text: previewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: previewOutgoingText)]))
|
||||
|
||||
entries.append(.changeColors(presentationData.theme, presentationData.strings.EditTheme_ChangeColors))
|
||||
if !hasSettings {
|
||||
@ -551,12 +551,12 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
|
||||
}
|
||||
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id))
|
||||
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
themeSpecificChatWallpapers[themeReference.index] = nil
|
||||
|
||||
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
}) |> deliverOnMainQueue).start(completed: {
|
||||
if !hasCustomFile {
|
||||
saveThemeTemplateFile(state.title, themeResource, {
|
||||
@ -585,12 +585,12 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
|
||||
}
|
||||
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id))
|
||||
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
themeSpecificChatWallpapers[themeReference.index] = nil
|
||||
|
||||
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
}) |> deliverOnMainQueue).start(completed: {
|
||||
if let themeResource = themeResource, !hasCustomFile {
|
||||
saveThemeTemplateFile(state.title, themeResource, {
|
||||
|
@ -260,7 +260,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper, creatorAccountId: context.account.id))
|
||||
|
||||
var updatedTheme = current.theme
|
||||
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
@ -276,7 +276,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
var themeSpecificAccentColors = current.themeSpecificAccentColors
|
||||
themeSpecificAccentColors[baseThemeReference.index] = PresentationThemeAccentColor(themeIndex: themeReference.index)
|
||||
|
||||
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
|> castError(CreateThemeError.self)
|
||||
} else {
|
||||
@ -289,7 +289,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper, creatorAccountId: context.account.id))
|
||||
|
||||
var updatedTheme = current.theme
|
||||
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
@ -305,7 +305,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
var themeSpecificAccentColors = current.themeSpecificAccentColors
|
||||
themeSpecificAccentColors[baseThemeReference.index] = PresentationThemeAccentColor(themeIndex: themeReference.index)
|
||||
|
||||
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
|> castError(CreateThemeError.self)
|
||||
} else {
|
||||
@ -443,8 +443,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
var defaultPatternWallpaper: TelegramWallpaper?
|
||||
|
||||
for wallpaper in wallpapers {
|
||||
//JqSUrO0-mFIBAAAAWwTvLzoWGQI, 25
|
||||
if case let .file(file) = wallpaper, file.slug == "-Xc-np9y2VMCAAAARKr0yNNPYW0" {
|
||||
if case let .file(file) = wallpaper, file.slug == "JqSUrO0-mFIBAAAAWwTvLzoWGQI" {
|
||||
defaultPatternWallpaper = wallpaper
|
||||
break
|
||||
}
|
||||
|
@ -226,6 +226,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.theme = theme
|
||||
self.wallpaper = self.presentationData.chatWallpaper
|
||||
let bubbleCorners = self.presentationData.chatBubbleCorners
|
||||
|
||||
self.ready = ready
|
||||
|
||||
@ -498,7 +499,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
updatedTheme = theme
|
||||
}
|
||||
|
||||
let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: updatedTheme!, wallpaper: wallpaper)
|
||||
let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: updatedTheme!, wallpaper: wallpaper, bubbleCorners: bubbleCorners)
|
||||
} else {
|
||||
updatedTheme = nil
|
||||
}
|
||||
@ -765,7 +766,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var items: [ChatListItem] = []
|
||||
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
|
||||
gesture?.cancel()
|
||||
})
|
||||
let chatListPresentationData = ChatListPresentationData(theme: self.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
@ -836,7 +837,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
private func updateMessagesLayout(layout: ContainerViewLayout, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
|
||||
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
|
||||
|
||||
var items: [ListViewItem] = []
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
|
||||
@ -878,7 +879,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
sampleMessages.append(message8)
|
||||
|
||||
items = sampleMessages.reversed().map { message in
|
||||
let item = self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: { [weak self] message in
|
||||
let item = self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: { [weak self] message in
|
||||
if message.flags.contains(.Incoming) {
|
||||
self?.updateSection(.accent)
|
||||
self?.requestSectionUpdate?(.accent)
|
||||
|
@ -550,7 +550,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
||||
|> mapToSignal { resolvedWallpaper -> Signal<Void, NoError> in
|
||||
var updatedTheme = theme
|
||||
if case let .cloud(info) = theme {
|
||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
|
||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
|
||||
}
|
||||
|
||||
updateSettings { settings in
|
||||
@ -572,7 +572,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
||||
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||
|
||||
let defaultThemes: [PresentationThemeReference] = [.builtin(.night), .builtin(.nightAccent)]
|
||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }
|
||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }
|
||||
|
||||
var availableThemes = defaultThemes
|
||||
availableThemes.append(contentsOf: cloudThemes)
|
||||
@ -587,6 +587,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.alwaysSynchronous = true
|
||||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root))
|
||||
}
|
||||
|
@ -234,9 +234,9 @@ public final class ThemePreviewController: ViewController {
|
||||
|> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in
|
||||
if let theme = theme {
|
||||
if case let .file(file) = wallpaper, file.id != 0 {
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)))
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? context.account.id : nil)))
|
||||
} else {
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)))
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
@ -313,7 +313,7 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
if case let .result(theme) = result, let file = theme.file {
|
||||
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
||||
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper)), true))
|
||||
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: theme.isCreator ? context.account.id : nil)), true))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
@ -332,7 +332,7 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
if case let .result(updatedTheme) = result, let file = updatedTheme.file {
|
||||
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
||||
return .single((.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper)), true))
|
||||
return .single((.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: updatedTheme.isCreator ? context.account.id : nil)), true))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
|
@ -350,7 +350,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var items: [ChatListItem] = []
|
||||
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in
|
||||
gesture?.cancel()
|
||||
})
|
||||
let chatListPresentationData = ChatListPresentationData(theme: self.previewTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
@ -442,7 +442,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
private func updateMessagesLayout(layout: ContainerViewLayout, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
|
||||
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
|
||||
|
||||
var items: [ListViewItem] = []
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
|
||||
@ -484,7 +484,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
sampleMessages.append(message8)
|
||||
|
||||
items = sampleMessages.reversed().map { message in
|
||||
self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.chatFontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: nil, clickThroughMessage: nil)
|
||||
self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: !message.media.isEmpty ? FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local) : nil, tapMessage: nil, clickThroughMessage: nil)
|
||||
}
|
||||
|
||||
let width: CGFloat
|
||||
|
@ -40,18 +40,20 @@ class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem {
|
||||
let strings: PresentationStrings
|
||||
let sectionId: ItemListSectionId
|
||||
let fontSize: PresentationFontSize
|
||||
let chatBubbleCorners: PresentationChatBubbleCorners
|
||||
let wallpaper: TelegramWallpaper
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let messageItems: [ChatPreviewMessageItem]
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [ChatPreviewMessageItem]) {
|
||||
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [ChatPreviewMessageItem]) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.componentTheme = componentTheme
|
||||
self.strings = strings
|
||||
self.sectionId = sectionId
|
||||
self.fontSize = fontSize
|
||||
self.chatBubbleCorners = chatBubbleCorners
|
||||
self.wallpaper = wallpaper
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -163,7 +165,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: message, theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, message: message, theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil))
|
||||
}
|
||||
|
||||
var nodes: [ListViewItemNode] = []
|
||||
|
@ -76,6 +76,7 @@ private final class ThemeSettingsControllerArguments {
|
||||
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
|
||||
let openAutoNightTheme: () -> Void
|
||||
let openTextSize: () -> Void
|
||||
let openBubbleSettings: () -> Void
|
||||
let toggleLargeEmoji: (Bool) -> Void
|
||||
let disableAnimations: (Bool) -> Void
|
||||
let selectAppIcon: (String) -> Void
|
||||
@ -83,7 +84,7 @@ private final class ThemeSettingsControllerArguments {
|
||||
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
|
||||
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
self.context = context
|
||||
self.selectTheme = selectTheme
|
||||
self.selectFontSize = selectFontSize
|
||||
@ -92,6 +93,7 @@ private final class ThemeSettingsControllerArguments {
|
||||
self.openAccentColorPicker = openAccentColorPicker
|
||||
self.openAutoNightTheme = openAutoNightTheme
|
||||
self.openTextSize = openTextSize
|
||||
self.openBubbleSettings = openBubbleSettings
|
||||
self.toggleLargeEmoji = toggleLargeEmoji
|
||||
self.disableAnimations = disableAnimations
|
||||
self.selectAppIcon = selectAppIcon
|
||||
@ -131,11 +133,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
case themeListHeader(PresentationTheme, String)
|
||||
case fontSizeHeader(PresentationTheme, String)
|
||||
case fontSize(PresentationTheme, PresentationFontSize)
|
||||
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
|
||||
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
|
||||
case wallpaper(PresentationTheme, String)
|
||||
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?)
|
||||
case autoNightTheme(PresentationTheme, String, String)
|
||||
case textSize(PresentationTheme, String, String)
|
||||
case bubbleSettings(PresentationTheme, String, String)
|
||||
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
|
||||
case iconHeader(PresentationTheme, String)
|
||||
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
|
||||
@ -150,7 +153,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
return ThemeSettingsControllerSection.chatPreview.rawValue
|
||||
case .fontSizeHeader, .fontSize:
|
||||
return ThemeSettingsControllerSection.fontSize.rawValue
|
||||
case .wallpaper, .autoNightTheme, .textSize:
|
||||
case .wallpaper, .autoNightTheme, .textSize, .bubbleSettings:
|
||||
return ThemeSettingsControllerSection.background.rawValue
|
||||
case .iconHeader, .iconItem:
|
||||
return ThemeSettingsControllerSection.icon.rawValue
|
||||
@ -175,29 +178,31 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
return 6
|
||||
case .textSize:
|
||||
return 7
|
||||
case .fontSizeHeader:
|
||||
case .bubbleSettings:
|
||||
return 8
|
||||
case .fontSize:
|
||||
case .fontSizeHeader:
|
||||
return 9
|
||||
case .iconHeader:
|
||||
case .fontSize:
|
||||
return 10
|
||||
case .iconItem:
|
||||
case .iconHeader:
|
||||
return 11
|
||||
case .otherHeader:
|
||||
case .iconItem:
|
||||
return 12
|
||||
case .largeEmoji:
|
||||
case .otherHeader:
|
||||
return 13
|
||||
case .animations:
|
||||
case .largeEmoji:
|
||||
return 14
|
||||
case .animationsInfo:
|
||||
case .animations:
|
||||
return 15
|
||||
case .animationsInfo:
|
||||
return 16
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: ThemeSettingsControllerEntry, rhs: ThemeSettingsControllerEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
|
||||
if case let .chatPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
|
||||
case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
|
||||
if case let .chatPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsChatBubbleCorners, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsChatBubbleCorners == rhsChatBubbleCorners, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -226,6 +231,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .bubbleSettings(lhsTheme, lhsText, lhsValue):
|
||||
if case let .bubbleSettings(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .themeListHeader(lhsTheme, lhsText):
|
||||
if case let .themeListHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -302,8 +313,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
|
||||
arguments.selectFontSize(value)
|
||||
}, tag: ThemeSettingsEntryTag.fontSize)
|
||||
case let .chatPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, items):
|
||||
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
|
||||
case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items):
|
||||
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
|
||||
case let .wallpaper(theme, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openWallpaperSettings()
|
||||
@ -387,6 +398,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.openTextSize()
|
||||
})
|
||||
case let .bubbleSettings(theme, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.openBubbleSettings()
|
||||
})
|
||||
case let .themeListHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
|
||||
@ -429,7 +444,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
||||
let strings = presentationData.strings
|
||||
let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased()
|
||||
entries.append(.themeListHeader(presentationData.theme, title))
|
||||
entries.append(.chatPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)]))
|
||||
entries.append(.chatPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)]))
|
||||
|
||||
let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
|
||||
if case let .cloud(theme) = reference {
|
||||
@ -497,6 +512,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
||||
}
|
||||
}
|
||||
entries.append(.textSize(presentationData.theme, strings.Appearance_TextSizeSetting, textSizeValue))
|
||||
entries.append(.bubbleSettings(presentationData.theme, strings.Appearance_BubbleCornersSetting, ""))
|
||||
|
||||
if !availableAppIcons.isEmpty {
|
||||
entries.append(.iconHeader(presentationData.theme, strings.Appearance_AppIcon.uppercased()))
|
||||
@ -570,6 +586,13 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
let settings = (view.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||
pushControllerImpl?(TextSizeSelectionController(context: context, presentationThemeSettings: settings))
|
||||
})
|
||||
}, openBubbleSettings: {
|
||||
let _ = (context.sharedContext.accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.presentationThemeSettings]))
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { view in
|
||||
let settings = (view.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||
pushControllerImpl?(BubbleSettingsController(context: context, presentationThemeSettings: settings))
|
||||
})
|
||||
}, toggleLargeEmoji: { largeEmoji in
|
||||
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return current.withUpdatedLargeEmoji(largeEmoji)
|
||||
@ -729,7 +752,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
|
||||
let newTheme: PresentationThemeReference
|
||||
if let previousThemeIndex = previousThemeIndex {
|
||||
newTheme = .cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil))
|
||||
let theme = themes[themes.index(before: previousThemeIndex.base)]
|
||||
newTheme = .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil))
|
||||
} else {
|
||||
newTheme = .builtin(.nightAccent)
|
||||
}
|
||||
@ -953,7 +977,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
|
||||
let newTheme: PresentationThemeReference
|
||||
if let previousThemeIndex = previousThemeIndex {
|
||||
selectThemeImpl?(.cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil)))
|
||||
let theme = themes[themes.index(before: previousThemeIndex.base)]
|
||||
selectThemeImpl?(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
|
||||
} else {
|
||||
if settings.baseTheme == .night {
|
||||
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
|
||||
@ -1014,7 +1039,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}
|
||||
defaultThemes.append(contentsOf: [.builtin(.night), .builtin(.nightAccent)])
|
||||
|
||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }.filter { !removedThemeIndexes.contains($0.index) }
|
||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }.filter { !removedThemeIndexes.contains($0.index) }
|
||||
|
||||
var availableThemes = defaultThemes
|
||||
if defaultThemes.first(where: { $0.index == themeReference.index }) == nil && cloudThemes.first(where: { $0.index == themeReference.index }) == nil {
|
||||
@ -1158,7 +1183,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
var baseThemeIndex: Int64?
|
||||
var updatedThemeBaseIndex: Int64?
|
||||
if case let .cloud(info) = theme {
|
||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
|
||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
|
||||
if let settings = info.theme.settings {
|
||||
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
|
||||
updatedThemeBaseIndex = baseThemeIndex
|
||||
@ -1270,7 +1295,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}
|
||||
}
|
||||
|
||||
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
}).start()
|
||||
|
||||
presentCrossfadeControllerImpl?(true)
|
||||
|
@ -16,17 +16,19 @@ class ThemeSettingsFontSizeItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let fontSize: PresentationFontSize
|
||||
let disableLeadingInset: Bool
|
||||
let displayIcons: Bool
|
||||
let force: Bool
|
||||
let enabled: Bool
|
||||
let sectionId: ItemListSectionId
|
||||
let updated: (PresentationFontSize) -> Void
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: PresentationTheme, fontSize: PresentationFontSize, enabled: Bool = true, disableLeadingInset: Bool = false, force: Bool = false, sectionId: ItemListSectionId, updated: @escaping (PresentationFontSize) -> Void, tag: ItemListItemTag? = nil) {
|
||||
init(theme: PresentationTheme, fontSize: PresentationFontSize, enabled: Bool = true, disableLeadingInset: Bool = false, displayIcons: Bool = true, force: Bool = false, sectionId: ItemListSectionId, updated: @escaping (PresentationFontSize) -> Void, tag: ItemListItemTag? = nil) {
|
||||
self.theme = theme
|
||||
self.fontSize = fontSize
|
||||
self.enabled = enabled
|
||||
self.disableLeadingInset = disableLeadingInset
|
||||
self.displayIcons = displayIcons
|
||||
self.force = force
|
||||
self.sectionId = sectionId
|
||||
self.updated = updated
|
||||
@ -164,7 +166,9 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
|
||||
sliderView.knobImage = generateKnobImage()
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 38.0, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 38.0 * 2.0, height: 44.0))
|
||||
let sliderInset: CGFloat = item.displayIcons ? 38.0 : 16.0
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
|
||||
}
|
||||
self.view.insertSubview(sliderView, belowSubview: self.disabledOverlayNode.view)
|
||||
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
|
||||
@ -271,6 +275,9 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.rightIconNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 14.0 - image.size.width, y: 21.0), size: CGSize(width: image.size.width, height: image.size.height))
|
||||
}
|
||||
|
||||
strongSelf.leftIconNode.isHidden = !item.displayIcons
|
||||
strongSelf.rightIconNode.isHidden = !item.displayIcons
|
||||
|
||||
if let sliderView = strongSelf.sliderView {
|
||||
sliderView.isUserInteractionEnabled = item.enabled
|
||||
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
|
||||
@ -302,7 +309,8 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
sliderView.value = value
|
||||
}
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 38.0, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 38.0 * 2.0, height: 44.0))
|
||||
let sliderInset: CGFloat = item.displayIcons ? 38.0 : 16.0
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -415,6 +415,7 @@ private struct ThemeSettingsThemeItemNodeTransition {
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let crossfade: Bool
|
||||
let entries: [ThemeSettingsThemeEntry]
|
||||
}
|
||||
|
||||
private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeSettingsThemeEntry], to toEntries: [ThemeSettingsThemeEntry], crossfade: Bool) -> ThemeSettingsThemeItemNodeTransition {
|
||||
@ -424,7 +425,7 @@ private func preparedTransition(context: AccountContext, action: @escaping (Pres
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: .Down) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: nil) }
|
||||
|
||||
return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade)
|
||||
return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade, entries: toEntries)
|
||||
}
|
||||
|
||||
private func ensureThemeVisible(listNode: ListView, themeReference: PresentationThemeReference, animated: Bool) -> Bool {
|
||||
@ -512,10 +513,13 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
if self.initialized && transition.crossfade {
|
||||
options.insert(.AnimateCrossfade)
|
||||
}
|
||||
options.insert(.Synchronous)
|
||||
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if !self.initialized {
|
||||
if let index = item.themes.firstIndex(where: { $0.index == item.currentTheme.index }) {
|
||||
if let index = transition.entries.firstIndex(where: { entry in
|
||||
return entry.theme.index == item.currentTheme.index
|
||||
}) {
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
|
||||
self.initialized = true
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user