Channel appearance

This commit is contained in:
Isaac 2023-12-19 13:46:23 +04:00
parent 3fc9d37ba7
commit 2708aa0a41
12 changed files with 170 additions and 101 deletions

View File

@ -1388,7 +1388,8 @@ public class PeerNameColors: Equatable {
profilePaletteDarkColors: [:], profilePaletteDarkColors: [:],
profileStoryColors: [:], profileStoryColors: [:],
profileStoryDarkColors: [:], profileStoryDarkColors: [:],
profileDisplayOrder: [] profileDisplayOrder: [],
nameColorsChannelMinRequiredBoostLevel: [:]
) )
} }
@ -1404,6 +1405,8 @@ public class PeerNameColors: Equatable {
public let profileStoryDarkColors: [Int32: Colors] public let profileStoryDarkColors: [Int32: Colors]
public let profileDisplayOrder: [Int32] public let profileDisplayOrder: [Int32]
public let nameColorsChannelMinRequiredBoostLevel: [Int32: Int32]
public func get(_ color: PeerNameColor, dark: Bool = false) -> Colors { public func get(_ color: PeerNameColor, dark: Bool = false) -> Colors {
if dark, let colors = self.darkColors[color.rawValue] { if dark, let colors = self.darkColors[color.rawValue] {
return colors return colors
@ -1453,7 +1456,8 @@ public class PeerNameColors: Equatable {
profilePaletteDarkColors: [Int32: Colors], profilePaletteDarkColors: [Int32: Colors],
profileStoryColors: [Int32: Colors], profileStoryColors: [Int32: Colors],
profileStoryDarkColors: [Int32: Colors], profileStoryDarkColors: [Int32: Colors],
profileDisplayOrder: [Int32] profileDisplayOrder: [Int32],
nameColorsChannelMinRequiredBoostLevel: [Int32: Int32]
) { ) {
self.colors = colors self.colors = colors
self.darkColors = darkColors self.darkColors = darkColors
@ -1465,6 +1469,7 @@ public class PeerNameColors: Equatable {
self.profileStoryColors = profileStoryColors self.profileStoryColors = profileStoryColors
self.profileStoryDarkColors = profileStoryDarkColors self.profileStoryDarkColors = profileStoryDarkColors
self.profileDisplayOrder = profileDisplayOrder self.profileDisplayOrder = profileDisplayOrder
self.nameColorsChannelMinRequiredBoostLevel = nameColorsChannelMinRequiredBoostLevel
} }
public static func with(availableReplyColors: EngineAvailableColorOptions, availableProfileColors: EngineAvailableColorOptions) -> PeerNameColors { public static func with(availableReplyColors: EngineAvailableColorOptions, availableProfileColors: EngineAvailableColorOptions) -> PeerNameColors {
@ -1479,8 +1484,14 @@ public class PeerNameColors: Equatable {
var profileStoryDarkColors: [Int32: Colors] = [:] var profileStoryDarkColors: [Int32: Colors] = [:]
var profileDisplayOrder: [Int32] = [] var profileDisplayOrder: [Int32] = []
var nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] = [:]
if !availableReplyColors.options.isEmpty { if !availableReplyColors.options.isEmpty {
for option in availableReplyColors.options { for option in availableReplyColors.options {
if let requiredChannelMinBoostLevel = option.value.requiredChannelMinBoostLevel {
nameColorsChannelMinRequiredBoostLevel[option.key] = requiredChannelMinBoostLevel
}
if let parsedLight = PeerNameColors.Colors(colors: option.value.light.background) { if let parsedLight = PeerNameColors.Colors(colors: option.value.light.background) {
colors[option.key] = parsedLight colors[option.key] = parsedLight
} }
@ -1539,7 +1550,8 @@ public class PeerNameColors: Equatable {
profilePaletteDarkColors: profilePaletteDarkColors, profilePaletteDarkColors: profilePaletteDarkColors,
profileStoryColors: profileStoryColors, profileStoryColors: profileStoryColors,
profileStoryDarkColors: profileStoryDarkColors, profileStoryDarkColors: profileStoryDarkColors,
profileDisplayOrder: profileDisplayOrder profileDisplayOrder: profileDisplayOrder,
nameColorsChannelMinRequiredBoostLevel: nameColorsChannelMinRequiredBoostLevel
) )
} }

View File

@ -4739,7 +4739,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if self.experimentalSnapScrollToItem { if self.experimentalSnapScrollToItem {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.visible, animated: animated, curve: ListViewAnimationCurve.Default(duration: nil), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.visible, animated: animated, curve: ListViewAnimationCurve.Default(duration: nil), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} else { } else {
if node.frame.minY < self.insets.top { if node.frame.minY < self.insets.top + overflow {
if !allowIntersection || node.frame.maxY < self.insets.top { if !allowIntersection || node.frame.maxY < self.insets.top {
let position: ListViewScrollPosition let position: ListViewScrollPosition
if allowIntersection { if allowIntersection {
@ -4749,7 +4749,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} }
} else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom - overflow {
if !allowIntersection || node.frame.minY > self.visibleSize.height - self.insets.bottom { if !allowIntersection || node.frame.minY > self.visibleSize.height - self.insets.bottom {
let position: ListViewScrollPosition let position: ListViewScrollPosition
if allowIntersection { if allowIntersection {

View File

@ -20,10 +20,37 @@ import SolidRoundedButtonComponent
import BlurredBackgroundComponent import BlurredBackgroundComponent
import UndoUI import UndoUI
func requiredBoostSubjectLevel(subject: BoostSubject, context: AccountContext, configuration: PremiumConfiguration) -> Int32 {
switch subject {
case .stories:
return 1
case let .channelReactions(reactionCount):
return reactionCount
case let .nameColors(colors):
if let value = context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[colors.rawValue] {
return value
} else {
return 1
}
case .nameIcon:
return configuration.minChannelNameIconLevel
case .profileColors:
return configuration.minChannelProfileColorLevel
case .profileIcon:
return configuration.minChannelProfileIconLevel
case .emojiStatus:
return configuration.minChannelEmojiStatusLevel
case .wallpaper:
return configuration.minChannelWallpaperLevel
case .customWallpaper:
return configuration.minChannelCustomWallpaperLevel
}
}
public enum BoostSubject: Equatable { public enum BoostSubject: Equatable {
case stories case stories
case channelReactions(reactionCount: Int32) case channelReactions(reactionCount: Int32)
case nameColors case nameColors(colors: PeerNameColor)
case nameIcon case nameIcon
case profileColors case profileColors
case profileIcon case profileIcon
@ -31,27 +58,8 @@ public enum BoostSubject: Equatable {
case wallpaper case wallpaper
case customWallpaper case customWallpaper
public func requiredLevel(_ configuration: PremiumConfiguration) -> Int32 { public func requiredLevel(context: AccountContext, configuration: PremiumConfiguration) -> Int32 {
switch self { return requiredBoostSubjectLevel(subject: self, context: context, configuration: configuration)
case .stories:
return 1
case let .channelReactions(reactionCount):
return reactionCount
case .nameColors:
return configuration.minChannelNameColorLevel
case .nameIcon:
return configuration.minChannelNameIconLevel
case .profileColors:
return configuration.minChannelProfileColorLevel
case .profileIcon:
return configuration.minChannelProfileIconLevel
case .emojiStatus:
return configuration.minChannelEmojiStatusLevel
case .wallpaper:
return configuration.minChannelWallpaperLevel
case .customWallpaper:
return configuration.minChannelCustomWallpaperLevel
}
} }
} }
@ -479,7 +487,9 @@ private final class LimitSheetContent: CombinedComponent {
textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string
needsSecondParagraph = false needsSecondParagraph = false
case .nameColors: case .nameColors:
textString = strings.ChannelBoost_EnableNameColorLevelText("\(premiumConfiguration.minChannelNameColorLevel)").string let colorLevel = component.subject.requiredLevel(context: context.component.context, configuration: premiumConfiguration)
textString = strings.ChannelBoost_EnableNameColorLevelText("\(colorLevel)").string
case .nameIcon: case .nameIcon:
textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string
case .profileColors: case .profileColors:

View File

@ -1180,6 +1180,7 @@ private final class LimitSheetContent: CombinedComponent {
if let remaining { if let remaining {
let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1) let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1)
let valueString = strings.ChannelBoost_MoreBoosts(remaining) let valueString = strings.ChannelBoost_MoreBoosts(remaining)
switch boostSubject { switch boostSubject {
case .stories: case .stories:
if level == 0 { if level == 0 {
@ -1189,9 +1190,12 @@ private final class LimitSheetContent: CombinedComponent {
titleText = strings.ChannelBoost_IncreaseLimit titleText = strings.ChannelBoost_IncreaseLimit
string = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string string = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string
} }
case .nameColors: case let .nameColors(colors):
titleText = strings.ChannelBoost_EnableColors titleText = strings.ChannelBoost_EnableColors
string = strings.ChannelBoost_EnableColorsLevelText("\(premiumConfiguration.minChannelNameColorLevel)").string
let colorLevel = requiredBoostSubjectLevel(subject: .nameColors(colors: colors), context: component.context, configuration: premiumConfiguration)
string = strings.ChannelBoost_EnableColorsLevelText("\(colorLevel)").string
case let .channelReactions(reactionCount): case let .channelReactions(reactionCount):
titleText = strings.ChannelBoost_CustomReactions titleText = strings.ChannelBoost_CustomReactions
string = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string string = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string
@ -1778,7 +1782,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
public enum BoostSubject: Equatable { public enum BoostSubject: Equatable {
case stories case stories
case nameColors case nameColors(colors: PeerNameColor)
case channelReactions(reactionCount: Int) case channelReactions(reactionCount: Int)
} }

View File

@ -193,10 +193,14 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode {
} }
let string: String? let string: String?
if let _ = item.themeReference.emoticon { if let themeReference = item.themeReference {
string = nil if let _ = themeReference.emoticon {
string = nil
} else {
string = themeDisplayName(strings: item.strings, reference: themeReference)
}
} else { } else {
string = themeDisplayName(strings: item.strings, reference: item.themeReference) string = nil
} }
let text = NSAttributedString(string: string ?? item.strings.Conversation_Theme_NoTheme, font: Font.bold(14.0), textColor: .white) let text = NSAttributedString(string: string ?? item.strings.Conversation_Theme_NoTheme, font: Font.bold(14.0), textColor: .white)
@ -207,16 +211,17 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode {
let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedSize { if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedSize {
var themeReference = item.themeReference if var themeReference = item.themeReference {
if case .builtin = themeReference, item.nightMode { if case .builtin = themeReference, item.nightMode {
themeReference = .builtin(.night) themeReference = .builtin(.night)
}
let color = item.themeSpecificAccentColors[themeReference.index]
let wallpaper = item.themeSpecificChatWallpapers[themeReference.index]
self.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, large: true))
self.imageNode.backgroundColor = nil
} }
let color = item.themeSpecificAccentColors[themeReference.index]
let wallpaper = item.themeSpecificChatWallpapers[themeReference.index]
self.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, large: true))
self.imageNode.backgroundColor = nil
} }
if updatedTheme || updatedSelected { if updatedTheme || updatedSelected {
@ -501,7 +506,9 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode {
let selected = item.currentTheme.index == theme.index 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, channelMode: false, 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) if let theme {
item.updatedTheme(theme)
}
}, contextAction: nil) }, contextAction: nil)
validIds.append(theme.index) validIds.append(theme.index)

View File

@ -283,8 +283,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items): case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
case let .themes(theme, strings, chatThemes, currentTheme, nightMode, animatedEmojiStickers, themeSpecificAccentColors, themeSpecificChatWallpapers): case let .themes(theme, strings, chatThemes, currentTheme, nightMode, animatedEmojiStickers, themeSpecificAccentColors, themeSpecificChatWallpapers):
return ThemeCarouselThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, nightMode: nightMode, currentTheme: currentTheme, updatedTheme: { theme in return ThemeCarouselThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: chatThemes, hasNoTheme: false, animatedEmojiStickers: animatedEmojiStickers, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, nightMode: nightMode, currentTheme: currentTheme, updatedTheme: { theme in
arguments.selectTheme(theme) if let theme {
arguments.selectTheme(theme)
}
}, contextAction: { theme, node, gesture in }, contextAction: { theme, node, gesture in
arguments.themeContextAction(false, theme, node, gesture) arguments.themeContextAction(false, theme, node, gesture)
}, tag: ThemeSettingsEntryTag.theme) }, tag: ThemeSettingsEntryTag.theme)

View File

@ -628,6 +628,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
if abs(panGestureState.offsetFraction) > 0.6 || abs(velocity.y) >= 100.0 { if abs(panGestureState.offsetFraction) > 0.6 || abs(velocity.y) >= 100.0 {
self.panGestureState = PanGestureState(offsetFraction: panGestureState.offsetFraction < 0.0 ? -1.0 : 1.0) self.panGestureState = PanGestureState(offsetFraction: panGestureState.offsetFraction < 0.0 ? -1.0 : 1.0)
self.notifyDismissedInteractivelyOnPanGestureApply = true self.notifyDismissedInteractivelyOnPanGestureApply = true
self.callScreen.beginPictureInPictureIfPossible()
} }
self.update(transition: .animated(duration: 0.4, curve: .spring)) self.update(transition: .animated(duration: 0.4, curve: .spring))

View File

@ -94,16 +94,19 @@ public final class EngineAvailableColorOptions: Codable, Equatable {
case light = "l" case light = "l"
case dark = "d" case dark = "d"
case isHidden = "h" case isHidden = "h"
case requiredChannelMinBoostLevel = "rcmb"
} }
public let light: ColorOption public let light: ColorOption
public let dark: ColorOption? public let dark: ColorOption?
public let isHidden: Bool public let isHidden: Bool
public let requiredChannelMinBoostLevel: Int32?
public init(light: ColorOption, dark: ColorOption?, isHidden: Bool) { public init(light: ColorOption, dark: ColorOption?, isHidden: Bool, requiredChannelMinBoostLevel: Int32?) {
self.light = light self.light = light
self.dark = dark self.dark = dark
self.isHidden = isHidden self.isHidden = isHidden
self.requiredChannelMinBoostLevel = requiredChannelMinBoostLevel
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -112,6 +115,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable {
self.light = try container.decode(ColorOption.self, forKey: .light) self.light = try container.decode(ColorOption.self, forKey: .light)
self.dark = try container.decodeIfPresent(ColorOption.self, forKey: .dark) self.dark = try container.decodeIfPresent(ColorOption.self, forKey: .dark)
self.isHidden = try container.decode(Bool.self, forKey: .isHidden) self.isHidden = try container.decode(Bool.self, forKey: .isHidden)
self.requiredChannelMinBoostLevel = try container.decodeIfPresent(Int32.self, forKey: .requiredChannelMinBoostLevel)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -120,6 +124,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable {
try container.encode(self.light, forKey: .light) try container.encode(self.light, forKey: .light)
try container.encodeIfPresent(self.dark, forKey: .dark) try container.encodeIfPresent(self.dark, forKey: .dark)
try container.encodeIfPresent(self.isHidden, forKey: .isHidden) try container.encodeIfPresent(self.isHidden, forKey: .isHidden)
try container.encodeIfPresent(self.requiredChannelMinBoostLevel, forKey: .requiredChannelMinBoostLevel)
} }
public static func ==(lhs: ColorOptionPack, rhs: ColorOptionPack) -> Bool { public static func ==(lhs: ColorOptionPack, rhs: ColorOptionPack) -> Bool {
@ -135,6 +140,9 @@ public final class EngineAvailableColorOptions: Codable, Equatable {
if lhs.isHidden != rhs.isHidden { if lhs.isHidden != rhs.isHidden {
return false return false
} }
if lhs.requiredChannelMinBoostLevel != rhs.requiredChannelMinBoostLevel {
return false
}
return true return true
} }
} }
@ -262,14 +270,14 @@ private extension EngineAvailableColorOptions {
var mappedOptions: [Option] = [] var mappedOptions: [Option] = []
for apiColor in apiColors { for apiColor in apiColors {
switch apiColor { switch apiColor {
case let .peerColorOption(flags, colorId, colors, darkColors, _): case let .peerColorOption(flags, colorId, colors, darkColors, requiredChannelMinBoostLevel):
let isHidden = (flags & (1 << 0)) != 0 let isHidden = (flags & (1 << 0)) != 0
let mappedColors = colors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) let mappedColors = colors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:))
let mappedDarkColors = darkColors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) let mappedDarkColors = darkColors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:))
if let mappedColors = mappedColors { if let mappedColors = mappedColors {
mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden))) mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel)))
} else if colorId >= 0 && colorId <= 6 { } else if colorId >= 0 && colorId <= 6 {
let staticMap: [UInt32] = [ let staticMap: [UInt32] = [
0xcc5049, 0xcc5049,
@ -282,7 +290,7 @@ private extension EngineAvailableColorOptions {
] ]
let colorPack = MultiColorPack(colors: [staticMap[Int(colorId)]]) let colorPack = MultiColorPack(colors: [staticMap[Int(colorId)]])
let defaultColors = EngineAvailableColorOptions.ColorOption(palette: colorPack, background: colorPack, stories: nil) let defaultColors = EngineAvailableColorOptions.ColorOption(palette: colorPack, background: colorPack, stories: nil)
mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden))) mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel)))
} }
} }
} }

