mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Improvements
This commit is contained in:
parent
36387e894a
commit
7508fc7290
@ -6928,3 +6928,5 @@ Sorry for the inconvenience.";
|
||||
"Conversation.RequestToJoinGroup" = "REQUEST TO JOIN";
|
||||
|
||||
"Channel.AdminLog.JoinedViaRequest" = "%1$@ joined via invite link %2$@, approved by %3$@";
|
||||
|
||||
"Appearance.NightTheme" = "Night Mode";
|
||||
|
@ -176,12 +176,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
|
||||
let importersContext = existingContext ?? context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
|
||||
|
||||
let arguments = InviteRequestsControllerArguments(context: context, openLinks: {
|
||||
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
|
||||
pushControllerImpl?(controller)
|
||||
}, openPeer: { peer in
|
||||
navigateToProfileImpl?(peer)
|
||||
}, approveRequest: { peer in
|
||||
let approveRequestImpl: (EnginePeer) -> Void = { peer in
|
||||
importersContext.update(peer.id, action: .approve)
|
||||
|
||||
let _ = (context.engine.data.get(
|
||||
@ -200,8 +195,21 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, text: string), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
})
|
||||
}, denyRequest: { peer in
|
||||
}
|
||||
|
||||
let denyRequestImpl: (EnginePeer) -> Void = { peer in
|
||||
importersContext.update(peer.id, action: .deny)
|
||||
}
|
||||
|
||||
let arguments = InviteRequestsControllerArguments(context: context, openLinks: {
|
||||
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
|
||||
pushControllerImpl?(controller)
|
||||
}, openPeer: { peer in
|
||||
navigateToProfileImpl?(peer)
|
||||
}, approveRequest: { peer in
|
||||
approveRequestImpl(peer)
|
||||
}, denyRequest: { peer in
|
||||
denyRequestImpl(peer)
|
||||
}, peerContextAction: { peer, node, gesture in
|
||||
guard let node = node as? ContextExtractedContentContainingNode else {
|
||||
return
|
||||
@ -209,11 +217,20 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MemberRequests_AddToGroup, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
approveRequestImpl(peer)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MemberRequests_Dismiss, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
denyRequestImpl(peer)
|
||||
})))
|
||||
|
||||
let dismissPromise = ValuePromise<Bool>(false)
|
||||
@ -290,7 +307,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
|
||||
let title: ItemListControllerTitle = .text(presentationData.strings.MemberRequests_Title)
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges, scrollEnabled: emptyStateItem == nil)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
|
||||
self.item = item
|
||||
|
||||
self.animationNode = AnimatedStickerNode()
|
||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Invite"), width: 192, height: 192, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "TwoFactorSetupRememberSuccess"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||
self.animationNode.visibility = true
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
@ -89,10 +89,10 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top += navigationBarHeight
|
||||
|
||||
let imageSpacing: CGFloat = 20.0
|
||||
let imageSpacing: CGFloat = 10.0
|
||||
let textSpacing: CGFloat = 8.0
|
||||
|
||||
let imageSize = CGSize(width: 96.0, height: 96.0)
|
||||
let imageSize = CGSize(width: 112.0, height: 112.0)
|
||||
let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0
|
||||
|
||||
self.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: -10.0), size: imageSize)
|
||||
@ -106,7 +106,7 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
|
||||
|
||||
transition.updateAlpha(node: self.animationNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0)
|
||||
transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + layout.intrinsicInsets.left + floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + layout.intrinsicInsets.left + floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize))
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let rect: CGRect
|
||||
if isExtracted {
|
||||
if extractedVerticalOffset > 0.0 {
|
||||
rect = CGRect(x: extractedRect.minX, y: extractedRect.minY + extractedVerticalOffset, width: extractedRect.width, height: extractedRect.height - extractedVerticalOffset)
|
||||
rect = CGRect(x: extractedRect.minX - 16.0, y: extractedRect.minY + extractedVerticalOffset, width: extractedRect.width, height: extractedRect.height - extractedVerticalOffset)
|
||||
} else {
|
||||
rect = extractedRect
|
||||
}
|
||||
@ -312,7 +312,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.avatarNode.transform = CATransform3DIdentity
|
||||
var avatarInitialRect = strongSelf.avatarNode.view.convert(strongSelf.avatarNode.bounds, to: strongSelf.offsetContainerNode.supernode?.view)
|
||||
if strongSelf.avatarTransitionNode == nil {
|
||||
let targetRect = CGRect(x: extractedRect.minX, y: extractedRect.minY, width: extractedRect.width, height: extractedRect.width)
|
||||
let targetRect = CGRect(x: extractedRect.minX - 16.0, y: extractedRect.minY, width: extractedRect.width, height: extractedRect.width)
|
||||
let initialScale = avatarInitialRect.width / targetRect.width
|
||||
avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * initialScale
|
||||
|
||||
@ -408,8 +408,9 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.avatarListContainerNode = nil
|
||||
strongSelf.avatarListNode = nil
|
||||
|
||||
avatarListContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak avatarListContainerNode] _ in
|
||||
avatarListContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak avatarListContainerNode, weak avatarListWrapperNode] _ in
|
||||
avatarListContainerNode?.removeFromSupernode()
|
||||
avatarListWrapperNode?.removeFromSupernode()
|
||||
})
|
||||
|
||||
avatarListWrapperNode.layer.animate(from: 1.0 as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
||||
@ -430,7 +431,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
alphaTransition.updateAlpha(node: strongSelf.dismissButton, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1)
|
||||
|
||||
let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform
|
||||
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -43 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
|
||||
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -48.0 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
|
||||
|
||||
let initialExtractedBackgroundPosition = strongSelf.extractedBackgroundImageNode.position
|
||||
strongSelf.extractedBackgroundImageNode.layer.position = rect.center
|
||||
@ -470,13 +471,6 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
transition.updateAlpha(node: strongSelf.expandedSubtitleNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? inset : 0.0, y: isExtracted ? extractedVerticalOffset : 0.0))
|
||||
|
||||
// transition.updateAlpha(node: strongSelf.backgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||
// if !isExtracted {
|
||||
// self?.backgroundImageNode.image = nil
|
||||
// self?.extractedBackgroundImageNode.image = nil
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -570,8 +564,8 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil
|
||||
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: layout.contentSize.width - 32.0, height: layout.contentSize.height))
|
||||
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: params.leftInset, dy: 0.0)
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height))
|
||||
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: params.leftInset + 16.0, dy: 0.0)
|
||||
var extractedHeight = extractedRect.height + expandedSubtitleLayout.size.height - subtitleLayout.size.height
|
||||
var extractedVerticalOffset: CGFloat = 0.0
|
||||
if item.importer?.peer.peer?.smallProfileImage != nil {
|
||||
@ -580,7 +574,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
extractedHeight += extractedVerticalOffset
|
||||
}
|
||||
|
||||
extractedRect.size.height = extractedHeight - 48.0
|
||||
extractedRect.size.height = extractedHeight - 46.0
|
||||
|
||||
strongSelf.extractedVerticalOffset = extractedVerticalOffset
|
||||
strongSelf.extractedRect = extractedRect
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
@ -30,6 +31,7 @@ public enum ItemListNavigationButtonContent: Equatable {
|
||||
case none
|
||||
case text(String)
|
||||
case icon(ItemListNavigationButtonContentIcon)
|
||||
case node(ASDisplayNode)
|
||||
}
|
||||
|
||||
public struct ItemListNavigationButton {
|
||||
@ -335,6 +337,11 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme)
|
||||
}
|
||||
item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
|
||||
case let .node(node):
|
||||
item = UIBarButtonItem(customDisplayNode: node)
|
||||
item.setCustomAction({ [weak self] in
|
||||
self?.navigationButtonActions.0?()
|
||||
})
|
||||
}
|
||||
strongSelf.leftNavigationButtonTitleAndStyle = (leftNavigationButton.content, leftNavigationButton.style)
|
||||
strongSelf.navigationItem.setLeftBarButton(item, animated: false)
|
||||
@ -392,6 +399,11 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme)
|
||||
}
|
||||
item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action)
|
||||
case let .node(node):
|
||||
item = UIBarButtonItem(customDisplayNode: node)
|
||||
item.setCustomAction({ [weak self] in
|
||||
self?.navigationButtonActions.1?()
|
||||
})
|
||||
}
|
||||
}
|
||||
items.append(item)
|
||||
|
@ -95,8 +95,10 @@ public final class JoinLinkPreviewController: ViewController {
|
||||
strongSelf.dismiss()
|
||||
case .invalidHash:
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLinks_InviteLinkExpired), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
strongSelf.dismiss()
|
||||
Queue.mainQueue().after(0.2) {
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLinks_InviteLinkExpired), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, error: { [weak self] error in
|
||||
|
@ -115,8 +115,6 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
|
||||
|
||||
self.backgroundNode.addSubnode(self.effectNode)
|
||||
self.backgroundNode.addSubnode(self.contentBackgroundNode)
|
||||
|
||||
@ -169,7 +167,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
|
||||
if let contentNode = contentNode, let previous = previous {
|
||||
contentNode.frame = previous.frame
|
||||
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
|
||||
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
|
||||
|
||||
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
|
||||
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
|
||||
@ -238,7 +236,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
if let contentNode = self.contentNode {
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: 0.0), size: gridSize))
|
||||
contentNode.updateLayout(size: gridSize, bottomInset: 0.0, transition: transition)
|
||||
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: 0.0, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,9 +83,9 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
|
||||
self.titleNode = ASTextNode()
|
||||
self.countNode = ASTextNode()
|
||||
self.aboutNode = ASTextNode()
|
||||
self.aboutNode.maximumNumberOfLines = 6
|
||||
self.aboutNode.maximumNumberOfLines = 8
|
||||
self.descriptionNode = ASTextNode()
|
||||
self.descriptionNode.maximumNumberOfLines = 0
|
||||
self.descriptionNode.maximumNumberOfLines = 3
|
||||
self.descriptionNode.textAlignment = .center
|
||||
self.peersScrollNode = ASScrollNode()
|
||||
self.peersScrollNode.view.showsHorizontalScrollIndicator = false
|
||||
@ -160,6 +160,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
|
||||
|
||||
self.actionButtonNode.pressed = { [weak self] in
|
||||
self?.join?()
|
||||
self?.actionButtonNode.transitionToProgress()
|
||||
}
|
||||
self.addSubnode(self.actionButtonNode)
|
||||
}
|
||||
@ -177,7 +178,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
|
||||
self.contentOffsetUpdated = f
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var nodeHeight: CGFloat = (self.peerNodes.isEmpty ? 264.0 : 364.0)
|
||||
|
||||
let paddedSize = CGSize(width: size.width - 60.0, height: size.height)
|
||||
@ -185,10 +186,21 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
|
||||
var aboutSize: CGSize?
|
||||
var descriptionSize: CGSize?
|
||||
if self.aboutNode.supernode != nil {
|
||||
if isLandscape {
|
||||
self.aboutNode.maximumNumberOfLines = 3
|
||||
} else {
|
||||
self.aboutNode.maximumNumberOfLines = 8
|
||||
}
|
||||
let measuredSize = self.aboutNode.measure(paddedSize)
|
||||
nodeHeight += measuredSize.height + 20.0
|
||||
aboutSize = measuredSize
|
||||
}
|
||||
|
||||
if isLandscape {
|
||||
self.descriptionNode.removeFromSupernode()
|
||||
} else if self.descriptionNode.supernode == nil {
|
||||
self.addSubnode(self.descriptionNode)
|
||||
}
|
||||
if self.descriptionNode.supernode != nil {
|
||||
let measuredSize = self.descriptionNode.measure(paddedSize)
|
||||
nodeHeight += measuredSize.height + 20.0 + 10.0
|
||||
@ -286,7 +298,7 @@ public final class JoinLinkPreviewLoadingContainerNode: ASDisplayNode, ShareCont
|
||||
self.contentOffsetUpdated = f
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
public func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let nodeHeight: CGFloat = 125.0
|
||||
|
||||
let indicatorSize = self.activityIndicator.calculateSizeThatFits(size)
|
||||
|
@ -82,7 +82,7 @@ final class LanguageLinkPreviewContentNode: ASDisplayNode, ShareContentContainer
|
||||
self.contentOffsetUpdated = f
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let insets = UIEdgeInsets(top: 12.0, left: 10.0, bottom: 12.0 + bottomInset, right: 10.0)
|
||||
let titleSpacing: CGFloat = 12.0
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: .greatestFiniteMagnitude))
|
||||
|
@ -188,7 +188,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, UIScro
|
||||
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
|
||||
if let contentNode = contentNode, let previous = previous {
|
||||
contentNode.frame = previous.frame
|
||||
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
|
||||
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
|
||||
|
||||
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
|
||||
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
|
||||
@ -274,7 +274,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, UIScro
|
||||
|
||||
if let contentNode = self.contentNode {
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
|
||||
contentNode.updateLayout(size: gridSize, bottomInset: bottomGridInset, transition: transition)
|
||||
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry {
|
||||
let arguments = arguments as! BlockedPeersControllerArguments
|
||||
switch self {
|
||||
case let .add(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: {
|
||||
arguments.addPeer()
|
||||
})
|
||||
case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
|
||||
|
836
submodules/SettingsUI/Sources/ThemeCarouselItem.swift
Normal file
836
submodules/SettingsUI/Sources/ThemeCarouselItem.swift
Normal file
@ -0,0 +1,836 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import MergeLists
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import WallpaperResources
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import ContextUI
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ShimmerEffect
|
||||
import StickerResources
|
||||
|
||||
private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let emojiFile: TelegramMediaFile?
|
||||
let themeReference: PresentationThemeReference
|
||||
let nightMode: Bool
|
||||
var selected: Bool
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let wallpaper: TelegramWallpaper?
|
||||
|
||||
var stableId: Int {
|
||||
return index
|
||||
}
|
||||
|
||||
static func ==(lhs: ThemeCarouselThemeEntry, rhs: ThemeCarouselThemeEntry) -> Bool {
|
||||
if lhs.index != rhs.index {
|
||||
return false
|
||||
}
|
||||
if lhs.themeReference.index != rhs.themeReference.index {
|
||||
return false
|
||||
}
|
||||
if lhs.nightMode != rhs.nightMode {
|
||||
return false
|
||||
}
|
||||
if lhs.selected != rhs.selected {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.wallpaper != rhs.wallpaper {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static func <(lhs: ThemeCarouselThemeEntry, rhs: ThemeCarouselThemeEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem {
|
||||
return ThemeCarouselThemeIconItem(context: context, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action, contextAction: contextAction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ThemeCarouselThemeIconItem: ListViewItem {
|
||||
let context: AccountContext
|
||||
let emojiFile: TelegramMediaFile?
|
||||
let themeReference: PresentationThemeReference
|
||||
let nightMode: Bool
|
||||
let selected: Bool
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let wallpaper: TelegramWallpaper?
|
||||
let action: (PresentationThemeReference) -> Void
|
||||
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
public init(context: AccountContext, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference, nightMode: Bool, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) {
|
||||
self.context = context
|
||||
self.emojiFile = emojiFile
|
||||
self.themeReference = themeReference
|
||||
self.nightMode = nightMode
|
||||
self.selected = selected
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.wallpaper = wallpaper
|
||||
self.action = action
|
||||
self.contextAction = contextAction
|
||||
}
|
||||
|
||||
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 = ThemeCarouselThemeItemIconNode()
|
||||
let (nodeLayout, apply) = node.asyncLayout()(self, params)
|
||||
node.insets = nodeLayout.insets
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in
|
||||
apply(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
assert(node() is ThemeCarouselThemeItemIconNode)
|
||||
if let nodeValue = node() as? ThemeCarouselThemeItemIconNode {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (nodeLayout, apply) = layout(self, params)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, { _ in
|
||||
apply(animation.isAnimated)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var selectable = true
|
||||
public func selected(listView: ListView) {
|
||||
self.action(self.themeReference)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let textFont = Font.regular(12.0)
|
||||
private let selectedTextFont = Font.bold(12.0)
|
||||
|
||||
private var cachedBorderImages: [String: UIImage] = [:]
|
||||
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
|
||||
let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)"
|
||||
if let image = cachedBorderImages[key] {
|
||||
return image
|
||||
} else {
|
||||
let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let lineWidth: CGFloat
|
||||
if selected {
|
||||
lineWidth = 2.0
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 3.0 + lineWidth / 2.0, dy: 3.0 + lineWidth / 2.0))
|
||||
|
||||
var accentColor = theme.list.itemAccentColor
|
||||
if accentColor.rgb == 0xffffff {
|
||||
accentColor = UIColor(rgb: 0x999999)
|
||||
}
|
||||
context.setStrokeColor(accentColor.cgColor)
|
||||
} else {
|
||||
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
|
||||
lineWidth = 1.0
|
||||
}
|
||||
|
||||
if bordered || selected {
|
||||
context.setLineWidth(lineWidth)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0))
|
||||
}
|
||||
})?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9)
|
||||
cachedBorderImages[key] = image
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
return .single(theme)
|
||||
|> map { theme -> (TransformImageArguments) -> DrawingContext? in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
|
||||
let drawingRect = arguments.drawingRect
|
||||
|
||||
context.withContext { c in
|
||||
c.clear(CGRect(origin: CGPoint(), size: drawingRect.size))
|
||||
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
|
||||
if let icon = generateTintedImage(image: UIImage(bundleImageName: "Settings/CreateThemeIcon"), color: theme.list.itemAccentColor) {
|
||||
c.draw(icon.cgImage!, in: CGRect(origin: CGPoint(x: floor((drawingRect.width - icon.size.width) / 2.0) - 3.0, y: floor((drawingRect.height - icon.size.height) / 2.0)), size: icon.size))
|
||||
}
|
||||
}
|
||||
addCorners(context, arguments: arguments)
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let emojiContainerNode: ASDisplayNode
|
||||
private let imageNode: TransformImageNode
|
||||
private let overlayNode: ASImageNode
|
||||
private let textNode: TextNode
|
||||
private let emojiNode: TextNode
|
||||
private let emojiImageNode: TransformImageNode
|
||||
private var animatedStickerNode: AnimatedStickerNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode
|
||||
var snapshotView: UIView?
|
||||
|
||||
var item: ThemeCarouselThemeIconItem?
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
self.visibilityStatus = self.visibility != .none
|
||||
}
|
||||
}
|
||||
|
||||
private var visibilityStatus: Bool = false {
|
||||
didSet {
|
||||
if self.visibilityStatus != oldValue {
|
||||
self.animatedStickerNode?.visibility = self.visibilityStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.emojiContainerNode = ASDisplayNode()
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 82.0, height: 108.0))
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.cornerRadius = 8.0
|
||||
self.imageNode.clipsToBounds = true
|
||||
|
||||
self.overlayNode = ASImageNode()
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 84.0, height: 110.0))
|
||||
self.overlayNode.isLayerBacked = true
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
|
||||
self.emojiNode = TextNode()
|
||||
self.emojiNode.isUserInteractionEnabled = false
|
||||
self.emojiNode.displaysAsynchronously = false
|
||||
|
||||
self.emojiImageNode = TransformImageNode()
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.imageNode)
|
||||
self.containerNode.addSubnode(self.overlayNode)
|
||||
self.containerNode.addSubnode(self.textNode)
|
||||
|
||||
self.addSubnode(self.emojiContainerNode)
|
||||
self.emojiContainerNode.addSubnode(self.emojiNode)
|
||||
self.emojiContainerNode.addSubnode(self.emojiImageNode)
|
||||
self.emojiContainerNode.addSubnode(self.placeholderNode)
|
||||
|
||||
var firstTime = true
|
||||
self.emojiImageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
if firstTime {
|
||||
strongSelf.emojiImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stickerFetchedDisposable.dispose()
|
||||
}
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if !animated {
|
||||
self.placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
self.placeholderNode.alpha = 0.0
|
||||
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.placeholderNode.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0))
|
||||
self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + emojiFrame.minX, y: rect.minY + emojiFrame.minY), size: emojiFrame.size), within: containerSize)
|
||||
}
|
||||
|
||||
override func selected() {
|
||||
let wasSelected = self.item?.selected ?? false
|
||||
super.selected()
|
||||
|
||||
if let animatedStickerNode = self.animatedStickerNode {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
if !wasSelected {
|
||||
animatedStickerNode.seekTo(.frameIndex(0))
|
||||
animatedStickerNode.play()
|
||||
|
||||
let scale: CGFloat = 2.6
|
||||
animatedStickerNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
animatedStickerNode.layer.animateSpring(from: 1.0 as NSNumber, to: scale as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
|
||||
animatedStickerNode.completed = { [weak animatedStickerNode, weak self] _ in
|
||||
guard let item = self?.item, item.selected else {
|
||||
return
|
||||
}
|
||||
animatedStickerNode?.transform = CATransform3DIdentity
|
||||
animatedStickerNode?.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func asyncLayout() -> (ThemeCarouselThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode)
|
||||
let makeImageLayout = self.imageNode.asyncLayout()
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { [weak self] item, params in
|
||||
var updatedThemeReference = false
|
||||
var updatedTheme = false
|
||||
var updatedNightMode = false
|
||||
var updatedWallpaper = false
|
||||
var updatedSelected = false
|
||||
|
||||
if currentItem?.themeReference != item.themeReference {
|
||||
updatedThemeReference = true
|
||||
}
|
||||
if currentItem?.nightMode != item.nightMode {
|
||||
updatedNightMode = true
|
||||
}
|
||||
if currentItem?.wallpaper != item.wallpaper {
|
||||
updatedWallpaper = true
|
||||
}
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = true
|
||||
}
|
||||
if currentItem?.selected != item.selected {
|
||||
updatedSelected = true
|
||||
}
|
||||
|
||||
|
||||
var string: String?
|
||||
if let _ = item.themeReference.emoticon {
|
||||
} else {
|
||||
string = "⚙️"
|
||||
}
|
||||
|
||||
let emojiTitle = NSAttributedString(string: string ?? "", font: Font.regular(20.0), textColor: .black)
|
||||
let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: emojiTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 120.0, height: 90.0), insets: UIEdgeInsets())
|
||||
return (itemLayout, { animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
if updatedThemeReference || updatedWallpaper || updatedNightMode {
|
||||
var themeReference = item.themeReference
|
||||
if case .builtin = themeReference, item.nightMode {
|
||||
themeReference = .builtin(.night)
|
||||
}
|
||||
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true))
|
||||
strongSelf.imageNode.backgroundColor = nil
|
||||
}
|
||||
|
||||
if updatedTheme || updatedSelected {
|
||||
strongSelf.overlayNode.image = generateBorderImage(theme: item.theme, bordered: false, selected: item.selected)
|
||||
}
|
||||
|
||||
if !item.selected && currentItem?.selected == true, let animatedStickerNode = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode.transform = CATransform3DIdentity
|
||||
|
||||
let initialScale: CGFloat = CGFloat((animatedStickerNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0)
|
||||
animatedStickerNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
|
||||
strongSelf.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0))
|
||||
|
||||
strongSelf.emojiContainerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
strongSelf.emojiContainerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0))
|
||||
|
||||
let _ = emojiApply()
|
||||
|
||||
let imageSize = CGSize(width: 82.0, height: 108.0)
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 6.0), size: imageSize)
|
||||
let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear))
|
||||
applyLayout()
|
||||
|
||||
strongSelf.overlayNode.frame = strongSelf.imageNode.frame.insetBy(dx: -1.0, dy: -1.0)
|
||||
strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 78.0), size: CGSize(width: 90.0, height: 30.0))
|
||||
strongSelf.emojiNode.isHidden = string == nil
|
||||
|
||||
let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0))
|
||||
if let file = item.emojiFile, currentItem == nil {
|
||||
let imageApply = strongSelf.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets()))
|
||||
imageApply()
|
||||
strongSelf.emojiImageNode.setSignal(chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: file.resource, animated: true, nilIfEmpty: true))
|
||||
strongSelf.emojiImageNode.frame = emojiFrame
|
||||
|
||||
let animatedStickerNode: AnimatedStickerNode
|
||||
if let current = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode = current
|
||||
} else {
|
||||
animatedStickerNode = AnimatedStickerNode()
|
||||
animatedStickerNode.started = { [weak self] in
|
||||
self?.emojiImageNode.isHidden = true
|
||||
}
|
||||
strongSelf.animatedStickerNode = animatedStickerNode
|
||||
strongSelf.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode)
|
||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix))
|
||||
|
||||
animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0)
|
||||
}
|
||||
animatedStickerNode.autoplay = true
|
||||
animatedStickerNode.visibility = strongSelf.visibilityStatus
|
||||
|
||||
strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start())
|
||||
|
||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, imageSize: thumbnailDimensions.cgSize)
|
||||
strongSelf.placeholderNode.frame = emojiFrame
|
||||
}
|
||||
|
||||
if let animatedStickerNode = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode.frame = emojiFrame
|
||||
animatedStickerNode.updateLayout(size: emojiFrame.size)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func crossfade() {
|
||||
// if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
// snapshotView.transform = self.containerNode.view.transform
|
||||
// snapshotView.frame = self.containerNode.view.frame
|
||||
// self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view)
|
||||
//
|
||||
// snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
// snapshotView?.removeFromSuperview()
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
super.animateInsertion(currentTimestamp, duration: duration, short: short)
|
||||
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
super.animateRemoved(currentTimestamp, duration: duration)
|
||||
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||
super.animateAdded(currentTimestamp, duration: duration)
|
||||
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
class ThemeCarouselThemeItem: ListViewItem, ItemListItem {
|
||||
var sectionId: ItemListSectionId
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let themes: [PresentationThemeReference]
|
||||
let animatedEmojiStickers: [String: [StickerPackItem]]
|
||||
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
|
||||
let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
|
||||
let nightMode: Bool
|
||||
let currentTheme: PresentationThemeReference
|
||||
let updatedTheme: (PresentationThemeReference) -> Void
|
||||
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], nightMode: Bool, currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.themes = themes
|
||||
self.animatedEmojiStickers = animatedEmojiStickers
|
||||
self.themeSpecificAccentColors = themeSpecificAccentColors
|
||||
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
|
||||
self.nightMode = nightMode
|
||||
self.currentTheme = currentTheme
|
||||
self.updatedTheme = updatedTheme
|
||||
self.contextAction = contextAction
|
||||
self.tag = tag
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
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 = ThemeCarouselThemeItemNode()
|
||||
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? ThemeCarouselThemeItemNode {
|
||||
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 struct ThemeCarouselThemeItemNodeTransition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let crossfade: Bool
|
||||
let entries: [ThemeCarouselThemeEntry]
|
||||
}
|
||||
|
||||
private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeCarouselThemeEntry], to toEntries: [ThemeCarouselThemeEntry], crossfade: Bool) -> ThemeCarouselThemeItemNodeTransition {
|
||||
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, 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 ThemeCarouselThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade, entries: toEntries)
|
||||
}
|
||||
|
||||
private func ensureThemeVisible(listNode: ListView, themeReference: PresentationThemeReference, animated: Bool) -> Bool {
|
||||
var resultNode: ThemeCarouselThemeItemIconNode?
|
||||
listNode.forEachItemNode { node in
|
||||
if resultNode == nil, let node = node as? ThemeCarouselThemeItemIconNode {
|
||||
if node.item?.themeReference.index == themeReference.index {
|
||||
resultNode = node
|
||||
}
|
||||
}
|
||||
}
|
||||
if let resultNode = resultNode {
|
||||
listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 57.0)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
private var snapshotView: UIView?
|
||||
|
||||
private let listNode: ListView
|
||||
private var entries: [ThemeCarouselThemeEntry]?
|
||||
private var enqueuedTransitions: [ThemeCarouselThemeItemNodeTransition] = []
|
||||
private var initialized = false
|
||||
|
||||
private var item: ThemeCarouselThemeItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
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.listNode = ListView()
|
||||
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.listNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: ThemeCarouselThemeItemNodeTransition) {
|
||||
self.enqueuedTransitions.append(transition)
|
||||
|
||||
if let _ = self.item {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransition() {
|
||||
guard let item = self.item, let transition = self.enqueuedTransitions.first else {
|
||||
return
|
||||
}
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if self.initialized && transition.crossfade {
|
||||
options.insert(.AnimateCrossfade)
|
||||
}
|
||||
options.insert(.Synchronous)
|
||||
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if !self.initialized {
|
||||
if let index = transition.entries.firstIndex(where: { entry in
|
||||
return entry.themeReference.index == item.currentTheme.index
|
||||
}) {
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
|
||||
self.initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ThemeCarouselThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
return { item, params, neighbors in
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
contentSize = CGSize(width: params.width, height: 133.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
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
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.containerNode.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.bottomStripeNode.isHidden = true
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height)
|
||||
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))
|
||||
|
||||
var listInsets = UIEdgeInsets()
|
||||
listInsets.top += params.leftInset + 12.0
|
||||
listInsets.bottom += params.rightInset + 12.0
|
||||
|
||||
strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width)
|
||||
strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 - 2.0)
|
||||
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
var entries: [ThemeCarouselThemeEntry] = []
|
||||
var index: Int = 0
|
||||
|
||||
var hasCurrentTheme = false
|
||||
for theme in item.themes {
|
||||
let selected = item.currentTheme.index == theme.index
|
||||
if selected {
|
||||
hasCurrentTheme = true
|
||||
}
|
||||
let emojiFile = theme.emoticon.flatMap { item.animatedEmojiStickers[$0]?.first?.file }
|
||||
entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: emojiFile, themeReference: theme, nightMode: item.nightMode, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil))
|
||||
index += 1
|
||||
}
|
||||
|
||||
if !hasCurrentTheme {
|
||||
entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: nil, themeReference: item.currentTheme, nightMode: false, selected: true, theme: item.theme, strings: item.strings, wallpaper: nil))
|
||||
}
|
||||
|
||||
let action: (PresentationThemeReference) -> Void = { [weak self] themeReference in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item?.updatedTheme(themeReference)
|
||||
let _ = ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true)
|
||||
}
|
||||
}
|
||||
let previousEntries = strongSelf.entries ?? []
|
||||
let crossfade = previousEntries.count != entries.count
|
||||
let transition = preparedTransition(context: item.context, action: action, contextAction: item.contextAction, from: previousEntries, to: entries, crossfade: crossfade)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
|
||||
strongSelf.entries = entries
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func prepareCrossfadeTransition() {
|
||||
guard self.snapshotView == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view)
|
||||
self.snapshotView = snapshotView
|
||||
}
|
||||
|
||||
// self.listNode.forEachVisibleItemNode { node in
|
||||
// if let node = node as? ThemeCarouselThemeItemIconNode {
|
||||
// node.prepareCrossfadeTransition()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func animateCrossfadeTransition() {
|
||||
guard self.snapshotView?.layer.animationKeys()?.isEmpty ?? true else {
|
||||
return
|
||||
}
|
||||
|
||||
var views: [UIView] = []
|
||||
if let snapshotView = self.snapshotView {
|
||||
views.append(snapshotView)
|
||||
self.snapshotView = nil
|
||||
}
|
||||
|
||||
self.listNode.forEachVisibleItemNode { node in
|
||||
if let node = node as? ThemeCarouselThemeItemIconNode {
|
||||
if let snapshotView = node.snapshotView {
|
||||
views.append(snapshotView)
|
||||
node.snapshotView = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
for view in views {
|
||||
view.alpha = 0.0
|
||||
}
|
||||
}, completion: { _ in
|
||||
for view in views {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
1227
submodules/SettingsUI/Sources/ThemePickerController.swift
Normal file
1227
submodules/SettingsUI/Sources/ThemePickerController.swift
Normal file
File diff suppressed because it is too large
Load Diff
588
submodules/SettingsUI/Sources/ThemePickerGridItem.swift
Normal file
588
submodules/SettingsUI/Sources/ThemePickerGridItem.swift
Normal file
@ -0,0 +1,588 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import MergeLists
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import WallpaperResources
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import ContextUI
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ShimmerEffect
|
||||
import StickerResources
|
||||
|
||||
private var cachedBorderImages: [String: UIImage] = [:]
|
||||
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
|
||||
let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)"
|
||||
if let image = cachedBorderImages[key] {
|
||||
return image
|
||||
} else {
|
||||
let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let lineWidth: CGFloat
|
||||
if selected {
|
||||
lineWidth = 2.0
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 3.0 + lineWidth / 2.0, dy: 3.0 + lineWidth / 2.0))
|
||||
|
||||
var accentColor = theme.list.itemAccentColor
|
||||
if accentColor.rgb == 0xffffff {
|
||||
accentColor = UIColor(rgb: 0x999999)
|
||||
}
|
||||
context.setStrokeColor(accentColor.cgColor)
|
||||
} else {
|
||||
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
|
||||
lineWidth = 1.0
|
||||
}
|
||||
|
||||
if bordered || selected {
|
||||
context.setLineWidth(lineWidth)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0))
|
||||
}
|
||||
})?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9)
|
||||
cachedBorderImages[key] = image
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
private final class ThemeGridThemeItemIconNode : ASDisplayNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let emojiContainerNode: ASDisplayNode
|
||||
private let imageNode: TransformImageNode
|
||||
private let overlayNode: ASImageNode
|
||||
private let textNode: TextNode
|
||||
private let emojiNode: TextNode
|
||||
private let emojiImageNode: TransformImageNode
|
||||
private var animatedStickerNode: AnimatedStickerNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode
|
||||
var snapshotView: UIView?
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
private var item: ThemeCarouselThemeIconItem?
|
||||
|
||||
override init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.emojiContainerNode = ASDisplayNode()
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 120.0, height: 150.0))
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.cornerRadius = 8.0
|
||||
self.imageNode.clipsToBounds = true
|
||||
|
||||
self.overlayNode = ASImageNode()
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 122.0, height: 152.0))
|
||||
self.overlayNode.isLayerBacked = true
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
|
||||
self.emojiNode = TextNode()
|
||||
self.emojiNode.isUserInteractionEnabled = false
|
||||
self.emojiNode.displaysAsynchronously = false
|
||||
|
||||
self.emojiImageNode = TransformImageNode()
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.imageNode)
|
||||
self.containerNode.addSubnode(self.overlayNode)
|
||||
self.containerNode.addSubnode(self.textNode)
|
||||
|
||||
self.addSubnode(self.emojiContainerNode)
|
||||
self.emojiContainerNode.addSubnode(self.emojiNode)
|
||||
self.emojiContainerNode.addSubnode(self.emojiImageNode)
|
||||
self.emojiContainerNode.addSubnode(self.placeholderNode)
|
||||
|
||||
var firstTime = true
|
||||
self.emojiImageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
if firstTime {
|
||||
strongSelf.emojiImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stickerFetchedDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
|
||||
}
|
||||
|
||||
@objc private func tap() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
let wasSelected = item.selected
|
||||
item.action(item.themeReference)
|
||||
|
||||
if let animatedStickerNode = self.animatedStickerNode {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
if !wasSelected {
|
||||
animatedStickerNode.seekTo(.frameIndex(0))
|
||||
animatedStickerNode.play()
|
||||
|
||||
// let scale: CGFloat = 2.6
|
||||
// animatedStickerNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
// animatedStickerNode.layer.animateSpring(from: 1.0 as NSNumber, to: scale as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
//
|
||||
// animatedStickerNode.completed = { [weak animatedStickerNode, weak self] _ in
|
||||
// guard let item = self?.item, item.selected else {
|
||||
// return
|
||||
// }
|
||||
// animatedStickerNode?.transform = CATransform3DIdentity
|
||||
// animatedStickerNode?.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if !animated {
|
||||
self.placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
self.placeholderNode.alpha = 0.0
|
||||
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.placeholderNode.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
// let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0))
|
||||
// self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + emojiFrame.minX, y: rect.minY + emojiFrame.minY), size: emojiFrame.size), within: containerSize)
|
||||
// }
|
||||
|
||||
func setup(item: ThemeCarouselThemeIconItem, size: CGSize) {
|
||||
let currentItem = self.item
|
||||
self.item = item
|
||||
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode)
|
||||
let makeImageLayout = self.imageNode.asyncLayout()
|
||||
|
||||
var updatedThemeReference = false
|
||||
var updatedTheme = false
|
||||
var updatedNightMode = false
|
||||
var updatedWallpaper = false
|
||||
var updatedSelected = false
|
||||
|
||||
if currentItem?.themeReference != item.themeReference {
|
||||
updatedThemeReference = true
|
||||
}
|
||||
if currentItem?.nightMode != item.nightMode {
|
||||
updatedNightMode = true
|
||||
}
|
||||
if currentItem?.wallpaper != item.wallpaper {
|
||||
updatedWallpaper = true
|
||||
}
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = true
|
||||
}
|
||||
if currentItem?.selected != item.selected {
|
||||
updatedSelected = true
|
||||
}
|
||||
|
||||
let string: String?
|
||||
if let _ = item.themeReference.emoticon {
|
||||
string = nil
|
||||
} else {
|
||||
string = themeDisplayName(strings: item.strings, reference: item.themeReference)
|
||||
}
|
||||
|
||||
let text = NSAttributedString(string: string ?? item.strings.Conversation_Theme_NoTheme, font: Font.bold(14.0), textColor: .white)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: 70.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
|
||||
let title = NSAttributedString(string: "", font: Font.regular(22.0), textColor: .black)
|
||||
let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
if updatedThemeReference || updatedWallpaper || updatedNightMode {
|
||||
var themeReference = item.themeReference
|
||||
if case .builtin = themeReference, item.nightMode {
|
||||
themeReference = .builtin(.night)
|
||||
}
|
||||
self.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true, large: true))
|
||||
self.imageNode.backgroundColor = nil
|
||||
}
|
||||
|
||||
if updatedTheme || updatedSelected {
|
||||
self.overlayNode.image = generateBorderImage(theme: item.theme, bordered: false, selected: item.selected)
|
||||
}
|
||||
|
||||
if !item.selected && currentItem?.selected == true, let animatedStickerNode = self.animatedStickerNode {
|
||||
animatedStickerNode.transform = CATransform3DIdentity
|
||||
|
||||
let initialScale: CGFloat = CGFloat((animatedStickerNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0)
|
||||
animatedStickerNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((90.0 - textLayout.size.width) / 2.0), y: 83.0), size: textLayout.size)
|
||||
self.textNode.isHidden = string == nil
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 120.0, height: 150.0))
|
||||
self.emojiContainerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 120.0, height: 150.0))
|
||||
|
||||
let _ = textApply()
|
||||
let _ = emojiApply()
|
||||
|
||||
let imageSize = CGSize(width: 120.0, height: 150.0)
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: imageSize)
|
||||
let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear))
|
||||
applyLayout()
|
||||
|
||||
self.overlayNode.frame = self.imageNode.frame.insetBy(dx: -1.0, dy: -1.0)
|
||||
self.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 79.0), size: CGSize(width: 90.0, height: 30.0))
|
||||
|
||||
let emojiFrame = CGRect(origin: CGPoint(x: 39.0, y: 98.0), size: CGSize(width: 42.0, height: 42.0))
|
||||
if let file = item.emojiFile, currentItem == nil {
|
||||
let imageApply = self.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets()))
|
||||
imageApply()
|
||||
self.emojiImageNode.setSignal(chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: file.resource, animated: true, nilIfEmpty: true))
|
||||
self.emojiImageNode.frame = emojiFrame
|
||||
|
||||
let animatedStickerNode: AnimatedStickerNode
|
||||
if let current = self.animatedStickerNode {
|
||||
animatedStickerNode = current
|
||||
} else {
|
||||
animatedStickerNode = AnimatedStickerNode()
|
||||
animatedStickerNode.started = { [weak self] in
|
||||
self?.emojiImageNode.isHidden = true
|
||||
}
|
||||
self.animatedStickerNode = animatedStickerNode
|
||||
self.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: self.placeholderNode)
|
||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix))
|
||||
|
||||
animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0)
|
||||
}
|
||||
animatedStickerNode.autoplay = true
|
||||
animatedStickerNode.visibility = true
|
||||
|
||||
self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start())
|
||||
|
||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, imageSize: thumbnailDimensions.cgSize)
|
||||
self.placeholderNode.frame = emojiFrame
|
||||
}
|
||||
|
||||
if let animatedStickerNode = self.animatedStickerNode {
|
||||
animatedStickerNode.frame = emojiFrame
|
||||
animatedStickerNode.updateLayout(size: emojiFrame.size)
|
||||
}
|
||||
}
|
||||
|
||||
func crossfade() {
|
||||
// if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
// snapshotView.transform = self.containerNode.view.transform
|
||||
// snapshotView.frame = self.containerNode.view.frame
|
||||
// self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view)
|
||||
//
|
||||
// snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
// snapshotView?.removeFromSuperview()
|
||||
// })
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
class ThemeGridThemeItem: ListViewItem, ItemListItem {
|
||||
var sectionId: ItemListSectionId
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let themes: [PresentationThemeReference]
|
||||
let animatedEmojiStickers: [String: [StickerPackItem]]
|
||||
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
|
||||
let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
|
||||
let nightMode: Bool
|
||||
let currentTheme: PresentationThemeReference
|
||||
let updatedTheme: (PresentationThemeReference) -> Void
|
||||
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], nightMode: Bool, currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.themes = themes
|
||||
self.animatedEmojiStickers = animatedEmojiStickers
|
||||
self.themeSpecificAccentColors = themeSpecificAccentColors
|
||||
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
|
||||
self.nightMode = nightMode
|
||||
self.currentTheme = currentTheme
|
||||
self.updatedTheme = updatedTheme
|
||||
self.contextAction = contextAction
|
||||
self.tag = tag
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
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 = ThemeGridThemeItemNode()
|
||||
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? ThemeGridThemeItemNode {
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
private var snapshotView: UIView?
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private var items: [ThemeCarouselThemeIconItem]?
|
||||
private var itemNodes: [Int64: ThemeGridThemeItemIconNode] = [:]
|
||||
private var initialized = false
|
||||
|
||||
private var item: ThemeGridThemeItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
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.scrollNode = ASScrollNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.scrollNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ThemeGridThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
return { item, params, neighbors in
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let itemSize = CGSize(width: 120.0, height: 150.0)
|
||||
let itemSpacing: CGFloat = 7.0
|
||||
let rows = ceil(CGFloat(item.themes.count) / 3.0)
|
||||
|
||||
contentSize = CGSize(width: params.width, height: itemSpacing + rows * (itemSize.height + itemSpacing) + 2.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
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
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
let updated = item.currentTheme != currentItem?.currentTheme || item.nightMode != currentItem?.nightMode
|
||||
|
||||
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.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height)
|
||||
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))
|
||||
|
||||
var validIds: [Int64] = []
|
||||
var index = 0
|
||||
for theme in item.themes {
|
||||
let selected = item.currentTheme.index == theme.index
|
||||
|
||||
let iconItem = ThemeCarouselThemeIconItem(context: item.context, emojiFile: theme.emoticon.flatMap { item.animatedEmojiStickers[$0]?.first?.file }, themeReference: theme, nightMode: item.nightMode, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil, action: { theme in
|
||||
item.updatedTheme(theme)
|
||||
}, contextAction: nil)
|
||||
|
||||
validIds.append(theme.index)
|
||||
var itemNode: ThemeGridThemeItemIconNode
|
||||
if let current = strongSelf.itemNodes[theme.index] {
|
||||
itemNode = current
|
||||
if updated {
|
||||
itemNode.setup(item: iconItem, size: itemSize)
|
||||
}
|
||||
} else {
|
||||
let addedItemNode = ThemeGridThemeItemIconNode()
|
||||
itemNode = addedItemNode
|
||||
addedItemNode.setup(item: iconItem, size: itemSize)
|
||||
strongSelf.itemNodes[theme.index] = addedItemNode
|
||||
strongSelf.addSubnode(addedItemNode)
|
||||
}
|
||||
|
||||
let col = CGFloat(index % 3)
|
||||
let row = floor(CGFloat(index) / 3.0)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: params.leftInset + itemSpacing + 1.0 + (itemSize.width + itemSpacing) * col, y: itemSpacing + 1.0 + (itemSize.height + itemSpacing) * row), size: itemSize)
|
||||
itemNode.frame = itemFrame
|
||||
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func prepareCrossfadeTransition() {
|
||||
guard self.snapshotView == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view)
|
||||
self.snapshotView = snapshotView
|
||||
}
|
||||
|
||||
// self.listNode.forEachVisibleItemNode { node in
|
||||
// if let node = node as? ThemeCarouselThemeItemIconNode {
|
||||
// node.prepareCrossfadeTransition()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func animateCrossfadeTransition() {
|
||||
// guard self.snapshotView?.layer.animationKeys()?.isEmpty ?? true else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var views: [UIView] = []
|
||||
// if let snapshotView = self.snapshotView {
|
||||
// views.append(snapshotView)
|
||||
// self.snapshotView = nil
|
||||
// }
|
||||
//
|
||||
// self.listNode.forEachVisibleItemNode { node in
|
||||
// if let node = node as? ThemeCarouselThemeItemIconNode {
|
||||
// if let snapshotView = node.snapshotView {
|
||||
// views.append(snapshotView)
|
||||
// node.snapshotView = nil
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// UIView.animate(withDuration: 0.3, animations: {
|
||||
// for view in views {
|
||||
// view.alpha = 0.0
|
||||
// }
|
||||
// }, completion: { _ in
|
||||
// for view in views {
|
||||
// view.removeFromSuperview()
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
@ -331,7 +331,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
previewThemePromise.set(.single(theme.withUpdated(name: "", defaultWallpaper: wallpaper)))
|
||||
case let .edit(info):
|
||||
hasSettings = info.theme.settings != nil
|
||||
settingsPromise.set(.single(info.theme.settings))
|
||||
settingsPromise.set(.single(info.theme.settings?.first))
|
||||
if let file = info.theme.file, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) {
|
||||
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
|
||||
previewThemePromise.set(cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|
||||
@ -629,7 +629,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
switch mode {
|
||||
case .create:
|
||||
if let themeResource = themeResource {
|
||||
let _ = (prepare |> then(createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData, settings: settings)
|
||||
let _ = (prepare |> then(createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData, settings: settings.flatMap { [$0] })
|
||||
|> deliverOnMainQueue)).start(next: { next in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
@ -663,7 +663,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
})
|
||||
}
|
||||
case let .edit(info):
|
||||
let _ = (prepare |> then(updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme, title: state.title, slug: state.slug, resource: state.converting ? nil : themeResource, settings: settings)
|
||||
let _ = (prepare |> then(updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme, title: state.title, slug: state.slug, resource: state.converting ? nil : themeResource, settings: settings.flatMap { [$0] })
|
||||
|> deliverOnMainQueue)).start(next: { next in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
|
@ -195,7 +195,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
if let settings = themeSettings {
|
||||
hasSettings = true
|
||||
baseTheme = settings.baseTheme
|
||||
} else if case let .cloud(theme) = generalThemeReference, let settings = theme.theme.settings {
|
||||
} else if case let .cloud(theme) = generalThemeReference, let settings = theme.theme.settings?.first {
|
||||
hasSettings = true
|
||||
baseTheme = settings.baseTheme
|
||||
} else if case let .builtin(theme) = generalThemeReference {
|
||||
@ -218,7 +218,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
} else if case let .colors(theme, create) = strongSelf.mode {
|
||||
var baseTheme: TelegramBaseTheme
|
||||
var telegramTheme: TelegramTheme?
|
||||
if case let .cloud(theme) = theme, let settings = theme.theme.settings {
|
||||
if case let .cloud(theme) = theme, let settings = theme.theme.settings?.first {
|
||||
telegramTheme = theme.theme
|
||||
baseTheme = settings.baseTheme
|
||||
} else if case let .builtin(theme) = theme {
|
||||
@ -234,7 +234,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
|
||||
let apply: Signal<Void, CreateThemeError>
|
||||
if create {
|
||||
apply = (prepareWallpaper |> then(createTheme(account: context.account, title: generateThemeName(accentColor: state.accentColor.color), resource: nil, thumbnailData: nil, settings: settings)))
|
||||
apply = (prepareWallpaper |> then(createTheme(account: context.account, title: generateThemeName(accentColor: state.accentColor.color), resource: nil, thumbnailData: nil, settings: [settings])))
|
||||
|> mapToSignal { next -> Signal<Void, CreateThemeError> in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
@ -263,7 +263,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
}
|
||||
}
|
||||
} else if let theme = telegramTheme {
|
||||
apply = (prepareWallpaper |> then(updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme, title: theme.title, slug: theme.slug, resource: nil, settings: settings)))
|
||||
apply = (prepareWallpaper |> then(updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme, title: theme.title, slug: theme.slug, resource: nil, settings: [settings])))
|
||||
|> mapToSignal { next -> Signal<Void, CreateThemeError> in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
@ -439,7 +439,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
if let color = themeSpecificAccentColor?.color, color != .clear {
|
||||
accentColor = color
|
||||
customAccentColor = accentColor
|
||||
} else if case let .cloud(cloudTheme) = initialThemeReference, let settings = cloudTheme.theme.settings {
|
||||
} else if case let .cloud(cloudTheme) = initialThemeReference, let settings = cloudTheme.theme.settings?.first {
|
||||
accentColor = UIColor(rgb: settings.accentColor)
|
||||
customAccentColor = accentColor
|
||||
} else {
|
||||
@ -457,7 +457,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
wallpaper = theme.chat.defaultWallpaper
|
||||
}
|
||||
|
||||
if case let .cloud(cloudTheme) = initialThemeReference, let settings = cloudTheme.theme.settings {
|
||||
if case let .cloud(cloudTheme) = initialThemeReference, let settings = cloudTheme.theme.settings?.first {
|
||||
animateMessageColors = settings.animateMessageColors
|
||||
outgoingAccentColor = settings.outgoingAccentColor.flatMap { UIColor(rgb: $0) }
|
||||
} else if let referenceTheme = referenceTheme {
|
||||
@ -493,7 +493,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
}
|
||||
} else {
|
||||
let presentationTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference)!
|
||||
if case let .cloud(theme) = themeReference, let themeSettings = theme.theme.settings {
|
||||
if case let .cloud(theme) = themeReference, let themeSettings = theme.theme.settings?.first {
|
||||
accentColor = UIColor(argb: themeSettings.accentColor)
|
||||
|
||||
if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] {
|
||||
|
@ -18,7 +18,7 @@ import UndoUI
|
||||
import TelegramNotices
|
||||
|
||||
public enum ThemePreviewSource {
|
||||
case settings(PresentationThemeReference, TelegramWallpaper?)
|
||||
case settings(PresentationThemeReference, TelegramWallpaper?, Bool)
|
||||
case theme(TelegramTheme)
|
||||
case slug(String, TelegramMediaFile)
|
||||
case themeSettings(String, TelegramThemeSettings)
|
||||
@ -106,15 +106,19 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
))
|
||||
hasInstallsCount = true
|
||||
case let .settings(themeReference, _):
|
||||
case let .settings(themeReference, _, _):
|
||||
if case let .cloud(theme) = themeReference {
|
||||
self.theme.set(getTheme(account: context.account, slug: theme.theme.slug)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in
|
||||
return .single(nil)
|
||||
})
|
||||
themeName = theme.theme.title
|
||||
hasInstallsCount = true
|
||||
if let emoticon = theme.theme.emoticon{
|
||||
themeName = emoticon
|
||||
} else {
|
||||
themeName = theme.theme.title
|
||||
hasInstallsCount = true
|
||||
}
|
||||
} else {
|
||||
self.theme.set(.single(nil))
|
||||
themeName = previewTheme.name.string
|
||||
@ -145,7 +149,7 @@ public final class ThemePreviewController: ViewController {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] theme, presentationTheme in
|
||||
if let strongSelf = self, let theme = theme {
|
||||
let titleView = CounterContollerTitleView(theme: strongSelf.previewTheme)
|
||||
titleView.title = CounterContollerTitle(title: themeName, counter: strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount ?? 0)))
|
||||
titleView.title = CounterContollerTitle(title: themeName, counter: hasInstallsCount ? strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount ?? 0)) : "")
|
||||
strongSelf.navigationItem.titleView = titleView
|
||||
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings))
|
||||
}
|
||||
@ -184,13 +188,14 @@ public final class ThemePreviewController: ViewController {
|
||||
super.loadDisplayNode()
|
||||
|
||||
var isPreview = false
|
||||
if case .settings = self.source {
|
||||
isPreview = true
|
||||
}
|
||||
|
||||
var forceReady = false
|
||||
var initialWallpaper: TelegramWallpaper?
|
||||
if case let .settings(_, currentWallpaper) = self.source, let wallpaper = currentWallpaper {
|
||||
initialWallpaper = wallpaper
|
||||
if case let .settings(_, currentWallpaper, preview) = self.source {
|
||||
isPreview = preview
|
||||
forceReady = true
|
||||
if let wallpaper = currentWallpaper {
|
||||
initialWallpaper = wallpaper
|
||||
}
|
||||
}
|
||||
|
||||
self.displayNode = ThemePreviewControllerNode(context: self.context, previewTheme: self.previewTheme, initialWallpaper: initialWallpaper, dismiss: { [weak self] in
|
||||
@ -201,7 +206,7 @@ public final class ThemePreviewController: ViewController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.apply()
|
||||
}
|
||||
}, isPreview: isPreview, ready: self._ready)
|
||||
}, isPreview: isPreview, forceReady: forceReady, ready: self._ready)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
let previewTheme = self.previewTheme
|
||||
@ -227,7 +232,7 @@ public final class ThemePreviewController: ViewController {
|
||||
let disposable = self.applyDisposable
|
||||
|
||||
switch self.source {
|
||||
case let .settings(reference, _):
|
||||
case let .settings(reference, _, _):
|
||||
theme = .single(reference)
|
||||
case .theme, .slug, .themeSettings:
|
||||
theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1))
|
||||
@ -380,7 +385,7 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
|
||||
var themeSpecificAccentColors = updatedSettings.themeSpecificAccentColors
|
||||
if case let .cloud(info) = updatedTheme, let settings = info.theme.settings {
|
||||
if case let .cloud(info) = updatedTheme, let settings = info.theme.settings?.first {
|
||||
let baseThemeReference = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
|
||||
themeSpecificAccentColors[baseThemeReference.index] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
|
||||
}
|
||||
@ -421,7 +426,9 @@ public final class ThemePreviewController: ViewController {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] previousDefaultTheme in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout {
|
||||
Queue.mainQueue().after(0.3) {
|
||||
if layout.size.width >= 375.0 {
|
||||
if case .settings = strongSelf.source {
|
||||
|
||||
} else if layout.size.width >= 375.0 {
|
||||
let navigationController = strongSelf.navigationController as? NavigationController
|
||||
if let (previousDefaultTheme, previousAccentColor, autoNightMode, theme, _) = previousDefaultTheme {
|
||||
let _ = (ApplicationSpecificNotice.getThemeChangeTip(accountManager: strongSelf.context.sharedContext.accountManager)
|
||||
|
@ -72,7 +72,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
private var wallpaper: TelegramWallpaper
|
||||
|
||||
init(context: AccountContext, previewTheme: PresentationTheme, initialWallpaper: TelegramWallpaper?, dismiss: @escaping () -> Void, apply: @escaping () -> Void, isPreview: Bool, ready: Promise<Bool>) {
|
||||
init(context: AccountContext, previewTheme: PresentationTheme, initialWallpaper: TelegramWallpaper?, dismiss: @escaping () -> Void, apply: @escaping () -> Void, isPreview: Bool, forceReady: Bool, ready: Promise<Bool>) {
|
||||
self.context = context
|
||||
self.previewTheme = previewTheme
|
||||
self.isPreview = isPreview
|
||||
@ -125,7 +125,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings, doneButtonType: .set)
|
||||
|
||||
if case .file = previewTheme.chat.defaultWallpaper {
|
||||
if case .file = previewTheme.chat.defaultWallpaper, !forceReady {
|
||||
self.toolbarNode.setDoneEnabled(false)
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ enum ThemeSettingsColorOption: Equatable {
|
||||
case let .accentColor(color):
|
||||
return color.color
|
||||
case let .theme(reference):
|
||||
if case let .cloud(theme) = reference, let settings = theme.theme.settings {
|
||||
if case let .cloud(theme) = reference, let settings = theme.theme.settings?.first {
|
||||
return UIColor(argb: settings.accentColor)
|
||||
} else {
|
||||
return nil
|
||||
@ -115,7 +115,7 @@ enum ThemeSettingsColorOption: Equatable {
|
||||
case let .accentColor(color):
|
||||
return color.plainBubbleColors
|
||||
case let .theme(reference):
|
||||
if case let .cloud(theme) = reference, let settings = theme.theme.settings, !settings.messageColors.isEmpty {
|
||||
if case let .cloud(theme) = reference, let settings = theme.theme.settings?.first, !settings.messageColors.isEmpty {
|
||||
return settings.messageColors
|
||||
} else {
|
||||
return []
|
||||
@ -128,7 +128,7 @@ enum ThemeSettingsColorOption: Equatable {
|
||||
case let .accentColor(color):
|
||||
return color.customBubbleColors
|
||||
case let .theme(reference):
|
||||
if case let .cloud(theme) = reference, let settings = theme.theme.settings, !settings.messageColors.isEmpty {
|
||||
if case let .cloud(theme) = reference, let settings = theme.theme.settings?.first, !settings.messageColors.isEmpty {
|
||||
return settings.messageColors
|
||||
} else {
|
||||
return []
|
||||
|
@ -18,34 +18,6 @@ import AccountContext
|
||||
import ContextUI
|
||||
import UndoUI
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
weak var sourceNode: ASDisplayNode?
|
||||
|
||||
let navigationController: NavigationController? = nil
|
||||
|
||||
let passthroughTouches: Bool = false
|
||||
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||
let sourceNode = self.sourceNode
|
||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||
if let sourceNode = sourceNode {
|
||||
return (sourceNode, sourceNode.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func animatedIn() {
|
||||
}
|
||||
}
|
||||
|
||||
func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
|
||||
let name: String
|
||||
switch reference {
|
||||
@ -63,7 +35,11 @@ func themeDisplayName(strings: PresentationStrings, reference: PresentationTheme
|
||||
case let .local(theme):
|
||||
name = theme.title
|
||||
case let .cloud(theme):
|
||||
name = theme.theme.title
|
||||
if let emoticon = theme.theme.emoticon {
|
||||
name = emoticon
|
||||
} else {
|
||||
name = theme.theme.title
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
@ -71,10 +47,11 @@ func themeDisplayName(strings: PresentationStrings, reference: PresentationTheme
|
||||
private final class ThemeSettingsControllerArguments {
|
||||
let context: AccountContext
|
||||
let selectTheme: (PresentationThemeReference) -> Void
|
||||
let selectFontSize: (PresentationFontSize) -> Void
|
||||
let openThemeSettings: () -> Void
|
||||
let openWallpaperSettings: () -> Void
|
||||
let selectAccentColor: (PresentationThemeAccentColor?) -> Void
|
||||
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
|
||||
let toggleNightTheme: (Bool) -> Void
|
||||
let openAutoNightTheme: () -> Void
|
||||
let openTextSize: () -> Void
|
||||
let openBubbleSettings: () -> Void
|
||||
@ -85,13 +62,14 @@ 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, 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) {
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (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
|
||||
self.openThemeSettings = openThemeSettings
|
||||
self.openWallpaperSettings = openWallpaperSettings
|
||||
self.selectAccentColor = selectAccentColor
|
||||
self.openAccentColorPicker = openAccentColorPicker
|
||||
self.toggleNightTheme = toggleNightTheme
|
||||
self.openAutoNightTheme = openAutoNightTheme
|
||||
self.openTextSize = openTextSize
|
||||
self.openBubbleSettings = openBubbleSettings
|
||||
@ -106,8 +84,8 @@ private final class ThemeSettingsControllerArguments {
|
||||
|
||||
private enum ThemeSettingsControllerSection: Int32 {
|
||||
case chatPreview
|
||||
case background
|
||||
case fontSize
|
||||
case nightMode
|
||||
case message
|
||||
case icon
|
||||
case other
|
||||
}
|
||||
@ -132,15 +110,14 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
|
||||
|
||||
private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
case themeListHeader(PresentationTheme, String)
|
||||
case fontSizeHeader(PresentationTheme, String)
|
||||
case fontSize(PresentationTheme, PresentationFontSize)
|
||||
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
|
||||
case themes(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, Bool, [String: [StickerPackItem]])
|
||||
case chatTheme(PresentationTheme, String)
|
||||
case wallpaper(PresentationTheme, String)
|
||||
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?)
|
||||
case autoNight(PresentationTheme, String, Bool)
|
||||
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?)
|
||||
case otherHeader(PresentationTheme, String)
|
||||
@ -150,12 +127,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .themeListHeader, .chatPreview, .themeItem, .accentColor:
|
||||
case .themeListHeader, .chatPreview, .themes, .chatTheme, .wallpaper:
|
||||
return ThemeSettingsControllerSection.chatPreview.rawValue
|
||||
case .fontSizeHeader, .fontSize:
|
||||
return ThemeSettingsControllerSection.fontSize.rawValue
|
||||
case .wallpaper, .autoNightTheme, .textSize, .bubbleSettings:
|
||||
return ThemeSettingsControllerSection.background.rawValue
|
||||
case .autoNight, .autoNightTheme:
|
||||
return ThemeSettingsControllerSection.nightMode.rawValue
|
||||
case .textSize, .bubbleSettings:
|
||||
return ThemeSettingsControllerSection.message.rawValue
|
||||
case .iconHeader, .iconItem:
|
||||
return ThemeSettingsControllerSection.icon.rawValue
|
||||
case .otherHeader, .largeEmoji, .animations, .animationsInfo:
|
||||
@ -169,11 +146,13 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
return 0
|
||||
case .chatPreview:
|
||||
return 1
|
||||
case .themeItem:
|
||||
case .themes:
|
||||
return 2
|
||||
case .accentColor:
|
||||
return 4
|
||||
case .chatTheme:
|
||||
return 3
|
||||
case .wallpaper:
|
||||
return 4
|
||||
case .autoNight:
|
||||
return 5
|
||||
case .autoNightTheme:
|
||||
return 6
|
||||
@ -181,22 +160,18 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
return 7
|
||||
case .bubbleSettings:
|
||||
return 8
|
||||
case .fontSizeHeader:
|
||||
return 9
|
||||
case .fontSize:
|
||||
return 10
|
||||
case .iconHeader:
|
||||
return 11
|
||||
return 9
|
||||
case .iconItem:
|
||||
return 12
|
||||
return 10
|
||||
case .otherHeader:
|
||||
return 13
|
||||
return 11
|
||||
case .largeEmoji:
|
||||
return 14
|
||||
return 12
|
||||
case .animations:
|
||||
return 15
|
||||
return 13
|
||||
case .animationsInfo:
|
||||
return 16
|
||||
return 14
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,14 +183,26 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .themes(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsNightMode, lhsAnimatedEmojiStickers):
|
||||
if case let .themes(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsNightMode, rhsAnimatedEmojiStickers) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsNightMode == rhsNightMode, lhsAnimatedEmojiStickers == rhsAnimatedEmojiStickers {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .chatTheme(lhsTheme, lhsText):
|
||||
if case let .chatTheme(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .wallpaper(lhsTheme, lhsText):
|
||||
if case let .wallpaper(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .accentColor(lhsTheme, _, lhsCurrentTheme, lhsThemes, lhsColor):
|
||||
if case let .accentColor(rhsTheme, _, rhsCurrentTheme, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsThemes == rhsThemes, lhsColor == rhsColor {
|
||||
case let .autoNight(lhsTheme, lhsText, lhsValue):
|
||||
if case let .autoNight(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -244,24 +231,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsAllThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeSpecificChatWallpapers, lhsCurrentColor):
|
||||
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsAllThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsThemeSpecificChatWallpapers, rhsCurrentColor) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsAllThemes == rhsAllThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsThemeSpecificChatWallpapers == rhsThemeSpecificChatWallpapers, lhsCurrentColor == rhsCurrentColor {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .fontSizeHeader(lhsTheme, lhsText):
|
||||
if case let .fontSizeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .fontSize(lhsTheme, lhsFontSize):
|
||||
if case let .fontSize(rhsTheme, rhsFontSize) = rhs, lhsTheme === rhsTheme, lhsFontSize == rhsFontSize {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .iconHeader(lhsTheme, lhsText):
|
||||
if case let .iconHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -308,101 +277,26 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! ThemeSettingsControllerArguments
|
||||
switch self {
|
||||
case let .fontSizeHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .fontSize(theme, fontSize):
|
||||
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
|
||||
arguments.selectFontSize(value)
|
||||
}, tag: ThemeSettingsEntryTag.fontSize)
|
||||
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 .themes(theme, strings, chatThemes, currentTheme, nightMode, animatedEmojiStickers):
|
||||
return ThemeCarouselThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], nightMode: nightMode, currentTheme: currentTheme, updatedTheme: { theme in
|
||||
arguments.selectTheme(theme)
|
||||
}, contextAction: { theme, node, gesture in
|
||||
arguments.themeContextAction(false, theme, node, gesture)
|
||||
}, tag: nil)
|
||||
case let .chatTheme(_, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openThemeSettings()
|
||||
})
|
||||
case let .wallpaper(_, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openWallpaperSettings()
|
||||
})
|
||||
case let .accentColor(theme, generalThemeReference, currentTheme, themes, color):
|
||||
var colorItems: [ThemeSettingsAccentColor] = []
|
||||
|
||||
for theme in themes {
|
||||
colorItems.append(.theme(theme))
|
||||
}
|
||||
|
||||
var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue)
|
||||
var colors = PresentationThemeBaseColor.allCases
|
||||
colors = colors.filter { $0 != .custom && $0 != .preset && $0 != .theme }
|
||||
if case let .builtin(name) = generalThemeReference {
|
||||
if name == .dayClassic {
|
||||
colorItems.append(.default)
|
||||
defaultColor = nil
|
||||
|
||||
for preset in dayClassicColorPresets {
|
||||
colorItems.append(.preset(preset))
|
||||
}
|
||||
} else if name == .day {
|
||||
colorItems.append(.color(.blue))
|
||||
colors = colors.filter { $0 != .blue }
|
||||
|
||||
for preset in dayColorPresets {
|
||||
colorItems.append(.preset(preset))
|
||||
}
|
||||
} else if name == .night {
|
||||
colorItems.append(.color(.blue))
|
||||
colors = colors.filter { $0 != .blue }
|
||||
|
||||
for preset in nightColorPresets {
|
||||
colorItems.append(.preset(preset))
|
||||
}
|
||||
}
|
||||
if name != .day {
|
||||
colors = colors.filter { $0 != .black }
|
||||
}
|
||||
if name == .night {
|
||||
colors = colors.filter { $0 != .gray }
|
||||
} else {
|
||||
colors = colors.filter { $0 != .white }
|
||||
}
|
||||
}
|
||||
var currentColor = color ?? defaultColor.flatMap { .accentColor($0) }
|
||||
if let color = currentColor, case let .accentColor(accentColor) = color, accentColor.baseColor == .theme {
|
||||
var themeExists = false
|
||||
if let _ = themes.first(where: { $0.index == accentColor.themeIndex }) {
|
||||
themeExists = true
|
||||
}
|
||||
if !themeExists {
|
||||
currentColor = defaultColor.flatMap { .accentColor($0) }
|
||||
}
|
||||
}
|
||||
colorItems.append(contentsOf: colors.map { .color($0) })
|
||||
if let index = colorItems.firstIndex(where: { item in
|
||||
if case .default = item {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
if index > 0 {
|
||||
let item = colorItems[index]
|
||||
colorItems.remove(at: index)
|
||||
colorItems.insert(item, at: 1)
|
||||
}
|
||||
}
|
||||
|
||||
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, generalThemeReference: generalThemeReference, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in
|
||||
if let color = color {
|
||||
switch color {
|
||||
case let .accentColor(color):
|
||||
arguments.selectAccentColor(color)
|
||||
case let .theme(theme):
|
||||
arguments.selectTheme(theme)
|
||||
}
|
||||
} else {
|
||||
arguments.selectAccentColor(nil)
|
||||
}
|
||||
}, contextAction: { isCurrent, theme, color, node, gesture in
|
||||
arguments.colorContextAction(isCurrent, theme, color, node, gesture)
|
||||
}, openColorPicker: { create in
|
||||
arguments.openAccentColorPicker(currentTheme, create)
|
||||
}, tag: ThemeSettingsEntryTag.accentColor)
|
||||
case let .autoNight(_, title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleNightTheme(value)
|
||||
}, tag: nil)
|
||||
case let .autoNightTheme(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.openAutoNightTheme()
|
||||
@ -417,18 +311,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
case let .themeListHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
|
||||
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: true, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in
|
||||
if case let .cloud(theme) = theme, theme.theme.file == nil && theme.theme.settings == nil {
|
||||
if theme.theme.isCreator {
|
||||
arguments.editTheme(theme)
|
||||
}
|
||||
} else {
|
||||
arguments.selectTheme(theme)
|
||||
}
|
||||
}, contextAction: { theme, node, gesture in
|
||||
arguments.themeContextAction(theme.index == currentTheme.index, theme, node, gesture)
|
||||
}, tag: ThemeSettingsEntryTag.theme)
|
||||
case let .iconHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .iconItem(theme, strings, icons, value):
|
||||
@ -451,7 +333,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] {
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] {
|
||||
var entries: [ThemeSettingsControllerEntry] = []
|
||||
|
||||
let strings = presentationData.strings
|
||||
@ -459,44 +341,46 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
||||
entries.append(.themeListHeader(presentationData.theme, title))
|
||||
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 {
|
||||
return theme.theme.settings == nil
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
let generalThemeReference: PresentationThemeReference
|
||||
if case let .cloud(theme) = themeReference, let settings = theme.theme.settings {
|
||||
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
|
||||
} else {
|
||||
generalThemeReference = themeReference
|
||||
}
|
||||
|
||||
entries.append(.themeItem(presentationData.theme, presentationData.strings, generalThemes, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
|
||||
|
||||
if case let .builtin(builtinTheme) = generalThemeReference {
|
||||
let colorThemes = availableThemes.filter { reference in
|
||||
if case let .cloud(theme) = reference, let settings = theme.theme.settings, settings.baseTheme == builtinTheme.baseTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var colorOption: ThemeSettingsColorOption?
|
||||
if case .builtin = themeReference {
|
||||
colorOption = presentationThemeSettings.themeSpecificAccentColors[themeReference.index].flatMap { .accentColor($0) }
|
||||
} else {
|
||||
colorOption = .theme(themeReference)
|
||||
}
|
||||
|
||||
entries.append(.accentColor(presentationData.theme, generalThemeReference, themeReference, colorThemes, colorOption))
|
||||
}
|
||||
entries.append(.themes(presentationData.theme, presentationData.strings, chatThemes, themeReference, presentationThemeSettings.automaticThemeSwitchSetting.force, animatedEmojiStickers))
|
||||
// let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
|
||||
// if case let .cloud(theme) = reference {
|
||||
// return theme.theme.settings == nil
|
||||
// } else {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let generalThemeReference: PresentationThemeReference
|
||||
// if case let .cloud(theme) = themeReference, let settings = theme.theme.settings {
|
||||
// generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
|
||||
// } else {
|
||||
// generalThemeReference = themeReference
|
||||
// }
|
||||
|
||||
// entries.append(.themeItem(presentationData.theme, presentationData.strings, generalThemes, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
|
||||
//
|
||||
// if case let .builtin(builtinTheme) = generalThemeReference {
|
||||
// let colorThemes = availableThemes.filter { reference in
|
||||
// if case let .cloud(theme) = reference, let settings = theme.theme.settings, settings.baseTheme == builtinTheme.baseTheme {
|
||||
// return true
|
||||
// } else {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var colorOption: ThemeSettingsColorOption?
|
||||
// if case .builtin = themeReference {
|
||||
// colorOption = presentationThemeSettings.themeSpecificAccentColors[themeReference.index].flatMap { .accentColor($0) }
|
||||
// } else {
|
||||
// colorOption = .theme(themeReference)
|
||||
// }
|
||||
//
|
||||
// entries.append(.accentColor(presentationData.theme, generalThemeReference, themeReference, colorThemes, colorOption))
|
||||
// }
|
||||
entries.append(.chatTheme(presentationData.theme, "Chat Themes"))
|
||||
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
|
||||
|
||||
entries.append(.autoNight(presentationData.theme, strings.Appearance_NightTheme, presentationThemeSettings.automaticThemeSwitchSetting.force))
|
||||
let autoNightMode: String
|
||||
switch presentationThemeSettings.automaticThemeSwitchSetting.trigger {
|
||||
case .system:
|
||||
@ -585,15 +469,43 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
let removedThemeIndexesPromise = Promise<Set<Int64>>(Set())
|
||||
let removedThemeIndexes = Atomic<Set<Int64>>(value: Set())
|
||||
|
||||
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|
||||
|> map { animatedEmoji -> [String: [StickerPackItem]] in
|
||||
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
||||
switch animatedEmoji {
|
||||
case let .result(_, items, _):
|
||||
for item in items {
|
||||
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
|
||||
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
|
||||
if animatedEmojiStickers[strippedEmoji] == nil {
|
||||
animatedEmojiStickers[strippedEmoji] = [item]
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return animatedEmojiStickers
|
||||
}
|
||||
|
||||
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
|
||||
selectThemeImpl?(theme)
|
||||
}, selectFontSize: { _ in
|
||||
}, openThemeSettings: {
|
||||
pushControllerImpl?(themePickerController(context: context))
|
||||
}, openWallpaperSettings: {
|
||||
pushControllerImpl?(ThemeGridController(context: context))
|
||||
}, selectAccentColor: { accentColor in
|
||||
selectAccentColorImpl?(accentColor)
|
||||
}, openAccentColorPicker: { themeReference, create in
|
||||
openAccentColorPickerImpl?(themeReference, create)
|
||||
}, toggleNightTheme: { value in
|
||||
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
var current = current
|
||||
current.automaticThemeSwitchSetting.force = value
|
||||
return current
|
||||
}).start()
|
||||
presentCrossfadeControllerImpl?(true)
|
||||
}, openAutoNightTheme: {
|
||||
pushControllerImpl?(themeAutoNightSettingsController(context: context))
|
||||
}, openTextSize: {
|
||||
@ -678,7 +590,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper))
|
||||
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper, true))
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if case let .cloud(theme) = reference {
|
||||
@ -764,7 +676,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
})))
|
||||
|
||||
if isCurrent, let currentThemeIndex = themes.firstIndex(where: { $0.id == theme.theme.id }) {
|
||||
if let settings = theme.theme.settings {
|
||||
if let settings = theme.theme.settings?.first {
|
||||
if settings.baseTheme == .night {
|
||||
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
|
||||
} else {
|
||||
@ -827,7 +739,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
return (accentColor, wallpaper)
|
||||
} |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, PresentationThemeReference, Bool, TelegramWallpaper?), NoError> in
|
||||
let generalThemeReference: PresentationThemeReference
|
||||
if let _ = accentColor, case let .cloud(theme) = reference, let settings = theme.theme.settings {
|
||||
if let _ = accentColor, case let .cloud(theme) = reference, let settings = theme.theme.settings?.first {
|
||||
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
|
||||
} else {
|
||||
generalThemeReference = reference
|
||||
@ -910,7 +822,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(effectiveThemeReference, wallpaper))
|
||||
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(effectiveThemeReference, wallpaper, true))
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if let accentColor = accentColor {
|
||||
@ -948,9 +860,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
|> deliverOnMainQueue).start(next: { wallpaper in
|
||||
var hasSettings = false
|
||||
var settings: TelegramThemeSettings?
|
||||
if case let .cloud(cloudTheme) = effectiveThemeReference, cloudTheme.theme.settings != nil {
|
||||
if case let .cloud(cloudTheme) = effectiveThemeReference, let themeSettings = cloudTheme.theme.settings?.first {
|
||||
hasSettings = true
|
||||
settings = cloudTheme.theme.settings
|
||||
settings = themeSettings
|
||||
}
|
||||
let controller = ThemeAccentColorController(context: context, mode: .edit(settings: settings, theme: theme, wallpaper: wallpaper, generalThemeReference: effectiveThemeReference.generalThemeReference, defaultThemeReference: nil, create: true, completion: { result, settings in
|
||||
let controller = editThemeController(context: context, mode: .create(hasSettings ? nil : result, hasSettings ? settings : nil), navigateToChat: { peerId in
|
||||
@ -1003,7 +915,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
return updated
|
||||
})))
|
||||
|
||||
if isCurrent, let settings = cloudTheme.theme.settings {
|
||||
if isCurrent, let settings = cloudTheme.theme.settings?.first {
|
||||
let colorThemes = themes.filter { theme in
|
||||
if let _ = theme.settings {
|
||||
return true
|
||||
@ -1046,13 +958,17 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
})
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get())
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers)
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
|
||||
|
||||
let themeReference: PresentationThemeReference
|
||||
if presentationData.autoNightModeTriggered {
|
||||
themeReference = settings.automaticThemeSwitchSetting.theme
|
||||
if let _ = settings.theme.emoticon {
|
||||
themeReference = settings.theme
|
||||
} else {
|
||||
themeReference = settings.automaticThemeSwitchSetting.theme
|
||||
}
|
||||
} else {
|
||||
themeReference = settings.theme
|
||||
}
|
||||
@ -1081,8 +997,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}
|
||||
availableThemes.append(contentsOf: cloudThemes)
|
||||
|
||||
var chatThemes = cloudThemes.filter { $0.emoticon != nil }
|
||||
chatThemes.insert(.builtin(.dayClassic), at: 0)
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -1208,7 +1127,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
|> map { resolvedWallpaper, currentTheme -> Bool in
|
||||
var updatedTheme = theme
|
||||
var currentThemeBaseIndex: Int64?
|
||||
if case let .cloud(info) = currentTheme, let settings = info.theme.settings {
|
||||
if case let .cloud(info) = currentTheme, let settings = info.theme.settings?.first {
|
||||
currentThemeBaseIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
|
||||
} else {
|
||||
currentThemeBaseIndex = currentTheme.index
|
||||
@ -1218,7 +1137,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
var updatedThemeBaseIndex: Int64?
|
||||
if case let .cloud(info) = theme {
|
||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
|
||||
if let settings = info.theme.settings {
|
||||
if let settings = info.theme.settings?.first {
|
||||
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
|
||||
updatedThemeBaseIndex = baseThemeIndex
|
||||
}
|
||||
@ -1227,19 +1146,30 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}
|
||||
|
||||
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
var updatedThemeSpecificAccentColors = current.themeSpecificAccentColors
|
||||
if let baseThemeIndex = baseThemeIndex {
|
||||
updatedThemeSpecificAccentColors[baseThemeIndex] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
|
||||
}
|
||||
|
||||
if autoNightModeTriggered {
|
||||
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
if case let .cloud(info) = updatedTheme, info.theme.settings?.contains(where: { $0.baseTheme == .night || $0.baseTheme == .tinted }) ?? false {
|
||||
updatedAutomaticThemeSwitchSetting.theme = updatedTheme
|
||||
|
||||
return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
|
||||
} else {
|
||||
return current.withUpdatedTheme(updatedTheme).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
|
||||
} else if case let .builtin(theme) = updatedTheme {
|
||||
if [.day, .dayClassic].contains(theme) {
|
||||
updatedAutomaticThemeSwitchSetting.theme = .builtin(.night)
|
||||
} else {
|
||||
updatedAutomaticThemeSwitchSetting.theme = updatedTheme
|
||||
}
|
||||
}
|
||||
return current.withUpdatedTheme(updatedTheme).withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting)
|
||||
// var updatedThemeSpecificAccentColors = current.themeSpecificAccentColors
|
||||
// if let baseThemeIndex = baseThemeIndex {
|
||||
// updatedThemeSpecificAccentColors[baseThemeIndex] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
|
||||
// }
|
||||
//
|
||||
// if autoNightModeTriggered {
|
||||
// var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
// updatedAutomaticThemeSwitchSetting.theme = updatedTheme
|
||||
//
|
||||
// return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
|
||||
// } else {
|
||||
// return current.withUpdatedTheme(updatedTheme).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
|
||||
// }
|
||||
}).start()
|
||||
|
||||
return currentThemeBaseIndex != updatedThemeBaseIndex
|
||||
@ -1280,7 +1210,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}
|
||||
|
||||
let generalThemeReference: PresentationThemeReference
|
||||
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings {
|
||||
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings?.first {
|
||||
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
|
||||
} else {
|
||||
generalThemeReference = currentTheme
|
||||
@ -1367,7 +1297,7 @@ public final class ThemeSettingsCrossfadeController: ViewController {
|
||||
private var bottomSnapshotView: UIView?
|
||||
private var sideSnapshotView: UIView?
|
||||
|
||||
fileprivate var didAppear: (() -> Void)?
|
||||
var didAppear: (() -> Void)?
|
||||
|
||||
public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil, leftOffset: CGFloat? = nil) {
|
||||
if let view = view {
|
||||
@ -1464,3 +1394,31 @@ public final class ThemeSettingsCrossfadeController: ViewController {
|
||||
self.didAppear?()
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
weak var sourceNode: ASDisplayNode?
|
||||
|
||||
let navigationController: NavigationController? = nil
|
||||
|
||||
let passthroughTouches: Bool = false
|
||||
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||
let sourceNode = self.sourceNode
|
||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||
if let sourceNode = sourceNode {
|
||||
return (sourceNode, sourceNode.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func animatedIn() {
|
||||
}
|
||||
}
|
||||
|
@ -629,14 +629,15 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
var themeWallpaper: TelegramWallpaper?
|
||||
if case let .cloud(theme) = theme {
|
||||
themeWallpaper = theme.resolvedWallpaper ?? theme.theme.settings?.wallpaper
|
||||
themeWallpaper = theme.resolvedWallpaper ?? theme.theme.settings?.first?.wallpaper
|
||||
}
|
||||
|
||||
let customWallpaper = item.themeSpecificChatWallpapers[theme.generalThemeReference.index]
|
||||
|
||||
let wallpaper = accentColor?.wallpaper ?? customWallpaper ?? themeWallpaper
|
||||
|
||||
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: item.currentTheme.index == theme.index, theme: item.theme, wallpaper: wallpaper))
|
||||
let selected = item.currentTheme.index == theme.index || item.currentTheme.generalThemeReference == theme
|
||||
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: selected, theme: item.theme, wallpaper: wallpaper))
|
||||
index += 1
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,6 @@ public protocol ShareContentContainerNode: AnyObject {
|
||||
func deactivate()
|
||||
func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?)
|
||||
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?)
|
||||
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition)
|
||||
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition)
|
||||
func updateSelectedPeers()
|
||||
}
|
||||
|
@ -366,7 +366,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
|
||||
if let contentNode = contentNode, let previous = previous {
|
||||
contentNode.frame = previous.frame
|
||||
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
|
||||
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
|
||||
|
||||
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
|
||||
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
|
||||
@ -472,7 +472,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
if let contentNode = self.contentNode {
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
|
||||
contentNode.updateLayout(size: gridSize, bottomInset: bottomGridInset, transition: transition)
|
||||
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain
|
||||
self.contentOffsetUpdated = f
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
public func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let nodeHeight: CGFloat = 125.0
|
||||
|
||||
let indicatorSize = self.activityIndicator.calculateSizeThatFits(size)
|
||||
|
@ -310,7 +310,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
func deactivate() {
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let firstLayout = self.validLayout == nil
|
||||
self.validLayout = (size, bottomInset)
|
||||
|
||||
|
@ -423,7 +423,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
return (gridTopInset, itemWidth)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let firstLayout = self.validLayout == nil
|
||||
self.validLayout = (size, bottomInset)
|
||||
|
||||
|
@ -3,6 +3,18 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: CGFloat = 22.0, lineWidth: CGFloat = 2.0) -> UIImage? {
|
||||
return generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setLineCap(.round)
|
||||
let cutoutAngle: CGFloat = CGFloat.pi * 30.0 / 180.0
|
||||
context.addArc(center: CGPoint(x: size.width / 2.0, y: size.height / 2.0), radius: size.width / 2.0 - lineWidth / 2.0, startAngle: 0.0, endAngle: CGFloat.pi * 2.0 - cutoutAngle, clockwise: false)
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
public final class SolidRoundedButtonTheme {
|
||||
public let backgroundColor: UIColor
|
||||
public let gradientBackgroundColor: UIColor?
|
||||
@ -31,6 +43,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let subtitleNode: ImmediateTextNode
|
||||
private let iconNode: ASImageNode
|
||||
private var progressNode: ASImageNode?
|
||||
|
||||
private let buttonHeight: CGFloat
|
||||
private let buttonCornerRadius: CGFloat
|
||||
@ -120,19 +133,62 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.iconNode.alpha = 0.55
|
||||
} else {
|
||||
strongSelf.buttonBackgroundNode.alpha = 1.0
|
||||
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.titleNode.alpha = 1.0
|
||||
strongSelf.titleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.subtitleNode.alpha = 1.0
|
||||
strongSelf.subtitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.iconNode.alpha = 1.0
|
||||
strongSelf.iconNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
if strongSelf.buttonBackgroundNode.alpha > 0.0 {
|
||||
strongSelf.buttonBackgroundNode.alpha = 1.0
|
||||
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.titleNode.alpha = 1.0
|
||||
strongSelf.titleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.subtitleNode.alpha = 1.0
|
||||
strongSelf.subtitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.iconNode.alpha = 1.0
|
||||
strongSelf.iconNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func transitionToProgress() {
|
||||
guard self.progressNode == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
let progressFrame = CGRect(origin: CGPoint(x: (self.frame.width - self.buttonHeight) / 2.0, y: 0.0), size: CGSize(width: self.buttonHeight, height: self.buttonHeight))
|
||||
let progressNode = ASImageNode()
|
||||
progressNode.displaysAsynchronously = false
|
||||
progressNode.frame = progressFrame
|
||||
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
|
||||
self.insertSubnode(progressNode, at: 0)
|
||||
self.progressNode = progressNode
|
||||
|
||||
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
basicAnimation.duration = 0.5
|
||||
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
||||
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
|
||||
basicAnimation.repeatCount = Float.infinity
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
basicAnimation.beginTime = 1.0
|
||||
progressNode.layer.add(basicAnimation, forKey: "progressRotation")
|
||||
|
||||
self.buttonBackgroundNode.cornerRadius = self.buttonHeight / 2.0
|
||||
self.buttonBackgroundNode.layer.animate(from: self.buttonCornerRadius as NSNumber, to: self.buttonHeight / 2.0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||
self.buttonBackgroundNode.layer.animateFrame(from: self.buttonBackgroundNode.frame, to: progressFrame, duration: 0.2)
|
||||
|
||||
self.buttonBackgroundNode.alpha = 0.0
|
||||
self.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
|
||||
progressNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
||||
|
||||
self.titleNode.alpha = 0.0
|
||||
self.titleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
||||
|
||||
self.subtitleNode.alpha = 0.0
|
||||
self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
||||
}
|
||||
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
|
@ -598,8 +598,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
|
||||
dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
|
||||
dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) }
|
||||
dict[-535699004] = { return Api.account.ChatThemes.parse_chatThemesNotModified($0) }
|
||||
dict[-28524867] = { return Api.account.ChatThemes.parse_chatThemes($0) }
|
||||
dict[-275956116] = { return Api.messages.AffectedFoundMessages.parse_affectedFoundMessages($0) }
|
||||
dict[795652779] = { return Api.BotCommandScope.parse_botCommandScopeDefault($0) }
|
||||
dict[1011811544] = { return Api.BotCommandScope.parse_botCommandScopeUsers($0) }
|
||||
@ -725,7 +723,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) }
|
||||
dict[-958657434] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) }
|
||||
dict[-2067782896] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) }
|
||||
dict[-318022605] = { return Api.ChatTheme.parse_chatTheme($0) }
|
||||
dict[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) }
|
||||
dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) }
|
||||
dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) }
|
||||
@ -872,7 +869,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1363483106] = { return Api.DialogPeer.parse_dialogPeerFolder($0) }
|
||||
dict[475467473] = { return Api.WebDocument.parse_webDocument($0) }
|
||||
dict[-104284986] = { return Api.WebDocument.parse_webDocumentNoProxy($0) }
|
||||
dict[-402474788] = { return Api.Theme.parse_theme($0) }
|
||||
dict[-1609668650] = { return Api.Theme.parse_theme($0) }
|
||||
dict[-1290580579] = { return Api.contacts.Found.parse_found($0) }
|
||||
dict[-368018716] = { return Api.ChannelAdminLogEventsFilter.parse_channelAdminLogEventsFilter($0) }
|
||||
dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) }
|
||||
@ -1388,8 +1385,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsDateRangeDays:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.account.ChatThemes:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.AffectedFoundMessages:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.BotCommandScope:
|
||||
@ -1502,8 +1497,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.FeaturedStickers:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChatTheme:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PhoneCallDiscardReason:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.NearestDc:
|
||||
|
@ -18828,52 +18828,6 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ChatTheme: TypeConstructorDescription {
|
||||
case chatTheme(emoticon: String, theme: Api.Theme, darkTheme: Api.Theme)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .chatTheme(let emoticon, let theme, let darkTheme):
|
||||
if boxed {
|
||||
buffer.appendInt32(-318022605)
|
||||
}
|
||||
serializeString(emoticon, buffer: buffer, boxed: false)
|
||||
theme.serialize(buffer, true)
|
||||
darkTheme.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .chatTheme(let emoticon, let theme, let darkTheme):
|
||||
return ("chatTheme", [("emoticon", emoticon), ("theme", theme), ("darkTheme", darkTheme)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_chatTheme(_ reader: BufferReader) -> ChatTheme? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Api.Theme?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.Theme
|
||||
}
|
||||
var _3: Api.Theme?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.Theme
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.ChatTheme.chatTheme(emoticon: _1!, theme: _2!, darkTheme: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PhoneCallDiscardReason: TypeConstructorDescription {
|
||||
case phoneCallDiscardReasonMissed
|
||||
@ -22248,13 +22202,13 @@ public extension Api {
|
||||
|
||||
}
|
||||
public enum Theme: TypeConstructorDescription {
|
||||
case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: Api.ThemeSettings?, installsCount: Int32?)
|
||||
case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let installsCount):
|
||||
case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount):
|
||||
if boxed {
|
||||
buffer.appendInt32(-402474788)
|
||||
buffer.appendInt32(-1609668650)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
@ -22262,7 +22216,12 @@ public extension Api {
|
||||
serializeString(slug, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(settings!.count))
|
||||
for item in settings! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(installsCount!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
@ -22270,8 +22229,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let installsCount):
|
||||
return ("theme", [("flags", flags), ("id", id), ("accessHash", accessHash), ("slug", slug), ("title", title), ("document", document), ("settings", settings), ("installsCount", installsCount)])
|
||||
case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount):
|
||||
return ("theme", [("flags", flags), ("id", id), ("accessHash", accessHash), ("slug", slug), ("title", title), ("document", document), ("settings", settings), ("emoticon", emoticon), ("installsCount", installsCount)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -22290,12 +22249,14 @@ public extension Api {
|
||||
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.Document
|
||||
} }
|
||||
var _7: Api.ThemeSettings?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.ThemeSettings
|
||||
var _7: [Api.ThemeSettings]?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self)
|
||||
} }
|
||||
var _8: Int32?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() }
|
||||
var _8: String?
|
||||
if Int(_1!) & Int(1 << 6) != 0 {_8 = parseString(reader) }
|
||||
var _9: Int32?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -22303,9 +22264,10 @@ public extension Api {
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, installsCount: _8)
|
||||
let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -1271,62 +1271,6 @@ public struct account {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ChatThemes: TypeConstructorDescription {
|
||||
case chatThemesNotModified
|
||||
case chatThemes(hash: Int32, themes: [Api.ChatTheme])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .chatThemesNotModified:
|
||||
if boxed {
|
||||
buffer.appendInt32(-535699004)
|
||||
}
|
||||
|
||||
break
|
||||
case .chatThemes(let hash, let themes):
|
||||
if boxed {
|
||||
buffer.appendInt32(-28524867)
|
||||
}
|
||||
serializeInt32(hash, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(themes.count))
|
||||
for item in themes {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .chatThemesNotModified:
|
||||
return ("chatThemesNotModified", [])
|
||||
case .chatThemes(let hash, let themes):
|
||||
return ("chatThemes", [("hash", hash), ("themes", themes)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_chatThemesNotModified(_ reader: BufferReader) -> ChatThemes? {
|
||||
return Api.account.ChatThemes.chatThemesNotModified
|
||||
}
|
||||
public static func parse_chatThemes(_ reader: BufferReader) -> ChatThemes? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: [Api.ChatTheme]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatTheme.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.account.ChatThemes.chatThemes(hash: _1!, themes: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum Authorizations: TypeConstructorDescription {
|
||||
case authorizations(authorizations: [Api.Authorization])
|
||||
@ -7568,14 +7512,18 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: Api.InputThemeSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
|
||||
public static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-2077048289)
|
||||
buffer.appendInt32(1697530880)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(slug, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(settings!.count))
|
||||
for item in settings! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
return (FunctionDescription(name: "account.createTheme", parameters: [("flags", flags), ("slug", slug), ("title", title), ("document", document), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Theme?
|
||||
@ -7586,16 +7534,20 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: Api.InputThemeSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
|
||||
public static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1555261397)
|
||||
buffer.appendInt32(737414348)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(format, buffer: buffer, boxed: false)
|
||||
theme.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(settings!.count))
|
||||
for item in settings! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", flags), ("format", format), ("theme", theme), ("slug", slug), ("title", title), ("document", document), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Theme?
|
||||
@ -7606,6 +7558,37 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-953697477)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeString(format!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {baseTheme!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "account.installTheme", parameters: [("flags", flags), ("theme", theme), ("format", format), ("baseTheme", baseTheme)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.Themes>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-700916087)
|
||||
serializeInt64(hash, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.account.Themes?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.account.Themes
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-229175188)
|
||||
@ -7621,22 +7604,6 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func installTheme(flags: Int32, format: String?, theme: Api.InputTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(2061776695)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(format!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "account.installTheme", parameters: [("flags", flags), ("format", format), ("theme", theme)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getTheme(format: String, theme: Api.InputTheme, documentId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1919060949)
|
||||
@ -7786,20 +7753,6 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getChatThemes(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.ChatThemes>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-690545285)
|
||||
serializeInt32(hash, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ChatThemes? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.account.ChatThemes?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.account.ChatThemes
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct langpack {
|
||||
public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) {
|
||||
|
@ -360,7 +360,7 @@ public final class VoiceChatJoinScreen: ViewController {
|
||||
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
|
||||
if let contentNode = contentNode, let previous = previous {
|
||||
contentNode.frame = previous.frame
|
||||
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
|
||||
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
|
||||
|
||||
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
|
||||
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
|
||||
@ -443,7 +443,7 @@ public final class VoiceChatJoinScreen: ViewController {
|
||||
|
||||
if let contentNode = self.contentNode {
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
|
||||
contentNode.updateLayout(size: gridSize, bottomInset: bottomGridInset, transition: transition)
|
||||
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -693,7 +693,7 @@ final class VoiceChatPreviewContentNode: ASDisplayNode, ShareContentContainerNod
|
||||
self.contentOffsetUpdated = f
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height))
|
||||
let countSize = self.countNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height))
|
||||
|
@ -7,8 +7,8 @@ import TelegramApi
|
||||
extension TelegramTheme {
|
||||
convenience init(apiTheme: Api.Theme) {
|
||||
switch apiTheme {
|
||||
case let .theme(flags, id, accessHash, slug, title, document, settings, installCount):
|
||||
self.init(id: id, accessHash: accessHash, slug: slug, title: title, file: document.flatMap(telegramMediaFileFromApiDocument), settings: settings.flatMap(TelegramThemeSettings.init(apiThemeSettings:)), isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0, installCount: installCount)
|
||||
case let .theme(flags, id, accessHash, slug, title, document, settings, emoticon, installCount):
|
||||
self.init(id: id, accessHash: accessHash, slug: slug, emoticon: emoticon, title: title, file: document.flatMap(telegramMediaFileFromApiDocument), settings: settings?.compactMap(TelegramThemeSettings.init(apiThemeSettings:)), isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0, installCount: installCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +352,7 @@ private enum SharedDataKeyValues: Int32 {
|
||||
case themeSettings = 6
|
||||
case countriesList = 7
|
||||
case wallapersState = 8
|
||||
case chatThemes = 9
|
||||
case chatThemes = 10
|
||||
}
|
||||
|
||||
public struct SharedDataKeys {
|
||||
|
@ -103,6 +103,7 @@ public struct TelegramThemeNativeCodable: Codable {
|
||||
let id = try container.decode(Int64.self, forKey: "id")
|
||||
let accessHash = try container.decode(Int64.self, forKey: "accessHash")
|
||||
let slug = try container.decode(String.self, forKey: "slug")
|
||||
let emoticon = try container.decodeIfPresent(String.self, forKey: "emoticon")
|
||||
let title = try container.decode(String.self, forKey: "title")
|
||||
|
||||
let file: TelegramMediaFile?
|
||||
@ -112,8 +113,12 @@ public struct TelegramThemeNativeCodable: Codable {
|
||||
file = nil
|
||||
}
|
||||
|
||||
let settings = try container.decodeIfPresent(TelegramThemeSettings.self, forKey: "settings")
|
||||
|
||||
let legacySettings = try container.decodeIfPresent(TelegramThemeSettings.self, forKey: "settings")
|
||||
var settings = try container.decodeIfPresent([TelegramThemeSettings].self, forKey: "settingsArray")
|
||||
if settings == nil, let legacySettings = legacySettings {
|
||||
settings = [legacySettings]
|
||||
}
|
||||
|
||||
let isCreator = try container.decode(Int32.self, forKey: "isCreator") != 0
|
||||
let isDefault = try container.decode(Int32.self, forKey: "isDefault") != 0
|
||||
let installCount = try container.decodeIfPresent(Int32.self, forKey: "installCount")
|
||||
@ -122,6 +127,7 @@ public struct TelegramThemeNativeCodable: Codable {
|
||||
id: id,
|
||||
accessHash: accessHash,
|
||||
slug: slug,
|
||||
emoticon: emoticon,
|
||||
title: title,
|
||||
file: file,
|
||||
settings: settings,
|
||||
@ -137,6 +143,7 @@ public struct TelegramThemeNativeCodable: Codable {
|
||||
try container.encode(self.value.id, forKey: "id")
|
||||
try container.encode(self.value.accessHash, forKey: "accessHash")
|
||||
try container.encode(self.value.slug, forKey: "slug")
|
||||
try container.encodeIfPresent(self.value.emoticon, forKey: "emoticon")
|
||||
try container.encode(self.value.title, forKey: "title")
|
||||
|
||||
if let file = self.value.file {
|
||||
@ -145,7 +152,7 @@ public struct TelegramThemeNativeCodable: Codable {
|
||||
try container.encodeNil(forKey: "file")
|
||||
}
|
||||
|
||||
try container.encodeIfPresent(self.value.settings, forKey: "settings")
|
||||
try container.encodeIfPresent(self.value.settings, forKey: "settingsArray")
|
||||
|
||||
try container.encode((self.value.isCreator ? 1 : 0) as Int32, forKey: "isCreator")
|
||||
try container.encode((self.value.isDefault ? 1 : 0) as Int32, forKey: "isDefault")
|
||||
@ -158,17 +165,19 @@ public final class TelegramTheme: Equatable {
|
||||
public let id: Int64
|
||||
public let accessHash: Int64
|
||||
public let slug: String
|
||||
public let emoticon: String?
|
||||
public let title: String
|
||||
public let file: TelegramMediaFile?
|
||||
public let settings: TelegramThemeSettings?
|
||||
public let settings: [TelegramThemeSettings]?
|
||||
public let isCreator: Bool
|
||||
public let isDefault: Bool
|
||||
public let installCount: Int32?
|
||||
|
||||
public init(id: Int64, accessHash: Int64, slug: String, title: String, file: TelegramMediaFile?, settings: TelegramThemeSettings?, isCreator: Bool, isDefault: Bool, installCount: Int32?) {
|
||||
public init(id: Int64, accessHash: Int64, slug: String, emoticon: String?, title: String, file: TelegramMediaFile?, settings: [TelegramThemeSettings]?, isCreator: Bool, isDefault: Bool, installCount: Int32?) {
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
self.slug = slug
|
||||
self.emoticon = emoticon
|
||||
self.title = title
|
||||
self.file = file
|
||||
self.settings = settings
|
||||
@ -187,6 +196,9 @@ public final class TelegramTheme: Equatable {
|
||||
if lhs.slug != rhs.slug {
|
||||
return false
|
||||
}
|
||||
if lhs.emoticon != rhs.emoticon {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
|
@ -3,44 +3,11 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
|
||||
public struct ChatTheme: Codable, Equatable {
|
||||
public static func == (lhs: ChatTheme, rhs: ChatTheme) -> Bool {
|
||||
return lhs.emoji == rhs.emoji && lhs.theme == rhs.theme && lhs.darkTheme == rhs.darkTheme
|
||||
}
|
||||
|
||||
public let emoji: String
|
||||
public let theme: TelegramTheme
|
||||
public let darkTheme: TelegramTheme
|
||||
|
||||
public init(emoji: String, theme: TelegramTheme, darkTheme: TelegramTheme) {
|
||||
self.emoji = emoji
|
||||
self.theme = theme
|
||||
self.darkTheme = darkTheme
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.emoji = try container.decode(String.self, forKey: "e")
|
||||
|
||||
self.theme = (try container.decode(TelegramThemeNativeCodable.self, forKey: "t")).value
|
||||
self.darkTheme = (try container.decode(TelegramThemeNativeCodable.self, forKey: "dt")).value
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encode(self.emoji, forKey: "e")
|
||||
try container.encode(TelegramThemeNativeCodable(self.theme), forKey: "t")
|
||||
try container.encode(TelegramThemeNativeCodable(self.darkTheme), forKey: "dt")
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatThemes: Codable, Equatable {
|
||||
public let chatThemes: [ChatTheme]
|
||||
public let hash: Int32
|
||||
public let chatThemes: [TelegramTheme]
|
||||
public let hash: Int64
|
||||
|
||||
public init(chatThemes: [ChatTheme], hash: Int32) {
|
||||
public init(chatThemes: [TelegramTheme], hash: Int64) {
|
||||
self.chatThemes = chatThemes
|
||||
self.hash = hash
|
||||
}
|
||||
@ -48,14 +15,14 @@ public final class ChatThemes: Codable, Equatable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.chatThemes = try container.decode([ChatTheme].self, forKey: "c")
|
||||
self.hash = try container.decode(Int32.self, forKey: "h")
|
||||
self.chatThemes = try container.decode([TelegramThemeNativeCodable].self, forKey: "c").map { $0.value }
|
||||
self.hash = try container.decode(Int64.self, forKey: "h")
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encode(self.chatThemes, forKey: "c")
|
||||
try container.encode(self.chatThemes.map { TelegramThemeNativeCodable($0) }, forKey: "c")
|
||||
try container.encode(self.hash, forKey: "h")
|
||||
}
|
||||
|
||||
@ -64,14 +31,14 @@ public final class ChatThemes: Codable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_getChatThemes(accountManager: AccountManager<TelegramAccountManagerTypes>, network: Network, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[ChatTheme], NoError> {
|
||||
let fetch: ([ChatTheme]?, Int32?) -> Signal<[ChatTheme], NoError> = { current, hash in
|
||||
return network.request(Api.functions.account.getChatThemes(hash: 0))
|
||||
func _internal_getChatThemes(accountManager: AccountManager<TelegramAccountManagerTypes>, network: Network, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[TelegramTheme], NoError> {
|
||||
let fetch: ([TelegramTheme]?, Int64?) -> Signal<[TelegramTheme], NoError> = { current, hash in
|
||||
return network.request(Api.functions.account.getChatThemes(hash: hash ?? 0))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[ChatTheme], NoError> in
|
||||
|> mapToSignal { result -> Signal<[TelegramTheme], NoError> in
|
||||
switch result {
|
||||
case let .chatThemes(hash, apiThemes):
|
||||
let result = apiThemes.compactMap { ChatTheme(apiChatTheme: $0) }
|
||||
case let .themes(hash, apiThemes):
|
||||
let result = apiThemes.compactMap { TelegramTheme(apiTheme: $0) }
|
||||
if result == current {
|
||||
return .complete()
|
||||
} else {
|
||||
@ -82,7 +49,7 @@ func _internal_getChatThemes(accountManager: AccountManager<TelegramAccountManag
|
||||
}.start()
|
||||
return .single(result)
|
||||
}
|
||||
case .chatThemesNotModified:
|
||||
case .themesNotModified:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
@ -93,14 +60,14 @@ func _internal_getChatThemes(accountManager: AccountManager<TelegramAccountManag
|
||||
} else {
|
||||
return accountManager.sharedData(keys: [SharedDataKeys.chatThemes])
|
||||
|> take(1)
|
||||
|> map { sharedData -> ([ChatTheme], Int32) in
|
||||
|> map { sharedData -> ([TelegramTheme], Int64) in
|
||||
if let chatThemes = sharedData.entries[SharedDataKeys.chatThemes]?.get(ChatThemes.self) {
|
||||
return (chatThemes.chatThemes, chatThemes.hash)
|
||||
} else {
|
||||
return ([], 0)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { current, hash -> Signal<[ChatTheme], NoError> in
|
||||
|> mapToSignal { current, hash -> Signal<[TelegramTheme], NoError> in
|
||||
if onlyCached && !current.isEmpty {
|
||||
return .single(current)
|
||||
} else {
|
||||
@ -143,15 +110,6 @@ func _internal_setChatTheme(postbox: Postbox, network: Network, stateManager: Ac
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatTheme {
|
||||
init(apiChatTheme: Api.ChatTheme) {
|
||||
switch apiChatTheme {
|
||||
case let .chatTheme(emoticon, theme, darkTheme):
|
||||
self.init(emoji: emoticon, theme: TelegramTheme(apiTheme: theme), darkTheme: TelegramTheme(apiTheme: darkTheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func managedChatThemesUpdates(accountManager: AccountManager<TelegramAccountManagerTypes>, network: Network) -> Signal<Void, NoError> {
|
||||
let poll = _internal_getChatThemes(accountManager: accountManager, network: network)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
|
@ -9,7 +9,7 @@ public extension TelegramEngine {
|
||||
self.account = account
|
||||
}
|
||||
|
||||
public func getChatThemes(accountManager: AccountManager<TelegramAccountManagerTypes>, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[ChatTheme], NoError> {
|
||||
public func getChatThemes(accountManager: AccountManager<TelegramAccountManagerTypes>, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[TelegramTheme], NoError> {
|
||||
return _internal_getChatThemes(accountManager: accountManager, network: self.account.network, forceUpdate: forceUpdate, onlyCached: onlyCached)
|
||||
}
|
||||
|
||||
|
@ -157,7 +157,7 @@ private func saveUnsaveTheme(account: Account, accountManager: AccountManager<Te
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
private func installTheme(account: Account, theme: TelegramTheme?, autoNight: Bool) -> Signal<Never, NoError> {
|
||||
private func installTheme(account: Account, theme: TelegramTheme?, baseTheme: TelegramBaseTheme? = nil, autoNight: Bool) -> Signal<Never, NoError> {
|
||||
var flags: Int32 = 0
|
||||
if autoNight {
|
||||
flags |= 1 << 0
|
||||
@ -171,7 +171,16 @@ private func installTheme(account: Account, theme: TelegramTheme?, autoNight: Bo
|
||||
inputTheme = nil
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.account.installTheme(flags: flags, format: telegramThemeFormat, theme: inputTheme))
|
||||
flags |= 1 << 2
|
||||
|
||||
let inputBaseTheme: Api.BaseTheme?
|
||||
if let baseTheme = baseTheme {
|
||||
inputBaseTheme = baseTheme.apiBaseTheme
|
||||
} else {
|
||||
inputBaseTheme = nil
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.account.installTheme(flags: flags, theme: inputTheme, format: telegramThemeFormat, baseTheme: inputBaseTheme))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
@ -278,16 +287,16 @@ public enum CreateThemeResult {
|
||||
case progress(Float)
|
||||
}
|
||||
|
||||
public func createTheme(account: Account, title: String, resource: MediaResource? = nil, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal<CreateThemeResult, CreateThemeError> {
|
||||
public func createTheme(account: Account, title: String, resource: MediaResource? = nil, thumbnailData: Data? = nil, settings: [TelegramThemeSettings]?) -> Signal<CreateThemeResult, CreateThemeError> {
|
||||
var flags: Int32 = 0
|
||||
|
||||
var inputSettings: Api.InputThemeSettings?
|
||||
var inputSettings: [Api.InputThemeSettings]?
|
||||
if let _ = resource {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
if let settings = settings {
|
||||
flags |= 1 << 3
|
||||
inputSettings = settings.apiInputThemeSettings
|
||||
inputSettings = settings.map { $0.apiInputThemeSettings }
|
||||
}
|
||||
|
||||
if let resource = resource {
|
||||
@ -345,7 +354,7 @@ public func createTheme(account: Account, title: String, resource: MediaResource
|
||||
}
|
||||
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
||||
var theme = TelegramTheme(apiTheme: apiTheme)
|
||||
theme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
|
||||
theme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, emoticon: nil, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
|
||||
|
||||
return account.postbox.transaction { transaction -> CreateThemeResult in
|
||||
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
||||
@ -367,7 +376,7 @@ public func createTheme(account: Account, title: String, resource: MediaResource
|
||||
}
|
||||
}
|
||||
|
||||
public func updateTheme(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal<CreateThemeResult, CreateThemeError> {
|
||||
public func updateTheme(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil, settings: [TelegramThemeSettings]?) -> Signal<CreateThemeResult, CreateThemeError> {
|
||||
guard title != nil || slug != nil || resource != nil else {
|
||||
return .complete()
|
||||
}
|
||||
@ -381,10 +390,10 @@ public func updateTheme(account: Account, accountManager: AccountManager<Telegra
|
||||
if let _ = resource {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
var inputSettings: Api.InputThemeSettings?
|
||||
var inputSettings: [Api.InputThemeSettings]?
|
||||
if let settings = settings {
|
||||
flags |= 1 << 3
|
||||
inputSettings = settings.apiInputThemeSettings
|
||||
inputSettings = settings.map { $0.apiInputThemeSettings }
|
||||
}
|
||||
let uploadSignal: Signal<UploadThemeResult?, UploadThemeError>
|
||||
if let resource = resource {
|
||||
@ -425,7 +434,7 @@ public func updateTheme(account: Account, accountManager: AccountManager<Telegra
|
||||
}
|
||||
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
||||
let theme = TelegramTheme(apiTheme: apiTheme)
|
||||
let updatedTheme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
|
||||
let updatedTheme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, emoticon: nil, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
|
||||
|
||||
let _ = accountManager.transaction { transaction in
|
||||
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in
|
||||
|
@ -38,15 +38,39 @@ public func makePresentationTheme(settings: TelegramThemeSettings, title: String
|
||||
return customizePresentationTheme(defaultTheme, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
|
||||
}
|
||||
|
||||
public func makePresentationTheme(cloudTheme: TelegramTheme) -> PresentationTheme? {
|
||||
guard let settings = cloudTheme.settings else {
|
||||
public func makePresentationTheme(cloudTheme: TelegramTheme, dark: Bool = false) -> PresentationTheme? {
|
||||
let settings: TelegramThemeSettings?
|
||||
if let exactSettings = cloudTheme.settings?.first(where: { dark ? ($0.baseTheme == .night || $0.baseTheme == .tinted) : ($0.baseTheme == .classic || $0.baseTheme == .day) }) {
|
||||
settings = exactSettings
|
||||
} else if let firstSettings = cloudTheme.settings?.first {
|
||||
settings = firstSettings
|
||||
} else {
|
||||
settings = nil
|
||||
}
|
||||
guard let settings = settings else {
|
||||
return nil
|
||||
}
|
||||
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: .cloud(PresentationCloudTheme(theme: cloudTheme, resolvedWallpaper: nil, creatorAccountId: nil)), serviceBackgroundColor: nil, preview: false)
|
||||
return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
|
||||
}
|
||||
|
||||
public func makePresentationTheme(cloudTheme: TelegramTheme, baseTheme: TelegramBaseTheme? = nil) -> PresentationTheme? {
|
||||
let settings: TelegramThemeSettings?
|
||||
if let exactSettings = cloudTheme.settings?.first(where: { $0.baseTheme == baseTheme }) {
|
||||
settings = exactSettings
|
||||
} else if let firstSettings = cloudTheme.settings?.first {
|
||||
settings = firstSettings
|
||||
} else {
|
||||
settings = nil
|
||||
}
|
||||
guard let settings = settings else {
|
||||
return nil
|
||||
}
|
||||
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: nil, preview: false)
|
||||
return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
|
||||
}
|
||||
|
||||
public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? {
|
||||
public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, baseTheme: TelegramBaseTheme? = nil, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? {
|
||||
var accentColor = accentColor
|
||||
if accentColor == .clear {
|
||||
accentColor = nil
|
||||
@ -63,7 +87,15 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati
|
||||
return nil
|
||||
}
|
||||
case let .cloud(info):
|
||||
if let settings = info.theme.settings {
|
||||
let settings: TelegramThemeSettings?
|
||||
if let exactSettings = info.theme.settings?.first(where: { $0.baseTheme == baseTheme }) {
|
||||
settings = exactSettings
|
||||
} else if let firstSettings = info.theme.settings?.first {
|
||||
settings = firstSettings
|
||||
} else {
|
||||
settings = nil
|
||||
}
|
||||
if let settings = settings {
|
||||
if let loadedTheme = makePresentationTheme(mediaBox: mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), extendingThemeReference: themeReference, accentColor: accentColor ?? UIColor(argb: settings.accentColor), outgoingAccentColor: outgoingAccentColor ?? settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: bubbleColors.isEmpty ? settings.messageColors : bubbleColors, animateBubbleColors: animateBubbleColors ?? settings.animateMessageColors, wallpaper: wallpaper ?? settings.wallpaper, serviceBackgroundColor: serviceBackgroundColor, preview: preview) {
|
||||
theme = loadedTheme
|
||||
} else {
|
||||
|
@ -330,6 +330,7 @@ private func roundTimeToDay(_ timestamp: Int32) -> Int32 {
|
||||
|
||||
private enum PreparedAutomaticThemeSwitchTrigger {
|
||||
case explicitNone
|
||||
case explicitForce
|
||||
case system
|
||||
case time(fromSeconds: Int32, toSeconds: Int32)
|
||||
case brightness(threshold: Double)
|
||||
@ -341,26 +342,30 @@ private struct AutomaticThemeSwitchParameters {
|
||||
|
||||
init(settings: AutomaticThemeSwitchSetting) {
|
||||
let trigger: PreparedAutomaticThemeSwitchTrigger
|
||||
switch settings.trigger {
|
||||
case .system:
|
||||
trigger = .system
|
||||
case .explicitNone:
|
||||
trigger = .explicitNone
|
||||
case let .timeBased(setting):
|
||||
let fromValue: Int32
|
||||
let toValue: Int32
|
||||
switch setting {
|
||||
case let .automatic(latitude, longitude, _):
|
||||
let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: latitude, longitude: longitude)!
|
||||
fromValue = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970))
|
||||
toValue = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970))
|
||||
case let .manual(fromSeconds, toSeconds):
|
||||
fromValue = fromSeconds
|
||||
toValue = toSeconds
|
||||
}
|
||||
trigger = .time(fromSeconds: fromValue, toSeconds: toValue)
|
||||
case let .brightness(threshold):
|
||||
trigger = .brightness(threshold: threshold)
|
||||
if settings.force {
|
||||
trigger = .explicitForce
|
||||
} else {
|
||||
switch settings.trigger {
|
||||
case .system:
|
||||
trigger = .system
|
||||
case .explicitNone:
|
||||
trigger = .explicitNone
|
||||
case let .timeBased(setting):
|
||||
let fromValue: Int32
|
||||
let toValue: Int32
|
||||
switch setting {
|
||||
case let .automatic(latitude, longitude, _):
|
||||
let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: latitude, longitude: longitude)!
|
||||
fromValue = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970))
|
||||
toValue = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970))
|
||||
case let .manual(fromSeconds, toSeconds):
|
||||
fromValue = fromSeconds
|
||||
toValue = toSeconds
|
||||
}
|
||||
trigger = .time(fromSeconds: fromValue, toSeconds: toValue)
|
||||
case let .brightness(threshold):
|
||||
trigger = .brightness(threshold: threshold)
|
||||
}
|
||||
}
|
||||
self.trigger = trigger
|
||||
self.theme = settings.theme
|
||||
@ -371,6 +376,8 @@ private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchPar
|
||||
switch parameters.trigger {
|
||||
case .explicitNone:
|
||||
return false
|
||||
case .explicitForce:
|
||||
return true
|
||||
case .system:
|
||||
return systemUserInterfaceStyle == .dark
|
||||
case let .time(fromValue, toValue):
|
||||
@ -605,6 +612,7 @@ public func updatedPresentationData(accountManager: AccountManager<TelegramAccou
|
||||
var effectiveColors = currentColors
|
||||
|
||||
var switchedToNightModeWallpaper = false
|
||||
var preferredBaseTheme: TelegramBaseTheme?
|
||||
if autoNightModeTriggered {
|
||||
let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
|
||||
effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
|
||||
@ -615,6 +623,7 @@ public func updatedPresentationData(accountManager: AccountManager<TelegramAccou
|
||||
switchedToNightModeWallpaper = true
|
||||
}
|
||||
effectiveTheme = automaticTheme
|
||||
preferredBaseTheme = .night
|
||||
} else {
|
||||
effectiveTheme = themeSettings.theme
|
||||
}
|
||||
@ -623,7 +632,7 @@ public func updatedPresentationData(accountManager: AccountManager<TelegramAccou
|
||||
effectiveColors = nil
|
||||
}
|
||||
|
||||
let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme
|
||||
let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme
|
||||
|
||||
if autoNightModeTriggered && !switchedToNightModeWallpaper {
|
||||
switch effectiveChatWallpaper {
|
||||
|
@ -3909,12 +3909,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var presentationData = presentationData
|
||||
var useDarkAppearance = presentationData.theme.overallDarkAppearance
|
||||
|
||||
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) {
|
||||
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon == themeEmoticon }) {
|
||||
if let darkAppearancePreview = darkAppearancePreview {
|
||||
useDarkAppearance = darkAppearancePreview
|
||||
}
|
||||
let customTheme = useDarkAppearance ? theme.darkTheme : theme.theme
|
||||
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
|
||||
if let theme = makePresentationTheme(cloudTheme: theme, dark: useDarkAppearance) {
|
||||
theme.forceSync = true
|
||||
presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
|
||||
|
||||
|
@ -498,7 +498,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
|
||||
@objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.controllerInteraction.openPeer(self.peerId, .info, nil)
|
||||
if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, id, _, _, _) = self.messageReference?.content {
|
||||
self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame)
|
||||
} else {
|
||||
self.controllerInteraction.openPeer(self.peerId, .info, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,10 +179,8 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
var presentationData = presentationData
|
||||
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) {
|
||||
let useDarkAppearance = presentationData.theme.overallDarkAppearance
|
||||
let customTheme = useDarkAppearance ? theme.darkTheme : theme.theme
|
||||
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
|
||||
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon == themeEmoticon }) {
|
||||
if let theme = makePresentationTheme(cloudTheme: theme, dark: presentationData.theme.overallDarkAppearance) {
|
||||
presentationData = presentationData.withUpdated(theme: theme)
|
||||
presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
|
||||
let emoticon: String?
|
||||
let emojiFile: TelegramMediaFile?
|
||||
let themeReference: PresentationThemeReference?
|
||||
let nightMode: Bool
|
||||
var selected: Bool
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
@ -67,6 +68,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
|
||||
if lhs.themeReference?.index != rhs.themeReference?.index {
|
||||
return false
|
||||
}
|
||||
if lhs.nightMode != rhs.nightMode {
|
||||
return false
|
||||
}
|
||||
if lhs.selected != rhs.selected {
|
||||
return false
|
||||
}
|
||||
@ -87,7 +91,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
|
||||
}
|
||||
|
||||
func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem {
|
||||
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
|
||||
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,17 +101,19 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
|
||||
let emoticon: String?
|
||||
let emojiFile: TelegramMediaFile?
|
||||
let themeReference: PresentationThemeReference?
|
||||
let nightMode: Bool
|
||||
let selected: Bool
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let wallpaper: TelegramWallpaper?
|
||||
let action: (String?) -> Void
|
||||
|
||||
public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) {
|
||||
public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, nightMode: Bool, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) {
|
||||
self.context = context
|
||||
self.emoticon = emoticon
|
||||
self.emojiFile = emojiFile
|
||||
self.themeReference = themeReference
|
||||
self.nightMode = nightMode
|
||||
self.selected = selected
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -419,7 +425,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
|
||||
if updatedThemeReference || updatedWallpaper {
|
||||
if let themeReference = item.themeReference {
|
||||
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, emoticon: true))
|
||||
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true))
|
||||
strongSelf.imageNode.backgroundColor = nil
|
||||
}
|
||||
}
|
||||
@ -844,10 +850,12 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
let presentationData = strongSelf.presentationData
|
||||
|
||||
var entries: [ThemeSettingsThemeEntry] = []
|
||||
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, nightMode: false, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||
for theme in themes {
|
||||
let emoticon = theme.emoji
|
||||
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||
guard let emoticon = theme.emoticon else {
|
||||
continue
|
||||
}
|
||||
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||
}
|
||||
|
||||
let action: (String?) -> Void = { [weak self] emoticon in
|
||||
@ -881,7 +889,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
|
||||
if isFirstTime {
|
||||
for theme in themes {
|
||||
if let wallpaper = theme.theme.settings?.wallpaper, case let .file(file) = wallpaper {
|
||||
if let wallpaper = theme.settings?.first?.wallpaper, case let .file(file) = wallpaper {
|
||||
let account = strongSelf.context.account
|
||||
let accountManager = strongSelf.context.sharedContext.accountManager
|
||||
let path = accountManager.mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: CachedPreparedPatternWallpaperRepresentation())
|
||||
|
@ -317,7 +317,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
|> mapToSignal { themeInfo -> Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> in
|
||||
return Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> { subscriber in
|
||||
let disposables = DisposableSet()
|
||||
if let settings = themeInfo.settings {
|
||||
if let settings = themeInfo.settings?.first {
|
||||
subscriber.putNext((nil, settings, themeInfo))
|
||||
subscriber.putCompletion()
|
||||
} else if let resource = themeInfo.file?.resource {
|
||||
|
@ -1270,10 +1270,11 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
let ItemMembers = 106
|
||||
let ItemPermissions = 107
|
||||
let ItemAdmins = 108
|
||||
let ItemRemovedUsers = 109
|
||||
let ItemLocationHeader = 110
|
||||
let ItemLocation = 111
|
||||
let ItemLocationSetup = 112
|
||||
let ItemMemberRequests = 109
|
||||
let ItemRemovedUsers = 110
|
||||
let ItemLocationHeader = 111
|
||||
let ItemLocation = 112
|
||||
let ItemLocationSetup = 113
|
||||
let ItemDeleteGroup = 114
|
||||
|
||||
let isCreator = channel.flags.contains(.isCreator)
|
||||
@ -1392,6 +1393,12 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text(cachedData.participantsSummary.adminCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
|
||||
interaction.openParticipantsSection(.admins)
|
||||
}))
|
||||
|
||||
if let count = data.requests?.count, count > 0 {
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: {
|
||||
interaction.openParticipantsSection(.memberRequests)
|
||||
}))
|
||||
}
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: {
|
||||
interaction.openParticipantsSection(.banned)
|
||||
@ -1411,6 +1418,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
let ItemPreHistory = 103
|
||||
let ItemPermissions = 104
|
||||
let ItemAdmins = 105
|
||||
let ItemMemberRequests = 106
|
||||
|
||||
var canViewAdminsAndBanned = false
|
||||
|
||||
@ -1477,6 +1485,12 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
|
||||
interaction.openParticipantsSection(.admins)
|
||||
}))
|
||||
|
||||
if let count = data.requests?.count, count > 0 {
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: {
|
||||
interaction.openParticipantsSection(.memberRequests)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6962,9 +6976,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
||||
presentationDataSignal = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager, onlyCached: false), themeEmoticon)
|
||||
|> map { presentationData, chatThemes, themeEmoticon -> PresentationData in
|
||||
var presentationData = presentationData
|
||||
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) {
|
||||
let customTheme = presentationData.theme.overallDarkAppearance ? theme.darkTheme : theme.theme
|
||||
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
|
||||
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon == themeEmoticon }) {
|
||||
if let theme = makePresentationTheme(cloudTheme: theme, dark: presentationData.theme.overallDarkAppearance) {
|
||||
presentationData = presentationData.withUpdated(theme: theme)
|
||||
presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
|
||||
}
|
||||
|
@ -148,11 +148,13 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
|
||||
self = .builtin(.dayClassic)
|
||||
}
|
||||
case 2:
|
||||
if let cloudTheme = decoder.decode(PresentationCloudTheme.self, forKey: "cloudTheme") {
|
||||
if let cloudTheme = decoder.decode(PresentationCloudTheme.self, forKey: "cloudTheme") {
|
||||
self = .cloud(cloudTheme)
|
||||
} else {
|
||||
self = .builtin(.dayClassic)
|
||||
}
|
||||
case 3:
|
||||
self = .builtin(.dayClassic)
|
||||
default:
|
||||
assertionFailure()
|
||||
self = .builtin(.dayClassic)
|
||||
@ -227,13 +229,24 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
|
||||
|
||||
public var generalThemeReference: PresentationThemeReference {
|
||||
let generalThemeReference: PresentationThemeReference
|
||||
if case let .cloud(theme) = self, let settings = theme.theme.settings {
|
||||
if case let .cloud(theme) = self, let settings = theme.theme.settings?.first {
|
||||
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
|
||||
} else {
|
||||
generalThemeReference = self
|
||||
}
|
||||
return generalThemeReference
|
||||
}
|
||||
|
||||
public var emoticon: String? {
|
||||
switch self {
|
||||
case .builtin:
|
||||
return "🏠"
|
||||
case let .cloud(theme):
|
||||
return theme.theme.emoticon
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func coloredThemeIndex(reference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?) -> Int64 {
|
||||
@ -336,10 +349,12 @@ public enum AutomaticThemeSwitchTrigger: Codable, Equatable {
|
||||
}
|
||||
|
||||
public struct AutomaticThemeSwitchSetting: Codable, Equatable {
|
||||
public var force: Bool
|
||||
public var trigger: AutomaticThemeSwitchTrigger
|
||||
public var theme: PresentationThemeReference
|
||||
|
||||
public init(trigger: AutomaticThemeSwitchTrigger, theme: PresentationThemeReference) {
|
||||
public init(force: Bool, trigger: AutomaticThemeSwitchTrigger, theme: PresentationThemeReference) {
|
||||
self.force = force
|
||||
self.trigger = trigger
|
||||
self.theme = theme
|
||||
}
|
||||
@ -347,6 +362,7 @@ public struct AutomaticThemeSwitchSetting: Codable, Equatable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.force = try container.decodeIfPresent(Bool.self, forKey: "force") ?? false
|
||||
self.trigger = try container.decode(AutomaticThemeSwitchTrigger.self, forKey: "trigger")
|
||||
if let themeData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "theme_v2") {
|
||||
self.theme = PresentationThemeReference(decoder: PostboxDecoder(buffer: MemoryBuffer(data: themeData.data)))
|
||||
@ -360,6 +376,7 @@ public struct AutomaticThemeSwitchSetting: Codable, Equatable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encode(self.force, forKey: "force")
|
||||
try container.encode(self.trigger, forKey: "trigger")
|
||||
|
||||
let themeData = PostboxEncoder().encodeObjectToRawData(self.theme)
|
||||
@ -620,7 +637,7 @@ public struct PresentationThemeSettings: Codable {
|
||||
}
|
||||
|
||||
public static var defaultSettings: PresentationThemeSettings {
|
||||
return PresentationThemeSettings(theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], useSystemFont: true, fontSize: .regular, listsFontSize: .regular, chatBubbleSettings: .default, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night)), largeEmoji: true, reduceMotion: false)
|
||||
return PresentationThemeSettings(theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], useSystemFont: true, fontSize: .regular, listsFontSize: .regular, chatBubbleSettings: .default, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(force: false, trigger: .system, theme: .builtin(.night)), largeEmoji: true, reduceMotion: false)
|
||||
}
|
||||
|
||||
public init(theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], useSystemFont: Bool, fontSize: PresentationFontSize, listsFontSize: PresentationFontSize, chatBubbleSettings: PresentationChatBubbleSettings, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, reduceMotion: Bool) {
|
||||
@ -667,7 +684,7 @@ public struct PresentationThemeSettings: Codable {
|
||||
self.listsFontSize = PresentationFontSize(rawValue: try container.decodeIfPresent(Int32.self, forKey: "lf") ?? PresentationFontSize.regular.rawValue) ?? fontSize
|
||||
|
||||
self.chatBubbleSettings = try container.decodeIfPresent(PresentationChatBubbleSettings.self, forKey: "chatBubbleSettings") ?? PresentationChatBubbleSettings.default
|
||||
self.automaticThemeSwitchSetting = try container.decodeIfPresent(AutomaticThemeSwitchSetting.self, forKey: "automaticThemeSwitchSetting") ?? AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night))
|
||||
self.automaticThemeSwitchSetting = try container.decodeIfPresent(AutomaticThemeSwitchSetting.self, forKey: "automaticThemeSwitchSetting") ?? AutomaticThemeSwitchSetting(force: false, trigger: .system, theme: .builtin(.night))
|
||||
|
||||
self.largeEmoji = try container.decodeIfPresent(Bool.self, forKey: "largeEmoji") ?? true
|
||||
self.reduceMotion = try container.decodeIfPresent(Bool.self, forKey: "reduceMotion") ?? false
|
||||
|
@ -1297,7 +1297,7 @@ public func themeImage(account: Account, accountManager: AccountManager<Telegram
|
||||
}
|
||||
}
|
||||
|
||||
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, emoticon: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, nightMode: Bool? = nil, emoticon: Bool = false, large: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Int32?), NoError>
|
||||
|
||||
var reference: MediaResourceReference?
|
||||
@ -1308,9 +1308,11 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
||||
}
|
||||
|
||||
let themeSignal: Signal<PresentationTheme?, NoError>
|
||||
if case let .builtin(theme) = theme {
|
||||
if case let .cloud(theme) = theme, let nightMode = nightMode {
|
||||
themeSignal = .single(makePresentationTheme(cloudTheme: theme.theme, dark: nightMode))
|
||||
} else if case let .builtin(theme) = theme {
|
||||
themeSignal = .single(makeDefaultPresentationTheme(reference: theme, serviceBackgroundColor: nil))
|
||||
} else if case let .cloud(theme) = theme, let settings = theme.theme.settings {
|
||||
} else if case let .cloud(theme) = theme, let settings = theme.theme.settings?.first {
|
||||
themeSignal = Signal { subscriber in
|
||||
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false)
|
||||
subscriber.putNext(theme)
|
||||
@ -1480,58 +1482,122 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
||||
|
||||
let incomingColors = colors.1
|
||||
if emoticon {
|
||||
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
|
||||
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
||||
c.clip()
|
||||
|
||||
if incomingColors.count >= 2 {
|
||||
let gradientColors = incomingColors.map { $0.cgColor } as CFArray
|
||||
if large {
|
||||
c.saveGState()
|
||||
|
||||
var locations: [CGFloat] = []
|
||||
for i in 0 ..< incomingColors.count {
|
||||
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
|
||||
locations.append(t)
|
||||
}
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
||||
} else if !incomingColors.isEmpty {
|
||||
c.setFillColor(incomingColors[0].cgColor)
|
||||
c.fill(rect)
|
||||
}
|
||||
c.translateBy(x: 5.0, y: 25.0)
|
||||
c.translateBy(x: 114.0, y: 32.0)
|
||||
c.scaleBy(x: 1.0, y: -1.0)
|
||||
c.translateBy(x: -114.0, y: -32.0)
|
||||
|
||||
c.resetClip()
|
||||
let _ = try? drawSvgPath(c, path: "M98.0061174,0 C106.734138,0 113.82927,6.99200411 113.996965,15.6850616 L114,16 C114,24.836556 106.830179,32 98.0061174,32 L21.9938826,32 C18.2292665,32 14.7684355,30.699197 12.0362474,28.5221601 C8.56516444,32.1765452 -1.77635684e-15,31.9985981 -1.77635684e-15,31.9985981 C5.69252399,28.6991366 5.98604874,24.4421608 5.99940747,24.1573436 L6,24.1422468 L6,16 C6,7.163444 13.1698213,0 21.9938826,0 L98.0061174,0 ")
|
||||
if Set(incomingColors.map(\.rgb)).count > 1 {
|
||||
c.clip()
|
||||
|
||||
var colors: [CGColor] = []
|
||||
var locations: [CGFloat] = []
|
||||
for i in 0 ..< incomingColors.count {
|
||||
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
|
||||
locations.append(t)
|
||||
colors.append(incomingColors[i].cgColor)
|
||||
}
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
|
||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 32.0), options: CGGradientDrawingOptions())
|
||||
} else {
|
||||
c.setFillColor(incomingColors[0].cgColor)
|
||||
c.fillPath()
|
||||
}
|
||||
|
||||
c.restoreGState()
|
||||
} else {
|
||||
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
|
||||
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
||||
c.clip()
|
||||
|
||||
if incomingColors.count >= 2 {
|
||||
let gradientColors = incomingColors.map { $0.cgColor } as CFArray
|
||||
|
||||
var locations: [CGFloat] = []
|
||||
for i in 0 ..< incomingColors.count {
|
||||
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
|
||||
locations.append(t)
|
||||
}
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
||||
} else if !incomingColors.isEmpty {
|
||||
c.setFillColor(incomingColors[0].cgColor)
|
||||
c.fill(rect)
|
||||
}
|
||||
|
||||
c.resetClip()
|
||||
}
|
||||
} else {
|
||||
let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors)
|
||||
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
|
||||
}
|
||||
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: -1.0, y: 1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
if !(emoticon && large) {
|
||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||
c.scaleBy(x: -1.0, y: 1.0)
|
||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||
}
|
||||
|
||||
let outgoingColors = colors.2
|
||||
if emoticon {
|
||||
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
|
||||
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
||||
c.clip()
|
||||
|
||||
if outgoingColors.count >= 2 {
|
||||
let gradientColors = outgoingColors.map { $0.cgColor } as CFArray
|
||||
if large {
|
||||
c.saveGState()
|
||||
|
||||
var locations: [CGFloat] = []
|
||||
for i in 0 ..< outgoingColors.count {
|
||||
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
|
||||
locations.append(t)
|
||||
c.translateBy(x: drawingRect.width - 114.0 - 5.0, y: 65.0)
|
||||
c.translateBy(x: 114.0, y: 32.0)
|
||||
c.scaleBy(x: -1.0, y: -1.0)
|
||||
c.translateBy(x: 0, y: -32.0)
|
||||
|
||||
let _ = try? drawSvgPath(c, path: "M98.0061174,0 C106.734138,0 113.82927,6.99200411 113.996965,15.6850616 L114,16 C114,24.836556 106.830179,32 98.0061174,32 L21.9938826,32 C18.2292665,32 14.7684355,30.699197 12.0362474,28.5221601 C8.56516444,32.1765452 -1.77635684e-15,31.9985981 -1.77635684e-15,31.9985981 C5.69252399,28.6991366 5.98604874,24.4421608 5.99940747,24.1573436 L6,24.1422468 L6,16 C6,7.163444 13.1698213,0 21.9938826,0 L98.0061174,0 ")
|
||||
if Set(outgoingColors.map(\.rgb)).count > 1 {
|
||||
c.clip()
|
||||
|
||||
var colors: [CGColor] = []
|
||||
var locations: [CGFloat] = []
|
||||
for i in 0 ..< outgoingColors.count {
|
||||
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
|
||||
locations.append(t)
|
||||
colors.append(outgoingColors[i].cgColor)
|
||||
}
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
|
||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 32.0), options: CGGradientDrawingOptions())
|
||||
} else {
|
||||
c.setFillColor(outgoingColors[0].cgColor)
|
||||
c.fillPath()
|
||||
}
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
c.restoreGState()
|
||||
} else {
|
||||
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
|
||||
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
||||
c.clip()
|
||||
|
||||
if outgoingColors.count >= 2 {
|
||||
let gradientColors = outgoingColors.map { $0.cgColor } as CFArray
|
||||
|
||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
||||
} else if !outgoingColors.isEmpty {
|
||||
c.setFillColor(outgoingColors[0].cgColor)
|
||||
c.fill(rect)
|
||||
var locations: [CGFloat] = []
|
||||
for i in 0 ..< outgoingColors.count {
|
||||
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
|
||||
locations.append(t)
|
||||
}
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
||||
} else if !outgoingColors.isEmpty {
|
||||
c.setFillColor(outgoingColors[0].cgColor)
|
||||
c.fill(rect)
|
||||
}
|
||||
}
|
||||
|
||||
c.resetClip()
|
||||
|
Loading…
x
Reference in New Issue
Block a user