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";
"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 arguments = InviteRequestsControllerArguments(context: context, openLinks: {
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
pushControllerImpl?(controller)
}, openPeer: { peer in
navigateToProfileImpl?(peer)
}, approveRequest: { peer in
let approveRequestImpl: (EnginePeer) -> Void = { peer in
importersContext.update(peer.id, action: .approve)
let _ = (context.engine.data.get(
@ -200,8 +195,21 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
}
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, text: string), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
})
}, denyRequest: { peer in
}
let denyRequestImpl: (EnginePeer) -> Void = { peer in
importersContext.update(peer.id, action: .deny)
}
let arguments = InviteRequestsControllerArguments(context: context, openLinks: {
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
pushControllerImpl?(controller)
}, openPeer: { peer in
navigateToProfileImpl?(peer)
}, approveRequest: { peer in
approveRequestImpl(peer)
}, denyRequest: { peer in
denyRequestImpl(peer)
}, peerContextAction: { peer, node, gesture in
guard let node = node as? ContextExtractedContentContainingNode else {
return
@ -209,11 +217,20 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MemberRequests_AddToGroup, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
approveRequestImpl(peer)
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MemberRequests_Dismiss, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
}, action: { _, f in
f(.dismissWithoutContent)
denyRequestImpl(peer)
})))
let dismissPromise = ValuePromise<Bool>(false)
@ -290,7 +307,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
let title: ItemListControllerTitle = .text(presentationData.strings.MemberRequests_Title)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges, scrollEnabled: emptyStateItem == nil)
return (controllerState, (listState, arguments))
}

View File

@ -59,7 +59,7 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
self.item = item
self.animationNode = AnimatedStickerNode()
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Invite"), width: 192, height: 192, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "TwoFactorSetupRememberSuccess"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true
self.titleNode = ASTextNode()
@ -89,10 +89,10 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
var insets = layout.insets(options: [])
insets.top += navigationBarHeight
let imageSpacing: CGFloat = 20.0
let imageSpacing: CGFloat = 10.0
let textSpacing: CGFloat = 8.0
let imageSize = CGSize(width: 96.0, height: 96.0)
let imageSize = CGSize(width: 112.0, height: 112.0)
let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0
self.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: -10.0), size: imageSize)
@ -106,7 +106,7 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
transition.updateAlpha(node: self.animationNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0)
transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + layout.intrinsicInsets.left + floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + layout.intrinsicInsets.left + floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize))
}
}

View File

@ -278,7 +278,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
let rect: CGRect
if isExtracted {
if extractedVerticalOffset > 0.0 {
rect = CGRect(x: extractedRect.minX, y: extractedRect.minY + extractedVerticalOffset, width: extractedRect.width, height: extractedRect.height - extractedVerticalOffset)
rect = CGRect(x: extractedRect.minX - 16.0, y: extractedRect.minY + extractedVerticalOffset, width: extractedRect.width, height: extractedRect.height - extractedVerticalOffset)
} else {
rect = extractedRect
}
@ -312,7 +312,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.avatarNode.transform = CATransform3DIdentity
var avatarInitialRect = strongSelf.avatarNode.view.convert(strongSelf.avatarNode.bounds, to: strongSelf.offsetContainerNode.supernode?.view)
if strongSelf.avatarTransitionNode == nil {
let targetRect = CGRect(x: extractedRect.minX, y: extractedRect.minY, width: extractedRect.width, height: extractedRect.width)
let targetRect = CGRect(x: extractedRect.minX - 16.0, y: extractedRect.minY, width: extractedRect.width, height: extractedRect.width)
let initialScale = avatarInitialRect.width / targetRect.width
avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * initialScale
@ -408,8 +408,9 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.avatarListContainerNode = nil
strongSelf.avatarListNode = nil
avatarListContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak avatarListContainerNode] _ in
avatarListContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak avatarListContainerNode, weak avatarListWrapperNode] _ in
avatarListContainerNode?.removeFromSupernode()
avatarListWrapperNode?.removeFromSupernode()
})
avatarListWrapperNode.layer.animate(from: 1.0 as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
@ -430,7 +431,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
alphaTransition.updateAlpha(node: strongSelf.dismissButton, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1)
let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -43 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -48.0 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
let initialExtractedBackgroundPosition = strongSelf.extractedBackgroundImageNode.position
strongSelf.extractedBackgroundImageNode.layer.position = rect.center
@ -470,13 +471,6 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
transition.updateAlpha(node: strongSelf.expandedSubtitleNode, alpha: isExtracted ? 1.0 : 0.0)
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? inset : 0.0, y: isExtracted ? extractedVerticalOffset : 0.0))
// transition.updateAlpha(node: strongSelf.backgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
// if !isExtracted {
// self?.backgroundImageNode.image = nil
// self?.extractedBackgroundImageNode.image = nil
// }
// })
}
}
}
@ -570,8 +564,8 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil
let nonExtractedRect = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: layout.contentSize.width - 32.0, height: layout.contentSize.height))
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: params.leftInset, dy: 0.0)
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height))
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: params.leftInset + 16.0, dy: 0.0)
var extractedHeight = extractedRect.height + expandedSubtitleLayout.size.height - subtitleLayout.size.height
var extractedVerticalOffset: CGFloat = 0.0
if item.importer?.peer.peer?.smallProfileImage != nil {
@ -580,7 +574,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
extractedHeight += extractedVerticalOffset
}
extractedRect.size.height = extractedHeight - 48.0
extractedRect.size.height = extractedHeight - 46.0
strongSelf.extractedVerticalOffset = extractedVerticalOffset
strongSelf.extractedRect = extractedRect

View File

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

View File

@ -95,8 +95,10 @@ public final class JoinLinkPreviewController: ViewController {
strongSelf.dismiss()
case .invalidHash:
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLinks_InviteLinkExpired), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
strongSelf.dismiss()
Queue.mainQueue().after(0.2) {
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLinks_InviteLinkExpired), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
strongSelf.dismiss()
}
}
}
}, error: { [weak self] error in

View File

@ -115,8 +115,6 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
self.backgroundNode.addSubnode(self.effectNode)
self.backgroundNode.addSubnode(self.contentBackgroundNode)
@ -169,7 +167,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -238,7 +236,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
if let contentNode = self.contentNode {
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: 0.0), size: gridSize))
contentNode.updateLayout(size: gridSize, bottomInset: 0.0, transition: transition)
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: 0.0, transition: transition)
}
}

View File

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

View File

@ -82,7 +82,7 @@ final class LanguageLinkPreviewContentNode: ASDisplayNode, ShareContentContainer
self.contentOffsetUpdated = f
}
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let insets = UIEdgeInsets(top: 12.0, left: 10.0, bottom: 12.0 + bottomInset, right: 10.0)
let titleSpacing: CGFloat = 12.0
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: .greatestFiniteMagnitude))

View File

@ -188,7 +188,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, UIScro
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -274,7 +274,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, UIScro
if let contentNode = self.contentNode {
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
contentNode.updateLayout(size: gridSize, bottomInset: bottomGridInset, transition: transition)
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
}
}

View File