View File

@ -499,7 +499,7 @@ final class ChannelAppearanceScreenComponent: Component {
return return
} }
let requiredLevel = requiredBoostSubject.requiredLevel(premiumConfiguration) let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration)
if let boostLevel = self.boostLevel, requiredLevel > boostLevel { if let boostLevel = self.boostLevel, requiredLevel > boostLevel {
self.displayBoostLevels(subject: requiredBoostSubject) self.displayBoostLevels(subject: requiredBoostSubject)
return return
@ -806,7 +806,7 @@ final class ChannelAppearanceScreenComponent: Component {
return availableSize return availableSize
} }
var requiredBoostSubject: BoostSubject = .nameColors var requiredBoostSubjects: [BoostSubject] = [.nameColors(colors: resolvedState.nameColor)]
let replyIconLevel = 5 let replyIconLevel = 5
var profileIconLevel = 7 var profileIconLevel = 7
@ -824,34 +824,27 @@ final class ChannelAppearanceScreenComponent: Component {
let replyFileId = resolvedState.replyFileId let replyFileId = resolvedState.replyFileId
if replyFileId != nil { if replyFileId != nil {
requiredBoostSubject = .nameIcon requiredBoostSubjects.append(.nameIcon)
} }
let profileColor = resolvedState.profileColor let profileColor = resolvedState.profileColor
if profileColor != nil { if profileColor != nil {
requiredBoostSubject = .profileColors requiredBoostSubjects.append(.profileColors)
} }
let backgroundFileId = resolvedState.backgroundFileId let backgroundFileId = resolvedState.backgroundFileId
if backgroundFileId != nil { if backgroundFileId != nil {
requiredBoostSubject = .profileIcon requiredBoostSubjects.append(.profileIcon)
} }
let emojiStatus = resolvedState.emojiStatus let emojiStatus = resolvedState.emojiStatus
if emojiStatus != nil { if emojiStatus != nil {
requiredBoostSubject = .emojiStatus requiredBoostSubjects.append(.emojiStatus)
} }
let statusFileId = emojiStatus?.fileId let statusFileId = emojiStatus?.fileId
let cloudThemes: [PresentationThemeReference] = contentsData.availableThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? component.context.account.id : nil)) } let cloudThemes: [PresentationThemeReference] = contentsData.availableThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? component.context.account.id : nil)) }
var chatThemes = cloudThemes.filter { $0.emoticon != nil } let chatThemes = cloudThemes.filter { $0.emoticon != nil }
chatThemes.insert(.builtin(.dayClassic), at: 0)
if !chatThemes.isEmpty {
if self.currentTheme == nil {
self.currentTheme = chatThemes[0]
}
}
if let currentTheme = self.currentTheme, (self.resolvedCurrentTheme?.reference != currentTheme || self.resolvedCurrentTheme?.isDark != environment.theme.overallDarkAppearance), (self.resolvingCurrentTheme?.reference != currentTheme || self.resolvingCurrentTheme?.isDark != environment.theme.overallDarkAppearance) { if let currentTheme = self.currentTheme, (self.resolvedCurrentTheme?.reference != currentTheme || self.resolvedCurrentTheme?.isDark != environment.theme.overallDarkAppearance), (self.resolvingCurrentTheme?.reference != currentTheme || self.resolvingCurrentTheme?.isDark != environment.theme.overallDarkAppearance) {
self.resolvingCurrentTheme?.disposable.dispose() self.resolvingCurrentTheme?.disposable.dispose()
@ -891,8 +884,8 @@ final class ChannelAppearanceScreenComponent: Component {
} }
} }
if self.currentTheme != nil && self.currentTheme != chatThemes.first { if self.currentTheme != nil {
requiredBoostSubject = .wallpaper requiredBoostSubjects.append(.wallpaper)
} }
if case let .user(user) = peer { if case let .user(user) = peer {
@ -913,6 +906,12 @@ final class ChannelAppearanceScreenComponent: Component {
) )
} }
let requiredBoostSubject: BoostSubject
if let maxBoostSubject = requiredBoostSubjects.max(by: { $0.requiredLevel(context: component.context, configuration: premiumConfiguration) < $1.requiredLevel(context: component.context, configuration: premiumConfiguration) }) {
requiredBoostSubject = maxBoostSubject
} else {
requiredBoostSubject = .nameColors(colors: resolvedState.nameColor)
}
self.requiredBoostSubject = requiredBoostSubject self.requiredBoostSubject = requiredBoostSubject
let topInset: CGFloat = 24.0 let topInset: CGFloat = 24.0
@ -951,7 +950,7 @@ final class ChannelAppearanceScreenComponent: Component {
)), )),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)))) ))))
if replyFileId != nil, let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelNameIconLevel { if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelNameIconLevel {
replyLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( replyLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent(
strings: environment.strings, strings: environment.strings,
level: replyIconLevel level: replyIconLevel
@ -1047,7 +1046,7 @@ final class ChannelAppearanceScreenComponent: Component {
contentHeight += sectionSpacing contentHeight += sectionSpacing
if !chatThemes.isEmpty, let currentTheme { if !chatThemes.isEmpty {
var wallpaperLogoContents: [AnyComponentWithIdentity<Empty>] = [] var wallpaperLogoContents: [AnyComponentWithIdentity<Empty>] = []
wallpaperLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( wallpaperLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -1057,7 +1056,7 @@ final class ChannelAppearanceScreenComponent: Component {
)), )),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)))) ))))
if currentTheme != chatThemes[0], let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelCustomWallpaperLevel { if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelCustomWallpaperLevel {
wallpaperLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( wallpaperLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent(
strings: environment.strings, strings: environment.strings,
level: themeLevel level: themeLevel
@ -1085,12 +1084,13 @@ final class ChannelAppearanceScreenComponent: Component {
strings: environment.strings, strings: environment.strings,
sectionId: 0, sectionId: 0,
themes: chatThemes, themes: chatThemes,
hasNoTheme: true,
animatedEmojiStickers: component.context.animatedEmojiStickers, animatedEmojiStickers: component.context.animatedEmojiStickers,
themeSpecificAccentColors: [:], themeSpecificAccentColors: [:],
themeSpecificChatWallpapers: [:], themeSpecificChatWallpapers: [:],
nightMode: environment.theme.overallDarkAppearance, nightMode: environment.theme.overallDarkAppearance,
channelMode: true, channelMode: true,
currentTheme: currentTheme, currentTheme: self.currentTheme,
updatedTheme: { [weak self] value in updatedTheme: { [weak self] value in
guard let self else { guard let self else {
return return
@ -1138,7 +1138,7 @@ final class ChannelAppearanceScreenComponent: Component {
)), )),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)))) ))))
if backgroundFileId != nil, let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelProfileIconLevel { if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelProfileIconLevel {
profileLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( profileLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent(
strings: environment.strings, strings: environment.strings,
level: profileIconLevel level: profileIconLevel
@ -1249,7 +1249,7 @@ final class ChannelAppearanceScreenComponent: Component {
)), )),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)))) ))))
if emojiStatus != nil, let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelEmojiStatusLevel { if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelEmojiStatusLevel {
emojiStatusContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( emojiStatusContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent(
strings: environment.strings, strings: environment.strings,
level: emojiStatusLevel level: emojiStatusLevel
@ -1361,7 +1361,7 @@ final class ChannelAppearanceScreenComponent: Component {
Text(text: "Apply Changes", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) Text(text: "Apply Changes", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
))) )))
let requiredLevel = requiredBoostSubject.requiredLevel(premiumConfiguration) let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration)
if let boostLevel = self.boostLevel, requiredLevel > boostLevel { if let boostLevel = self.boostLevel, requiredLevel > boostLevel {
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(PremiumLockButtonSubtitleComponent( buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(PremiumLockButtonSubtitleComponent(
count: Int(requiredLevel), count: Int(requiredLevel),

View File

@ -416,7 +416,7 @@ private func ensureColorVisible(listNode: ListView, color: PeerNameColor, animat
} }
} }
if let resultNode = resultNode { if let resultNode = resultNode {
listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 24.0) listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 76.0)
return true return true
} else { } else {
return false return false

View File

@ -891,7 +891,7 @@ public func PeerNameColorScreen(
} }
let link = status.url let link = status.url
let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors(colors: .blue), isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: {
UIPasteboard.general.string = link UIPasteboard.general.string = link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false })) presentImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }))

