Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-12-16 15:58:35 +04:00
commit 6e3463bfa6
73 changed files with 3560 additions and 374 deletions

View File

@ -592,9 +592,9 @@ public final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
return nil
}
self.animation = animation
let frameCount = Int(animation.frameCount)
let frameCount = max(1, Int(animation.frameCount))
self.frameCount = frameCount
self.frameRate = Int(animation.frameRate)
self.frameRate = max(1, Int(animation.frameRate))
self.cache = cachePathPrefix.flatMap { cachePathPrefix in
AnimatedStickerDirectFrameSourceCache(queue: queue, pathPrefix: cachePathPrefix, width: width, height: height, frameCount: frameCount, fitzModifier: fitzModifier, useHardware: useMetalCache)

View File

@ -903,10 +903,17 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
guard let publicToken = nativeParams["public_token"] as? String else {
return
}
var customTokenizeUrl: String?
if let value = nativeParams["public_token"] as? String, let url = URL(string: value), let host = url.host {
if url.scheme == "https" && (host == "smart-glocal.com" || host.hasSuffix(".smart-glocal.com")) {
customTokenizeUrl = value
}
}
var dismissImpl: (() -> Void)?
let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing
let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, provider: .smartglobal(isTesting: paymentForm.invoice.isTest, publicToken: publicToken), completion: { method in
let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, provider: .smartglobal(isTesting: paymentForm.invoice.isTest, publicToken: publicToken, customTokenizeUrl: customTokenizeUrl), completion: { method in
guard let strongSelf = self else {
return
}

View File

@ -30,7 +30,7 @@ struct BotCheckoutNativeCardEntryAdditionalFields: OptionSet {
final class BotCheckoutNativeCardEntryController: ViewController {
enum Provider {
case stripe(additionalFields: BotCheckoutNativeCardEntryAdditionalFields, publishableKey: String)
case smartglobal(isTesting: Bool, publicToken: String)
case smartglobal(isTesting: Bool, publicToken: String, customTokenizeUrl: String?)
}
private var controllerNode: BotCheckoutNativeCardEntryControllerNode {

View File

@ -310,9 +310,11 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
}))
self.updateDone()
case let .smartglobal(isTesting, publicToken):
case let .smartglobal(isTesting, publicToken, customTokenizeUrl):
let url: String
if isTesting {
if let customTokenizeUrl {
url = customTokenizeUrl
} else if isTesting {
url = "https://tgb-playground.smart-glocal.com/cds/v1/tokenize/card"
} else {
url = "https://tgb.smart-glocal.com/cds/v1/tokenize/card"

View File

@ -2477,7 +2477,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
} else if peer.isFake {
currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
} else if peer.isVerified {
currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)
@ -2493,7 +2493,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
} else if peer.isFake {
currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
} else if peer.isVerified {
currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)

View File

@ -609,7 +609,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
currentCredibilityIcon = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_ScamAccount.uppercased())
} else if item.peer.isFake {
currentCredibilityIcon = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = item.peer, let emojiStatus = user.emojiStatus {
} else if let emojiStatus = item.peer.emojiStatus {
currentCredibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: UIColor(white: 0.0, alpha: 0.1), themeColor: presentationData.theme.list.itemAccentColor, loopMode: .count(2))
} else if item.peer.isVerified {
currentCredibilityIcon = .verified(fillColor: presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)

View File

@ -225,12 +225,25 @@ open class ViewControllerComponentContainer: ViewController {
public var wasDismissed: (() -> Void)?
public init<C: Component>(context: AccountContext, component: C, navigationBarAppearance: NavigationBarAppearance, statusBarStyle: StatusBarStyle = .default, presentationMode: PresentationMode = .default, theme: Theme = .default) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
public init<C: Component>(
context: AccountContext,
component: C,
navigationBarAppearance: NavigationBarAppearance,
statusBarStyle: StatusBarStyle = .default,
presentationMode: PresentationMode = .default,
theme: Theme = .default,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil
) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.context = context
self.component = AnyComponent(component)
self.theme = theme
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let presentationData: PresentationData
if let updatedPresentationData {
presentationData = updatedPresentationData.initial
} else {
presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
}
let navigationBarPresentationData: NavigationBarPresentationData?
switch navigationBarAppearance {
@ -243,7 +256,7 @@ open class ViewControllerComponentContainer: ViewController {
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.presentationDataDisposable = (self.context.sharedContext.presentationData
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? self.context.sharedContext.presentationData)
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
var theme = presentationData.theme
@ -266,6 +279,19 @@ open class ViewControllerComponentContainer: ViewController {
strongSelf.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
}
let navigationBarPresentationData: NavigationBarPresentationData?
switch navigationBarAppearance {
case .none:
navigationBarPresentationData = nil
case .transparent:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true)
case .default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
}
if let navigationBarPresentationData {
strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData)
}
if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
}

View File

@ -699,7 +699,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
} else if peer.isFake {
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
} else if let emojiStatus = peer.emojiStatus {
credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
} else if peer.isVerified {
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)

View File

@ -428,6 +428,7 @@ public func generateScaledImage(image: UIImage?, size: CGSize, opaque: Bool = tr
public func generateSingleColorImage(size: CGSize, color: UIColor, scale: CGFloat = 0.0) -> UIImage? {
return generateImage(size, contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}, scale: scale)

View File

@ -52,17 +52,19 @@ public enum ListViewItemNodeVisibility: Equatable {
case visible(CGFloat, CGRect)
}
public struct ListViewItemLayoutParams {
public struct ListViewItemLayoutParams: Equatable {
public let width: CGFloat
public let leftInset: CGFloat
public let rightInset: CGFloat
public let availableHeight: CGFloat
public let isStandalone: Bool
public init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, availableHeight: CGFloat) {
public init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, availableHeight: CGFloat, isStandalone: Bool = false) {
self.width = width
self.leftInset = leftInset
self.rightInset = rightInset
self.availableHeight = availableHeight
self.isStandalone = isStandalone
}
}

View File

@ -913,7 +913,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
} else if item.peer.isFake {
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = item.peer, let emojiStatus = user.emojiStatus {
} else if let emojiStatus = item.peer.emojiStatus {
credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
} else if item.peer.isVerified {
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)

View File

@ -122,6 +122,10 @@ public func itemListNeighborsPlainInsets(_ neighbors: ItemListNeighbors) -> UIEd
}
public func itemListNeighborsGroupedInsets(_ neighbors: ItemListNeighbors, _ params: ListViewItemLayoutParams) -> UIEdgeInsets {
if params.isStandalone {
return UIEdgeInsets()
}
let topInset: CGFloat
switch neighbors.top {
case .none:

View File

@ -599,7 +599,12 @@ private final class VariableBlurView: UIVisualEffectView {
fatalError("init(coder:) has not been implemented")
}
override func updateTraitsIfNeeded() {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if #available(iOS 13.0, *) {
if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
self.resetEffect()
}
}
}
private func resetEffect() {

View File

@ -589,6 +589,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
strings: strongSelf.presentationData.strings,
deviceMetrics: DeviceMetrics.iPhone13,
emojiContent: emojiContent,
color: nil,
backgroundColor: .clear,
separatorColor: strongSelf.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5),
hideTopPanel: hideTopPanel,
@ -1215,6 +1216,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
strings: self.presentationData.strings,
deviceMetrics: DeviceMetrics.iPhone13,
emojiContent: emojiContent,
color: nil,
backgroundColor: .clear,
separatorColor: self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5),
hideTopPanel: hideTopPanel,

View File

@ -117,6 +117,8 @@ swift_library(
"//submodules/TelegramUI/Components/Settings/PeerNameColorScreen",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
"//submodules/TelegramUI/Components/Settings/QuickReactionSetupController",
"//submodules/TelegramUI/Components/Settings/ThemeCarouselItem",
"//submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem",
],
visibility = [
"//visibility:public",

View File

@ -307,10 +307,10 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let timestamp = self.referenceTimestamp

View File

@ -19,6 +19,7 @@ import ContextUI
import UndoUI
import ItemListPeerActionItem
import AnimationUI
import ThemeSettingsThemeItem
private final class ThemePickerControllerArguments {
let context: AccountContext

View File

@ -18,7 +18,7 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ShimmerEffect
import StickerResources
import ThemeCarouselItem
private var cachedBorderImages: [String: UIImage] = [:]
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
@ -500,7 +500,7 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode {
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, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil, action: { theme in
let iconItem = ThemeCarouselThemeIconItem(context: item.context, emojiFile: theme.emoticon.flatMap { item.animatedEmojiStickers[$0]?.first?.file }, themeReference: theme, nightMode: item.nightMode, channelMode: false, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil, action: { theme in
item.updatedTheme(theme)
}, contextAction: nil)

View File

@ -948,7 +948,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))

View File

@ -14,6 +14,7 @@ import DeviceLocationManager
import Geocoding
import WallpaperResources
import Sunrise
import ThemeSettingsThemeItem
private enum TriggerMode {
case system

View File

@ -455,10 +455,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil))
let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
let peer7: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))

View File

@ -19,32 +19,7 @@ import ContextUI
import UndoUI
import PremiumUI
import PeerNameColorScreen
func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
let name: String
switch reference {
case let .builtin(theme):
switch theme {
case .dayClassic:
name = strings.Appearance_ThemeCarouselClassic
case .day:
name = strings.Appearance_ThemeCarouselDay
case .night:
name = strings.Appearance_ThemeCarouselNewNight
case .nightAccent:
name = strings.Appearance_ThemeCarouselTintedNight
}
case let .local(theme):
name = theme.title
case let .cloud(theme):
if let emoticon = theme.theme.emoticon {
name = emoticon
} else {
name = theme.theme.title
}
}
return name
}
import ThemeCarouselItem
private final class ThemeSettingsControllerArguments {
let context: AccountContext

View File

@ -1262,7 +1262,7 @@ public extension Api {
return parser(reader)
}
else {
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found")
return nil
}
}

View File

@ -839,7 +839,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
} else if item.peer.isFake {
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = item.peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
} else if let emojiStatus = item.peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
} else if item.peer.isVerified {
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)

View File

@ -61,7 +61,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .chatForbidden(id, title):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames, _, color, profileColor, _, _):
case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel):
let isMin = (flags & (1 << 12)) != 0
let participationStatus: TelegramChannelParticipationStatus
@ -173,7 +173,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
}
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: 0, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId)
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: 0, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), approximateBoostLevel: boostLevel)
case let .channelForbidden(flags, id, accessHash, title, untilDate):
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
@ -182,7 +182,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
info = .broadcast(TelegramChannelBroadcastInfo(flags: []))
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil)
}
}
@ -190,7 +190,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
switch rhs {
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
return parseTelegramGroupOrChannel(chat: rhs)
case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames, _, color, profileColor, _, _):
case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel):
let isMin = (flags & (1 << 12)) != 0
if accessHash != nil && !isMin {
return parseTelegramGroupOrChannel(chat: rhs)
@ -252,7 +252,9 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
}
}
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId)
let parsedEmojiStatus = emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:))
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: parsedEmojiStatus, approximateBoostLevel: boostLevel)
} else {
return parseTelegramGroupOrChannel(chat: rhs)
}
@ -306,6 +308,6 @@ func mergeChannel(lhs: TelegramChannel?, rhs: TelegramChannel) -> TelegramChanne
let storiesHidden: Bool? = rhs.storiesHidden ?? lhs.storiesHidden
return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: rhs.creationDate, version: rhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: rhs.restrictionInfo, adminRights: rhs.adminRights, bannedRights: rhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights, usernames: rhs.usernames, storiesHidden: storiesHidden, nameColor: rhs.nameColor, backgroundEmojiId: rhs.backgroundEmojiId, profileColor: rhs.profileColor, profileBackgroundEmojiId: rhs.profileBackgroundEmojiId)
return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: rhs.creationDate, version: rhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: rhs.restrictionInfo, adminRights: rhs.adminRights, bannedRights: rhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights, usernames: rhs.usernames, storiesHidden: storiesHidden, nameColor: rhs.nameColor, backgroundEmojiId: rhs.backgroundEmojiId, profileColor: rhs.profileColor, profileBackgroundEmojiId: rhs.profileBackgroundEmojiId, emojiStatus: rhs.emojiStatus, approximateBoostLevel: rhs.approximateBoostLevel)
}

View File

