Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-10-18 17:50:15 +04:00
parent 36387e894a
commit 7508fc7290
47 changed files with 3406 additions and 663 deletions

View File

@ -6928,3 +6928,5 @@ Sorry for the inconvenience.";
"Conversation.RequestToJoinGroup" = "REQUEST TO JOIN"; "Conversation.RequestToJoinGroup" = "REQUEST TO JOIN";
"Channel.AdminLog.JoinedViaRequest" = "%1$@ joined via invite link %2$@, approved by %3$@"; "Channel.AdminLog.JoinedViaRequest" = "%1$@ joined via invite link %2$@, approved by %3$@";
"Appearance.NightTheme" = "Night Mode";

View File

@ -176,12 +176,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
let importersContext = existingContext ?? context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil)) let importersContext = existingContext ?? context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
let arguments = InviteRequestsControllerArguments(context: context, openLinks: { let approveRequestImpl: (EnginePeer) -> Void = { peer in
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
pushControllerImpl?(controller)
}, openPeer: { peer in
navigateToProfileImpl?(peer)
}, approveRequest: { peer in
importersContext.update(peer.id, action: .approve) importersContext.update(peer.id, action: .approve)
let _ = (context.engine.data.get( 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) 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) 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 }, peerContextAction: { peer, node, gesture in
guard let node = node as? ContextExtractedContentContainingNode else { guard let node = node as? ContextExtractedContentContainingNode else {
return return
@ -209,11 +217,20 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.MemberRequests_AddToGroup, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) 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) let dismissPromise = ValuePromise<Bool>(false)
@ -290,7 +307,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
let title: ItemListControllerTitle = .text(presentationData.strings.MemberRequests_Title) 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 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)) return (controllerState, (listState, arguments))
} }

View File

@ -59,7 +59,7 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
self.item = item self.item = item
self.animationNode = AnimatedStickerNode() 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.animationNode.visibility = true
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
@ -89,10 +89,10 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
var insets = layout.insets(options: []) var insets = layout.insets(options: [])
insets.top += navigationBarHeight insets.top += navigationBarHeight
let imageSpacing: CGFloat = 20.0 let imageSpacing: CGFloat = 10.0
let textSpacing: CGFloat = 8.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 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) 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.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.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.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: 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.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))
} }
} }

View File

@ -278,7 +278,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
let rect: CGRect let rect: CGRect
if isExtracted { if isExtracted {
if extractedVerticalOffset > 0.0 { 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 { } else {
rect = extractedRect rect = extractedRect
} }
@ -312,7 +312,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.avatarNode.transform = CATransform3DIdentity strongSelf.avatarNode.transform = CATransform3DIdentity
var avatarInitialRect = strongSelf.avatarNode.view.convert(strongSelf.avatarNode.bounds, to: strongSelf.offsetContainerNode.supernode?.view) var avatarInitialRect = strongSelf.avatarNode.view.convert(strongSelf.avatarNode.bounds, to: strongSelf.offsetContainerNode.supernode?.view)
if strongSelf.avatarTransitionNode == nil { 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 let initialScale = avatarInitialRect.width / targetRect.width
avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * initialScale avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * initialScale
@ -408,8 +408,9 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.avatarListContainerNode = nil strongSelf.avatarListContainerNode = nil
strongSelf.avatarListNode = 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() 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) 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) alphaTransition.updateAlpha(node: strongSelf.dismissButton, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1)
let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform 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 let initialExtractedBackgroundPosition = strongSelf.extractedBackgroundImageNode.position
strongSelf.extractedBackgroundImageNode.layer.position = rect.center 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.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.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.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil 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)) 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, dy: 0.0) 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 extractedHeight = extractedRect.height + expandedSubtitleLayout.size.height - subtitleLayout.size.height
var extractedVerticalOffset: CGFloat = 0.0 var extractedVerticalOffset: CGFloat = 0.0
if item.importer?.peer.peer?.smallProfileImage != nil { if item.importer?.peer.peer?.smallProfileImage != nil {
@ -580,7 +574,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
extractedHeight += extractedVerticalOffset extractedHeight += extractedVerticalOffset
} }
extractedRect.size.height = extractedHeight - 48.0 extractedRect.size.height = extractedHeight - 46.0
strongSelf.extractedVerticalOffset = extractedVerticalOffset strongSelf.extractedVerticalOffset = extractedVerticalOffset
strongSelf.extractedRect = extractedRect strongSelf.extractedRect = extractedRect

View File

@ -1,5 +1,6 @@
import Foundation import Foundation
import UIKit import UIKit
import AsyncDisplayKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import TelegramPresentationData import TelegramPresentationData
@ -30,6 +31,7 @@ public enum ItemListNavigationButtonContent: Equatable {
case none case none
case text(String) case text(String)
case icon(ItemListNavigationButtonContentIcon) case icon(ItemListNavigationButtonContentIcon)
case node(ASDisplayNode)
} }
public struct ItemListNavigationButton { public struct ItemListNavigationButton {
@ -335,6 +337,11 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme) image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme)
} }
item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) 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.leftNavigationButtonTitleAndStyle = (leftNavigationButton.content, leftNavigationButton.style)
strongSelf.navigationItem.setLeftBarButton(item, animated: false) strongSelf.navigationItem.setLeftBarButton(item, animated: false)
@ -392,6 +399,11 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme) image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme)
} }
item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action) 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) items.append(item)

View File

@ -95,8 +95,10 @@ public final class JoinLinkPreviewController: ViewController {
strongSelf.dismiss() strongSelf.dismiss()
case .invalidHash: case .invalidHash:
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } 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)) Queue.mainQueue().after(0.2) {
strongSelf.dismiss() 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 }, error: { [weak self] error in

View File

@ -115,8 +115,6 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
self.backgroundNode.addSubnode(self.effectNode) self.backgroundNode.addSubnode(self.effectNode)
self.backgroundNode.addSubnode(self.contentBackgroundNode) self.backgroundNode.addSubnode(self.contentBackgroundNode)
@ -169,7 +167,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout { if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous { if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame 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 contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition) self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -238,7 +236,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
if let contentNode = self.contentNode { 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)) 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)
} }
} }

View File

@ -83,9 +83,9 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
self.countNode = ASTextNode() self.countNode = ASTextNode()
self.aboutNode = ASTextNode() self.aboutNode = ASTextNode()
self.aboutNode.maximumNumberOfLines = 6 self.aboutNode.maximumNumberOfLines = 8
self.descriptionNode = ASTextNode() self.descriptionNode = ASTextNode()
self.descriptionNode.maximumNumberOfLines = 0 self.descriptionNode.maximumNumberOfLines = 3
self.descriptionNode.textAlignment = .center self.descriptionNode.textAlignment = .center
self.peersScrollNode = ASScrollNode() self.peersScrollNode = ASScrollNode()
self.peersScrollNode.view.showsHorizontalScrollIndicator = false self.peersScrollNode.view.showsHorizontalScrollIndicator = false
@ -160,6 +160,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
self.actionButtonNode.pressed = { [weak self] in self.actionButtonNode.pressed = { [weak self] in
self?.join?() self?.join?()
self?.actionButtonNode.transitionToProgress()
} }
self.addSubnode(self.actionButtonNode) self.addSubnode(self.actionButtonNode)
} }
@ -177,7 +178,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
self.contentOffsetUpdated = f 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) var nodeHeight: CGFloat = (self.peerNodes.isEmpty ? 264.0 : 364.0)
let paddedSize = CGSize(width: size.width - 60.0, height: size.height) let paddedSize = CGSize(width: size.width - 60.0, height: size.height)
@ -185,10 +186,21 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
var aboutSize: CGSize? var aboutSize: CGSize?
var descriptionSize: CGSize? var descriptionSize: CGSize?
if self.aboutNode.supernode != nil { if self.aboutNode.supernode != nil {
if isLandscape {
self.aboutNode.maximumNumberOfLines = 3
} else {
self.aboutNode.maximumNumberOfLines = 8
}
let measuredSize = self.aboutNode.measure(paddedSize) let measuredSize = self.aboutNode.measure(paddedSize)
nodeHeight += measuredSize.height + 20.0 nodeHeight += measuredSize.height + 20.0
aboutSize = measuredSize aboutSize = measuredSize
} }
if isLandscape {
self.descriptionNode.removeFromSupernode()
} else if self.descriptionNode.supernode == nil {
self.addSubnode(self.descriptionNode)
}
if self.descriptionNode.supernode != nil { if self.descriptionNode.supernode != nil {
let measuredSize = self.descriptionNode.measure(paddedSize) let measuredSize = self.descriptionNode.measure(paddedSize)
nodeHeight += measuredSize.height + 20.0 + 10.0 nodeHeight += measuredSize.height + 20.0 + 10.0
@ -286,7 +298,7 @@ public final class JoinLinkPreviewLoadingContainerNode: ASDisplayNode, ShareCont
self.contentOffsetUpdated = f 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 nodeHeight: CGFloat = 125.0
let indicatorSize = self.activityIndicator.calculateSizeThatFits(size) let indicatorSize = self.activityIndicator.calculateSizeThatFits(size)

View File

@ -82,7 +82,7 @@ final class LanguageLinkPreviewContentNode: ASDisplayNode, ShareContentContainer
self.contentOffsetUpdated = f 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 insets = UIEdgeInsets(top: 12.0, left: 10.0, bottom: 12.0 + bottomInset, right: 10.0)
let titleSpacing: CGFloat = 12.0 let titleSpacing: CGFloat = 12.0
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: .greatestFiniteMagnitude)) let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: .greatestFiniteMagnitude))