View File

@ -24,7 +24,7 @@ import HexColor
private struct ThemeCarouselThemeEntry: Comparable, Identifiable { private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
let index: Int let index: Int
let emojiFile: TelegramMediaFile? let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference let themeReference: PresentationThemeReference?
let nightMode: Bool let nightMode: Bool
let channelMode: Bool let channelMode: Bool
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
@ -45,7 +45,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
if lhs.emojiFile?.fileId != rhs.emojiFile?.fileId { if lhs.emojiFile?.fileId != rhs.emojiFile?.fileId {
return false return false
} }
if lhs.themeReference.index != rhs.themeReference.index { if lhs.themeReference?.index != rhs.themeReference?.index {
return false return false
} }
if lhs.nightMode != rhs.nightMode { if lhs.nightMode != rhs.nightMode {
@ -79,7 +79,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
return lhs.index < rhs.index return lhs.index < rhs.index
} }
func item(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { 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, 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) 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)
} }
} }
@ -88,7 +88,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
public class ThemeCarouselThemeIconItem: ListViewItem { public class ThemeCarouselThemeIconItem: ListViewItem {
public let context: AccountContext public let context: AccountContext
public let emojiFile: TelegramMediaFile? public let emojiFile: TelegramMediaFile?
public let themeReference: PresentationThemeReference public let themeReference: PresentationThemeReference?
public let nightMode: Bool public let nightMode: Bool
public let channelMode: Bool public let channelMode: Bool
public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
@ -97,10 +97,10 @@ public class ThemeCarouselThemeIconItem: ListViewItem {
public let theme: PresentationTheme public let theme: PresentationTheme
public let strings: PresentationStrings public let strings: PresentationStrings
public let wallpaper: TelegramWallpaper? public let wallpaper: TelegramWallpaper?
public let action: (PresentationThemeReference) -> Void public let action: (PresentationThemeReference?) -> Void
public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)? public let 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)?) { 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.context = context
self.emojiFile = emojiFile self.emojiFile = emojiFile
self.themeReference = themeReference self.themeReference = themeReference
@ -336,6 +336,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
} }
func asyncLayout() -> (ThemeCarouselThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { func asyncLayout() -> (ThemeCarouselThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode) let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode)
let makeImageLayout = self.imageNode.asyncLayout() let makeImageLayout = self.imageNode.asyncLayout()
@ -368,9 +369,14 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
updatedSelected = true updatedSelected = true
} }
//TODO:localize
let text = NSAttributedString(string: "No\nWallpaper", font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
var string: String? var string: String?
if let _ = item.themeReference.emoticon { if item.themeReference == nil {
string = ""
} else if let _ = item.themeReference?.emoticon {
} else { } else {
string = "🎨" string = "🎨"
} }
@ -384,16 +390,19 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
strongSelf.item = item strongSelf.item = item
if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedChannelMode { if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedChannelMode {
var themeReference = item.themeReference if var themeReference = item.themeReference {
if case .builtin = themeReference, item.nightMode { if case .builtin = themeReference, item.nightMode {
themeReference = .builtin(.night) themeReference = .builtin(.night)
}
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, channelMode: item.channelMode, emoticon: true))
strongSelf.imageNode.backgroundColor = nil
} else {
} }
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, channelMode: item.channelMode, emoticon: true))
strongSelf.imageNode.backgroundColor = nil
} }
if updatedTheme || updatedSelected { if updatedTheme || updatedSelected {
@ -424,6 +433,10 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 78.0), size: CGSize(width: 90.0, height: 30.0)) strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 78.0), size: CGSize(width: 90.0, height: 30.0))
strongSelf.emojiNode.isHidden = string == nil strongSelf.emojiNode.isHidden = string == nil
let _ = textApply()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((90.0 - textLayout.size.width) / 2.0), y: 24.0), size: textLayout.size)
strongSelf.textNode.isHidden = item.themeReference != nil
let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0)) let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0))
if let file = item.emojiFile, currentItem?.emojiFile == nil { if let file = item.emojiFile, currentItem?.emojiFile == nil {
let imageApply = strongSelf.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets())) let imageApply = strongSelf.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets()))
@ -462,7 +475,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
} }
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.activateAreaNode.accessibilityLabel = item.themeReference.emoticon.flatMap { presentationData.strings.Appearance_VoiceOver_Theme($0).string } strongSelf.activateAreaNode.accessibilityLabel = item.themeReference?.emoticon.flatMap { presentationData.strings.Appearance_VoiceOver_Theme($0).string }
if item.selected { if item.selected {
strongSelf.activateAreaNode.accessibilityTraits = [.button, .selected] strongSelf.activateAreaNode.accessibilityTraits = [.button, .selected]
} else { } else {
@ -525,21 +538,23 @@ public class ThemeCarouselThemeItem: ListViewItem, ItemListItem, ListItemCompone
public let theme: PresentationTheme public let theme: PresentationTheme
public let strings: PresentationStrings public let strings: PresentationStrings
public let themes: [PresentationThemeReference] public let themes: [PresentationThemeReference]
public let hasNoTheme: Bool
public let animatedEmojiStickers: [String: [StickerPackItem]] public let animatedEmojiStickers: [String: [StickerPackItem]]
public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
public let themeSpecificChatWallpapers: [Int64: TelegramWallpaper] public let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
public let nightMode: Bool public let nightMode: Bool
public let channelMode: Bool public let channelMode: Bool
public let currentTheme: PresentationThemeReference public let currentTheme: PresentationThemeReference?
public let updatedTheme: (PresentationThemeReference) -> Void public let updatedTheme: (PresentationThemeReference?) -> Void
public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)? public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
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) { public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], hasNoTheme: Bool, 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.context = context
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.themes = themes self.themes = themes
self.hasNoTheme = hasNoTheme
self.animatedEmojiStickers = animatedEmojiStickers self.animatedEmojiStickers = animatedEmojiStickers
self.themeSpecificAccentColors = themeSpecificAccentColors self.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
@ -634,7 +649,7 @@ private struct ThemeCarouselThemeItemNodeTransition {
let updatePosition: Bool let updatePosition: Bool
} }
private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeCarouselThemeEntry], to toEntries: [ThemeCarouselThemeEntry], crossfade: Bool, updatePosition: Bool) -> ThemeCarouselThemeItemNodeTransition { private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference?) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeCarouselThemeEntry], to toEntries: [ThemeCarouselThemeEntry], crossfade: Bool, updatePosition: Bool) -> ThemeCarouselThemeItemNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -648,7 +663,7 @@ private func ensureThemeVisible(listNode: ListView, themeReference: Presentation
var resultNode: ThemeCarouselThemeItemIconNode? var resultNode: ThemeCarouselThemeItemIconNode?
listNode.forEachItemNode { node in listNode.forEachItemNode { node in
if resultNode == nil, let node = node as? ThemeCarouselThemeItemIconNode { if resultNode == nil, let node = node as? ThemeCarouselThemeItemIconNode {
if node.item?.themeReference.index == themeReference.index { if node.item?.themeReference?.index == themeReference.index {
resultNode = node resultNode = node
} }
} }
@ -736,7 +751,7 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
var scrollToItem: ListViewScrollToItem? var scrollToItem: ListViewScrollToItem?
if !self.initialized || !self.tapping { if !self.initialized || !self.tapping {
if let index = transition.entries.firstIndex(where: { entry in if let index = transition.entries.firstIndex(where: { entry in
return entry.themeReference.index == item.currentTheme.index return entry.themeReference?.index == item.currentTheme?.index
}) { }) {
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
self.initialized = true self.initialized = true
@ -836,8 +851,16 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
var index: Int = 0 var index: Int = 0
var hasCurrentTheme = false var hasCurrentTheme = false
if item.hasNoTheme {
let selected = item.currentTheme == nil
if selected {
hasCurrentTheme = true
}
entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: nil, themeReference: nil, nightMode: item.nightMode, channelMode: item.channelMode, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil))
index += 1
}
for theme in item.themes { for theme in item.themes {
let selected = item.currentTheme.index == theme.index let selected = item.currentTheme?.index == theme.index
if selected { if selected {
hasCurrentTheme = true hasCurrentTheme = true
} }
@ -850,11 +873,13 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
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)) 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 let action: (PresentationThemeReference?) -> Void = { [weak self] themeReference in
if let strongSelf = self { if let strongSelf = self {
strongSelf.tapping = true strongSelf.tapping = true
strongSelf.item?.updatedTheme(themeReference) strongSelf.item?.updatedTheme(themeReference)
let _ = ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true) if let themeReference {
let _ = ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true)
}
Queue.mainQueue().after(0.4) { Queue.mainQueue().after(0.4) {
strongSelf.tapping = false strongSelf.tapping = false
} }