@ -1876,6 +1876,12 @@ private func sendStandaloneMessage(auxiliaryMethods: AccountAuxiliaryMethods, po
if let value = contents.media {
media.append(value)
}
var attributes = contents.attributes
if !attributes.contains(where: { $0 is AutoremoveTimeoutMessageAttribute }), let messageAutoremoveTimeout = state.messageAutoremoveTimeout {
attributes.append(AutoclearTimeoutMessageAttribute(timeout: messageAutoremoveTimeout, countdownBeginTime: nil))
}
let message = Message(
stableId: 1,
stableVersion: 0,
@ -1892,7 +1898,7 @@ private func sendStandaloneMessage(auxiliaryMethods: AccountAuxiliaryMethods, po
forwardInfo: nil,
author: nil,
text: contents.text,
attributes: contents.attributes,
attributes: attributes,
media: media,
peers: SimpleDictionary(),
associatedMessages: SimpleDictionary(),
@ -1967,7 +1973,7 @@ private func sendStandaloneMessage(auxiliaryMethods: AccountAuxiliaryMethods, po
}
let entitiesAttribute = message.textEntitiesAttribute
let (tags, globalTags) = tagsForStoreMessage(incoming: false, attributes: contents.attributes, media: updatedMedia, textEntities: entitiesAttribute?.entities, isPinned: false)
let (tags, globalTags) = tagsForStoreMessage(incoming: false, attributes: attributes, media: updatedMedia, textEntities: entitiesAttribute?.entities, isPinned: false)
let storedMessage = StoreMessage(
peerId: peerId,

View File

@ -30,6 +30,9 @@ public struct StickerPackCollectionInfoFlags: OptionSet {
if flags.contains(StickerPackCollectionInfoFlags.isEmoji) {
rawValue |= StickerPackCollectionInfoFlags.isEmoji.rawValue
}
if flags.contains(StickerPackCollectionInfoFlags.isAvailableAsChannelStatus) {
rawValue |= StickerPackCollectionInfoFlags.isAvailableAsChannelStatus.rawValue
}
self.rawValue = rawValue
}
@ -39,6 +42,7 @@ public struct StickerPackCollectionInfoFlags: OptionSet {
public static let isAnimated = StickerPackCollectionInfoFlags(rawValue: 1 << 2)
public static let isVideo = StickerPackCollectionInfoFlags(rawValue: 1 << 3)
public static let isEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 4)
public static let isAvailableAsChannelStatus = StickerPackCollectionInfoFlags(rawValue: 1 << 5)
}

View File

@ -171,6 +171,8 @@ public final class TelegramChannel: Peer, Equatable {
public let backgroundEmojiId: Int64?
public let profileColor: PeerNameColor?
public let profileBackgroundEmojiId: Int64?
public let emojiStatus: PeerEmojiStatus?
public let approximateBoostLevel: Int32?
public var indexName: PeerIndexNameRepresentation {
var addressNames = self.usernames.map { $0.username }
@ -181,8 +183,19 @@ public final class TelegramChannel: Peer, Equatable {
}
public var associatedMediaIds: [MediaId]? {
if let backgroundEmojiId = self.backgroundEmojiId {
return [MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)]
if let emojiStatus = self.emojiStatus, let backgroundEmojiId = self.backgroundEmojiId {
return [
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId),
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)
]
} else if let emojiStatus = self.emojiStatus {
return [
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId)
]
} else if let backgroundEmojiId = self.backgroundEmojiId {
return [
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)
]
} else {
return nil
}
@ -191,7 +204,17 @@ public final class TelegramChannel: Peer, Equatable {
public let associatedPeerId: PeerId? = nil
public let notificationSettingsPeerId: PeerId? = nil
public var timeoutAttribute: UInt32? { return nil }
public var timeoutAttribute: UInt32? {
if let emojiStatus = self.emojiStatus {
if let expirationDate = emojiStatus.expirationDate {
return UInt32(max(0, expirationDate))
} else {
return nil
}
} else {
return nil
}
}
public init(
id: PeerId,
@ -213,7 +236,9 @@ public final class TelegramChannel: Peer, Equatable {
nameColor: PeerNameColor?,
backgroundEmojiId: Int64?,
profileColor: PeerNameColor?,
profileBackgroundEmojiId: Int64?
profileBackgroundEmojiId: Int64?,
emojiStatus: PeerEmojiStatus?,
approximateBoostLevel: Int32?
) {
self.id = id
self.accessHash = accessHash
@ -235,6 +260,8 @@ public final class TelegramChannel: Peer, Equatable {
self.backgroundEmojiId = backgroundEmojiId
self.profileColor = profileColor
self.profileBackgroundEmojiId = profileBackgroundEmojiId
self.emojiStatus = emojiStatus
self.approximateBoostLevel = approximateBoostLevel
}
public init(decoder: PostboxDecoder) {
@ -268,6 +295,8 @@ public final class TelegramChannel: Peer, Equatable {
self.backgroundEmojiId = decoder.decodeOptionalInt64ForKey("bgem")
self.profileColor = decoder.decodeOptionalInt32ForKey("pclr").flatMap { PeerNameColor(rawValue: $0) }
self.profileBackgroundEmojiId = decoder.decodeOptionalInt64ForKey("pgem")
self.emojiStatus = decoder.decode(PeerEmojiStatus.self, forKey: "emjs")
self.approximateBoostLevel = decoder.decodeOptionalInt32ForKey("abl")
}
public func encode(_ encoder: PostboxEncoder) {
@ -347,6 +376,18 @@ public final class TelegramChannel: Peer, Equatable {
} else {
encoder.encodeNil(forKey: "pgem")
}
if let emojiStatus = self.emojiStatus {
encoder.encode(emojiStatus, forKey: "emjs")
} else {
encoder.encodeNil(forKey: "emjs")
}
if let approximateBoostLevel = self.approximateBoostLevel {
encoder.encodeInt32(approximateBoostLevel, forKey: "abl")
} else {
encoder.encodeNil(forKey: "abl")
}
}
public func isEqual(_ other: Peer) -> Bool {
@ -399,43 +440,57 @@ public final class TelegramChannel: Peer, Equatable {
if lhs.profileBackgroundEmojiId != rhs.profileBackgroundEmojiId {
return false
}
if lhs.emojiStatus != rhs.emojiStatus {
return false
}
if lhs.approximateBoostLevel != rhs.approximateBoostLevel {
return false
}
return true
}
public func withUpdatedAddressName(_ addressName: String?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: addressName, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: addressName, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedAddressNames(_ addressNames: [TelegramPeerUsername]) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: addressNames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: addressNames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedDefaultBannedRights(_ defaultBannedRights: TelegramChatBannedRights?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedFlags(_ flags: TelegramChannelFlags) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedStoriesHidden(_ storiesHidden: Bool?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedNameColor(_ nameColor: PeerNameColor?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedBackgroundEmojiId(_ backgroundEmojiId: Int64?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedProfileColor(_ profileColor: PeerNameColor?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedProfileBackgroundEmojiId(_ profileBackgroundEmojiId: Int64?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId)
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedEmojiStatus(_ emojiStatus: PeerEmojiStatus?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: emojiStatus, approximateBoostLevel: self.approximateBoostLevel)
}
public func withUpdatedApproximateBoostLevel(_ approximateBoostLevel: Int32?) -> TelegramChannel {
return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden, nameColor: self.nameColor, backgroundEmojiId: self.backgroundEmojiId, profileColor: self.profileColor, profileBackgroundEmojiId: self.profileBackgroundEmojiId, emojiStatus: self.emojiStatus, approximateBoostLevel: approximateBoostLevel)
}
}

View File

@ -315,7 +315,9 @@ private class AdMessagesHistoryContextImpl {
nameColor: invite.nameColor,
backgroundEmojiId: nil,
profileColor: nil,
profileBackgroundEmojiId: nil
profileBackgroundEmojiId: nil,
emojiStatus: nil,
approximateBoostLevel: nil
)
case let .webPage(webPage):
author = TelegramChannel(
@ -338,7 +340,9 @@ private class AdMessagesHistoryContextImpl {
nameColor: .blue,
backgroundEmojiId: nil,
profileColor: nil,
profileBackgroundEmojiId: nil
profileBackgroundEmojiId: nil,
emojiStatus: nil,
approximateBoostLevel: nil
)
}

View File

@ -1423,6 +1423,13 @@ public func _internal_pollPeerStories(postbox: Postbox, network: Network, accoun
guard let inputPeer = inputPeer else {
return .complete()
}
#if DEBUG
if "".isEmpty {
return .complete()
}
#endif
return network.request(Api.functions.stories.getPeerStories(peer: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.stories.PeerStories?, NoError> in

View File

@ -514,6 +514,10 @@ public extension EnginePeer {
return self._asPeer().profileColor
}
var emojiStatus: PeerEmojiStatus? {
return self._asPeer().emojiStatus
}
var backgroundEmojiId: Int64? {
return self._asPeer().backgroundEmojiId
}

View File

@ -718,6 +718,10 @@ public extension TelegramEngine {
return _internal_updatePeerNameColorAndEmoji(account: self.account, peerId: peerId, nameColor: nameColor, backgroundEmojiId: backgroundEmojiId, profileColor: profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId)
}
public func updatePeerEmojiStatus(peerId: EnginePeer.Id, fileId: Int64?, expirationDate: Int32?) -> Signal<Never, UpdatePeerEmojiStatusError> {
return _internal_updatePeerEmojiStatus(account: self.account, peerId: peerId, fileId: fileId, expirationDate: expirationDate)
}
public func getChatListPeers(filterPredicate: ChatListFilterPredicate) -> Signal<[EnginePeer], NoError> {
return self.account.postbox.transaction { transaction -> [EnginePeer] in
return transaction.getChatListPeers(groupId: .root, filterPredicate: filterPredicate, additionalFilter: nil).map(EnginePeer.init)

View File

@ -100,9 +100,9 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer.
if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
let flagsReplies: Int32 = (1 << 0) | (1 << 2)
var flagsProfile: Int32 = (1 << 0) | (1 << 2)
var flagsProfile: Int32 = (1 << 0) | (1 << 1)
if profileColor != nil {
flagsProfile |= (1 << 1)
flagsProfile |= (1 << 2)
}
return combineLatest(
@ -161,3 +161,45 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer.
|> castError(UpdatePeerNameColorAndEmojiError.self)
|> switchToLatest
}
public enum UpdatePeerEmojiStatusError {
case generic
}
func _internal_updatePeerEmojiStatus(account: Account, peerId: PeerId, fileId: Int64?, expirationDate: Int32?) -> Signal<Never, UpdatePeerEmojiStatusError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in
let updatedStatus = fileId.flatMap {
PeerEmojiStatus(fileId: $0, expirationDate: expirationDate)
}
if let peer = transaction.getPeer(peerId) as? TelegramChannel {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(updatedStatus)], update: { _, updated in updated })
}
return transaction.getPeer(peerId).flatMap(apiInputChannel)
}
|> castError(UpdatePeerEmojiStatusError.self)
|> mapToSignal { inputChannel -> Signal<Never, UpdatePeerEmojiStatusError> in
guard let inputChannel = inputChannel else {
return .fail(.generic)
}
let mappedStatus: Api.EmojiStatus
if let fileId = fileId {
if let expirationDate = expirationDate {
mappedStatus = .emojiStatusUntil(documentId: fileId, until: expirationDate)
} else {
mappedStatus = .emojiStatus(documentId: fileId)
}
} else {
mappedStatus = .emojiStatusEmpty
}
return account.network.request(Api.functions.channels.updateEmojiStatus(channel: inputChannel, emojiStatus: mappedStatus))
|> ignoreValues
|> `catch` { error -> Signal<Never, UpdatePeerEmojiStatusError> in
if error.errorDescription == "CHAT_NOT_MODIFIED" {
return .complete()
} else {
return .fail(.generic)
}
}
}
}

View File

@ -49,6 +49,9 @@ extension StickerPackCollectionInfo {
if (flags & (1 << 7)) != 0 {
setFlags.insert(.isEmoji)
}
if (flags & (1 << 10)) != 0 {
setFlags.insert(.isAvailableAsChannelStatus)
}
var thumbnailRepresentation: TelegramMediaImageRepresentation?
var immediateThumbnailData: Data?

View File

@ -255,6 +255,17 @@ public extension Peer {
return false
}
var emojiStatus: PeerEmojiStatus? {
switch self {
case let user as TelegramUser:
return user.emojiStatus
case let channel as TelegramChannel:
return channel.emojiStatus
default:
return nil
}
}
var backgroundEmojiId: Int64? {
switch self {
case let user as TelegramUser:

View File

@ -875,3 +875,29 @@ public extension PresentationData {
return PresentationData(strings: strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
}
public func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
let name: String
switch reference {
case let .builtin(theme):
switch theme {
case .dayClassic:
name = strings.Appearance_ThemeCarouselClassic
case .day:
name = strings.Appearance_ThemeCarouselDay
case .night:
name = strings.Appearance_ThemeCarouselNewNight
case .nightAccent:
name = strings.Appearance_ThemeCarouselTintedNight
}
case let .local(theme):
name = theme.title
case let .cloud(theme):
if let emoticon = theme.theme.emoticon {
name = emoticon
} else {
name = theme.theme.title
}
}
return name
}

View File

@ -1953,11 +1953,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
authorNameColor = color
if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId {
if effectiveAuthor is TelegramChannel, let emojiStatus = effectiveAuthor.emojiStatus {
currentCredibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor, themeColor: color.withMultipliedAlpha(0.4), loopMode: .count(2))
}
} else if effectiveAuthor.isScam {
currentCredibilityIcon = .text(color: incoming ? item.presentationData.theme.theme.chat.message.incoming.scamColor : item.presentationData.theme.theme.chat.message.outgoing.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
} else if effectiveAuthor.isFake {
currentCredibilityIcon = .text(color: incoming ? item.presentationData.theme.theme.chat.message.incoming.scamColor : item.presentationData.theme.theme.chat.message.outgoing.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if let user = effectiveAuthor as? TelegramUser, let emojiStatus = user.emojiStatus {
} else if let emojiStatus = effectiveAuthor.emojiStatus {
currentCredibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor, themeColor: color.withMultipliedAlpha(0.4), loopMode: .count(2))
} else if effectiveAuthor.isVerified {
currentCredibilityIcon = .verified(fillColor: item.presentationData.theme.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)

View File

@ -69,7 +69,7 @@ private func filterOriginalMessageFlags(_ message: Message) -> Message {
private func filterMessageChannelPeer(_ peer: Peer) -> Peer {
if let peer = peer as? TelegramChannel {
return TelegramChannel(id: peer.id, accessHash: peer.accessHash, title: peer.title, username: peer.username, photo: peer.photo, creationDate: peer.creationDate, version: peer.version, participationStatus: peer.participationStatus, info: .group(TelegramChannelGroupInfo(flags: [])), flags: peer.flags, restrictionInfo: peer.restrictionInfo, adminRights: peer.adminRights, bannedRights: peer.bannedRights, defaultBannedRights: peer.defaultBannedRights, usernames: peer.usernames, storiesHidden: peer.storiesHidden, nameColor: peer.nameColor, backgroundEmojiId: peer.backgroundEmojiId, profileColor: peer.profileColor, profileBackgroundEmojiId: peer.profileBackgroundEmojiId)
return TelegramChannel(id: peer.id, accessHash: peer.accessHash, title: peer.title, username: peer.username, photo: peer.photo, creationDate: peer.creationDate, version: peer.version, participationStatus: peer.participationStatus, info: .group(TelegramChannelGroupInfo(flags: [])), flags: peer.flags, restrictionInfo: peer.restrictionInfo, adminRights: peer.adminRights, bannedRights: peer.bannedRights, defaultBannedRights: peer.defaultBannedRights, usernames: peer.usernames, storiesHidden: peer.storiesHidden, nameColor: peer.nameColor, backgroundEmojiId: peer.backgroundEmojiId, profileColor: peer.profileColor, profileBackgroundEmojiId: peer.profileBackgroundEmojiId, emojiStatus: peer.emojiStatus, approximateBoostLevel: peer.approximateBoostLevel)
}
return peer
}

View File

@ -334,7 +334,6 @@ private final class LineView: UIView {
let _ = previousParams
self.backgroundView.tintColor = primaryColor
if let secondaryColor {
@ -406,6 +405,10 @@ private final class LineView: UIView {
self.dashBackgroundView = nil
dashBackgroundView.removeFromSuperview()
}
if let dashThirdBackgroundView = self.dashThirdBackgroundView {
self.dashThirdBackgroundView = nil
dashThirdBackgroundView.removeFromSuperview()
}
self.backgroundView.alpha = 1.0
}

View File

@ -257,7 +257,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
titleCredibilityIcon = .fake
} else if peer.isScam {
titleCredibilityIcon = .scam
} else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
titleCredibilityIcon = .emojiStatus(emojiStatus)
} else if peer.isVerified {
titleCredibilityIcon = .verified

View File

@ -0,0 +1,18 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "DynamicCornerRadiusView",
module_name = "DynamicCornerRadiusView",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/ComponentFlow"
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,90 @@
import Foundation
import UIKit
import ComponentFlow
private func generatePath(size: CGSize, corners: DynamicCornerRadiusView.Corners) -> CGPath {
let path = CGMutablePath()
var corners = corners
corners.minXMinY = max(0.01, corners.minXMinY)
corners.maxXMinY = max(0.01, corners.maxXMinY)
corners.minXMaxY = max(0.01, corners.minXMaxY)
corners.maxXMaxY = max(0.01, corners.maxXMaxY)
path.move(to: CGPoint(x: 0.0, y: corners.minXMinY))
path.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: corners.minXMinY, y: 0.0), radius: corners.minXMinY)
path.addLine(to: CGPoint(x: size.width - corners.maxXMinY, y: 0.0))
path.addArc(tangent1End: CGPoint(x: size.width, y: 0.0), tangent2End: CGPoint(x: size.width, y: corners.maxXMinY), radius: corners.maxXMinY)
path.addLine(to: CGPoint(x: size.width, y: size.height - corners.maxXMaxY))
path.addArc(tangent1End: CGPoint(x: size.width, y: size.height), tangent2End: CGPoint(x: size.width - corners.maxXMaxY, y: size.height), radius: corners.maxXMaxY)
path.addLine(to: CGPoint(x: corners.minXMaxY, y: size.height))
path.addArc(tangent1End: CGPoint(x: 0.0, y: size.height), tangent2End: CGPoint(x: 0.0, y: size.height - corners.minXMaxY), radius: corners.minXMaxY)
path.closeSubpath()
return path
}
open class DynamicCornerRadiusView: UIView {
override public static var layerClass: AnyClass {
return CAShapeLayer.self
}
public struct Corners: Equatable {
public var minXMinY: CGFloat
public var maxXMinY: CGFloat
public var minXMaxY: CGFloat
public var maxXMaxY: CGFloat
public init(minXMinY: CGFloat, maxXMinY: CGFloat, minXMaxY: CGFloat, maxXMaxY: CGFloat) {
self.minXMinY = minXMinY
self.maxXMinY = maxXMinY
self.minXMaxY = minXMaxY
self.maxXMaxY = maxXMaxY
}
}
private struct Params: Equatable {
var size: CGSize
var corners: Corners
init(size: CGSize, corners: Corners) {
self.size = size
self.corners = corners
}
}
private var params: Params?
override public init(frame: CGRect) {
super.init(frame: frame)
if let shapeLayer = self.layer as? CAShapeLayer {
shapeLayer.strokeColor = nil
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func update(size: CGSize, corners: Corners, transition: Transition) {
let params = Params(size: size, corners: corners)
if self.params == params {
return
}
self.params = params
self.update(params: params, transition: transition)
}
public func updateColor(color: UIColor, transition: Transition) {
if let shapeLayer = self.layer as? CAShapeLayer {
transition.setShapeLayerFillColor(layer: shapeLayer, color: color)
}
}
private func update(params: Params, transition: Transition) {
if let shapeLayer = self.layer as? CAShapeLayer {
transition.setShapeLayerPath(layer: shapeLayer, path: generatePath(size: params.size, corners: params.corners))
}
}
}

View File

@ -64,6 +64,7 @@ public final class EmojiStatusSelectionComponent: Component {
public let emojiContent: EmojiPagerContentComponent
public let backgroundColor: UIColor
public let separatorColor: UIColor
public let color: UIColor?
public let hideTopPanel: Bool
public let disableTopPanel: Bool
public let hideTopPanelUpdated: (Bool, Transition) -> Void
@ -73,6 +74,7 @@ public final class EmojiStatusSelectionComponent: Component {
strings: PresentationStrings,
deviceMetrics: DeviceMetrics,
emojiContent: EmojiPagerContentComponent,
color: UIColor?,
backgroundColor: UIColor,
separatorColor: UIColor,
hideTopPanel: Bool,
@ -83,6 +85,7 @@ public final class EmojiStatusSelectionComponent: Component {
self.strings = strings
self.deviceMetrics = deviceMetrics
self.emojiContent = emojiContent
self.color = color
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
self.hideTopPanel = hideTopPanel
@ -103,6 +106,9 @@ public final class EmojiStatusSelectionComponent: Component {
if lhs.emojiContent != rhs.emojiContent {
return false
}
if lhs.color != rhs.color {
return false
}
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
@ -169,7 +175,7 @@ public final class EmojiStatusSelectionComponent: Component {
isContentInFocus: true,
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: 0.0, right: 0.0),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
emojiContent: component.emojiContent,
emojiContent: component.emojiContent.withCustomTintColor(component.color),
stickerContent: nil,
maskContent: nil,
gifContent: nil,
@ -200,7 +206,8 @@ public final class EmojiStatusSelectionComponent: Component {
isExpanded: false,
clipContentToTopPanel: false,
useExternalSearchContainer: false,
hidePanels: component.disableTopPanel
hidePanels: component.disableTopPanel,
customTintColor: component.color
)),
environment: {},
containerSize: availableSize
@ -277,6 +284,7 @@ public final class EmojiStatusSelectionController: ViewController {
private var validLayout: ContainerViewLayout?
private let currentSelection: Int64?
private let color: UIColor?
private var emojiContentDisposable: Disposable?
private var emojiContent: EmojiPagerContentComponent?
@ -312,10 +320,11 @@ public final class EmojiStatusSelectionController: ViewController {
private var isReactionSearchActive: Bool = false
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?) {
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?, color: UIColor?) {
self.controller = controller
self.context = context
self.currentSelection = currentSelection
self.color = color
if let sourceView = sourceView {
self.globalSourceRect = sourceView.convert(sourceView.bounds, to: nil)
@ -826,17 +835,26 @@ public final class EmojiStatusSelectionController: ViewController {
renderer: animationRenderer,
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
pointSize: CGSize(width: 32.0, height: 32.0),
dynamicColor: self.presentationData.theme.list.itemAccentColor
dynamicColor: self.color ?? self.presentationData.theme.list.itemAccentColor
)
switch item.tintMode {
case let .custom(color):
baseItemLayer.contentTintColor = color
case .accent:
baseItemLayer.contentTintColor = self.presentationData.theme.list.itemAccentColor
case .primary:
baseItemLayer.contentTintColor = self.presentationData.theme.list.itemPrimaryTextColor
case .none:
break
if let color = self.color {
switch item.tintMode {
case .none:
break
default:
baseItemLayer.contentTintColor = color
}
} else {
switch item.tintMode {
case let .custom(color):
baseItemLayer.contentTintColor = color
case .accent:
baseItemLayer.contentTintColor = self.presentationData.theme.list.itemAccentColor
case .primary:
baseItemLayer.contentTintColor = self.presentationData.theme.list.itemPrimaryTextColor
case .none:
break
}
}
if let sublayers = animationLayer.sublayers {
@ -988,6 +1006,7 @@ public final class EmojiStatusSelectionController: ViewController {
strings: self.presentationData.strings,
deviceMetrics: layout.deviceMetrics,
emojiContent: emojiContent,
color: self.color,
backgroundColor: listBackgroundColor,
separatorColor: separatorColor,
hideTopPanel: self.isReactionSearchActive,
@ -1024,11 +1043,18 @@ public final class EmojiStatusSelectionController: ViewController {
sourceOrigin = CGPoint(x: layout.size.width / 2.0, y: floor(layout.size.height / 2.0 - componentSize.height))
}
var componentFrame: CGRect
let pointsToTop: Bool
if sourceOrigin.y + 5.0 + componentSize.height > layout.size.height - layout.insets(options: []).bottom {
sourceOrigin.y = layout.size.height - layout.insets(options: []).bottom - componentSize.height - 5.0
componentFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - componentSize.width) / 2.0), y: sourceOrigin.y - 25.0 - componentSize.height), size: componentSize)
pointsToTop = false
} else {
componentFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - componentSize.width) / 2.0), y: sourceOrigin.y + 5.0), size: componentSize)
pointsToTop = true
}
if componentFrame.minY < layout.insets(options: [.statusBar]).top {
componentFrame.origin.y = layout.insets(options: [.statusBar]).top
}
let componentFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - componentSize.width) / 2.0), y: sourceOrigin.y + 5.0), size: componentSize)
if self.componentShadowLayer.bounds.size != componentFrame.size {
let componentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: componentFrame.size), cornerRadius: 24.0).cgPath
@ -1038,7 +1064,12 @@ public final class EmojiStatusSelectionController: ViewController {
let cloudOffset0: CGFloat = 30.0
let cloudSize0: CGFloat = 16.0
var cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.minY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0))
var cloudFrame0: CGRect
if pointsToTop {
cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.minY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0))
} else {
cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.maxY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0))
}
var invertX = false
if cloudFrame0.maxX >= layout.size.width - layout.safeInsets.right - 32.0 {
cloudFrame0.origin.x = floor(sourceOrigin.x - cloudSize0 - cloudOffset0 + cloudSize0 / 2.0)
@ -1055,7 +1086,12 @@ public final class EmojiStatusSelectionController: ViewController {
let cloudOffset1 = CGPoint(x: -9.0, y: -14.0)
let cloudSize1: CGFloat = 8.0
var cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY + cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1))
var cloudFrame1: CGRect
if pointsToTop {
cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY + cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1))
} else {
cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY - cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1))
}
if invertX {
cloudFrame1.origin.x = floor(cloudFrame0.midX - cloudSize1 - cloudOffset1.x + cloudSize1 / 2.0)
}
@ -1076,7 +1112,7 @@ public final class EmojiStatusSelectionController: ViewController {
let contentDuration: Double = 0.3
let contentDelay: Double = 0.14
let initialContentFrame = CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0))
let initialContentFrame = CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: pointsToTop ? componentFrame.minY : (componentFrame.maxY - 24.0 * 2.0)), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0))
if let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View {
emojiView.animateIn(fromLocation: self.view.convert(initialContentFrame.center, to: emojiView))
@ -1084,7 +1120,7 @@ public final class EmojiStatusSelectionController: ViewController {
componentView.layer.animatePosition(from: initialContentFrame.center, to: componentFrame.center, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
componentView.layer.animateBounds(from: CGRect(origin: CGPoint(x: -(componentFrame.minX - initialContentFrame.minX), y: -(componentFrame.minY - initialContentFrame.minY)), size: initialContentFrame.size), to: CGRect(origin: CGPoint(), size: componentFrame.size), duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
self.componentShadowLayer.animateFrame(from: CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0)), to: componentView.frame, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
self.componentShadowLayer.animateFrame(from: CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: pointsToTop ? componentFrame.minY : (componentFrame.maxY - 24.0 * 2.0)), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0)), to: componentView.frame, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
self.componentShadowLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
@ -1264,7 +1300,17 @@ public final class EmojiStatusSelectionController: ViewController {
return
}
if let _ = item, let destinationView = controller.destinationItemView() {
var animateOutToView = false
switch controller.mode {
case .backgroundSelection, .customStatusSelection, .quickReactionSelection:
if let itemFile = item?.itemFile, itemFile.fileId.id != 0 {
animateOutToView = true
}
case .statusSelection:
animateOutToView = true
}
if animateOutToView, item != nil, let destinationView = controller.destinationItemView() {
if let snapshotView = destinationView.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = destinationView.frame
destinationView.superview?.insertSubview(snapshotView, belowSubview: destinationView)
@ -1279,6 +1325,10 @@ public final class EmojiStatusSelectionController: ViewController {
case .statusSelection:
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile, expirationDate: nil)
|> deliverOnMainQueue).start()
case let .backgroundSelection(completion):
completion(item?.itemFile)
case let .customStatusSelection(completion):
completion(item?.itemFile, nil)
case let .quickReactionSelection(completion):
if let item = item, let itemFile = item.itemFile {
var selectedReaction: MessageReaction.Reaction?
@ -1304,7 +1354,7 @@ public final class EmojiStatusSelectionController: ViewController {
completion()
}
if let item = item, let destinationView = controller.destinationItemView() {
if animateOutToView, let item = item, let destinationView = controller.destinationItemView() {
var emojiString: String?
if let itemFile = item.itemFile {
attributeLoop: for attribute in itemFile.attributes {
@ -1364,6 +1414,8 @@ public final class EmojiStatusSelectionController: ViewController {
public enum Mode {
case statusSelection
case backgroundSelection(completion: (TelegramMediaFile?) -> Void)
case customStatusSelection(completion: (TelegramMediaFile?, Int32?) -> Void)
case quickReactionSelection(completion: () -> Void)
}
@ -1371,6 +1423,7 @@ public final class EmojiStatusSelectionController: ViewController {
private weak var sourceView: UIView?
private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
private let currentSelection: Int64?
private let color: UIColor?
private let mode: Mode
private let destinationItemView: () -> UIView?
@ -1383,12 +1436,13 @@ public final class EmojiStatusSelectionController: ViewController {
return true
}
public init(context: AccountContext, mode: Mode, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?, destinationItemView: @escaping () -> UIView?) {
public init(context: AccountContext, mode: Mode, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?, color: UIColor? = nil, destinationItemView: @escaping () -> UIView?) {
self.context = context
self.mode = mode
self.sourceView = sourceView
self.emojiContent = emojiContent
self.currentSelection = currentSelection
self.color = color
self.destinationItemView = destinationItemView
super.init(navigationBarPresentationData: nil)
@ -1418,7 +1472,7 @@ public final class EmojiStatusSelectionController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = Node(controller: self, context: self.context, sourceView: self.sourceView, emojiContent: self.emojiContent, currentSelection: self.currentSelection)
self.displayNode = Node(controller: self, context: self.context, sourceView: self.sourceView, emojiContent: self.emojiContent, currentSelection: self.currentSelection, color: self.color)
super.displayNodeDidLoad()
}

View File

@ -7188,6 +7188,7 @@ public final class EmojiPagerContentComponent: Component {
public enum Subject: Equatable {
case generic
case status
case channelStatus
case reaction(onlyTop: Bool)
case emoji
case topicIcon
@ -7239,6 +7240,19 @@ public final class EmojiPagerContentComponent: Component {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji)
iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false)
|> map { result -> [TelegramMediaFile] in
switch result {
case let .result(_, items, _):
return items.map(\.file)
default:
return []
}
}
|> take(1)
} else if case .channelStatus = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false)
|> map { result -> [TelegramMediaFile] in
switch result {
@ -7283,6 +7297,8 @@ public final class EmojiPagerContentComponent: Component {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .emoji)
} else if case .status = subject {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .status)
} else if case .channelStatus = subject {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .status)
} else if [.profilePhoto, .groupPhoto].contains(subject) {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .avatar)
} else {
@ -7564,6 +7580,166 @@ public final class EmojiPagerContentComponent: Component {
}
}
if let recentStatusEmoji = recentStatusEmoji {
for item in recentStatusEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
tintMode = .accent
}
default:
break
}
}
}
let resultItem: EmojiPagerContentComponent.Item
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
if itemGroups[groupIndex].items.count >= (5 + 8) * 8 {
break
}
itemGroups[groupIndex].items.append(resultItem)
}
}
}
if let featuredStatusEmoji = featuredStatusEmoji {
for item in featuredStatusEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
let resultItem: EmojiPagerContentComponent.Item
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
tintMode = .accent
}
default:
break
}
}
}
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
if itemGroups[groupIndex].items.count >= (5 + 8) * 8 {
break
}
itemGroups[groupIndex].items.append(resultItem)
}
}
}
} else if case .channelStatus = subject {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.stop),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .accent
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem]))
}
var existingIds = Set<MediaId>()
for file in iconStatusEmoji.prefix(7) {
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
tintMode = .accent
}
default:
break
}
}
}
let resultItem: EmojiPagerContentComponent.Item
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
}
}
if let recentStatusEmoji = recentStatusEmoji {
for item in recentStatusEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
@ -8143,7 +8319,7 @@ public final class EmojiPagerContentComponent: Component {
var isTemplate = false
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
if [.status, .backgroundIcon].contains(subject) {
if [.status, .channelStatus, .backgroundIcon].contains(subject) {
if let backgroundIconColor {
tintMode = .custom(backgroundIconColor)
} else {
@ -8229,7 +8405,7 @@ public final class EmojiPagerContentComponent: Component {
for item in featuredEmojiPack.topItems {
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
if [.status, .backgroundIcon].contains(subject) {
if [.status, .channelStatus, .backgroundIcon].contains(subject) {
if let backgroundIconColor {
tintMode = .custom(backgroundIconColor)
} else {
@ -8361,8 +8537,8 @@ public final class EmojiPagerContentComponent: Component {
)
}
let warpContentsOnEdges = [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .profilePhoto, .groupPhoto, .backgroundIcon].contains(subject)
let enableLongPress = [.reaction(onlyTop: true), .reaction(onlyTop: false), .status].contains(subject)
let warpContentsOnEdges = [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .channelStatus, .profilePhoto, .groupPhoto, .backgroundIcon].contains(subject)
let enableLongPress = [.reaction(onlyTop: true), .reaction(onlyTop: false), .status, .channelStatus].contains(subject)
return EmojiPagerContentComponent(
id: "emoji",

View File

@ -0,0 +1,21 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ListActionItemComponent",
module_name = "ListActionItemComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/TelegramUI/Components/ListSectionComponent",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,187 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import ListSectionComponent
public final class ListActionItemComponent: Component {
public let theme: PresentationTheme
public let title: AnyComponent<Empty>
public let icon: AnyComponentWithIdentity<Empty>?
public let hasArrow: Bool
public let action: ((UIView) -> Void)?
public init(
theme: PresentationTheme,
title: AnyComponent<Empty>,
icon: AnyComponentWithIdentity<Empty>?,
hasArrow: Bool = true,
action: ((UIView) -> Void)?
) {
self.theme = theme
self.title = title
self.icon = icon
self.hasArrow = hasArrow
self.action = action
}
public static func ==(lhs: ListActionItemComponent, rhs: ListActionItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.icon != rhs.icon {
return false
}
if lhs.hasArrow != rhs.hasArrow {
return false
}
if (lhs.action == nil) != (rhs.action == nil) {
return false
}
return true
}
public final class View: HighlightTrackingButton, ListSectionComponent.ChildView {
private let title = ComponentView<Empty>()
private var icon: ComponentView<Empty>?
private let arrowView: UIImageView
private var component: ListActionItemComponent?
public var iconView: UIView? {
return self.icon?.view
}
public var customUpdateIsHighlighted: ((Bool) -> Void)?
public override init(frame: CGRect) {
self.arrowView = UIImageView()
super.init(frame: CGRect())
self.addSubview(self.arrowView)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.internalHighligthedChanged = { [weak self] isHighlighted in
guard let self else {
return
}
if let customUpdateIsHighlighted = self.customUpdateIsHighlighted {
customUpdateIsHighlighted(isHighlighted)
}
}
}
required public init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func pressed() {
self.component?.action?(self)
}
func update(component: ListActionItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let previousComponent = self.component
self.component = component
self.isEnabled = component.action != nil
let verticalInset: CGFloat = 11.0
let contentLeftInset: CGFloat = 16.0
let contentRightInset: CGFloat = component.hasArrow ? 30.0 : 16.0
var contentHeight: CGFloat = 0.0
contentHeight += verticalInset
let titleSize = self.title.update(
transition: transition,
component: component.title,
environment: {},
containerSize: CGSize(width: availableSize.width - contentLeftInset, height: availableSize.height)
)
let titleFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: verticalInset), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: titleFrame)
}
contentHeight += titleSize.height
contentHeight += verticalInset
if let iconValue = component.icon {
if previousComponent?.icon?.id != iconValue.id, let icon = self.icon {
self.icon = nil
if let iconView = icon.view {
transition.setAlpha(view: iconView, alpha: 0.0, completion: { [weak iconView] _ in
iconView?.removeFromSuperview()
})
}
}
var iconTransition = transition
let icon: ComponentView<Empty>
if let current = self.icon {
icon = current
} else {
iconTransition = iconTransition.withAnimation(.none)
icon = ComponentView()
self.icon = icon
}
let iconSize = icon.update(
transition: iconTransition,
component: iconValue.component,
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
)
let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - contentRightInset - iconSize.width, y: floor((contentHeight - iconSize.height) * 0.5)), size: iconSize)
if let iconView = icon.view {
if iconView.superview == nil {
iconView.isUserInteractionEnabled = false
self.addSubview(iconView)
transition.animateAlpha(view: iconView, from: 0.0, to: 1.0)
}
iconTransition.setFrame(view: iconView, frame: iconFrame)
}
} else {
if let icon = self.icon {
self.icon = nil
if let iconView = icon.view {
transition.setAlpha(view: iconView, alpha: 0.0, completion: { [weak iconView] _ in
iconView?.removeFromSuperview()
})
}
}
}
if self.arrowView.image == nil {
self.arrowView.image = PresentationResourcesItemList.disclosureArrowImage(component.theme)?.withRenderingMode(.alwaysTemplate)
}
self.arrowView.tintColor = component.theme.list.disclosureArrowColor
if let image = self.arrowView.image {
let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 7.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size)
transition.setFrame(view: self.arrowView, frame: arrowFrame)
}
transition.setAlpha(view: self.arrowView, alpha: component.hasArrow ? 1.0 : 0.0)
return CGSize(width: availableSize.width, height: contentHeight)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,21 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ListItemComponentAdaptor",
module_name = "ListItemComponentAdaptor",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,131 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import ComponentDisplayAdapters
public protocol _ListItemComponentAdaptorItemGenerator: AnyObject, Equatable {
func item() -> ListViewItem
}
public final class ListItemComponentAdaptor: Component {
public typealias ItemGenerator = _ListItemComponentAdaptorItemGenerator
private let itemGenerator: AnyObject
private let isEqualImpl: (AnyObject) -> Bool
private let itemImpl: () -> ListViewItem
private let params: ListViewItemLayoutParams
public init<ItemGeneratorType: ItemGenerator>(
itemGenerator: ItemGeneratorType,
params: ListViewItemLayoutParams
) {
self.itemGenerator = itemGenerator
self.isEqualImpl = { other in
if let other = other as? ItemGeneratorType, itemGenerator == other {
return true
} else {
return false
}
}
self.itemImpl = {
return itemGenerator.item()
}
self.params = params
}
public static func ==(lhs: ListItemComponentAdaptor, rhs: ListItemComponentAdaptor) -> Bool {
if !lhs.isEqualImpl(rhs.itemGenerator) {
return false
}
if lhs.params != rhs.params {
return false
}
return true
}
public final class View: UIView {
private var itemNode: ListViewItemNode?
func update(component: ListItemComponentAdaptor, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let item = component.itemImpl()
if let itemNode = self.itemNode {
let mappedAnimation: ListViewItemUpdateAnimation
switch transition.animation {
case .none:
mappedAnimation = .None
case let .curve(duration, curve):
mappedAnimation = .System(duration: duration, transition: ControlledTransition(duration: duration, curve: curve.containedViewLayoutTransitionCurve, interactive: false))
}
var resultSize: CGSize?
item.updateNode(
async: { f in f() },
node: { return itemNode },
params: component.params,
previousItem: nil,
nextItem: nil,
animation: mappedAnimation,
completion: { [weak itemNode] layout, apply in
resultSize = layout.size
guard let itemNode else {
return
}
let nodeFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
apply(ListViewItemApply(isOnScreen: true))
}
)
if let resultSize {
transition.setFrame(view: itemNode.view, frame: CGRect(origin: CGPoint(), size: resultSize))
return resultSize
} else {
#if DEBUG
assertionFailure()
#endif
return self.bounds.size
}
} else {
var itemNode: ListViewItemNode?
item.nodeConfiguredForParams(
async: { f in f() },
params: component.params,
synchronousLoads: true,
previousItem: nil,
nextItem: nil,
completion: { result, apply in
itemNode = result
apply().1(ListViewItemApply(isOnScreen: true))
}
)
if let itemNode {
self.itemNode = itemNode
self.addSubnode(itemNode)
return itemNode.bounds.size
} else {
#if DEBUG
assertionFailure()
#endif
return self.bounds.size
}
}
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,21 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ListSectionComponent",
module_name = "ListSectionComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,288 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import DynamicCornerRadiusView
public protocol ListSectionComponentChildView: AnyObject {
var customUpdateIsHighlighted: ((Bool) -> Void)? { get set }
}
public final class ListSectionComponent: Component {
public typealias ChildView = ListSectionComponentChildView
public enum Background: Equatable {
case none
case all
case range(from: AnyHashable, corners: DynamicCornerRadiusView.Corners)
}
public let theme: PresentationTheme
public let background: Background
public let header: AnyComponent<Empty>?
public let footer: AnyComponent<Empty>?
public let items: [AnyComponentWithIdentity<Empty>]
public init(
theme: PresentationTheme,
background: Background = .all,
header: AnyComponent<Empty>?,
footer: AnyComponent<Empty>?,
items: [AnyComponentWithIdentity<Empty>]
) {
self.theme = theme
self.background = background
self.header = header
self.footer = footer
self.items = items
}
public static func ==(lhs: ListSectionComponent, rhs: ListSectionComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.background != rhs.background {
return false
}
if lhs.header != rhs.header {
return false
}
if lhs.footer != rhs.footer {
return false
}
if lhs.items != rhs.items {
return false
}
return true
}
public final class View: UIView {
private let contentView: UIView
private let contentBackgroundView: DynamicCornerRadiusView
private var header: ComponentView<Empty>?
private var footer: ComponentView<Empty>?
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
private var isHighlighted: Bool = false
private var component: ListSectionComponent?
public override init(frame: CGRect) {
self.contentView = UIView()
self.contentView.layer.cornerRadius = 11.0
self.contentView.clipsToBounds = true
self.contentBackgroundView = DynamicCornerRadiusView()
super.init(frame: CGRect())
self.addSubview(self.contentBackgroundView)
self.addSubview(self.contentView)
}
required public init?(coder: NSCoder) {
preconditionFailure()
}
private func updateIsHighlighted(isHighlighted: Bool) {
if self.isHighlighted == isHighlighted {
return
}
self.isHighlighted = isHighlighted
guard let component = self.component else {
return
}
let transition: Transition
let backgroundColor: UIColor
if isHighlighted {
transition = .immediate
backgroundColor = component.theme.list.itemHighlightedBackgroundColor
} else {
transition = .easeInOut(duration: 0.2)
backgroundColor = component.theme.list.itemBlocksBackgroundColor
}
self.contentBackgroundView.updateColor(color: backgroundColor, transition: transition)
}
func update(component: ListSectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
let backgroundColor: UIColor
if self.isHighlighted {
backgroundColor = component.theme.list.itemHighlightedBackgroundColor
} else {
backgroundColor = component.theme.list.itemBlocksBackgroundColor
}
self.contentBackgroundView.updateColor(color: backgroundColor, transition: transition)
let headerSideInset: CGFloat = 16.0
var contentHeight: CGFloat = 0.0
if let headerValue = component.header {
let header: ComponentView<Empty>
var headerTransition = transition
if let current = self.header {
header = current
} else {
headerTransition = headerTransition.withAnimation(.none)
header = ComponentView()
self.header = header
}
let headerSize = header.update(
transition: headerTransition,
component: headerValue,
environment: {},
containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: availableSize.height)
)
if let headerView = header.view {
if headerView.superview == nil {
self.addSubview(headerView)
}
headerTransition.setFrame(view: headerView, frame: CGRect(origin: CGPoint(x: headerSideInset, y: contentHeight), size: headerSize))
}
contentHeight += headerSize.height
} else {
if let header = self.header {
self.header = nil
header.view?.removeFromSuperview()
}
}
var innerContentHeight: CGFloat = 0.0
var validItemIds: [AnyHashable] = []
for item in component.items {
validItemIds.append(item.id)
let itemView: ComponentView<Empty>
var itemTransition = transition
if let current = self.itemViews[item.id] {
itemView = current
} else {
itemTransition = itemTransition.withAnimation(.none)
itemView = ComponentView()
self.itemViews[item.id] = itemView
}
let itemSize = itemView.update(
transition: itemTransition,
component: item.component,
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: innerContentHeight), size: itemSize)
if let itemComponentView = itemView.view {
if itemComponentView.superview == nil {
self.contentView.addSubview(itemComponentView)
transition.animateAlpha(view: itemComponentView, from: 0.0, to: 1.0)
if let itemComponentView = itemComponentView as? ChildView {
itemComponentView.customUpdateIsHighlighted = { [weak self] isHighlighted in
guard let self else {
return
}
self.updateIsHighlighted(isHighlighted: isHighlighted)
}
}
}
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
}
innerContentHeight += itemSize.height
}
var removedItemIds: [AnyHashable] = []
for (id, itemView) in self.itemViews {
if !validItemIds.contains(id) {
removedItemIds.append(id)
if let itemComponentView = itemView.view {
transition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
itemComponentView?.removeFromSuperview()
})
}
}
}
for id in removedItemIds {
self.itemViews.removeValue(forKey: id)
}
if innerContentHeight != 0.0 && contentHeight != 0.0 {
contentHeight += 7.0
}
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: availableSize.width, height: innerContentHeight))
transition.setFrame(view: self.contentView, frame: contentFrame)
let backgroundFrame: CGRect
var backgroundAlpha: CGFloat = 1.0
switch component.background {
case .none:
backgroundFrame = contentFrame
backgroundAlpha = 0.0
self.contentBackgroundView.update(size: backgroundFrame.size, corners: DynamicCornerRadiusView.Corners(minXMinY: 11.0, maxXMinY: 11.0, minXMaxY: 11.0, maxXMaxY: 11.0), transition: transition)
case .all:
backgroundFrame = contentFrame
self.contentBackgroundView.update(size: backgroundFrame.size, corners: DynamicCornerRadiusView.Corners(minXMinY: 11.0, maxXMinY: 11.0, minXMaxY: 11.0, maxXMaxY: 11.0), transition: transition)
case let .range(from, corners):
if let itemComponentView = self.itemViews[from]?.view, itemComponentView.frame.minY < contentFrame.height {
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.minY + itemComponentView.frame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height - itemComponentView.frame.minY))
} else {
backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minY, y: contentFrame.height), size: CGSize(width: contentFrame.width, height: 0.0))
}
self.contentBackgroundView.update(size: backgroundFrame.size, corners: corners, transition: transition)
}
transition.setFrame(view: self.contentBackgroundView, frame: backgroundFrame)
transition.setAlpha(view: self.contentBackgroundView, alpha: backgroundAlpha)
contentHeight += innerContentHeight
if let footerValue = component.footer {
let footer: ComponentView<Empty>
var footerTransition = transition
if let current = self.footer {
footer = current
} else {
footerTransition = footerTransition.withAnimation(.none)
footer = ComponentView()
self.footer = footer
}
let footerSize = footer.update(
transition: footerTransition,
component: footerValue,
environment: {},
containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: availableSize.height)
)
if contentHeight != 0.0 {
contentHeight += 7.0
}
if let footerView = footer.view {
if footerView.superview == nil {
self.addSubview(footerView)
}
footerTransition.setFrame(view: footerView, frame: CGRect(origin: CGPoint(x: headerSideInset, y: contentHeight), size: footerSize))
}
contentHeight += footerSize.height
} else {
if let footer = self.footer {
self.footer = nil
footer.view?.removeFromSuperview()
}
}
return CGSize(width: availableSize.width, height: contentHeight)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -332,7 +332,7 @@ private func notificationsPeerCategoryEntries(peerId: EnginePeer.Id, notificatio
}
}
existingThreadIds.insert(value.threadId)
entries.append(.exception(Int32(index), presentationData.dateTimeFormat, presentationData.nameDisplayOrder, .channel(TelegramChannel(id: peerId, accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(TelegramChannelGroupInfo(flags: [])), flags: [.isForum], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)), value.threadId, value.info, title, value.notificationSettings._asNotificationSettings(), state.editing, state.revealedThreadId == value.threadId))
entries.append(.exception(Int32(index), presentationData.dateTimeFormat, presentationData.nameDisplayOrder, .channel(TelegramChannel(id: peerId, accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(TelegramChannelGroupInfo(flags: [])), flags: [.isForum], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil)), value.threadId, value.info, title, value.notificationSettings._asNotificationSettings(), state.editing, state.revealedThreadId == value.threadId))
index += 1
}