View File

@ -188,7 +188,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, UIScro
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout { if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous { if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame 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 contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition) self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -274,7 +274,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, UIScro
if let contentNode = self.contentNode { 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)) 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)
} }
} }

View File

@ -124,7 +124,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry {
let arguments = arguments as! BlockedPeersControllerArguments let arguments = arguments as! BlockedPeersControllerArguments
switch self { switch self {
case let .add(theme, text): 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() arguments.addPeer()
}) })
case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):

View 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()
}
})
}
}

File diff suppressed because it is too large Load Diff

View 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()
// }
// })
}
}

View File

@ -331,7 +331,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
previewThemePromise.set(.single(theme.withUpdated(name: "", defaultWallpaper: wallpaper))) previewThemePromise.set(.single(theme.withUpdated(name: "", defaultWallpaper: wallpaper)))
case let .edit(info): case let .edit(info):
hasSettings = info.theme.settings != nil 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 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 { if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
previewThemePromise.set(cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) 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 { switch mode {
case .create: case .create:
if let themeResource = themeResource { 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 |> deliverOnMainQueue)).start(next: { next in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
@ -663,7 +663,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
}) })
} }
case let .edit(info): 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 |> deliverOnMainQueue)).start(next: { next in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()

View File

@ -195,7 +195,7 @@ final class ThemeAccentColorController: ViewController {
if let settings = themeSettings { if let settings = themeSettings {
hasSettings = true hasSettings = true
baseTheme = settings.baseTheme 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 hasSettings = true
baseTheme = settings.baseTheme baseTheme = settings.baseTheme
} else if case let .builtin(theme) = generalThemeReference { } else if case let .builtin(theme) = generalThemeReference {
@ -218,7 +218,7 @@ final class ThemeAccentColorController: ViewController {
} else if case let .colors(theme, create) = strongSelf.mode { } else if case let .colors(theme, create) = strongSelf.mode {
var baseTheme: TelegramBaseTheme var baseTheme: TelegramBaseTheme
var telegramTheme: TelegramTheme? 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 telegramTheme = theme.theme
baseTheme = settings.baseTheme baseTheme = settings.baseTheme
} else if case let .builtin(theme) = theme { } else if case let .builtin(theme) = theme {
@ -234,7 +234,7 @@ final class ThemeAccentColorController: ViewController {
let apply: Signal<Void, CreateThemeError> let apply: Signal<Void, CreateThemeError>
if create { 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 |> mapToSignal { next -> Signal<Void, CreateThemeError> in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
@ -263,7 +263,7 @@ final class ThemeAccentColorController: ViewController {
} }
} }
} else if let theme = telegramTheme { } 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 |> mapToSignal { next -> Signal<Void, CreateThemeError> in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
@ -439,7 +439,7 @@ final class ThemeAccentColorController: ViewController {
if let color = themeSpecificAccentColor?.color, color != .clear { if let color = themeSpecificAccentColor?.color, color != .clear {
accentColor = color accentColor = color
customAccentColor = accentColor 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) accentColor = UIColor(rgb: settings.accentColor)
customAccentColor = accentColor customAccentColor = accentColor
} else { } else {
@ -457,7 +457,7 @@ final class ThemeAccentColorController: ViewController {
wallpaper = theme.chat.defaultWallpaper 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 animateMessageColors = settings.animateMessageColors
outgoingAccentColor = settings.outgoingAccentColor.flatMap { UIColor(rgb: $0) } outgoingAccentColor = settings.outgoingAccentColor.flatMap { UIColor(rgb: $0) }
} else if let referenceTheme = referenceTheme { } else if let referenceTheme = referenceTheme {
@ -493,7 +493,7 @@ final class ThemeAccentColorController: ViewController {
} }
} else { } else {
let presentationTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference)! 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) accentColor = UIColor(argb: themeSettings.accentColor)
if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] { if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] {

View File

@ -18,7 +18,7 @@ import UndoUI
import TelegramNotices import TelegramNotices
public enum ThemePreviewSource { public enum ThemePreviewSource {
case settings(PresentationThemeReference, TelegramWallpaper?) case settings(PresentationThemeReference, TelegramWallpaper?, Bool)
case theme(TelegramTheme) case theme(TelegramTheme)
case slug(String, TelegramMediaFile) case slug(String, TelegramMediaFile)
case themeSettings(String, TelegramThemeSettings) case themeSettings(String, TelegramThemeSettings)
@ -106,15 +106,19 @@ public final class ThemePreviewController: ViewController {
} }
)) ))
hasInstallsCount = true hasInstallsCount = true
case let .settings(themeReference, _): case let .settings(themeReference, _, _):
if case let .cloud(theme) = themeReference { if case let .cloud(theme) = themeReference {
self.theme.set(getTheme(account: context.account, slug: theme.theme.slug) self.theme.set(getTheme(account: context.account, slug: theme.theme.slug)
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in |> `catch` { _ -> Signal<TelegramTheme?, NoError> in
return .single(nil) return .single(nil)
}) })
themeName = theme.theme.title if let emoticon = theme.theme.emoticon{
hasInstallsCount = true themeName = emoticon
} else {
themeName = theme.theme.title
hasInstallsCount = true
}
} else { } else {
self.theme.set(.single(nil)) self.theme.set(.single(nil))
themeName = previewTheme.name.string themeName = previewTheme.name.string
@ -145,7 +149,7 @@ public final class ThemePreviewController: ViewController {
|> deliverOnMainQueue).start(next: { [weak self] theme, presentationTheme in |> deliverOnMainQueue).start(next: { [weak self] theme, presentationTheme in
if let strongSelf = self, let theme = theme { if let strongSelf = self, let theme = theme {
let titleView = CounterContollerTitleView(theme: strongSelf.previewTheme) 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.navigationItem.titleView = titleView
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings)) strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings))
} }
@ -184,13 +188,14 @@ public final class ThemePreviewController: ViewController {
super.loadDisplayNode() super.loadDisplayNode()
var isPreview = false var isPreview = false
if case .settings = self.source { var forceReady = false
isPreview = true
}
var initialWallpaper: TelegramWallpaper? var initialWallpaper: TelegramWallpaper?
if case let .settings(_, currentWallpaper) = self.source, let wallpaper = currentWallpaper { if case let .settings(_, currentWallpaper, preview) = self.source {
initialWallpaper = wallpaper 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 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 { if let strongSelf = self {
strongSelf.apply() strongSelf.apply()
} }
}, isPreview: isPreview, ready: self._ready) }, isPreview: isPreview, forceReady: forceReady, ready: self._ready)
self.displayNodeDidLoad() self.displayNodeDidLoad()
let previewTheme = self.previewTheme let previewTheme = self.previewTheme
@ -227,7 +232,7 @@ public final class ThemePreviewController: ViewController {
let disposable = self.applyDisposable let disposable = self.applyDisposable
switch self.source { switch self.source {
case let .settings(reference, _): case let .settings(reference, _, _):
theme = .single(reference) theme = .single(reference)
case .theme, .slug, .themeSettings: case .theme, .slug, .themeSettings:
theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1)) theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1))
@ -380,7 +385,7 @@ public final class ThemePreviewController: ViewController {
} }
var themeSpecificAccentColors = updatedSettings.themeSpecificAccentColors 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)) let baseThemeReference = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
themeSpecificAccentColors[baseThemeReference.index] = PresentationThemeAccentColor(themeIndex: updatedTheme.index) themeSpecificAccentColors[baseThemeReference.index] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
} }
@ -421,7 +426,9 @@ public final class ThemePreviewController: ViewController {
|> deliverOnMainQueue).start(next: { [weak self] previousDefaultTheme in |> deliverOnMainQueue).start(next: { [weak self] previousDefaultTheme in
if let strongSelf = self, let layout = strongSelf.validLayout { if let strongSelf = self, let layout = strongSelf.validLayout {
Queue.mainQueue().after(0.3) { 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 let navigationController = strongSelf.navigationController as? NavigationController
if let (previousDefaultTheme, previousAccentColor, autoNightMode, theme, _) = previousDefaultTheme { if let (previousDefaultTheme, previousAccentColor, autoNightMode, theme, _) = previousDefaultTheme {
let _ = (ApplicationSpecificNotice.getThemeChangeTip(accountManager: strongSelf.context.sharedContext.accountManager) let _ = (ApplicationSpecificNotice.getThemeChangeTip(accountManager: strongSelf.context.sharedContext.accountManager)

View File

@ -72,7 +72,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var wallpaper: TelegramWallpaper 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.context = context
self.previewTheme = previewTheme self.previewTheme = previewTheme
self.isPreview = isPreview self.isPreview = isPreview
@ -125,7 +125,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings, doneButtonType: .set) 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) self.toolbarNode.setDoneEnabled(false)
} }

View File

@ -93,7 +93,7 @@ enum ThemeSettingsColorOption: Equatable {
case let .accentColor(color): case let .accentColor(color):
return color.color return color.color
case let .theme(reference): 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) return UIColor(argb: settings.accentColor)
} else { } else {
return nil return nil
@ -115,7 +115,7 @@ enum ThemeSettingsColorOption: Equatable {
case let .accentColor(color): case let .accentColor(color):
return color.plainBubbleColors return color.plainBubbleColors
case let .theme(reference): 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 return settings.messageColors
} else { } else {
return [] return []
@ -128,7 +128,7 @@ enum ThemeSettingsColorOption: Equatable {
case let .accentColor(color): case let .accentColor(color):
return color.customBubbleColors return color.customBubbleColors
case let .theme(reference): 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 return settings.messageColors
} else { } else {
return [] return []

View File

@ -18,34 +18,6 @@ import AccountContext
import ContextUI import ContextUI
import UndoUI 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 { func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
let name: String let name: String
switch reference { switch reference {
@ -63,7 +35,11 @@ func themeDisplayName(strings: PresentationStrings, reference: PresentationTheme
case let .local(theme): case let .local(theme):
name = theme.title name = theme.title
case let .cloud(theme): case let .cloud(theme):
name = theme.theme.title if let emoticon = theme.theme.emoticon {
name = emoticon
} else {
name = theme.theme.title
}
} }
return name return name
} }
@ -71,10 +47,11 @@ func themeDisplayName(strings: PresentationStrings, reference: PresentationTheme
private final class ThemeSettingsControllerArguments { private final class ThemeSettingsControllerArguments {
let context: AccountContext let context: AccountContext
let selectTheme: (PresentationThemeReference) -> Void let selectTheme: (PresentationThemeReference) -> Void
let selectFontSize: (PresentationFontSize) -> Void let openThemeSettings: () -> Void
let openWallpaperSettings: () -> Void let openWallpaperSettings: () -> Void
let selectAccentColor: (PresentationThemeAccentColor?) -> Void let selectAccentColor: (PresentationThemeAccentColor?) -> Void
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
let toggleNightTheme: (Bool) -> Void
let openAutoNightTheme: () -> Void let openAutoNightTheme: () -> Void
let openTextSize: () -> Void let openTextSize: () -> Void
let openBubbleSettings: () -> Void let openBubbleSettings: () -> Void
@ -85,13 +62,14 @@ private final class ThemeSettingsControllerArguments {
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, 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.context = context
self.selectTheme = selectTheme self.selectTheme = selectTheme
self.selectFontSize = selectFontSize self.openThemeSettings = openThemeSettings
self.openWallpaperSettings = openWallpaperSettings self.openWallpaperSettings = openWallpaperSettings
self.selectAccentColor = selectAccentColor self.selectAccentColor = selectAccentColor
self.openAccentColorPicker = openAccentColorPicker self.openAccentColorPicker = openAccentColorPicker
self.toggleNightTheme = toggleNightTheme
self.openAutoNightTheme = openAutoNightTheme self.openAutoNightTheme = openAutoNightTheme
self.openTextSize = openTextSize self.openTextSize = openTextSize
self.openBubbleSettings = openBubbleSettings self.openBubbleSettings = openBubbleSettings
@ -106,8 +84,8 @@ private final class ThemeSettingsControllerArguments {
private enum ThemeSettingsControllerSection: Int32 { private enum ThemeSettingsControllerSection: Int32 {
case chatPreview case chatPreview
case background case nightMode
case fontSize case message
case icon case icon
case other case other
} }
@ -132,15 +110,14 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
private enum ThemeSettingsControllerEntry: ItemListNodeEntry { private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case themeListHeader(PresentationTheme, String) case themeListHeader(PresentationTheme, String)
case fontSizeHeader(PresentationTheme, String)
case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem]) 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 wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?) case autoNight(PresentationTheme, String, Bool)
case autoNightTheme(PresentationTheme, String, String) case autoNightTheme(PresentationTheme, String, String)
case textSize(PresentationTheme, String, String) case textSize(PresentationTheme, String, String)
case bubbleSettings(PresentationTheme, String, String) case bubbleSettings(PresentationTheme, String, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
case iconHeader(PresentationTheme, String) case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?) case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
case otherHeader(PresentationTheme, String) case otherHeader(PresentationTheme, String)
@ -150,12 +127,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .themeListHeader, .chatPreview, .themeItem, .accentColor: case .themeListHeader, .chatPreview, .themes, .chatTheme, .wallpaper:
return ThemeSettingsControllerSection.chatPreview.rawValue return ThemeSettingsControllerSection.chatPreview.rawValue
case .fontSizeHeader, .fontSize: case .autoNight, .autoNightTheme:
return ThemeSettingsControllerSection.fontSize.rawValue return ThemeSettingsControllerSection.nightMode.rawValue
case .wallpaper, .autoNightTheme, .textSize, .bubbleSettings: case .textSize, .bubbleSettings:
return ThemeSettingsControllerSection.background.rawValue return ThemeSettingsControllerSection.message.rawValue
case .iconHeader, .iconItem: case .iconHeader, .iconItem:
return ThemeSettingsControllerSection.icon.rawValue return ThemeSettingsControllerSection.icon.rawValue
case .otherHeader, .largeEmoji, .animations, .animationsInfo: case .otherHeader, .largeEmoji, .animations, .animationsInfo:
@ -169,11 +146,13 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 0 return 0
case .chatPreview: case .chatPreview:
return 1 return 1
case .themeItem: case .themes:
return 2 return 2
case .accentColor: case .chatTheme:
return 4 return 3
case .wallpaper: case .wallpaper:
return 4
case .autoNight:
return 5 return 5
case .autoNightTheme: case .autoNightTheme:
return 6 return 6
@ -181,22 +160,18 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 7 return 7
case .bubbleSettings: case .bubbleSettings:
return 8 return 8
case .fontSizeHeader:
return 9
case .fontSize:
return 10
case .iconHeader: case .iconHeader:
return 11 return 9
case .iconItem: case .iconItem:
return 12 return 10
case .otherHeader: case .otherHeader:
return 13 return 11
case .largeEmoji: case .largeEmoji:
return 14 return 12
case .animations: case .animations:
return 15 return 13
case .animationsInfo: case .animationsInfo:
return 16 return 14
} }
} }
@ -208,14 +183,26 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false 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): case let .wallpaper(lhsTheme, lhsText):
if case let .wallpaper(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .wallpaper(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
} }
case let .accentColor(lhsTheme, _, lhsCurrentTheme, lhsThemes, lhsColor): case let .autoNight(lhsTheme, lhsText, lhsValue):
if case let .accentColor(rhsTheme, _, rhsCurrentTheme, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsThemes == rhsThemes, lhsColor == rhsColor { if case let .autoNight(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
} else { } else {
return false return false
@ -244,24 +231,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false 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): case let .iconHeader(lhsTheme, lhsText):
if case let .iconHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .iconHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -308,101 +277,26 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ThemeSettingsControllerArguments let arguments = arguments as! ThemeSettingsControllerArguments
switch self { 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): 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) 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): case let .wallpaper(_, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings() arguments.openWallpaperSettings()
}) })
case let .accentColor(theme, generalThemeReference, currentTheme, themes, color): case let .autoNight(_, title, value):
var colorItems: [ThemeSettingsAccentColor] = [] return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleNightTheme(value)
for theme in themes { }, tag: nil)
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 .autoNightTheme(_, text, value): 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: { return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openAutoNightTheme() arguments.openAutoNightTheme()
@ -417,18 +311,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
}) })
case let .themeListHeader(_, text): case let .themeListHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
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): case let .iconHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .iconItem(theme, strings, icons, value): 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] = [] var entries: [ThemeSettingsControllerEntry] = []
let strings = presentationData.strings let strings = presentationData.strings
@ -459,44 +341,46 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
entries.append(.themeListHeader(presentationData.theme, title)) 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)])) 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 entries.append(.themes(presentationData.theme, presentationData.strings, chatThemes, themeReference, presentationThemeSettings.automaticThemeSwitchSetting.force, animatedEmojiStickers))
if case let .cloud(theme) = reference { // let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
return theme.theme.settings == nil // if case let .cloud(theme) = reference {
} else { // return theme.theme.settings == nil
return true // } else {
} // return true
} // }
// }
let generalThemeReference: PresentationThemeReference //
if case let .cloud(theme) = themeReference, let settings = theme.theme.settings { // let generalThemeReference: PresentationThemeReference
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)) // if case let .cloud(theme) = themeReference, let settings = theme.theme.settings {
} else { // generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
generalThemeReference = themeReference // } 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(.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(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
entries.append(.autoNight(presentationData.theme, strings.Appearance_NightTheme, presentationThemeSettings.automaticThemeSwitchSetting.force))
let autoNightMode: String let autoNightMode: String
switch presentationThemeSettings.automaticThemeSwitchSetting.trigger { switch presentationThemeSettings.automaticThemeSwitchSetting.trigger {
case .system: case .system:
@ -585,15 +469,43 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let removedThemeIndexesPromise = Promise<Set<Int64>>(Set()) let removedThemeIndexesPromise = Promise<Set<Int64>>(Set())
let removedThemeIndexes = Atomic<Set<Int64>>(value: 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 let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
selectThemeImpl?(theme) selectThemeImpl?(theme)
}, selectFontSize: { _ in }, openThemeSettings: {
pushControllerImpl?(themePickerController(context: context))
}, openWallpaperSettings: { }, openWallpaperSettings: {
pushControllerImpl?(ThemeGridController(context: context)) pushControllerImpl?(ThemeGridController(context: context))
}, selectAccentColor: { accentColor in }, selectAccentColor: { accentColor in
selectAccentColorImpl?(accentColor) selectAccentColorImpl?(accentColor)
}, openAccentColorPicker: { themeReference, create in }, openAccentColorPicker: { themeReference, create in
openAccentColorPickerImpl?(themeReference, create) 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: { }, openAutoNightTheme: {
pushControllerImpl?(themeAutoNightSettingsController(context: context)) pushControllerImpl?(themeAutoNightSettingsController(context: context))
}, openTextSize: { }, openTextSize: {
@ -678,7 +590,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings 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] = [] var items: [ContextMenuItem] = []
if case let .cloud(theme) = reference { 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 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 { if settings.baseTheme == .night {
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue)) selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
} else { } else {
@ -827,7 +739,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
return (accentColor, wallpaper) return (accentColor, wallpaper)
} |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, PresentationThemeReference, Bool, TelegramWallpaper?), NoError> in } |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, PresentationThemeReference, Bool, TelegramWallpaper?), NoError> in
let generalThemeReference: PresentationThemeReference 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)) generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else { } else {
generalThemeReference = reference generalThemeReference = reference
@ -910,7 +822,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings 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] = [] var items: [ContextMenuItem] = []
if let accentColor = accentColor { if let accentColor = accentColor {
@ -948,9 +860,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|> deliverOnMainQueue).start(next: { wallpaper in |> deliverOnMainQueue).start(next: { wallpaper in
var hasSettings = false var hasSettings = false
var settings: TelegramThemeSettings? 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 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 = 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 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 return updated
}))) })))
if isCurrent, let settings = cloudTheme.theme.settings { if isCurrent, let settings = cloudTheme.theme.settings?.first {
let colorThemes = themes.filter { theme in let colorThemes = themes.filter { theme in
if let _ = theme.settings { if let _ = theme.settings {
return true 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()) 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 -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
let themeReference: PresentationThemeReference let themeReference: PresentationThemeReference
if presentationData.autoNightModeTriggered { if presentationData.autoNightModeTriggered {
themeReference = settings.automaticThemeSwitchSetting.theme if let _ = settings.theme.emoticon {
themeReference = settings.theme
} else {
themeReference = settings.automaticThemeSwitchSetting.theme
}
} else { } else {
themeReference = settings.theme themeReference = settings.theme
} }
@ -1081,8 +997,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
availableThemes.append(contentsOf: cloudThemes) 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 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)) return (controllerState, (listState, arguments))
} }
@ -1208,7 +1127,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|> map { resolvedWallpaper, currentTheme -> Bool in |> map { resolvedWallpaper, currentTheme -> Bool in
var updatedTheme = theme var updatedTheme = theme
var currentThemeBaseIndex: Int64? 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 currentThemeBaseIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
} else { } else {
currentThemeBaseIndex = currentTheme.index currentThemeBaseIndex = currentTheme.index
@ -1218,7 +1137,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
var updatedThemeBaseIndex: Int64? var updatedThemeBaseIndex: Int64?
if case let .cloud(info) = theme { if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil)) 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 baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
updatedThemeBaseIndex = baseThemeIndex updatedThemeBaseIndex = baseThemeIndex
} }
@ -1227,19 +1146,30 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var updatedThemeSpecificAccentColors = current.themeSpecificAccentColors var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if let baseThemeIndex = baseThemeIndex { if case let .cloud(info) = updatedTheme, info.theme.settings?.contains(where: { $0.baseTheme == .night || $0.baseTheme == .tinted }) ?? false {
updatedThemeSpecificAccentColors[baseThemeIndex] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
}
if autoNightModeTriggered {
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
updatedAutomaticThemeSwitchSetting.theme = updatedTheme updatedAutomaticThemeSwitchSetting.theme = updatedTheme
} else if case let .builtin(theme) = updatedTheme {
return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors) if [.day, .dayClassic].contains(theme) {
} else { updatedAutomaticThemeSwitchSetting.theme = .builtin(.night)
return current.withUpdatedTheme(updatedTheme).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors) } 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() }).start()
return currentThemeBaseIndex != updatedThemeBaseIndex return currentThemeBaseIndex != updatedThemeBaseIndex
@ -1280,7 +1210,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
let generalThemeReference: PresentationThemeReference 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)) generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else { } else {
generalThemeReference = currentTheme generalThemeReference = currentTheme
@ -1367,7 +1297,7 @@ public final class ThemeSettingsCrossfadeController: ViewController {
private var bottomSnapshotView: UIView? private var bottomSnapshotView: UIView?
private var sideSnapshotView: 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) { public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil, leftOffset: CGFloat? = nil) {
if let view = view { if let view = view {
@ -1464,3 +1394,31 @@ public final class ThemeSettingsCrossfadeController: ViewController {
self.didAppear?() 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() {
}
}

View File

@ -629,14 +629,15 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
var themeWallpaper: TelegramWallpaper? var themeWallpaper: TelegramWallpaper?
if case let .cloud(theme) = theme { 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 customWallpaper = item.themeSpecificChatWallpapers[theme.generalThemeReference.index]
let wallpaper = accentColor?.wallpaper ?? customWallpaper ?? themeWallpaper 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 index += 1
} }

View File

@ -9,6 +9,6 @@ public protocol ShareContentContainerNode: AnyObject {
func deactivate() func deactivate()
func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?)
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) 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() func updateSelectedPeers()
} }