@ -124,7 +124,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry {
let arguments = arguments as! BlockedPeersControllerArguments
switch self {
case let .add(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: false, action: {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: {
arguments.addPeer()
})
case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):

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)))
case let .edit(info):
hasSettings = info.theme.settings != nil
settingsPromise.set(.single(info.theme.settings))
settingsPromise.set(.single(info.theme.settings?.first))
if let file = info.theme.file, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) {
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
previewThemePromise.set(cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
@ -629,7 +629,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
switch mode {
case .create:
if let themeResource = themeResource {
let _ = (prepare |> then(createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData, settings: settings)
let _ = (prepare |> then(createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData, settings: settings.flatMap { [$0] })
|> deliverOnMainQueue)).start(next: { next in
if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
@ -663,7 +663,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
})
}
case let .edit(info):
let _ = (prepare |> then(updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme, title: state.title, slug: state.slug, resource: state.converting ? nil : themeResource, settings: settings)
let _ = (prepare |> then(updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme, title: state.title, slug: state.slug, resource: state.converting ? nil : themeResource, settings: settings.flatMap { [$0] })
|> deliverOnMainQueue)).start(next: { next in
if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()

View File

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

View File

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

View File

@ -72,7 +72,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var wallpaper: TelegramWallpaper
init(context: AccountContext, previewTheme: PresentationTheme, initialWallpaper: TelegramWallpaper?, dismiss: @escaping () -> Void, apply: @escaping () -> Void, isPreview: Bool, ready: Promise<Bool>) {
init(context: AccountContext, previewTheme: PresentationTheme, initialWallpaper: TelegramWallpaper?, dismiss: @escaping () -> Void, apply: @escaping () -> Void, isPreview: Bool, forceReady: Bool, ready: Promise<Bool>) {
self.context = context
self.previewTheme = previewTheme
self.isPreview = isPreview
@ -125,7 +125,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings, doneButtonType: .set)
if case .file = previewTheme.chat.defaultWallpaper {
if case .file = previewTheme.chat.defaultWallpaper, !forceReady {
self.toolbarNode.setDoneEnabled(false)
}

View File

@ -93,7 +93,7 @@ enum ThemeSettingsColorOption: Equatable {
case let .accentColor(color):
return color.color
case let .theme(reference):
if case let .cloud(theme) = reference, let settings = theme.theme.settings {
if case let .cloud(theme) = reference, let settings = theme.theme.settings?.first {
return UIColor(argb: settings.accentColor)
} else {
return nil
@ -115,7 +115,7 @@ enum ThemeSettingsColorOption: Equatable {
case let .accentColor(color):
return color.plainBubbleColors
case let .theme(reference):
if case let .cloud(theme) = reference, let settings = theme.theme.settings, !settings.messageColors.isEmpty {
if case let .cloud(theme) = reference, let settings = theme.theme.settings?.first, !settings.messageColors.isEmpty {
return settings.messageColors
} else {
return []
@ -128,7 +128,7 @@ enum ThemeSettingsColorOption: Equatable {
case let .accentColor(color):
return color.customBubbleColors
case let .theme(reference):
if case let .cloud(theme) = reference, let settings = theme.theme.settings, !settings.messageColors.isEmpty {
if case let .cloud(theme) = reference, let settings = theme.theme.settings?.first, !settings.messageColors.isEmpty {
return settings.messageColors
} else {
return []

View File

@ -18,34 +18,6 @@ import AccountContext
import ContextUI
import UndoUI
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
weak var sourceNode: ASDisplayNode?
let navigationController: NavigationController? = nil
let passthroughTouches: Bool = false
init(controller: ViewController, sourceNode: ASDisplayNode?) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerTakeControllerInfo? {
let sourceNode = self.sourceNode
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
if let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds)
} else {
return nil
}
})
}
func animatedIn() {
}
}
func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
let name: String
switch reference {
@ -63,7 +35,11 @@ func themeDisplayName(strings: PresentationStrings, reference: PresentationTheme
case let .local(theme):
name = theme.title
case let .cloud(theme):
name = theme.theme.title
if let emoticon = theme.theme.emoticon {
name = emoticon
} else {
name = theme.theme.title
}
}
return name
}
@ -71,10 +47,11 @@ func themeDisplayName(strings: PresentationStrings, reference: PresentationTheme
private final class ThemeSettingsControllerArguments {
let context: AccountContext
let selectTheme: (PresentationThemeReference) -> Void
let selectFontSize: (PresentationFontSize) -> Void
let openThemeSettings: () -> Void
let openWallpaperSettings: () -> Void
let selectAccentColor: (PresentationThemeAccentColor?) -> Void
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
let toggleNightTheme: (Bool) -> Void
let openAutoNightTheme: () -> Void
let openTextSize: () -> Void
let openBubbleSettings: () -> Void
@ -85,13 +62,14 @@ private final class ThemeSettingsControllerArguments {
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context
self.selectTheme = selectTheme
self.selectFontSize = selectFontSize
self.openThemeSettings = openThemeSettings
self.openWallpaperSettings = openWallpaperSettings
self.selectAccentColor = selectAccentColor
self.openAccentColorPicker = openAccentColorPicker
self.toggleNightTheme = toggleNightTheme
self.openAutoNightTheme = openAutoNightTheme
self.openTextSize = openTextSize
self.openBubbleSettings = openBubbleSettings
@ -106,8 +84,8 @@ private final class ThemeSettingsControllerArguments {
private enum ThemeSettingsControllerSection: Int32 {
case chatPreview
case background
case fontSize
case nightMode
case message
case icon
case other
}
@ -132,15 +110,14 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case themeListHeader(PresentationTheme, String)
case fontSizeHeader(PresentationTheme, String)
case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case themes(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, Bool, [String: [StickerPackItem]])
case chatTheme(PresentationTheme, String)
case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?)
case autoNight(PresentationTheme, String, Bool)
case autoNightTheme(PresentationTheme, String, String)
case textSize(PresentationTheme, String, String)
case bubbleSettings(PresentationTheme, String, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
case otherHeader(PresentationTheme, String)
@ -150,12 +127,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .themeListHeader, .chatPreview, .themeItem, .accentColor:
case .themeListHeader, .chatPreview, .themes, .chatTheme, .wallpaper:
return ThemeSettingsControllerSection.chatPreview.rawValue
case .fontSizeHeader, .fontSize:
return ThemeSettingsControllerSection.fontSize.rawValue
case .wallpaper, .autoNightTheme, .textSize, .bubbleSettings:
return ThemeSettingsControllerSection.background.rawValue
case .autoNight, .autoNightTheme:
return ThemeSettingsControllerSection.nightMode.rawValue
case .textSize, .bubbleSettings:
return ThemeSettingsControllerSection.message.rawValue
case .iconHeader, .iconItem:
return ThemeSettingsControllerSection.icon.rawValue
case .otherHeader, .largeEmoji, .animations, .animationsInfo:
@ -169,11 +146,13 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 0
case .chatPreview:
return 1
case .themeItem:
case .themes:
return 2
case .accentColor:
return 4
case .chatTheme:
return 3
case .wallpaper:
return 4
case .autoNight:
return 5
case .autoNightTheme:
return 6
@ -181,22 +160,18 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 7
case .bubbleSettings:
return 8
case .fontSizeHeader:
return 9
case .fontSize:
return 10
case .iconHeader:
return 11
return 9
case .iconItem:
return 12
return 10
case .otherHeader:
return 13
return 11
case .largeEmoji:
return 14
return 12
case .animations:
return 15
return 13
case .animationsInfo:
return 16
return 14
}
}
@ -208,14 +183,26 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .themes(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsNightMode, lhsAnimatedEmojiStickers):
if case let .themes(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsNightMode, rhsAnimatedEmojiStickers) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsNightMode == rhsNightMode, lhsAnimatedEmojiStickers == rhsAnimatedEmojiStickers {
return true
} else {
return false
}
case let .chatTheme(lhsTheme, lhsText):
if case let .chatTheme(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .wallpaper(lhsTheme, lhsText):
if case let .wallpaper(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .accentColor(lhsTheme, _, lhsCurrentTheme, lhsThemes, lhsColor):
if case let .accentColor(rhsTheme, _, rhsCurrentTheme, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsThemes == rhsThemes, lhsColor == rhsColor {
case let .autoNight(lhsTheme, lhsText, lhsValue):
if case let .autoNight(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
@ -244,24 +231,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsAllThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeSpecificChatWallpapers, lhsCurrentColor):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsAllThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsThemeSpecificChatWallpapers, rhsCurrentColor) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsAllThemes == rhsAllThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsThemeSpecificChatWallpapers == rhsThemeSpecificChatWallpapers, lhsCurrentColor == rhsCurrentColor {
return true
} else {
return false
}
case let .fontSizeHeader(lhsTheme, lhsText):
if case let .fontSizeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .fontSize(lhsTheme, lhsFontSize):
if case let .fontSize(rhsTheme, rhsFontSize) = rhs, lhsTheme === rhsTheme, lhsFontSize == rhsFontSize {
return true
} else {
return false
}
case let .iconHeader(lhsTheme, lhsText):
if case let .iconHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -308,101 +277,26 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ThemeSettingsControllerArguments
switch self {
case let .fontSizeHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .fontSize(theme, fontSize):
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
arguments.selectFontSize(value)
}, tag: ThemeSettingsEntryTag.fontSize)
case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
case let .themes(theme, strings, chatThemes, currentTheme, nightMode, animatedEmojiStickers):
return ThemeCarouselThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], nightMode: nightMode, currentTheme: currentTheme, updatedTheme: { theme in
arguments.selectTheme(theme)
}, contextAction: { theme, node, gesture in
arguments.themeContextAction(false, theme, node, gesture)
}, tag: nil)
case let .chatTheme(_, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openThemeSettings()
})
case let .wallpaper(_, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings()
})
case let .accentColor(theme, generalThemeReference, currentTheme, themes, color):
var colorItems: [ThemeSettingsAccentColor] = []
for theme in themes {
colorItems.append(.theme(theme))
}
var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue)
var colors = PresentationThemeBaseColor.allCases
colors = colors.filter { $0 != .custom && $0 != .preset && $0 != .theme }
if case let .builtin(name) = generalThemeReference {
if name == .dayClassic {
colorItems.append(.default)
defaultColor = nil
for preset in dayClassicColorPresets {
colorItems.append(.preset(preset))
}
} else if name == .day {
colorItems.append(.color(.blue))
colors = colors.filter { $0 != .blue }
for preset in dayColorPresets {
colorItems.append(.preset(preset))
}
} else if name == .night {
colorItems.append(.color(.blue))
colors = colors.filter { $0 != .blue }
for preset in nightColorPresets {
colorItems.append(.preset(preset))
}
}
if name != .day {
colors = colors.filter { $0 != .black }
}
if name == .night {
colors = colors.filter { $0 != .gray }
} else {
colors = colors.filter { $0 != .white }
}
}
var currentColor = color ?? defaultColor.flatMap { .accentColor($0) }
if let color = currentColor, case let .accentColor(accentColor) = color, accentColor.baseColor == .theme {
var themeExists = false
if let _ = themes.first(where: { $0.index == accentColor.themeIndex }) {
themeExists = true
}
if !themeExists {
currentColor = defaultColor.flatMap { .accentColor($0) }
}
}
colorItems.append(contentsOf: colors.map { .color($0) })
if let index = colorItems.firstIndex(where: { item in
if case .default = item {
return true
} else {
return false
}
}) {
if index > 0 {
let item = colorItems[index]
colorItems.remove(at: index)
colorItems.insert(item, at: 1)
}
}
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, generalThemeReference: generalThemeReference, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in
if let color = color {
switch color {
case let .accentColor(color):
arguments.selectAccentColor(color)
case let .theme(theme):
arguments.selectTheme(theme)
}
} else {
arguments.selectAccentColor(nil)
}
}, contextAction: { isCurrent, theme, color, node, gesture in
arguments.colorContextAction(isCurrent, theme, color, node, gesture)
}, openColorPicker: { create in
arguments.openAccentColorPicker(currentTheme, create)
}, tag: ThemeSettingsEntryTag.accentColor)
case let .autoNight(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleNightTheme(value)
}, tag: nil)
case let .autoNightTheme(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openAutoNightTheme()
@ -417,18 +311,6 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
})
case let .themeListHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: true, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in
if case let .cloud(theme) = theme, theme.theme.file == nil && theme.theme.settings == nil {
if theme.theme.isCreator {
arguments.editTheme(theme)
}
} else {
arguments.selectTheme(theme)
}
}, contextAction: { theme, node, gesture in
arguments.themeContextAction(theme.index == currentTheme.index, theme, node, gesture)
}, tag: ThemeSettingsEntryTag.theme)
case let .iconHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .iconItem(theme, strings, icons, value):
@ -451,7 +333,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
}
}
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] {
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] {
var entries: [ThemeSettingsControllerEntry] = []
let strings = presentationData.strings
@ -459,44 +341,46 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
entries.append(.themeListHeader(presentationData.theme, title))
entries.append(.chatPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)]))
let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
if case let .cloud(theme) = reference {
return theme.theme.settings == nil
} else {
return true
}
}
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = themeReference, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = themeReference
}
entries.append(.themeItem(presentationData.theme, presentationData.strings, generalThemes, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
if case let .builtin(builtinTheme) = generalThemeReference {
let colorThemes = availableThemes.filter { reference in
if case let .cloud(theme) = reference, let settings = theme.theme.settings, settings.baseTheme == builtinTheme.baseTheme {
return true
} else {
return false
}
}
var colorOption: ThemeSettingsColorOption?
if case .builtin = themeReference {
colorOption = presentationThemeSettings.themeSpecificAccentColors[themeReference.index].flatMap { .accentColor($0) }
} else {
colorOption = .theme(themeReference)
}
entries.append(.accentColor(presentationData.theme, generalThemeReference, themeReference, colorThemes, colorOption))
}
entries.append(.themes(presentationData.theme, presentationData.strings, chatThemes, themeReference, presentationThemeSettings.automaticThemeSwitchSetting.force, animatedEmojiStickers))
// let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
// if case let .cloud(theme) = reference {
// return theme.theme.settings == nil
// } else {
// return true
// }
// }
//
// let generalThemeReference: PresentationThemeReference
// if case let .cloud(theme) = themeReference, let settings = theme.theme.settings {
// generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
// } else {
// generalThemeReference = themeReference
// }
// entries.append(.themeItem(presentationData.theme, presentationData.strings, generalThemes, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
//
// if case let .builtin(builtinTheme) = generalThemeReference {
// let colorThemes = availableThemes.filter { reference in
// if case let .cloud(theme) = reference, let settings = theme.theme.settings, settings.baseTheme == builtinTheme.baseTheme {
// return true
// } else {
// return false
// }
// }
//
// var colorOption: ThemeSettingsColorOption?
// if case .builtin = themeReference {
// colorOption = presentationThemeSettings.themeSpecificAccentColors[themeReference.index].flatMap { .accentColor($0) }
// } else {
// colorOption = .theme(themeReference)
// }
//
// entries.append(.accentColor(presentationData.theme, generalThemeReference, themeReference, colorThemes, colorOption))
// }
entries.append(.chatTheme(presentationData.theme, "Chat Themes"))
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
entries.append(.autoNight(presentationData.theme, strings.Appearance_NightTheme, presentationThemeSettings.automaticThemeSwitchSetting.force))
let autoNightMode: String
switch presentationThemeSettings.automaticThemeSwitchSetting.trigger {
case .system:
@ -585,15 +469,43 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let removedThemeIndexesPromise = Promise<Set<Int64>>(Set())
let removedThemeIndexes = Atomic<Set<Int64>>(value: Set())
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: [StickerPackItem]] in
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for item in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
if animatedEmojiStickers[strippedEmoji] == nil {
animatedEmojiStickers[strippedEmoji] = [item]
}
}
}
default:
break
}
return animatedEmojiStickers
}
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
selectThemeImpl?(theme)
}, selectFontSize: { _ in
}, openThemeSettings: {
pushControllerImpl?(themePickerController(context: context))
}, openWallpaperSettings: {
pushControllerImpl?(ThemeGridController(context: context))
}, selectAccentColor: { accentColor in
selectAccentColorImpl?(accentColor)
}, openAccentColorPicker: { themeReference, create in
openAccentColorPickerImpl?(themeReference, create)
}, toggleNightTheme: { value in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var current = current
current.automaticThemeSwitchSetting.force = value
return current
}).start()
presentCrossfadeControllerImpl?(true)
}, openAutoNightTheme: {
pushControllerImpl?(themeAutoNightSettingsController(context: context))
}, openTextSize: {
@ -678,7 +590,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper))
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper, true))
var items: [ContextMenuItem] = []
if case let .cloud(theme) = reference {
@ -764,7 +676,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})))
if isCurrent, let currentThemeIndex = themes.firstIndex(where: { $0.id == theme.theme.id }) {
if let settings = theme.theme.settings {
if let settings = theme.theme.settings?.first {
if settings.baseTheme == .night {
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
} else {
@ -827,7 +739,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
return (accentColor, wallpaper)
} |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, PresentationThemeReference, Bool, TelegramWallpaper?), NoError> in
let generalThemeReference: PresentationThemeReference
if let _ = accentColor, case let .cloud(theme) = reference, let settings = theme.theme.settings {
if let _ = accentColor, case let .cloud(theme) = reference, let settings = theme.theme.settings?.first {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = reference
@ -910,7 +822,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(effectiveThemeReference, wallpaper))
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(effectiveThemeReference, wallpaper, true))
var items: [ContextMenuItem] = []
if let accentColor = accentColor {
@ -948,9 +860,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|> deliverOnMainQueue).start(next: { wallpaper in
var hasSettings = false
var settings: TelegramThemeSettings?
if case let .cloud(cloudTheme) = effectiveThemeReference, cloudTheme.theme.settings != nil {
if case let .cloud(cloudTheme) = effectiveThemeReference, let themeSettings = cloudTheme.theme.settings?.first {
hasSettings = true
settings = cloudTheme.theme.settings
settings = themeSettings
}
let controller = ThemeAccentColorController(context: context, mode: .edit(settings: settings, theme: theme, wallpaper: wallpaper, generalThemeReference: effectiveThemeReference.generalThemeReference, defaultThemeReference: nil, create: true, completion: { result, settings in
let controller = editThemeController(context: context, mode: .create(hasSettings ? nil : result, hasSettings ? settings : nil), navigateToChat: { peerId in
@ -1003,7 +915,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
return updated
})))
if isCurrent, let settings = cloudTheme.theme.settings {
if isCurrent, let settings = cloudTheme.theme.settings?.first {
let colorThemes = themes.filter { theme in
if let _ = theme.settings {
return true
@ -1046,13 +958,17 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})
})
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get())
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes -> (ItemListControllerState, (ItemListNodeState, Any)) in
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers)
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
let themeReference: PresentationThemeReference
if presentationData.autoNightModeTriggered {
themeReference = settings.automaticThemeSwitchSetting.theme
if let _ = settings.theme.emoticon {
themeReference = settings.theme
} else {
themeReference = settings.automaticThemeSwitchSetting.theme
}
} else {
themeReference = settings.theme
}
@ -1081,8 +997,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
availableThemes.append(contentsOf: cloudThemes)
var chatThemes = cloudThemes.filter { $0.emoticon != nil }
chatThemes.insert(.builtin(.dayClassic), at: 0)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
return (controllerState, (listState, arguments))
}
@ -1208,7 +1127,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|> map { resolvedWallpaper, currentTheme -> Bool in
var updatedTheme = theme
var currentThemeBaseIndex: Int64?
if case let .cloud(info) = currentTheme, let settings = info.theme.settings {
if case let .cloud(info) = currentTheme, let settings = info.theme.settings?.first {
currentThemeBaseIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
} else {
currentThemeBaseIndex = currentTheme.index
@ -1218,7 +1137,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
var updatedThemeBaseIndex: Int64?
if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
if let settings = info.theme.settings {
if let settings = info.theme.settings?.first {
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
updatedThemeBaseIndex = baseThemeIndex
}
@ -1227,19 +1146,30 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var updatedThemeSpecificAccentColors = current.themeSpecificAccentColors
if let baseThemeIndex = baseThemeIndex {
updatedThemeSpecificAccentColors[baseThemeIndex] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
}
if autoNightModeTriggered {
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if case let .cloud(info) = updatedTheme, info.theme.settings?.contains(where: { $0.baseTheme == .night || $0.baseTheme == .tinted }) ?? false {
updatedAutomaticThemeSwitchSetting.theme = updatedTheme
return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
} else {
return current.withUpdatedTheme(updatedTheme).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
} else if case let .builtin(theme) = updatedTheme {
if [.day, .dayClassic].contains(theme) {
updatedAutomaticThemeSwitchSetting.theme = .builtin(.night)
} else {
updatedAutomaticThemeSwitchSetting.theme = updatedTheme
}
}
return current.withUpdatedTheme(updatedTheme).withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting)
// var updatedThemeSpecificAccentColors = current.themeSpecificAccentColors
// if let baseThemeIndex = baseThemeIndex {
// updatedThemeSpecificAccentColors[baseThemeIndex] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
// }
//
// if autoNightModeTriggered {
// var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
// updatedAutomaticThemeSwitchSetting.theme = updatedTheme
//
// return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
// } else {
// return current.withUpdatedTheme(updatedTheme).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
// }
}).start()
return currentThemeBaseIndex != updatedThemeBaseIndex
@ -1280,7 +1210,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings {
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings?.first {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = currentTheme
@ -1367,7 +1297,7 @@ public final class ThemeSettingsCrossfadeController: ViewController {
private var bottomSnapshotView: UIView?
private var sideSnapshotView: UIView?
fileprivate var didAppear: (() -> Void)?
var didAppear: (() -> Void)?
public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil, leftOffset: CGFloat? = nil) {
if let view = view {
@ -1464,3 +1394,31 @@ public final class ThemeSettingsCrossfadeController: ViewController {
self.didAppear?()
}
}
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
weak var sourceNode: ASDisplayNode?
let navigationController: NavigationController? = nil
let passthroughTouches: Bool = false
init(controller: ViewController, sourceNode: ASDisplayNode?) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerTakeControllerInfo? {
let sourceNode = self.sourceNode
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
if let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds)
} else {
return nil
}
})
}
func animatedIn() {
}
}

View File

@ -629,14 +629,15 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
var themeWallpaper: TelegramWallpaper?
if case let .cloud(theme) = theme {
themeWallpaper = theme.resolvedWallpaper ?? theme.theme.settings?.wallpaper
themeWallpaper = theme.resolvedWallpaper ?? theme.theme.settings?.first?.wallpaper
}
let customWallpaper = item.themeSpecificChatWallpapers[theme.generalThemeReference.index]
let wallpaper = accentColor?.wallpaper ?? customWallpaper ?? themeWallpaper
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: item.currentTheme.index == theme.index, theme: item.theme, wallpaper: wallpaper))
let selected = item.currentTheme.index == theme.index || item.currentTheme.generalThemeReference == theme
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: selected, theme: item.theme, wallpaper: wallpaper))
index += 1
}