View File

@ -26,6 +26,7 @@ swift_library(
"//submodules/Markdown",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/Components/PagerComponent",
"//submodules/PremiumUI",

View File

@ -20,82 +20,7 @@ import BundleIconComponent
import AnimatedTextComponent
import TextFormat
import AudioToolbox
private final class ButtonSubtitleComponent: CombinedComponent {
let count: Int
let theme: PresentationTheme
let strings: PresentationStrings
init(count: Int, theme: PresentationTheme, strings: PresentationStrings) {
self.count = count
self.theme = theme
self.strings = strings
}
static func ==(lhs: ButtonSubtitleComponent, rhs: ButtonSubtitleComponent) -> Bool {
if lhs.count != rhs.count {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
return true
}
static var body: Body {
let icon = Child(BundleIconComponent.self)
let text = Child(AnimatedTextComponent.self)
return { context in
let icon = icon.update(
component: BundleIconComponent(
name: "Chat/Input/Accessory Panels/TextLockIcon",
tintColor: context.component.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7),
maxSize: CGSize(width: 10.0, height: 10.0)
),
availableSize: CGSize(width: 100.0, height: 100.0),
transition: context.transition
)
var textItems: [AnimatedTextComponent.Item] = []
let levelString = context.component.strings.ChannelReactions_LevelRequiredLabel("")
var previousIndex = 0
let nsLevelString = levelString.string as NSString
for range in levelString.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) {
if range.range.lowerBound > previousIndex {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(range.index), content: .text(nsLevelString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex)))))
}
if range.index == 0 {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(range.index), content: .number(context.component.count, minDigits: 1)))
}
previousIndex = range.range.upperBound
}
if nsLevelString.length > previousIndex {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(100), content: .text(nsLevelString.substring(with: NSRange(location: previousIndex, length: nsLevelString.length - previousIndex)))))
}
let text = text.update(
component: AnimatedTextComponent(font: Font.medium(11.0), color: context.component.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7), items: textItems),
availableSize: CGSize(width: context.availableSize.width - 20.0, height: 100.0),
transition: context.transition
)
let spacing: CGFloat = 3.0
let size = CGSize(width: icon.size.width + spacing + text.size.width, height: text.size.height)
context.add(icon
.position(icon.size.centered(in: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: icon.size.width, height: size.height))).center)
)
context.add(text
.position(text.size.centered(in: CGRect(origin: CGPoint(x: icon.size.width + spacing, y: 0.0), size: text.size)).center)
)
return size
}
}
}
import PremiumLockButtonSubtitleComponent
final class PeerAllowedReactionsScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -904,7 +829,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
}).count : 0
if let boostStatus = self.boostStatus, customReactionCount > boostStatus.level {
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(ButtonSubtitleComponent(
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(PremiumLockButtonSubtitleComponent(
count: customReactionCount,
theme: environment.theme,
strings: environment.strings

View File

@ -34,15 +34,17 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
let id: AnyHashable
let label: Label
let additionalBadgeLabel: String?
let additionalBadgeIcon: UIImage?
let text: String
let icon: UIImage?
let iconSignal: Signal<UIImage?, NoError>?
let action: (() -> Void)?
init(id: AnyHashable, label: Label = .none, additionalBadgeLabel: String? = nil, text: String, icon: UIImage? = nil, iconSignal: Signal<UIImage?, NoError>? = nil, action: (() -> Void)?) {
init(id: AnyHashable, label: Label = .none, additionalBadgeLabel: String? = nil, additionalBadgeIcon: UIImage? = nil, text: String, icon: UIImage? = nil, iconSignal: Signal<UIImage?, NoError>? = nil, action: (() -> Void)?) {
self.id = id
self.label = label
self.additionalBadgeLabel = additionalBadgeLabel
self.additionalBadgeIcon = additionalBadgeIcon
self.text = text
self.icon = icon
self.iconSignal = iconSignal
@ -61,6 +63,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
private let labelBadgeNode: ASImageNode
private let labelNode: ImmediateTextNode
private var additionalLabelNode: ImmediateTextNode?
private var additionalLabelBadgeNode: ASImageNode?
private let textNode: ImmediateTextNode
private let arrowNode: ASImageNode
private let bottomSeparatorNode: ASDisplayNode
@ -249,6 +252,24 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
self.labelBadgeNode.removeFromSupernode()
}
if let additionalBadgeIcon = item.additionalBadgeIcon {
let additionalLabelBadgeNode: ASImageNode
if let current = self.additionalLabelBadgeNode {
additionalLabelBadgeNode = current
} else {
additionalLabelBadgeNode = ASImageNode()
additionalLabelBadgeNode.isUserInteractionEnabled = false
self.additionalLabelBadgeNode = additionalLabelBadgeNode
self.insertSubnode(additionalLabelBadgeNode, belowSubnode: self.labelNode)
}
additionalLabelBadgeNode.image = additionalBadgeIcon
} else {
if let additionalLabelBadgeNode = self.additionalLabelBadgeNode {
self.additionalLabelBadgeNode = nil
additionalLabelBadgeNode.removeFromSupernode()
}
}
var badgeWidth = max(badgeDiameter, labelSize.width + 10.0)
if case .semitransparentBadge = item.label {
badgeWidth += 2.0
@ -283,6 +304,11 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
additionalLabelNode.removeFromSupernode()
}
if let additionalLabelBadgeNode = self.additionalLabelBadgeNode, let image = additionalLabelBadgeNode.image {
let additionalLabelSize = image.size
additionalLabelBadgeNode.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 6.0, y: floor((height - additionalLabelSize.height) / 2.0) + 1.0), size: additionalLabelSize)
}
let labelBadgeNodeFrame: CGRect
if case let .image(_, imageSize) = item.label {
labelBadgeNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - imageSize.width, y: floorToScreenPixels(textFrame.midY - imageSize.height / 2.0)), size:imageSize)

View File

@ -476,7 +476,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
credibilityIcon = .fake
} else if peer.isScam {
credibilityIcon = .scam
} else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
credibilityIcon = .emojiStatus(emojiStatus)
} else if peer.isVerified {
credibilityIcon = .verified

View File

@ -1675,7 +1675,12 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
}
let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors)
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), text: presentationData.strings.Channel_ChannelColor, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
//TODO:localize
var boostIcon: UIImage?
if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 {
boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: "Level 1+")
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: "Appearance", icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
interaction.editingOpenNameColorSetup()
}))
@ -3835,7 +3840,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
var currentSelectedFileId: Int64?
var topStatusTitle = strongSelf.presentationData.strings.PeerStatusSetup_NoTimerTitle
if let peer = strongSelf.data?.peer {
if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus {
if let emojiStatus = peer.emojiStatus {
selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId))
currentSelectedFileId = emojiStatus.fileId
@ -7200,9 +7205,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if self.peerId == self.context.account.peerId {
let controller = PeerNameColorScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, subject: .account)
self.controller?.push(controller)
} else {
let controller = PeerNameColorScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, subject: .channel(self.peerId))
self.controller?.push(controller)
} else if let peer = self.data?.peer, peer is TelegramChannel {
self.controller?.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId))
}
}
@ -12218,7 +12222,7 @@ private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustom
self.avatarNode.setPeer(context: self.item.context, account: self.item.account, theme: self.presentationData.theme, peer: self.item.peer)
if case let .user(user) = self.item.peer, let _ = user.emojiStatus {
if self.item.peer.emojiStatus != nil {
rightTextInset += 32.0
}
@ -12236,6 +12240,10 @@ private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustom
} else if user.isPremium {
iconContent = .premium(color: self.presentationData.theme.list.itemAccentColor)
}
} else if case let .channel(channel) = self.item.peer {
if let emojiStatus = channel.emojiStatus {
iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever)
}
}
if let iconContent {
let emojiStatusSize = self.emojiStatusView.update(

View File

@ -0,0 +1,22 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "PremiumLockButtonSubtitleComponent",
module_name = "PremiumLockButtonSubtitleComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,83 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import BundleIconComponent
import AnimatedTextComponent
public final class PremiumLockButtonSubtitleComponent: CombinedComponent {
public let count: Int
public let theme: PresentationTheme
public let strings: PresentationStrings
public init(count: Int, theme: PresentationTheme, strings: PresentationStrings) {
self.count = count
self.theme = theme
self.strings = strings
}
public static func ==(lhs: PremiumLockButtonSubtitleComponent, rhs: PremiumLockButtonSubtitleComponent) -> Bool {
if lhs.count != rhs.count {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
return true
}
public static var body: Body {
let icon = Child(BundleIconComponent.self)
let text = Child(AnimatedTextComponent.self)
return { context in
let icon = icon.update(
component: BundleIconComponent(
name: "Chat/Input/Accessory Panels/TextLockIcon",
tintColor: context.component.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7),
maxSize: CGSize(width: 10.0, height: 10.0)
),
availableSize: CGSize(width: 100.0, height: 100.0),
transition: context.transition
)
var textItems: [AnimatedTextComponent.Item] = []
let levelString = context.component.strings.ChannelReactions_LevelRequiredLabel("")
var previousIndex = 0
let nsLevelString = levelString.string as NSString
for range in levelString.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) {
if range.range.lowerBound > previousIndex {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(range.index), content: .text(nsLevelString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex)))))
}
if range.index == 0 {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(range.index), content: .number(context.component.count, minDigits: 1)))
}
previousIndex = range.range.upperBound
}
if nsLevelString.length > previousIndex {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(100), content: .text(nsLevelString.substring(with: NSRange(location: previousIndex, length: nsLevelString.length - previousIndex)))))
}
let text = text.update(
component: AnimatedTextComponent(font: Font.medium(11.0), color: context.component.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7), items: textItems),
availableSize: CGSize(width: context.availableSize.width - 20.0, height: 100.0),
transition: context.transition
)
let spacing: CGFloat = 3.0
let size = CGSize(width: icon.size.width + spacing + text.size.width, height: text.size.height)
context.add(icon
.position(icon.size.centered(in: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: icon.size.width, height: size.height))).center)
)
context.add(text
.position(text.size.centered(in: CGRect(origin: CGPoint(x: icon.size.width + spacing, y: 0.0), size: text.size)).center)
)
return size
}
}
}