View File

@ -366,7 +366,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout { if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous { if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame 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 contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition) self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -472,7 +472,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
if let contentNode = self.contentNode { 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)) 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)
} }
} }

View File

@ -67,7 +67,7 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain
self.contentOffsetUpdated = f 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 nodeHeight: CGFloat = 125.0
let indicatorSize = self.activityIndicator.calculateSizeThatFits(size) let indicatorSize = self.activityIndicator.calculateSizeThatFits(size)

View File

@ -310,7 +310,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
func deactivate() { 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 let firstLayout = self.validLayout == nil
self.validLayout = (size, bottomInset) self.validLayout = (size, bottomInset)

View File

@ -423,7 +423,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
return (gridTopInset, itemWidth) 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 let firstLayout = self.validLayout == nil
self.validLayout = (size, bottomInset) self.validLayout = (size, bottomInset)

View File

@ -3,6 +3,18 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display 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 final class SolidRoundedButtonTheme {
public let backgroundColor: UIColor public let backgroundColor: UIColor
public let gradientBackgroundColor: UIColor? public let gradientBackgroundColor: UIColor?
@ -31,6 +43,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode
private let iconNode: ASImageNode private let iconNode: ASImageNode
private var progressNode: ASImageNode?
private let buttonHeight: CGFloat private let buttonHeight: CGFloat
private let buttonCornerRadius: CGFloat private let buttonCornerRadius: CGFloat
@ -120,19 +133,62 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.iconNode.alpha = 0.55 strongSelf.iconNode.alpha = 0.55
} else { } else {
strongSelf.buttonBackgroundNode.alpha = 1.0 if strongSelf.buttonBackgroundNode.alpha > 0.0 {
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) strongSelf.buttonBackgroundNode.alpha = 1.0
strongSelf.titleNode.alpha = 1.0 strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.titleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) strongSelf.titleNode.alpha = 1.0
strongSelf.subtitleNode.alpha = 1.0 strongSelf.titleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.subtitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) strongSelf.subtitleNode.alpha = 1.0
strongSelf.iconNode.alpha = 1.0 strongSelf.subtitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.iconNode.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() { public override func didLoad() {
super.didLoad() super.didLoad()

View File

@ -598,8 +598,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) } dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) } dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($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[-275956116] = { return Api.messages.AffectedFoundMessages.parse_affectedFoundMessages($0) }
dict[795652779] = { return Api.BotCommandScope.parse_botCommandScopeDefault($0) } dict[795652779] = { return Api.BotCommandScope.parse_botCommandScopeDefault($0) }
dict[1011811544] = { return Api.BotCommandScope.parse_botCommandScopeUsers($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[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) }
dict[-958657434] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) } dict[-958657434] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) }
dict[-2067782896] = { return Api.messages.FeaturedStickers.parse_featuredStickers($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[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) }
dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) } dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) }
dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($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[1363483106] = { return Api.DialogPeer.parse_dialogPeerFolder($0) }
dict[475467473] = { return Api.WebDocument.parse_webDocument($0) } dict[475467473] = { return Api.WebDocument.parse_webDocument($0) }
dict[-104284986] = { return Api.WebDocument.parse_webDocumentNoProxy($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[-1290580579] = { return Api.contacts.Found.parse_found($0) }
dict[-368018716] = { return Api.ChannelAdminLogEventsFilter.parse_channelAdminLogEventsFilter($0) } dict[-368018716] = { return Api.ChannelAdminLogEventsFilter.parse_channelAdminLogEventsFilter($0) }
dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) } dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) }
@ -1388,8 +1385,6 @@ public struct Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.StatsDateRangeDays: case let _1 as Api.StatsDateRangeDays:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.account.ChatThemes:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.AffectedFoundMessages: case let _1 as Api.messages.AffectedFoundMessages:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.BotCommandScope: case let _1 as Api.BotCommandScope:
@ -1502,8 +1497,6 @@ public struct Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.FeaturedStickers: case let _1 as Api.messages.FeaturedStickers:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.ChatTheme:
_1.serialize(buffer, boxed)
case let _1 as Api.PhoneCallDiscardReason: case let _1 as Api.PhoneCallDiscardReason:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.NearestDc: case let _1 as Api.NearestDc:

View File

@ -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 { public enum PhoneCallDiscardReason: TypeConstructorDescription {
case phoneCallDiscardReasonMissed case phoneCallDiscardReasonMissed
@ -22248,13 +22202,13 @@ public extension Api {
} }
public enum Theme: TypeConstructorDescription { 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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 { if boxed {
buffer.appendInt32(-402474788) buffer.appendInt32(-1609668650)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -22262,7 +22216,12 @@ public extension Api {
serializeString(slug, buffer: buffer, boxed: false) serializeString(slug, buffer: buffer, boxed: false)
serializeString(title, 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 << 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)} if Int(flags) & Int(1 << 4) != 0 {serializeInt32(installsCount!, buffer: buffer, boxed: false)}
break break
} }
@ -22270,8 +22229,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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):
return ("theme", [("flags", flags), ("id", id), ("accessHash", accessHash), ("slug", slug), ("title", title), ("document", document), ("settings", settings), ("installsCount", 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() { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.Document _6 = Api.parse(reader, signature: signature) as? Api.Document
} } } }
var _7: Api.ThemeSettings? var _7: [Api.ThemeSettings]?
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ThemeSettings _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self)
} } } }
var _8: Int32? var _8: String?
if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } 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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -22303,9 +22264,10 @@ public extension Api {
let _c5 = _5 != nil let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil
return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, installsCount: _8) 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 { else {
return nil return nil

View File

@ -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 { public enum Authorizations: TypeConstructorDescription {
case authorizations(authorizations: [Api.Authorization]) 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() let buffer = Buffer()
buffer.appendInt32(-2077048289) buffer.appendInt32(1697530880)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(slug, buffer: buffer, boxed: false) serializeString(slug, buffer: buffer, boxed: false)
serializeString(title, 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 << 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 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) let reader = BufferReader(buffer)
var result: Api.Theme? 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() let buffer = Buffer()
buffer.appendInt32(1555261397) buffer.appendInt32(737414348)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(format, buffer: buffer, boxed: false) serializeString(format, buffer: buffer, boxed: false)
theme.serialize(buffer, true) theme.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)} 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 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} 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 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) let reader = BufferReader(buffer)
var result: Api.Theme? 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>) { public static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-229175188) 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>) { public static func getTheme(format: String, theme: Api.InputTheme, documentId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1919060949) buffer.appendInt32(-1919060949)
@ -7786,20 +7753,6 @@ public extension Api {
return result 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 struct langpack {
public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) { public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) {

View File

@ -360,7 +360,7 @@ public final class VoiceChatJoinScreen: ViewController {
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout { if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous { if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame 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 contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition) self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -443,7 +443,7 @@ public final class VoiceChatJoinScreen: ViewController {
if let contentNode = self.contentNode { 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)) 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 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 sideInset: CGFloat = 16.0
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height)) 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)) let countSize = self.countNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height))