View File

@ -9,6 +9,6 @@ public protocol ShareContentContainerNode: AnyObject {
func deactivate()
func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?)
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?)
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition)
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition)
func updateSelectedPeers()
}

View File

@ -366,7 +366,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -472,7 +472,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
if let contentNode = self.contentNode {
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
contentNode.updateLayout(size: gridSize, bottomInset: bottomGridInset, transition: transition)
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
}
}

View File

@ -67,7 +67,7 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain
self.contentOffsetUpdated = f
}
public func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
public func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let nodeHeight: CGFloat = 125.0
let indicatorSize = self.activityIndicator.calculateSizeThatFits(size)

View File

@ -310,7 +310,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
func deactivate() {
}
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let firstLayout = self.validLayout == nil
self.validLayout = (size, bottomInset)

View File

@ -423,7 +423,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
return (gridTopInset, itemWidth)
}
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let firstLayout = self.validLayout == nil
self.validLayout = (size, bottomInset)

View File

@ -3,6 +3,18 @@ import UIKit
import AsyncDisplayKit
import Display
private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: CGFloat = 22.0, lineWidth: CGFloat = 2.0) -> UIImage? {
return generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setLineWidth(lineWidth)
context.setLineCap(.round)
let cutoutAngle: CGFloat = CGFloat.pi * 30.0 / 180.0
context.addArc(center: CGPoint(x: size.width / 2.0, y: size.height / 2.0), radius: size.width / 2.0 - lineWidth / 2.0, startAngle: 0.0, endAngle: CGFloat.pi * 2.0 - cutoutAngle, clockwise: false)
context.strokePath()
})
}
public final class SolidRoundedButtonTheme {
public let backgroundColor: UIColor
public let gradientBackgroundColor: UIColor?
@ -31,6 +43,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
private let iconNode: ASImageNode
private var progressNode: ASImageNode?
private let buttonHeight: CGFloat
private let buttonCornerRadius: CGFloat
@ -120,19 +133,62 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.iconNode.alpha = 0.55
} else {
strongSelf.buttonBackgroundNode.alpha = 1.0
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.titleNode.alpha = 1.0
strongSelf.titleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.subtitleNode.alpha = 1.0
strongSelf.subtitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.iconNode.alpha = 1.0
strongSelf.iconNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
if strongSelf.buttonBackgroundNode.alpha > 0.0 {
strongSelf.buttonBackgroundNode.alpha = 1.0
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.titleNode.alpha = 1.0
strongSelf.titleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.subtitleNode.alpha = 1.0
strongSelf.subtitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.iconNode.alpha = 1.0
strongSelf.iconNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
}
}
}
}
}
public func transitionToProgress() {
guard self.progressNode == nil else {
return
}
self.isUserInteractionEnabled = false
let progressFrame = CGRect(origin: CGPoint(x: (self.frame.width - self.buttonHeight) / 2.0, y: 0.0), size: CGSize(width: self.buttonHeight, height: self.buttonHeight))
let progressNode = ASImageNode()
progressNode.displaysAsynchronously = false
progressNode.frame = progressFrame
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
self.insertSubnode(progressNode, at: 0)
self.progressNode = progressNode
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
basicAnimation.duration = 0.5
basicAnimation.fromValue = NSNumber(value: Float(0.0))
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
basicAnimation.repeatCount = Float.infinity
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
basicAnimation.beginTime = 1.0
progressNode.layer.add(basicAnimation, forKey: "progressRotation")
self.buttonBackgroundNode.cornerRadius = self.buttonHeight / 2.0
self.buttonBackgroundNode.layer.animate(from: self.buttonCornerRadius as NSNumber, to: self.buttonHeight / 2.0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
self.buttonBackgroundNode.layer.animateFrame(from: self.buttonBackgroundNode.frame, to: progressFrame, duration: 0.2)
self.buttonBackgroundNode.alpha = 0.0
self.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2, removeOnCompletion: false)
progressNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
self.titleNode.alpha = 0.0
self.titleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
self.subtitleNode.alpha = 0.0
self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
}
public override func didLoad() {
super.didLoad()

View File

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

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

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 {
case authorizations(authorizations: [Api.Authorization])
@ -7568,14 +7512,18 @@ public extension Api {
})
}
public static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: Api.InputThemeSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
public static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
let buffer = Buffer()
buffer.appendInt32(-2077048289)
buffer.appendInt32(1697530880)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(slug, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(settings!.count))
for item in settings! {
item.serialize(buffer, true)
}}
return (FunctionDescription(name: "account.createTheme", parameters: [("flags", flags), ("slug", slug), ("title", title), ("document", document), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in
let reader = BufferReader(buffer)
var result: Api.Theme?
@ -7586,16 +7534,20 @@ public extension Api {
})
}
public static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: Api.InputThemeSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
public static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
let buffer = Buffer()
buffer.appendInt32(1555261397)
buffer.appendInt32(737414348)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(format, buffer: buffer, boxed: false)
theme.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(settings!.count))
for item in settings! {
item.serialize(buffer, true)
}}
return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", flags), ("format", format), ("theme", theme), ("slug", slug), ("title", title), ("document", document), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in
let reader = BufferReader(buffer)
var result: Api.Theme?
@ -7606,6 +7558,37 @@ public extension Api {
})
}
public static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-953697477)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(format!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {baseTheme!.serialize(buffer, true)}
return (FunctionDescription(name: "account.installTheme", parameters: [("flags", flags), ("theme", theme), ("format", format), ("baseTheme", baseTheme)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.Themes>) {
let buffer = Buffer()
buffer.appendInt32(-700916087)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in
let reader = BufferReader(buffer)
var result: Api.account.Themes?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.account.Themes
}
return result
})
}
public static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-229175188)
@ -7621,22 +7604,6 @@ public extension Api {
})
}
public static func installTheme(flags: Int32, format: String?, theme: Api.InputTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(2061776695)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeString(format!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)}
return (FunctionDescription(name: "account.installTheme", parameters: [("flags", flags), ("format", format), ("theme", theme)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func getTheme(format: String, theme: Api.InputTheme, documentId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
let buffer = Buffer()
buffer.appendInt32(-1919060949)
@ -7786,20 +7753,6 @@ public extension Api {
return result
})
}
public static func getChatThemes(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.ChatThemes>) {
let buffer = Buffer()
buffer.appendInt32(-690545285)
serializeInt32(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ChatThemes? in
let reader = BufferReader(buffer)
var result: Api.account.ChatThemes?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.account.ChatThemes
}
return result
})
}
}
public struct langpack {
public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) {

View File

@ -360,7 +360,7 @@ public final class VoiceChatJoinScreen: ViewController {
if let (layout, navigationBarHeight, bottomGridInset) = self.containerLayout {
if let contentNode = contentNode, let previous = previous {
contentNode.frame = previous.frame
contentNode.updateLayout(size: previous.bounds.size, bottomInset: bottomGridInset, transition: .immediate)
contentNode.updateLayout(size: previous.bounds.size, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: .immediate)
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
@ -443,7 +443,7 @@ public final class VoiceChatJoinScreen: ViewController {
if let contentNode = self.contentNode {
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
contentNode.updateLayout(size: gridSize, bottomInset: bottomGridInset, transition: transition)
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
}
}
@ -693,7 +693,7 @@ final class VoiceChatPreviewContentNode: ASDisplayNode, ShareContentContainerNod
self.contentOffsetUpdated = f
}
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let sideInset: CGFloat = 16.0
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height))
let countSize = self.countNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: size.height))