View File

@ -21,6 +21,7 @@ swift_library(
"//submodules/PresentationDataUtils",
"//submodules/UndoUI",
"//submodules/WallpaperBackgroundNode",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/EntityKeyboard",
"//submodules/SolidRoundedButtonNode",
@ -28,6 +29,20 @@ swift_library(
"//submodules/PremiumUI",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
"//submodules/AvatarNode",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent",
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/TelegramUI/Components/Settings/ThemeCarouselItem",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/WallpaperResources",
],
visibility = [
"//visibility:public",

View File

@ -11,10 +11,11 @@ import ItemListUI
import PresentationDataUtils
import AccountContext
import WallpaperBackgroundNode
import ListItemComponentAdaptor
final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem {
final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
struct MessageItem: Equatable {
static func == (lhs: MessageItem, rhs: MessageItem) -> Bool {
static func ==(lhs: MessageItem, rhs: MessageItem) -> Bool {
if lhs.outgoing != rhs.outgoing {
return false
}
@ -118,6 +119,44 @@ final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem {
}
}
}
public func item() -> ListViewItem {
return self
}
public static func ==(lhs: PeerNameColorChatPreviewItem, rhs: PeerNameColorChatPreviewItem) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.componentTheme !== rhs.componentTheme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.fontSize != rhs.fontSize {
return false
}
if lhs.chatBubbleCorners != rhs.chatBubbleCorners {
return false
}
if lhs.wallpaper != rhs.wallpaper {
return false
}
if lhs.dateTimeFormat != rhs.dateTimeFormat {
return false
}
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
return false
}
if lhs.messageItems != rhs.messageItems {
return false
}
return true
}
}
final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
@ -260,7 +299,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId {
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId || currentItem.theme !== item.theme || currentItem.wallpaper != item.wallpaper {
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
strongSelf.view.addSubview(snapshot)
@ -329,31 +368,42 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
if params.isStandalone {
strongSelf.topStripeNode.isHidden = true
strongSelf.bottomStripeNode.isHidden = true
strongSelf.maskNode.isHidden = true
} else {
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 = 0.0
bottomStripeOffset = -separatorHeight
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
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))
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = 0.0
bottomStripeOffset = -separatorHeight
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
@ -377,8 +427,6 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
backgroundNode.updateLayout(size: backgroundNode.bounds.size, displayMode: displayMode, transition: .immediate)
}
strongSelf.maskNode.frame = backgroundFrame.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))
}
})
}