View File

@ -7,8 +7,8 @@ import TelegramApi
extension TelegramTheme { extension TelegramTheme {
convenience init(apiTheme: Api.Theme) { convenience init(apiTheme: Api.Theme) {
switch apiTheme { switch apiTheme {
case let .theme(flags, id, accessHash, slug, title, document, settings, installCount): case let .theme(flags, id, accessHash, slug, title, document, settings, emoticon, 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) 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)
} }
} }
} }

View File

@ -352,7 +352,7 @@ private enum SharedDataKeyValues: Int32 {
case themeSettings = 6 case themeSettings = 6
case countriesList = 7 case countriesList = 7
case wallapersState = 8 case wallapersState = 8
case chatThemes = 9 case chatThemes = 10
} }
public struct SharedDataKeys { public struct SharedDataKeys {

View File

@ -103,6 +103,7 @@ public struct TelegramThemeNativeCodable: Codable {
let id = try container.decode(Int64.self, forKey: "id") let id = try container.decode(Int64.self, forKey: "id")
let accessHash = try container.decode(Int64.self, forKey: "accessHash") let accessHash = try container.decode(Int64.self, forKey: "accessHash")
let slug = try container.decode(String.self, forKey: "slug") 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 title = try container.decode(String.self, forKey: "title")
let file: TelegramMediaFile? let file: TelegramMediaFile?
@ -112,8 +113,12 @@ public struct TelegramThemeNativeCodable: Codable {
file = nil 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 isCreator = try container.decode(Int32.self, forKey: "isCreator") != 0
let isDefault = try container.decode(Int32.self, forKey: "isDefault") != 0 let isDefault = try container.decode(Int32.self, forKey: "isDefault") != 0
let installCount = try container.decodeIfPresent(Int32.self, forKey: "installCount") let installCount = try container.decodeIfPresent(Int32.self, forKey: "installCount")
@ -122,6 +127,7 @@ public struct TelegramThemeNativeCodable: Codable {
id: id, id: id,
accessHash: accessHash, accessHash: accessHash,
slug: slug, slug: slug,
emoticon: emoticon,
title: title, title: title,
file: file, file: file,
settings: settings, settings: settings,
@ -137,6 +143,7 @@ public struct TelegramThemeNativeCodable: Codable {
try container.encode(self.value.id, forKey: "id") try container.encode(self.value.id, forKey: "id")
try container.encode(self.value.accessHash, forKey: "accessHash") try container.encode(self.value.accessHash, forKey: "accessHash")
try container.encode(self.value.slug, forKey: "slug") try container.encode(self.value.slug, forKey: "slug")
try container.encodeIfPresent(self.value.emoticon, forKey: "emoticon")
try container.encode(self.value.title, forKey: "title") try container.encode(self.value.title, forKey: "title")
if let file = self.value.file { if let file = self.value.file {
@ -145,7 +152,7 @@ public struct TelegramThemeNativeCodable: Codable {
try container.encodeNil(forKey: "file") 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.isCreator ? 1 : 0) as Int32, forKey: "isCreator")
try container.encode((self.value.isDefault ? 1 : 0) as Int32, forKey: "isDefault") 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 id: Int64
public let accessHash: Int64 public let accessHash: Int64
public let slug: String public let slug: String
public let emoticon: String?
public let title: String public let title: String
public let file: TelegramMediaFile? public let file: TelegramMediaFile?
public let settings: TelegramThemeSettings? public let settings: [TelegramThemeSettings]?
public let isCreator: Bool public let isCreator: Bool
public let isDefault: Bool public let isDefault: Bool
public let installCount: Int32? 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.id = id
self.accessHash = accessHash self.accessHash = accessHash
self.slug = slug self.slug = slug
self.emoticon = emoticon
self.title = title self.title = title
self.file = file self.file = file
self.settings = settings self.settings = settings
@ -187,6 +196,9 @@ public final class TelegramTheme: Equatable {
if lhs.slug != rhs.slug { if lhs.slug != rhs.slug {
return false return false
} }
if lhs.emoticon != rhs.emoticon {
return false
}
if lhs.title != rhs.title { if lhs.title != rhs.title {
return false return false
} }

View File

@ -3,44 +3,11 @@ import Postbox
import SwiftSignalKit import SwiftSignalKit
import TelegramApi 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 final class ChatThemes: Codable, Equatable {
public let chatThemes: [ChatTheme] public let chatThemes: [TelegramTheme]
public let hash: Int32 public let hash: Int64
public init(chatThemes: [ChatTheme], hash: Int32) { public init(chatThemes: [TelegramTheme], hash: Int64) {
self.chatThemes = chatThemes self.chatThemes = chatThemes
self.hash = hash self.hash = hash
} }
@ -48,14 +15,14 @@ public final class ChatThemes: Codable, Equatable {
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self) let container = try decoder.container(keyedBy: StringCodingKey.self)
self.chatThemes = try container.decode([ChatTheme].self, forKey: "c") self.chatThemes = try container.decode([TelegramThemeNativeCodable].self, forKey: "c").map { $0.value }
self.hash = try container.decode(Int32.self, forKey: "h") self.hash = try container.decode(Int64.self, forKey: "h")
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self) 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") 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> { func _internal_getChatThemes(accountManager: AccountManager<TelegramAccountManagerTypes>, network: Network, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[TelegramTheme], NoError> {
let fetch: ([ChatTheme]?, Int32?) -> Signal<[ChatTheme], NoError> = { current, hash in let fetch: ([TelegramTheme]?, Int64?) -> Signal<[TelegramTheme], NoError> = { current, hash in
return network.request(Api.functions.account.getChatThemes(hash: 0)) return network.request(Api.functions.account.getChatThemes(hash: hash ?? 0))
|> retryRequest |> retryRequest
|> mapToSignal { result -> Signal<[ChatTheme], NoError> in |> mapToSignal { result -> Signal<[TelegramTheme], NoError> in
switch result { switch result {
case let .chatThemes(hash, apiThemes): case let .themes(hash, apiThemes):
let result = apiThemes.compactMap { ChatTheme(apiChatTheme: $0) } let result = apiThemes.compactMap { TelegramTheme(apiTheme: $0) }
if result == current { if result == current {
return .complete() return .complete()
} else { } else {
@ -82,7 +49,7 @@ func _internal_getChatThemes(accountManager: AccountManager<TelegramAccountManag
}.start() }.start()
return .single(result) return .single(result)
} }
case .chatThemesNotModified: case .themesNotModified:
return .complete() return .complete()
} }
} }
@ -93,14 +60,14 @@ func _internal_getChatThemes(accountManager: AccountManager<TelegramAccountManag
} else { } else {
return accountManager.sharedData(keys: [SharedDataKeys.chatThemes]) return accountManager.sharedData(keys: [SharedDataKeys.chatThemes])
|> take(1) |> take(1)
|> map { sharedData -> ([ChatTheme], Int32) in |> map { sharedData -> ([TelegramTheme], Int64) in
if let chatThemes = sharedData.entries[SharedDataKeys.chatThemes]?.get(ChatThemes.self) { if let chatThemes = sharedData.entries[SharedDataKeys.chatThemes]?.get(ChatThemes.self) {
return (chatThemes.chatThemes, chatThemes.hash) return (chatThemes.chatThemes, chatThemes.hash)
} else { } else {
return ([], 0) return ([], 0)
} }
} }
|> mapToSignal { current, hash -> Signal<[ChatTheme], NoError> in |> mapToSignal { current, hash -> Signal<[TelegramTheme], NoError> in
if onlyCached && !current.isEmpty { if onlyCached && !current.isEmpty {
return .single(current) return .single(current)
} else { } 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> { func managedChatThemesUpdates(accountManager: AccountManager<TelegramAccountManagerTypes>, network: Network) -> Signal<Void, NoError> {
let poll = _internal_getChatThemes(accountManager: accountManager, network: network) let poll = _internal_getChatThemes(accountManager: accountManager, network: network)
|> mapToSignal { _ -> Signal<Void, NoError> in |> mapToSignal { _ -> Signal<Void, NoError> in

View File

@ -9,7 +9,7 @@ public extension TelegramEngine {
self.account = account 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) return _internal_getChatThemes(accountManager: accountManager, network: self.account.network, forceUpdate: forceUpdate, onlyCached: onlyCached)
} }

View File

@ -157,7 +157,7 @@ private func saveUnsaveTheme(account: Account, accountManager: AccountManager<Te
} |> switchToLatest } |> 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 var flags: Int32 = 0
if autoNight { if autoNight {
flags |= 1 << 0 flags |= 1 << 0
@ -171,7 +171,16 @@ private func installTheme(account: Account, theme: TelegramTheme?, autoNight: Bo
inputTheme = nil 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 |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .complete() return .complete()
} }
@ -278,16 +287,16 @@ public enum CreateThemeResult {
case progress(Float) 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 flags: Int32 = 0
var inputSettings: Api.InputThemeSettings? var inputSettings: [Api.InputThemeSettings]?
if let _ = resource { if let _ = resource {
flags |= 1 << 2 flags |= 1 << 2
} }
if let settings = settings { if let settings = settings {
flags |= 1 << 3 flags |= 1 << 3
inputSettings = settings.apiInputThemeSettings inputSettings = settings.map { $0.apiInputThemeSettings }
} }
if let resource = resource { if let resource = resource {
@ -345,7 +354,7 @@ public func createTheme(account: Account, title: String, resource: MediaResource
} }
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in |> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
var theme = TelegramTheme(apiTheme: apiTheme) 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 return account.postbox.transaction { transaction -> CreateThemeResult in
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) 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 { guard title != nil || slug != nil || resource != nil else {
return .complete() return .complete()
} }
@ -381,10 +390,10 @@ public func updateTheme(account: Account, accountManager: AccountManager<Telegra
if let _ = resource { if let _ = resource {
flags |= 1 << 2 flags |= 1 << 2
} }
var inputSettings: Api.InputThemeSettings? var inputSettings: [Api.InputThemeSettings]?
if let settings = settings { if let settings = settings {
flags |= 1 << 3 flags |= 1 << 3
inputSettings = settings.apiInputThemeSettings inputSettings = settings.map { $0.apiInputThemeSettings }
} }
let uploadSignal: Signal<UploadThemeResult?, UploadThemeError> let uploadSignal: Signal<UploadThemeResult?, UploadThemeError>
if let resource = resource { if let resource = resource {
@ -425,7 +434,7 @@ public func updateTheme(account: Account, accountManager: AccountManager<Telegra
} }
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in |> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
let theme = TelegramTheme(apiTheme: apiTheme) 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 let _ = accountManager.transaction { transaction in
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in transaction.updateSharedData(SharedDataKeys.themeSettings, { current in

View File

@ -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) 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? { public func makePresentationTheme(cloudTheme: TelegramTheme, dark: Bool = false) -> PresentationTheme? {
guard let settings = cloudTheme.settings else { 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 return nil
} }
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: nil, preview: false) 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) 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 var accentColor = accentColor
if accentColor == .clear { if accentColor == .clear {
accentColor = nil accentColor = nil
@ -63,7 +87,15 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati
return nil return nil
} }
case let .cloud(info): 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) { 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 theme = loadedTheme
} else { } else {

View File

@ -330,6 +330,7 @@ private func roundTimeToDay(_ timestamp: Int32) -> Int32 {
private enum PreparedAutomaticThemeSwitchTrigger { private enum PreparedAutomaticThemeSwitchTrigger {
case explicitNone case explicitNone
case explicitForce
case system case system
case time(fromSeconds: Int32, toSeconds: Int32) case time(fromSeconds: Int32, toSeconds: Int32)
case brightness(threshold: Double) case brightness(threshold: Double)
@ -341,26 +342,30 @@ private struct AutomaticThemeSwitchParameters {
init(settings: AutomaticThemeSwitchSetting) { init(settings: AutomaticThemeSwitchSetting) {
let trigger: PreparedAutomaticThemeSwitchTrigger let trigger: PreparedAutomaticThemeSwitchTrigger
switch settings.trigger { if settings.force {
case .system: trigger = .explicitForce
trigger = .system } else {
case .explicitNone: switch settings.trigger {
trigger = .explicitNone case .system:
case let .timeBased(setting): trigger = .system
let fromValue: Int32 case .explicitNone:
let toValue: Int32 trigger = .explicitNone
switch setting { case let .timeBased(setting):
case let .automatic(latitude, longitude, _): let fromValue: Int32
let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: latitude, longitude: longitude)! let toValue: Int32
fromValue = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970)) switch setting {
toValue = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970)) case let .automatic(latitude, longitude, _):
case let .manual(fromSeconds, toSeconds): let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: latitude, longitude: longitude)!
fromValue = fromSeconds fromValue = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970))
toValue = toSeconds toValue = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970))
} case let .manual(fromSeconds, toSeconds):
trigger = .time(fromSeconds: fromValue, toSeconds: toValue) fromValue = fromSeconds
case let .brightness(threshold): toValue = toSeconds
trigger = .brightness(threshold: threshold) }
trigger = .time(fromSeconds: fromValue, toSeconds: toValue)
case let .brightness(threshold):
trigger = .brightness(threshold: threshold)
}
} }
self.trigger = trigger self.trigger = trigger
self.theme = settings.theme self.theme = settings.theme
@ -371,6 +376,8 @@ private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchPar
switch parameters.trigger { switch parameters.trigger {
case .explicitNone: case .explicitNone:
return false return false
case .explicitForce:
return true
case .system: case .system:
return systemUserInterfaceStyle == .dark return systemUserInterfaceStyle == .dark
case let .time(fromValue, toValue): case let .time(fromValue, toValue):
@ -605,6 +612,7 @@ public func updatedPresentationData(accountManager: AccountManager<TelegramAccou
var effectiveColors = currentColors var effectiveColors = currentColors
var switchedToNightModeWallpaper = false var switchedToNightModeWallpaper = false
var preferredBaseTheme: TelegramBaseTheme?
if autoNightModeTriggered { if autoNightModeTriggered {
let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index] effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
@ -615,6 +623,7 @@ public func updatedPresentationData(accountManager: AccountManager<TelegramAccou
switchedToNightModeWallpaper = true switchedToNightModeWallpaper = true
} }
effectiveTheme = automaticTheme effectiveTheme = automaticTheme
preferredBaseTheme = .night
} else { } else {
effectiveTheme = themeSettings.theme effectiveTheme = themeSettings.theme
} }
@ -623,7 +632,7 @@ public func updatedPresentationData(accountManager: AccountManager<TelegramAccou
effectiveColors = nil 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 { if autoNightModeTriggered && !switchedToNightModeWallpaper {
switch effectiveChatWallpaper { switch effectiveChatWallpaper {

View File

@ -3909,12 +3909,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var presentationData = presentationData var presentationData = presentationData
var useDarkAppearance = presentationData.theme.overallDarkAppearance 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 { if let darkAppearancePreview = darkAppearancePreview {
useDarkAppearance = darkAppearancePreview useDarkAppearance = darkAppearancePreview
} }
let customTheme = useDarkAppearance ? theme.darkTheme : theme.theme if let theme = makePresentationTheme(cloudTheme: theme, dark: useDarkAppearance) {
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
theme.forceSync = true theme.forceSync = true
presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper) presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper)

View File

@ -498,7 +498,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
@objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { @objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) {
if case .ended = recognizer.state { 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)
}
} }
} }
} }

View File

@ -179,10 +179,8 @@ final class ChatRecentActionsController: TelegramBaseController {
let previousStrings = strongSelf.presentationData.strings let previousStrings = strongSelf.presentationData.strings
var presentationData = presentationData var presentationData = presentationData
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) { if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon == themeEmoticon }) {
let useDarkAppearance = presentationData.theme.overallDarkAppearance if let theme = makePresentationTheme(cloudTheme: theme, dark: presentationData.theme.overallDarkAppearance) {
let customTheme = useDarkAppearance ? theme.darkTheme : theme.theme
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
presentationData = presentationData.withUpdated(theme: theme) presentationData = presentationData.withUpdated(theme: theme)
presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper) presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
} }

View File

@ -47,6 +47,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let emoticon: String? let emoticon: String?
let emojiFile: TelegramMediaFile? let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference? let themeReference: PresentationThemeReference?
let nightMode: Bool
var selected: Bool var selected: Bool
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
@ -67,6 +68,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
if lhs.themeReference?.index != rhs.themeReference?.index { if lhs.themeReference?.index != rhs.themeReference?.index {
return false return false
} }
if lhs.nightMode != rhs.nightMode {
return false
}
if lhs.selected != rhs.selected { if lhs.selected != rhs.selected {
return false return false
} }
@ -87,7 +91,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
} }
func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem { 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 emoticon: String?
let emojiFile: TelegramMediaFile? let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference? let themeReference: PresentationThemeReference?
let nightMode: Bool
let selected: Bool let selected: Bool
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let wallpaper: TelegramWallpaper? let wallpaper: TelegramWallpaper?
let action: (String?) -> Void 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.context = context
self.emoticon = emoticon self.emoticon = emoticon
self.emojiFile = emojiFile self.emojiFile = emojiFile
self.themeReference = themeReference self.themeReference = themeReference
self.nightMode = nightMode
self.selected = selected self.selected = selected
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
@ -419,7 +425,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
if updatedThemeReference || updatedWallpaper { if updatedThemeReference || updatedWallpaper {
if let themeReference = item.themeReference { 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 strongSelf.imageNode.backgroundColor = nil
} }
} }
@ -844,10 +850,12 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
var entries: [ThemeSettingsThemeEntry] = [] 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 { for theme in themes {
let emoticon = theme.emoji guard let emoticon = theme.emoticon else {
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)) 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 let action: (String?) -> Void = { [weak self] emoticon in
@ -881,7 +889,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
if isFirstTime { if isFirstTime {
for theme in themes { 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 account = strongSelf.context.account
let accountManager = strongSelf.context.sharedContext.accountManager let accountManager = strongSelf.context.sharedContext.accountManager
let path = accountManager.mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: CachedPreparedPatternWallpaperRepresentation()) let path = accountManager.mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: CachedPreparedPatternWallpaperRepresentation())

View File

@ -317,7 +317,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|> mapToSignal { themeInfo -> Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> in |> mapToSignal { themeInfo -> Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> in
return Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> { subscriber in return Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> { subscriber in
let disposables = DisposableSet() let disposables = DisposableSet()
if let settings = themeInfo.settings { if let settings = themeInfo.settings?.first {
subscriber.putNext((nil, settings, themeInfo)) subscriber.putNext((nil, settings, themeInfo))
subscriber.putCompletion() subscriber.putCompletion()
} else if let resource = themeInfo.file?.resource { } else if let resource = themeInfo.file?.resource {

View File

@ -1270,10 +1270,11 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
let ItemMembers = 106 let ItemMembers = 106
let ItemPermissions = 107 let ItemPermissions = 107
let ItemAdmins = 108 let ItemAdmins = 108
let ItemRemovedUsers = 109 let ItemMemberRequests = 109
let ItemLocationHeader = 110 let ItemRemovedUsers = 110
let ItemLocation = 111 let ItemLocationHeader = 111
let ItemLocationSetup = 112 let ItemLocation = 112
let ItemLocationSetup = 113
let ItemDeleteGroup = 114 let ItemDeleteGroup = 114
let isCreator = channel.flags.contains(.isCreator) 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: { 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) 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: { 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) interaction.openParticipantsSection(.banned)
@ -1411,6 +1418,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
let ItemPreHistory = 103 let ItemPreHistory = 103
let ItemPermissions = 104 let ItemPermissions = 104
let ItemAdmins = 105 let ItemAdmins = 105
let ItemMemberRequests = 106
var canViewAdminsAndBanned = false 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: { items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
interaction.openParticipantsSection(.admins) 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) 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 |> map { presentationData, chatThemes, themeEmoticon -> PresentationData in
var presentationData = presentationData var presentationData = presentationData
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) { if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon == themeEmoticon }) {
let customTheme = presentationData.theme.overallDarkAppearance ? theme.darkTheme : theme.theme if let theme = makePresentationTheme(cloudTheme: theme, dark: presentationData.theme.overallDarkAppearance) {
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
presentationData = presentationData.withUpdated(theme: theme) presentationData = presentationData.withUpdated(theme: theme)
presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper) presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
} }

View File

@ -148,11 +148,13 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
self = .builtin(.dayClassic) self = .builtin(.dayClassic)
} }
case 2: case 2:
if let cloudTheme = decoder.decode(PresentationCloudTheme.self, forKey: "cloudTheme") { if let cloudTheme = decoder.decode(PresentationCloudTheme.self, forKey: "cloudTheme") {
self = .cloud(cloudTheme) self = .cloud(cloudTheme)
} else { } else {
self = .builtin(.dayClassic) self = .builtin(.dayClassic)
} }
case 3:
self = .builtin(.dayClassic)
default: default:
assertionFailure() assertionFailure()
self = .builtin(.dayClassic) self = .builtin(.dayClassic)
@ -227,13 +229,24 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
public var generalThemeReference: PresentationThemeReference { public var generalThemeReference: PresentationThemeReference {
let 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)) generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else { } else {
generalThemeReference = self generalThemeReference = self
} }
return generalThemeReference 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 { public func coloredThemeIndex(reference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?) -> Int64 {
@ -336,10 +349,12 @@ public enum AutomaticThemeSwitchTrigger: Codable, Equatable {
} }
public struct AutomaticThemeSwitchSetting: Codable, Equatable { public struct AutomaticThemeSwitchSetting: Codable, Equatable {
public var force: Bool
public var trigger: AutomaticThemeSwitchTrigger public var trigger: AutomaticThemeSwitchTrigger
public var theme: PresentationThemeReference 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.trigger = trigger
self.theme = theme self.theme = theme
} }
@ -347,6 +362,7 @@ public struct AutomaticThemeSwitchSetting: Codable, Equatable {
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self) 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") self.trigger = try container.decode(AutomaticThemeSwitchTrigger.self, forKey: "trigger")
if let themeData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "theme_v2") { if let themeData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "theme_v2") {
self.theme = PresentationThemeReference(decoder: PostboxDecoder(buffer: MemoryBuffer(data: themeData.data))) 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 { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self) var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.force, forKey: "force")
try container.encode(self.trigger, forKey: "trigger") try container.encode(self.trigger, forKey: "trigger")
let themeData = PostboxEncoder().encodeObjectToRawData(self.theme) let themeData = PostboxEncoder().encodeObjectToRawData(self.theme)
@ -620,7 +637,7 @@ public struct PresentationThemeSettings: Codable {
} }
public static var defaultSettings: PresentationThemeSettings { 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) { 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.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.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.largeEmoji = try container.decodeIfPresent(Bool.self, forKey: "largeEmoji") ?? true
self.reduceMotion = try container.decodeIfPresent(Bool.self, forKey: "reduceMotion") ?? false self.reduceMotion = try container.decodeIfPresent(Bool.self, forKey: "reduceMotion") ?? false

View File

@ -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> let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Int32?), NoError>
var reference: MediaResourceReference? var reference: MediaResourceReference?
@ -1308,9 +1308,11 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
} }
let themeSignal: Signal<PresentationTheme?, NoError> 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)) 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 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) 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) subscriber.putNext(theme)
@ -1480,58 +1482,122 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
let incomingColors = colors.1 let incomingColors = colors.1
if emoticon { if emoticon {
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0) if large {
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath) c.saveGState()
c.clip()
if incomingColors.count >= 2 {
let gradientColors = incomingColors.map { $0.cgColor } as CFArray
var locations: [CGFloat] = [] c.translateBy(x: 5.0, y: 25.0)
for i in 0 ..< incomingColors.count { c.translateBy(x: 114.0, y: 32.0)
let t = CGFloat(i) / CGFloat(incomingColors.count - 1) c.scaleBy(x: 1.0, y: -1.0)
locations.append(t) c.translateBy(x: -114.0, y: -32.0)
}
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() 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 { } else {
let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors) 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.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) if !(emoticon && large) {
c.scaleBy(x: -1.0, y: 1.0) c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.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)
}
let outgoingColors = colors.2 let outgoingColors = colors.2
if emoticon { if emoticon {
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0) if large {
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath) c.saveGState()
c.clip()
if outgoingColors.count >= 2 {
let gradientColors = outgoingColors.map { $0.cgColor } as CFArray
var locations: [CGFloat] = [] c.translateBy(x: drawingRect.width - 114.0 - 5.0, y: 65.0)
for i in 0 ..< outgoingColors.count { c.translateBy(x: 114.0, y: 32.0)
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1) c.scaleBy(x: -1.0, y: -1.0)
locations.append(t) 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()) var locations: [CGFloat] = []
} else if !outgoingColors.isEmpty { for i in 0 ..< outgoingColors.count {
c.setFillColor(outgoingColors[0].cgColor) let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
c.fill(rect) 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() c.resetClip()