View File

@ -7,8 +7,8 @@ import TelegramApi
extension TelegramTheme {
convenience init(apiTheme: Api.Theme) {
switch apiTheme {
case let .theme(flags, id, accessHash, slug, title, document, settings, installCount):
self.init(id: id, accessHash: accessHash, slug: slug, title: title, file: document.flatMap(telegramMediaFileFromApiDocument), settings: settings.flatMap(TelegramThemeSettings.init(apiThemeSettings:)), isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0, installCount: installCount)
case let .theme(flags, id, accessHash, slug, title, document, settings, emoticon, installCount):
self.init(id: id, accessHash: accessHash, slug: slug, emoticon: emoticon, title: title, file: document.flatMap(telegramMediaFileFromApiDocument), settings: settings?.compactMap(TelegramThemeSettings.init(apiThemeSettings:)), isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0, installCount: installCount)
}
}
}

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ public extension TelegramEngine {
self.account = account
}
public func getChatThemes(accountManager: AccountManager<TelegramAccountManagerTypes>, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[ChatTheme], NoError> {
public func getChatThemes(accountManager: AccountManager<TelegramAccountManagerTypes>, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[TelegramTheme], NoError> {
return _internal_getChatThemes(accountManager: accountManager, network: self.account.network, forceUpdate: forceUpdate, onlyCached: onlyCached)
}

View File

@ -157,7 +157,7 @@ private func saveUnsaveTheme(account: Account, accountManager: AccountManager<Te
} |> switchToLatest
}
private func installTheme(account: Account, theme: TelegramTheme?, autoNight: Bool) -> Signal<Never, NoError> {
private func installTheme(account: Account, theme: TelegramTheme?, baseTheme: TelegramBaseTheme? = nil, autoNight: Bool) -> Signal<Never, NoError> {
var flags: Int32 = 0
if autoNight {
flags |= 1 << 0
@ -171,7 +171,16 @@ private func installTheme(account: Account, theme: TelegramTheme?, autoNight: Bo
inputTheme = nil
}
return account.network.request(Api.functions.account.installTheme(flags: flags, format: telegramThemeFormat, theme: inputTheme))
flags |= 1 << 2
let inputBaseTheme: Api.BaseTheme?
if let baseTheme = baseTheme {
inputBaseTheme = baseTheme.apiBaseTheme
} else {
inputBaseTheme = nil
}
return account.network.request(Api.functions.account.installTheme(flags: flags, theme: inputTheme, format: telegramThemeFormat, baseTheme: inputBaseTheme))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .complete()
}
@ -278,16 +287,16 @@ public enum CreateThemeResult {
case progress(Float)
}
public func createTheme(account: Account, title: String, resource: MediaResource? = nil, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal<CreateThemeResult, CreateThemeError> {
public func createTheme(account: Account, title: String, resource: MediaResource? = nil, thumbnailData: Data? = nil, settings: [TelegramThemeSettings]?) -> Signal<CreateThemeResult, CreateThemeError> {
var flags: Int32 = 0
var inputSettings: Api.InputThemeSettings?
var inputSettings: [Api.InputThemeSettings]?
if let _ = resource {
flags |= 1 << 2
}
if let settings = settings {
flags |= 1 << 3
inputSettings = settings.apiInputThemeSettings
inputSettings = settings.map { $0.apiInputThemeSettings }
}
if let resource = resource {
@ -345,7 +354,7 @@ public func createTheme(account: Account, title: String, resource: MediaResource
}
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
var theme = TelegramTheme(apiTheme: apiTheme)
theme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
theme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, emoticon: nil, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
return account.postbox.transaction { transaction -> CreateThemeResult in
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
@ -367,7 +376,7 @@ public func createTheme(account: Account, title: String, resource: MediaResource
}
}
public func updateTheme(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal<CreateThemeResult, CreateThemeError> {
public func updateTheme(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil, settings: [TelegramThemeSettings]?) -> Signal<CreateThemeResult, CreateThemeError> {
guard title != nil || slug != nil || resource != nil else {
return .complete()
}
@ -381,10 +390,10 @@ public func updateTheme(account: Account, accountManager: AccountManager<Telegra
if let _ = resource {
flags |= 1 << 2
}
var inputSettings: Api.InputThemeSettings?
var inputSettings: [Api.InputThemeSettings]?
if let settings = settings {
flags |= 1 << 3
inputSettings = settings.apiInputThemeSettings
inputSettings = settings.map { $0.apiInputThemeSettings }
}
let uploadSignal: Signal<UploadThemeResult?, UploadThemeError>
if let resource = resource {
@ -425,7 +434,7 @@ public func updateTheme(account: Account, accountManager: AccountManager<Telegra
}
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
let theme = TelegramTheme(apiTheme: apiTheme)
let updatedTheme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
let updatedTheme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, emoticon: nil, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
let _ = accountManager.transaction { transaction in
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in

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)
}
public func makePresentationTheme(cloudTheme: TelegramTheme) -> PresentationTheme? {
guard let settings = cloudTheme.settings else {
public func makePresentationTheme(cloudTheme: TelegramTheme, dark: Bool = false) -> PresentationTheme? {
let settings: TelegramThemeSettings?
if let exactSettings = cloudTheme.settings?.first(where: { dark ? ($0.baseTheme == .night || $0.baseTheme == .tinted) : ($0.baseTheme == .classic || $0.baseTheme == .day) }) {
settings = exactSettings
} else if let firstSettings = cloudTheme.settings?.first {
settings = firstSettings
} else {
settings = nil
}
guard let settings = settings else {
return nil
}
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: .cloud(PresentationCloudTheme(theme: cloudTheme, resolvedWallpaper: nil, creatorAccountId: nil)), serviceBackgroundColor: nil, preview: false)
return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
}
public func makePresentationTheme(cloudTheme: TelegramTheme, baseTheme: TelegramBaseTheme? = nil) -> PresentationTheme? {
let settings: TelegramThemeSettings?
if let exactSettings = cloudTheme.settings?.first(where: { $0.baseTheme == baseTheme }) {
settings = exactSettings
} else if let firstSettings = cloudTheme.settings?.first {
settings = firstSettings
} else {
settings = nil
}
guard let settings = settings else {
return nil
}
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: nil, preview: false)
return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
}
public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? {
public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, baseTheme: TelegramBaseTheme? = nil, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? {
var accentColor = accentColor
if accentColor == .clear {
accentColor = nil
@ -63,7 +87,15 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati
return nil
}
case let .cloud(info):
if let settings = info.theme.settings {
let settings: TelegramThemeSettings?
if let exactSettings = info.theme.settings?.first(where: { $0.baseTheme == baseTheme }) {
settings = exactSettings
} else if let firstSettings = info.theme.settings?.first {
settings = firstSettings
} else {
settings = nil
}
if let settings = settings {
if let loadedTheme = makePresentationTheme(mediaBox: mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), extendingThemeReference: themeReference, accentColor: accentColor ?? UIColor(argb: settings.accentColor), outgoingAccentColor: outgoingAccentColor ?? settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: bubbleColors.isEmpty ? settings.messageColors : bubbleColors, animateBubbleColors: animateBubbleColors ?? settings.animateMessageColors, wallpaper: wallpaper ?? settings.wallpaper, serviceBackgroundColor: serviceBackgroundColor, preview: preview) {
theme = loadedTheme
} else {

View File

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

View File

@ -3909,12 +3909,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var presentationData = presentationData
var useDarkAppearance = presentationData.theme.overallDarkAppearance
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) {
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon == themeEmoticon }) {
if let darkAppearancePreview = darkAppearancePreview {
useDarkAppearance = darkAppearancePreview
}
let customTheme = useDarkAppearance ? theme.darkTheme : theme.theme
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
if let theme = makePresentationTheme(cloudTheme: theme, dark: useDarkAppearance) {
theme.forceSync = true
presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper)

View File

@ -498,7 +498,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
@objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) {
if case .ended = recognizer.state {
self.controllerInteraction.openPeer(self.peerId, .info, nil)
if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, id, _, _, _) = self.messageReference?.content {
self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame)
} else {
self.controllerInteraction.openPeer(self.peerId, .info, nil)
}
}
}
}

View File

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

View File

@ -47,6 +47,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let emoticon: String?
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference?
let nightMode: Bool
var selected: Bool
let theme: PresentationTheme
let strings: PresentationStrings
@ -67,6 +68,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
if lhs.themeReference?.index != rhs.themeReference?.index {
return false
}
if lhs.nightMode != rhs.nightMode {
return false
}
if lhs.selected != rhs.selected {
return false
}
@ -87,7 +91,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
}
func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem {
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
}
}
@ -97,17 +101,19 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
let emoticon: String?
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference?
let nightMode: Bool
let selected: Bool
let theme: PresentationTheme
let strings: PresentationStrings
let wallpaper: TelegramWallpaper?
let action: (String?) -> Void
public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) {
public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, nightMode: Bool, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) {
self.context = context
self.emoticon = emoticon
self.emojiFile = emojiFile
self.themeReference = themeReference
self.nightMode = nightMode
self.selected = selected
self.theme = theme
self.strings = strings
@ -419,7 +425,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
if updatedThemeReference || updatedWallpaper {
if let themeReference = item.themeReference {
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, emoticon: true))
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true))
strongSelf.imageNode.backgroundColor = nil
}
}
@ -844,10 +850,12 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let presentationData = strongSelf.presentationData
var entries: [ThemeSettingsThemeEntry] = []
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, nightMode: false, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
for theme in themes {
let emoticon = theme.emoji
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
guard let emoticon = theme.emoticon else {
continue
}
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
}
let action: (String?) -> Void = { [weak self] emoticon in
@ -881,7 +889,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
if isFirstTime {
for theme in themes {
if let wallpaper = theme.theme.settings?.wallpaper, case let .file(file) = wallpaper {
if let wallpaper = theme.settings?.first?.wallpaper, case let .file(file) = wallpaper {
let account = strongSelf.context.account
let accountManager = strongSelf.context.sharedContext.accountManager
let path = accountManager.mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: CachedPreparedPatternWallpaperRepresentation())

View File

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

View File

@ -1270,10 +1270,11 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
let ItemMembers = 106
let ItemPermissions = 107
let ItemAdmins = 108
let ItemRemovedUsers = 109
let ItemLocationHeader = 110
let ItemLocation = 111
let ItemLocationSetup = 112
let ItemMemberRequests = 109
let ItemRemovedUsers = 110
let ItemLocationHeader = 111
let ItemLocation = 112
let ItemLocationSetup = 113
let ItemDeleteGroup = 114
let isCreator = channel.flags.contains(.isCreator)
@ -1392,6 +1393,12 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text(cachedData.participantsSummary.adminCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
interaction.openParticipantsSection(.admins)
}))
if let count = data.requests?.count, count > 0 {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: {
interaction.openParticipantsSection(.memberRequests)
}))
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: {
interaction.openParticipantsSection(.banned)
@ -1411,6 +1418,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
let ItemPreHistory = 103
let ItemPermissions = 104
let ItemAdmins = 105
let ItemMemberRequests = 106
var canViewAdminsAndBanned = false
@ -1477,6 +1485,12 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
interaction.openParticipantsSection(.admins)
}))
if let count = data.requests?.count, count > 0 {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: {
interaction.openParticipantsSection(.memberRequests)
}))
}
}
}
}
@ -6962,9 +6976,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
presentationDataSignal = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager, onlyCached: false), themeEmoticon)
|> map { presentationData, chatThemes, themeEmoticon -> PresentationData in
var presentationData = presentationData
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) {
let customTheme = presentationData.theme.overallDarkAppearance ? theme.darkTheme : theme.theme
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon == themeEmoticon }) {
if let theme = makePresentationTheme(cloudTheme: theme, dark: presentationData.theme.overallDarkAppearance) {
presentationData = presentationData.withUpdated(theme: theme)
presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
}

View File

@ -148,11 +148,13 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
self = .builtin(.dayClassic)
}
case 2:
if let cloudTheme = decoder.decode(PresentationCloudTheme.self, forKey: "cloudTheme") {
if let cloudTheme = decoder.decode(PresentationCloudTheme.self, forKey: "cloudTheme") {
self = .cloud(cloudTheme)
} else {
self = .builtin(.dayClassic)
}
case 3:
self = .builtin(.dayClassic)
default:
assertionFailure()
self = .builtin(.dayClassic)
@ -227,13 +229,24 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
public var generalThemeReference: PresentationThemeReference {
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = self, let settings = theme.theme.settings {
if case let .cloud(theme) = self, let settings = theme.theme.settings?.first {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = self
}
return generalThemeReference
}
public var emoticon: String? {
switch self {
case .builtin:
return "🏠"
case let .cloud(theme):
return theme.theme.emoticon
default:
return nil
}
}
}
public func coloredThemeIndex(reference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?) -> Int64 {
@ -336,10 +349,12 @@ public enum AutomaticThemeSwitchTrigger: Codable, Equatable {
}
public struct AutomaticThemeSwitchSetting: Codable, Equatable {
public var force: Bool
public var trigger: AutomaticThemeSwitchTrigger
public var theme: PresentationThemeReference
public init(trigger: AutomaticThemeSwitchTrigger, theme: PresentationThemeReference) {
public init(force: Bool, trigger: AutomaticThemeSwitchTrigger, theme: PresentationThemeReference) {
self.force = force
self.trigger = trigger
self.theme = theme
}
@ -347,6 +362,7 @@ public struct AutomaticThemeSwitchSetting: Codable, Equatable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.force = try container.decodeIfPresent(Bool.self, forKey: "force") ?? false
self.trigger = try container.decode(AutomaticThemeSwitchTrigger.self, forKey: "trigger")
if let themeData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "theme_v2") {
self.theme = PresentationThemeReference(decoder: PostboxDecoder(buffer: MemoryBuffer(data: themeData.data)))
@ -360,6 +376,7 @@ public struct AutomaticThemeSwitchSetting: Codable, Equatable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.force, forKey: "force")
try container.encode(self.trigger, forKey: "trigger")
let themeData = PostboxEncoder().encodeObjectToRawData(self.theme)
@ -620,7 +637,7 @@ public struct PresentationThemeSettings: Codable {
}
public static var defaultSettings: PresentationThemeSettings {
return PresentationThemeSettings(theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], useSystemFont: true, fontSize: .regular, listsFontSize: .regular, chatBubbleSettings: .default, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night)), largeEmoji: true, reduceMotion: false)
return PresentationThemeSettings(theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], useSystemFont: true, fontSize: .regular, listsFontSize: .regular, chatBubbleSettings: .default, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(force: false, trigger: .system, theme: .builtin(.night)), largeEmoji: true, reduceMotion: false)
}
public init(theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], useSystemFont: Bool, fontSize: PresentationFontSize, listsFontSize: PresentationFontSize, chatBubbleSettings: PresentationChatBubbleSettings, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, reduceMotion: Bool) {
@ -667,7 +684,7 @@ public struct PresentationThemeSettings: Codable {
self.listsFontSize = PresentationFontSize(rawValue: try container.decodeIfPresent(Int32.self, forKey: "lf") ?? PresentationFontSize.regular.rawValue) ?? fontSize
self.chatBubbleSettings = try container.decodeIfPresent(PresentationChatBubbleSettings.self, forKey: "chatBubbleSettings") ?? PresentationChatBubbleSettings.default
self.automaticThemeSwitchSetting = try container.decodeIfPresent(AutomaticThemeSwitchSetting.self, forKey: "automaticThemeSwitchSetting") ?? AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night))
self.automaticThemeSwitchSetting = try container.decodeIfPresent(AutomaticThemeSwitchSetting.self, forKey: "automaticThemeSwitchSetting") ?? AutomaticThemeSwitchSetting(force: false, trigger: .system, theme: .builtin(.night))
self.largeEmoji = try container.decodeIfPresent(Bool.self, forKey: "largeEmoji") ?? true
self.reduceMotion = try container.decodeIfPresent(Bool.self, forKey: "reduceMotion") ?? false

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>
var reference: MediaResourceReference?
@ -1308,9 +1308,11 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
}
let themeSignal: Signal<PresentationTheme?, NoError>
if case let .builtin(theme) = theme {
if case let .cloud(theme) = theme, let nightMode = nightMode {
themeSignal = .single(makePresentationTheme(cloudTheme: theme.theme, dark: nightMode))
} else if case let .builtin(theme) = theme {
themeSignal = .single(makeDefaultPresentationTheme(reference: theme, serviceBackgroundColor: nil))
} else if case let .cloud(theme) = theme, let settings = theme.theme.settings {
} else if case let .cloud(theme) = theme, let settings = theme.theme.settings?.first {
themeSignal = Signal { subscriber in
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false)
subscriber.putNext(theme)
@ -1480,58 +1482,122 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
let incomingColors = colors.1
if emoticon {
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
c.clip()
if incomingColors.count >= 2 {
let gradientColors = incomingColors.map { $0.cgColor } as CFArray
if large {
c.saveGState()
var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
} else if !incomingColors.isEmpty {
c.setFillColor(incomingColors[0].cgColor)
c.fill(rect)
}
c.translateBy(x: 5.0, y: 25.0)
c.translateBy(x: 114.0, y: 32.0)
c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: -114.0, y: -32.0)
c.resetClip()
let _ = try? drawSvgPath(c, path: "M98.0061174,0 C106.734138,0 113.82927,6.99200411 113.996965,15.6850616 L114,16 C114,24.836556 106.830179,32 98.0061174,32 L21.9938826,32 C18.2292665,32 14.7684355,30.699197 12.0362474,28.5221601 C8.56516444,32.1765452 -1.77635684e-15,31.9985981 -1.77635684e-15,31.9985981 C5.69252399,28.6991366 5.98604874,24.4421608 5.99940747,24.1573436 L6,24.1422468 L6,16 C6,7.163444 13.1698213,0 21.9938826,0 L98.0061174,0 ")
if Set(incomingColors.map(\.rgb)).count > 1 {
c.clip()
var colors: [CGColor] = []
var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t)
colors.append(incomingColors[i].cgColor)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 32.0), options: CGGradientDrawingOptions())
} else {
c.setFillColor(incomingColors[0].cgColor)
c.fillPath()
}
c.restoreGState()
} else {
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
c.clip()
if incomingColors.count >= 2 {
let gradientColors = incomingColors.map { $0.cgColor } as CFArray
var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
} else if !incomingColors.isEmpty {
c.setFillColor(incomingColors[0].cgColor)
c.fill(rect)
}
c.resetClip()
}
} else {
let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors)
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
}
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
c.scaleBy(x: -1.0, y: 1.0)
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
if !(emoticon && large) {
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
c.scaleBy(x: -1.0, y: 1.0)
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
}
let outgoingColors = colors.2
if emoticon {
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
c.clip()
if outgoingColors.count >= 2 {
let gradientColors = outgoingColors.map { $0.cgColor } as CFArray
if large {
c.saveGState()
var locations: [CGFloat] = []
for i in 0 ..< outgoingColors.count {
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
locations.append(t)
c.translateBy(x: drawingRect.width - 114.0 - 5.0, y: 65.0)
c.translateBy(x: 114.0, y: 32.0)
c.scaleBy(x: -1.0, y: -1.0)
c.translateBy(x: 0, y: -32.0)
let _ = try? drawSvgPath(c, path: "M98.0061174,0 C106.734138,0 113.82927,6.99200411 113.996965,15.6850616 L114,16 C114,24.836556 106.830179,32 98.0061174,32 L21.9938826,32 C18.2292665,32 14.7684355,30.699197 12.0362474,28.5221601 C8.56516444,32.1765452 -1.77635684e-15,31.9985981 -1.77635684e-15,31.9985981 C5.69252399,28.6991366 5.98604874,24.4421608 5.99940747,24.1573436 L6,24.1422468 L6,16 C6,7.163444 13.1698213,0 21.9938826,0 L98.0061174,0 ")
if Set(outgoingColors.map(\.rgb)).count > 1 {
c.clip()
var colors: [CGColor] = []
var locations: [CGFloat] = []
for i in 0 ..< outgoingColors.count {
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
locations.append(t)
colors.append(outgoingColors[i].cgColor)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 32.0), options: CGGradientDrawingOptions())
} else {
c.setFillColor(outgoingColors[0].cgColor)
c.fillPath()
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.restoreGState()
} else {
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
c.clip()
if outgoingColors.count >= 2 {
let gradientColors = outgoingColors.map { $0.cgColor } as CFArray
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
} else if !outgoingColors.isEmpty {
c.setFillColor(outgoingColors[0].cgColor)
c.fill(rect)
var locations: [CGFloat] = []
for i in 0 ..< outgoingColors.count {
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
locations.append(t)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
} else if !outgoingColors.isEmpty {
c.setFillColor(outgoingColors[0].cgColor)
c.fill(rect)
}
}
c.resetClip()