View File

@ -10,6 +10,7 @@ import MergeLists
import ItemListUI
import PresentationDataUtils
import AccountContext
import ListItemComponentAdaptor
private enum PeerNameColorEntryId: Hashable {
case color(Int32)
@ -313,7 +314,7 @@ private final class PeerNameColorIconItemNode : ListViewItemNode {
}
}
final class PeerNameColorItem: ListViewItem, ItemListItem {
final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
var sectionId: ItemListSectionId
let theme: PresentationTheme
@ -365,6 +366,27 @@ final class PeerNameColorItem: ListViewItem, ItemListItem {
}
}
}
public func item() -> ListViewItem {
return self
}
public static func ==(lhs: PeerNameColorItem, rhs: PeerNameColorItem) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.colors != rhs.colors {
return false
}
if lhs.isProfile != rhs.isProfile {
return false
}
if lhs.currentColor != rhs.currentColor {
return false
}
return true
}
}
private struct PeerNameColorItemNodeTransition {
@ -528,24 +550,30 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
if item.currentColor != nil {
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
} else {
if params.isStandalone {
strongSelf.backgroundNode.isHidden = true
strongSelf.topStripeNode.isHidden = true
hasTopCorners = true
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
strongSelf.bottomStripeNode.isHidden = true
strongSelf.maskNode.isHidden = true
} else {
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
if item.currentColor != nil {
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
} else {
strongSelf.topStripeNode.isHidden = true
hasTopCorners = true
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = params.leftInset + 16.0
bottomStripeOffset = -separatorHeight
@ -555,15 +583,17 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
}
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 + 8.0

View File

@ -14,24 +14,28 @@ import ComponentFlow
import PeerInfoCoverComponent
import AvatarNode
import EmojiStatusComponent
import ListItemComponentAdaptor
import ComponentDisplayAdapters
final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem {
final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
let context: AccountContext
let theme: PresentationTheme
let componentTheme: PresentationTheme
let strings: PresentationStrings
let sectionId: ItemListSectionId
let peer: EnginePeer?
let subtitleString: String?
let files: [Int64: TelegramMediaFile]
let nameDisplayOrder: PresentationPersonNameOrder
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, peer: EnginePeer?, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder) {
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, peer: EnginePeer?, subtitleString: String? = nil, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder) {
self.context = context
self.theme = theme
self.componentTheme = componentTheme
self.strings = strings
self.sectionId = sectionId
self.peer = peer
self.subtitleString = subtitleString
self.files = files
self.nameDisplayOrder = nameDisplayOrder
}
@ -46,7 +50,7 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem {
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
return (nil, { _ in apply(.None) })
})
}
}
@ -61,13 +65,43 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
apply(animation)
})
}
}
}
}
}
func item() -> ListViewItem {
return self
}
static func ==(lhs: PeerNameColorProfilePreviewItem, rhs: PeerNameColorProfilePreviewItem) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.componentTheme !== rhs.componentTheme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.files != rhs.files {
return false
}
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
return false
}
return true
}
}
final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
@ -104,7 +138,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
deinit {
}
func asyncLayout() -> (_ item: PeerNameColorProfilePreviewItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
func asyncLayout() -> (_ item: PeerNameColorProfilePreviewItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
return { [weak self] item, params, neighbors in
let separatorHeight = UIScreenPixel
@ -117,7 +151,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
return (layout, { [weak self] in
return (layout, { [weak self] animation in
guard let self else {
return
}
@ -139,32 +173,41 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
if self.maskNode.supernode == nil {
self.addSubnode(self.maskNode)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
if params.isStandalone {
self.topStripeNode.isHidden = true
self.bottomStripeNode.isHidden = true
self.maskNode.isHidden = true
} else {
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
self.topStripeNode.isHidden = true
default:
hasTopCorners = true
self.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = 0.0
bottomStripeOffset = -separatorHeight
self.bottomStripeNode.isHidden = item.peer?.profileColor == nil
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
self.bottomStripeNode.isHidden = hasCorners
}
self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
self.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = 0.0
bottomStripeOffset = -separatorHeight
self.bottomStripeNode.isHidden = item.peer?.profileColor == nil
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
self.bottomStripeNode.isHidden = hasCorners
}
self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
@ -217,6 +260,61 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
}
self.avatarNode.frame = avatarFrame.offsetBy(dx: coverFrame.minX, dy: coverFrame.minY)
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
enum CredibilityIcon {
case none
case premium
case verified
case fake
case scam
case emojiStatus(PeerEmojiStatus)
}
let credibilityIcon: CredibilityIcon
if let peer = item.peer {
if peer.isFake {
credibilityIcon = .fake
} else if peer.isScam {
credibilityIcon = .scam
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
credibilityIcon = .emojiStatus(emojiStatus)
} else if case let .channel(channel) = peer, let emojiStatus = channel.emojiStatus, !premiumConfiguration.isPremiumDisabled {
credibilityIcon = .emojiStatus(emojiStatus)
} else if peer.isVerified {
credibilityIcon = .verified
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != item.context.account.peerId) {
credibilityIcon = .premium
} else {
credibilityIcon = .none
}
} else {
credibilityIcon = .none
}
let statusColor: UIColor
if let peer = item.peer, peer.profileColor != nil {
statusColor = .white
} else {
statusColor = item.theme.list.itemCheckColors.fillColor
}
let emojiStatusContent: EmojiStatusComponent.Content
switch credibilityIcon {
case .none:
emojiStatusContent = .none
case .premium:
emojiStatusContent = .premium(color: statusColor)
case .verified:
emojiStatusContent = .verified(fillColor: statusColor, foregroundColor: .clear, sizeType: .large)
case .fake:
emojiStatusContent = .text(color: item.theme.chat.message.incoming.scamColor, string: item.strings.Message_FakeAccount.uppercased())
case .scam:
emojiStatusContent = .text(color: item.theme.chat.message.incoming.scamColor, string: item.strings.Message_ScamAccount.uppercased())
case let .emojiStatus(emojiStatus):
emojiStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: item.theme.list.mediaPlaceholderColor, themeColor: statusColor, loopMode: .forever)
}
let backgroundColor: UIColor
let titleColor: UIColor
let subtitleColor: UIColor
@ -239,15 +337,64 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
environment: {},
containerSize: CGSize(width: coverFrame.width - 16.0, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: coverFrame.minX + floor((coverFrame.width - titleSize.width) * 0.5), y: avatarFrame.maxY + 10.0), size: titleSize)
var titleContentWidth = titleSize.width
var hasStatusIcon = false
if case .none = emojiStatusContent {
} else {
hasStatusIcon = true
titleContentWidth += 4.0 + 34.0
}
let _ = hasStatusIcon
let titleFrame = CGRect(origin: CGPoint(x: coverFrame.minX + floor((coverFrame.width - titleContentWidth) * 0.5), y: avatarFrame.maxY + 10.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.0)
self.view.addSubview(titleView)
}
titleView.frame = titleFrame
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
animation.animator.updatePosition(layer: titleView.layer, position: titleFrame.origin, completion: nil)
}
let subtitleString: String = item.strings.LastSeen_JustNow
let icon: ComponentView<Empty>
if let current = self.icon {
icon = current
} else {
icon = ComponentView()
self.icon = icon
}
let iconSize = CGSize(width: 34.0, height: 34.0)
let _ = icon.update(
transition: Transition(animation.transition),
component: AnyComponent(EmojiStatusComponent(
context: item.context,
animationCache: item.context.animationCache,
animationRenderer: item.context.animationRenderer,
content: emojiStatusContent,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: iconSize
)
if let iconView = icon.view {
if iconView.superview == nil {
self.view.addSubview(iconView)
}
let iconFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - iconSize.height) * 0.5)), size: iconSize)
iconView.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
animation.animator.updatePosition(layer: iconView.layer, position: iconFrame.center, completion: nil)
}
let subtitleString: String
if let value = item.subtitleString {
subtitleString = value
} else if case .channel = item.peer {
subtitleString = item.strings.Channel_Status
} else {
subtitleString = item.strings.LastSeen_JustNow
}
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(Text(
@ -265,8 +412,6 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
}
self.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0)
self.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
})
}
}

View File

@ -0,0 +1,37 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ThemeCarouselItem",
module_name = "ThemeCarouselItem",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/AsyncDisplayKit",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/TelegramPresentationData",
"//submodules/MergeLists",
"//submodules/TelegramUIPreferences",
"//submodules/ItemListUI",
"//submodules/PresentationDataUtils",
"//submodules/WallpaperResources",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/ContextUI",
"//submodules/AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode",
"//submodules/ShimmerEffect",
"//submodules/StickerResources",
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
"//submodules/HexColor",
],
visibility = [
"//visibility:public",
],
)

View File

@ -18,12 +18,15 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ShimmerEffect
import StickerResources
import ListItemComponentAdaptor
import HexColor
private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
let index: Int
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference
let nightMode: Bool
let channelMode: Bool
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
var selected: Bool
@ -48,6 +51,9 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
if lhs.nightMode != rhs.nightMode {
return false
}
if lhs.channelMode != rhs.channelMode {
return false
}
if lhs.themeSpecificAccentColors != rhs.themeSpecificAccentColors {
return false
}
@ -74,30 +80,32 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
}
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, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action, contextAction: contextAction)
return ThemeCarouselThemeIconItem(context: context, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, channelMode: self.channelMode, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, 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 themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
let selected: Bool
let theme: PresentationTheme
let strings: PresentationStrings
let wallpaper: TelegramWallpaper?
let action: (PresentationThemeReference) -> Void
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
public class ThemeCarouselThemeIconItem: ListViewItem {
public let context: AccountContext
public let emojiFile: TelegramMediaFile?
public let themeReference: PresentationThemeReference
public let nightMode: Bool
public let channelMode: Bool
public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
public let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
public let selected: Bool
public let theme: PresentationTheme
public let strings: PresentationStrings
public let wallpaper: TelegramWallpaper?
public let action: (PresentationThemeReference) -> Void
public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
public init(context: AccountContext, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference, nightMode: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) {
public init(context: AccountContext, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference, nightMode: Bool, channelMode: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], 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.channelMode = channelMode
self.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
self.selected = selected
@ -148,7 +156,6 @@ class ThemeCarouselThemeIconItem: ListViewItem {
}
}
private let textFont = Font.regular(12.0)
private let selectedTextFont = Font.bold(12.0)
@ -338,6 +345,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
var updatedThemeReference = false
var updatedTheme = false
var updatedNightMode = false
var updatedChannelMode = false
var updatedWallpaper = false
var updatedSelected = false
@ -347,6 +355,9 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
if currentItem?.nightMode != item.nightMode {
updatedNightMode = true
}
if currentItem?.channelMode != item.channelMode {
updatedChannelMode = true
}
if currentItem?.wallpaper != item.wallpaper {
updatedWallpaper = true
}
@ -372,7 +383,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
if let strongSelf = self {
strongSelf.item = item
if updatedThemeReference || updatedWallpaper || updatedNightMode {
if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedChannelMode {
var themeReference = item.themeReference
if case .builtin = themeReference, item.nightMode {
themeReference = .builtin(.night)
@ -381,7 +392,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
let color = item.themeSpecificAccentColors[themeReference.index]
let wallpaper = item.themeSpecificChatWallpapers[themeReference.index]
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: color, wallpaper: wallpaper ?? item.wallpaper, nightMode: item.nightMode, emoticon: true))
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: color, wallpaper: wallpaper ?? item.wallpaper, nightMode: item.nightMode, channelMode: item.channelMode, emoticon: true))
strongSelf.imageNode.backgroundColor = nil
}
@ -507,23 +518,24 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
}
}
class ThemeCarouselThemeItem: ListViewItem, ItemListItem {
var sectionId: ItemListSectionId
public class ThemeCarouselThemeItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
public 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?
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let themes: [PresentationThemeReference]
public let animatedEmojiStickers: [String: [StickerPackItem]]
public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
public let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
public let nightMode: Bool
public let channelMode: Bool
public let currentTheme: PresentationThemeReference
public let updatedTheme: (PresentationThemeReference) -> Void
public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
public 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) {
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], nightMode: Bool, channelMode: Bool = false, currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) {
self.context = context
self.theme = theme
self.strings = strings
@ -532,6 +544,7 @@ class ThemeCarouselThemeItem: ListViewItem, ItemListItem {
self.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
self.nightMode = nightMode
self.channelMode = channelMode
self.currentTheme = currentTheme
self.updatedTheme = updatedTheme
self.contextAction = contextAction
@ -539,7 +552,7 @@ class ThemeCarouselThemeItem: ListViewItem, ItemListItem {
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) {
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 = ThemeCarouselThemeItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
@ -555,7 +568,7 @@ class ThemeCarouselThemeItem: ListViewItem, ItemListItem {
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ThemeCarouselThemeItemNode {
let makeLayout = nodeValue.asyncLayout()
@ -571,6 +584,45 @@ class ThemeCarouselThemeItem: ListViewItem, ItemListItem {
}
}
}
public func item() -> ListViewItem {
return self
}
public static func ==(lhs: ThemeCarouselThemeItem, rhs: ThemeCarouselThemeItem) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.themes != rhs.themes {
return false
}
if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers {
return false
}
if lhs.themeSpecificAccentColors != rhs.themeSpecificAccentColors {
return false
}
if lhs.themeSpecificChatWallpapers != rhs.themeSpecificChatWallpapers {
return false
}
if lhs.nightMode != rhs.nightMode {
return false
}
if lhs.channelMode != rhs.channelMode {
return false
}
if lhs.currentTheme != rhs.currentTheme {
return false
}
return true
}
}
private struct ThemeCarouselThemeItemNodeTransition {
@ -609,7 +661,7 @@ private func ensureThemeVisible(listNode: ListView, themeReference: Presentation
}
}
class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
private let containerNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
@ -627,11 +679,11 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
private var tapping = false
var tag: ItemListItemTag? {
public var tag: ItemListItemTag? {
return self.item?.tag
}
init() {
public init() {
self.containerNode = ASDisplayNode()
self.backgroundNode = ASDisplayNode()
@ -654,7 +706,7 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
self.addSubnode(self.listNode)
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
}
@ -695,7 +747,7 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
})
}
func asyncLayout() -> (_ item: ThemeCarouselThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
public func asyncLayout() -> (_ item: ThemeCarouselThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
return { item, params, neighbors in
let contentSize: CGSize
let insets: UIEdgeInsets
@ -728,20 +780,26 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
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 {
if params.isStandalone {
strongSelf.topStripeNode.isHidden = true
strongSelf.bottomStripeNode.isHidden = true
strongSelf.maskNode.isHidden = true
strongSelf.backgroundNode.isHidden = true
} else {
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 {
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = params.leftInset + 16.0
bottomStripeOffset = -separatorHeight
@ -751,17 +809,20 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.bottomStripeNode.isHidden = true
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
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))
}
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
@ -781,12 +842,12 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
hasCurrentTheme = true
}
let emojiFile = theme.emoticon.flatMap { item.animatedEmojiStickers[$0]?.first?.file }
entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: emojiFile, themeReference: theme, nightMode: item.nightMode, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil))
entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: emojiFile, themeReference: theme, nightMode: item.nightMode, channelMode: item.channelMode, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, 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, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: true, theme: item.theme, strings: item.strings, wallpaper: nil))
entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: nil, themeReference: item.currentTheme, nightMode: false, channelMode: item.channelMode, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: true, theme: item.theme, strings: item.strings, wallpaper: nil))
}
let action: (PresentationThemeReference) -> Void = { [weak self] themeReference in
@ -810,15 +871,15 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
override public 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) {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
func prepareCrossfadeTransition() {
public func prepareCrossfadeTransition() {
guard self.snapshotView == nil else {
return
}
@ -836,7 +897,7 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
}
}
func animateCrossfadeTransition() {
public func animateCrossfadeTransition() {
guard self.snapshotView?.layer.animationKeys()?.isEmpty ?? true else {
return
}

View File

@ -0,0 +1,33 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ThemeSettingsThemeItem",
module_name = "ThemeSettingsThemeItem",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/AsyncDisplayKit",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/TelegramPresentationData",
"//submodules/MergeLists",
"//submodules/TelegramUIPreferences",
"//submodules/ItemListUI",
"//submodules/PresentationDataUtils",
"//submodules/WallpaperResources",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/ContextUI",
"//submodules/HexColor",
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
],
visibility = [
"//visibility:public",
],
)

View File

@ -14,6 +14,8 @@ import WallpaperResources
import AccountContext
import AppBundle
import ContextUI
import ListItemComponentAdaptor
import HexColor
private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let index: Int
@ -369,24 +371,24 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
}
}
class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
var sectionId: ItemListSectionId
public class ThemeSettingsThemeItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
public var sectionId: ItemListSectionId
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let themes: [PresentationThemeReference]
let allThemes: [PresentationThemeReference]
let displayUnsupported: Bool
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
let themePreferredBaseTheme: [Int64: TelegramBaseTheme]
let currentTheme: PresentationThemeReference
let updatedTheme: (PresentationThemeReference) -> Void
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
let tag: ItemListItemTag?
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let themes: [PresentationThemeReference]
public let allThemes: [PresentationThemeReference]
public let displayUnsupported: Bool
public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
public let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
public let themePreferredBaseTheme: [Int64: TelegramBaseTheme]
public let currentTheme: PresentationThemeReference
public let updatedTheme: (PresentationThemeReference) -> Void
public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
public let tag: ItemListItemTag?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], allThemes: [PresentationThemeReference], displayUnsupported: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], themePreferredBaseTheme: [Int64: TelegramBaseTheme], currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) {
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], allThemes: [PresentationThemeReference], displayUnsupported: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], themePreferredBaseTheme: [Int64: TelegramBaseTheme], currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) {
self.context = context
self.theme = theme
self.strings = strings
@ -403,7 +405,7 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
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) {
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 = ThemeSettingsThemeItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
@ -419,7 +421,7 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ThemeSettingsThemeItemNode {
let makeLayout = nodeValue.asyncLayout()
@ -435,6 +437,42 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
}
}
}
public func item() -> ListViewItem {
return self
}
public static func ==(lhs: ThemeSettingsThemeItem, rhs: ThemeSettingsThemeItem) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.themes != rhs.themes {
return false
}
if lhs.displayUnsupported != rhs.displayUnsupported {
return false
}
if lhs.themeSpecificAccentColors != rhs.themeSpecificAccentColors {
return false
}
if lhs.themeSpecificChatWallpapers != rhs.themeSpecificChatWallpapers {
return false
}
if lhs.themePreferredBaseTheme != rhs.themePreferredBaseTheme {
return false
}
if lhs.currentTheme != rhs.currentTheme {
return false
}
return true
}
}
private struct ThemeSettingsThemeItemNodeTransition {
@ -472,7 +510,7 @@ private func ensureThemeVisible(listNode: ListView, themeReference: Presentation
}
}
class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
public class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
private let containerNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
@ -488,13 +526,13 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
private var item: ThemeSettingsThemeItem?
private var layoutParams: ListViewItemLayoutParams?
var tag: ItemListItemTag? {
public var tag: ItemListItemTag? {
return self.item?.tag
}
private var tapping = false
init() {
public init() {
self.containerNode = ASDisplayNode()
self.backgroundNode = ASDisplayNode()
@ -517,7 +555,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
self.addSubnode(self.listNode)
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
}
@ -558,7 +596,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
})
}
func asyncLayout() -> (_ item: ThemeSettingsThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
public func asyncLayout() -> (_ item: ThemeSettingsThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
return { item, params, neighbors in
let contentSize: CGSize
let insets: UIEdgeInsets
@ -591,20 +629,26 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
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 {
if params.isStandalone {
strongSelf.topStripeNode.isHidden = true
strongSelf.bottomStripeNode.isHidden = true
strongSelf.maskNode.isHidden = true
strongSelf.backgroundNode.isHidden = true
} else {
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 {
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = params.leftInset + 16.0
bottomStripeOffset = -separatorHeight
@ -614,15 +658,18 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
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))
}
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 + 4.0
@ -694,15 +741,15 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
override public 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) {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
func prepareCrossfadeTransition() {
public func prepareCrossfadeTransition() {
guard self.snapshotView == nil else {
return
}
@ -719,7 +766,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
}
}
func animateCrossfadeTransition() {
public func animateCrossfadeTransition() {
guard self.snapshotView?.layer.animationKeys()?.isEmpty ?? true else {
return
}

View File

@ -604,7 +604,7 @@ public final class PeerListItemComponent: Component {
statusIcon = .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_ScamAccount.uppercased())
} else if peer.isFake {
statusIcon = .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
} else if let emojiStatus = peer.emojiStatus {
statusIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(2))
} else if peer.isVerified {
statusIcon = .verified(fillColor: component.theme.list.itemCheckColors.fillColor, foregroundColor: component.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)

View File

@ -368,6 +368,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.titleAccessoryPanelContainer = ChatControllerTitlePanelNodeContainer()
self.titleAccessoryPanelContainer.clipsToBounds = true
setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat)
self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer()
self.inputContextOverTextPanelContainer = ChatControllerTitlePanelNodeContainer()
@ -1012,7 +1014,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
if let historyNodeContainer = self.historyNodeContainer as? HistoryNodeContainer {
historyNodeContainer.isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat
let isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat
if historyNodeContainer.isSecret != isSecret {
historyNodeContainer.isSecret = isSecret
setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, isSecret)
}
}
var previousListBottomInset: CGFloat?

View File

@ -540,6 +540,11 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
} else {
emojiStatus = emojiStatusValue
}
} else if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel, let emojiStatusValue = channel.emojiStatus {
if channel.isFake || channel.isScam {
} else {
emojiStatus = emojiStatusValue
}
}
/*#if DEBUG

View File

@ -363,6 +363,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
if themeUpdated {
updateLegacyTheme()
/*if #available(iOS 13.0, *) {
let userInterfaceStyle: UIUserInterfaceStyle
if strongSelf.currentPresentationData.with({ $0 }).theme.overallDarkAppearance {
userInterfaceStyle = .dark
} else {
userInterfaceStyle = .light
}
if let eventView = strongSelf.mainWindow?.hostView.eventView, eventView.overrideUserInterfaceStyle != userInterfaceStyle {
eventView.overrideUserInterfaceStyle = userInterfaceStyle
}
}*/
}
if themeNameUpdated {
strongSelf.presentCrossfadeController()
@ -962,6 +974,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
})
}
/*if #available(iOS 13.0, *) {
let userInterfaceStyle: UIUserInterfaceStyle
if self.currentPresentationData.with({ $0 }).theme.overallDarkAppearance {
userInterfaceStyle = .dark
} else {
userInterfaceStyle = .light
}
if let eventView = self.mainWindow?.hostView.eventView, eventView.overrideUserInterfaceStyle != userInterfaceStyle {
eventView.overrideUserInterfaceStyle = userInterfaceStyle
}
}*/
}
deinit {

View File

@ -1398,7 +1398,7 @@ private let messageImage: UIImage = {
return messageBubbleImage(maxCornerRadius: 16.0, minCornerRadius: 16.0, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .none, shadow: nil, wallpaper: .color(0x000000), knockout: false)
}()
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, nightMode: Bool? = nil, emoticon: Bool = false, large: Bool = false, qr: Bool = false, message: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, nightMode: Bool? = nil, channelMode: Bool? = nil, emoticon: Bool = false, large: Bool = false, qr: Bool = false, message: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError>
var reference: MediaResourceReference?
@ -1693,6 +1693,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
c.translateBy(x: -114.0, y: -32.0)
let _ = try? drawSvgPath(c, path: "M12.8304,29.8712 C10.0551,31.8416 6.6628,33 2.99998,33 C1.98426,33 0.989361,32.9109 0.022644,32.7402 C2.97318,31.9699 5.24596,29.5785 5.84625,26.5607 C5.99996,25.7879 5.99996,24.8586 5.99996,23 V16.0 H6.00743 C6.27176,7.11861 13.5546,0 22.5,0 H61.5 C70.6127,0 78,7.3873 78,16.5 C78,25.6127 70.6127,33 61.5,33 H22.5 C18.8883,33 15.5476,31.8396 12.8304,29.8712 ")
if Set(incomingColors.map(\.rgb)).count > 1 {
c.clip()
@ -1748,7 +1749,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
}
let outgoingColors = colors.2
let outgoingColors = channelMode == true ? colors.1 : colors.2
if emoticon {
if large {
c.saveGState()
@ -1780,7 +1781,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
c.restoreGState()
} else {
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
let rect = CGRect(x: 8.0, y: arguments.drawingSize.height - 24.0 - 9.0 - 3.0, width: arguments.drawingSize.width - 8.0 * 2.0, height: 24.0)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
c.clip()

View File

@ -49,7 +49,9 @@
}
_frameCount = (int32_t)_animation->totalFrame();
_frameCount = MAX(1, _frameCount);
_frameRate = (int32_t)_animation->frameRate();
_frameRate = MAX(1, _frameRate);
size_t width = 0;
size_t height = 0;
@ -59,6 +61,9 @@
return nil;
}
width = MAX(1, width);
height = MAX(1, height);
_dimensions = CGSizeMake(width, height);
if ((_frameRate > 360) || _animation->duration() > 9.0) {