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

View File

@ -4739,7 +4739,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
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 })
} else {
if node.frame.minY < self.insets.top {
if node.frame.minY < self.insets.top + overflow {
if !allowIntersection || node.frame.maxY < self.insets.top {
let position: ListViewScrollPosition
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 })
}
} 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 {
let position: ListViewScrollPosition
if allowIntersection {

View File

@ -20,10 +20,37 @@ import SolidRoundedButtonComponent
import BlurredBackgroundComponent
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 {
case stories
case channelReactions(reactionCount: Int32)
case nameColors
case nameColors(colors: PeerNameColor)
case nameIcon
case profileColors
case profileIcon
@ -31,27 +58,8 @@ public enum BoostSubject: Equatable {
case wallpaper
case customWallpaper
public func requiredLevel(_ configuration: PremiumConfiguration) -> Int32 {
switch self {
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
}
public func requiredLevel(context: AccountContext, configuration: PremiumConfiguration) -> Int32 {
return requiredBoostSubjectLevel(subject: self, context: context, configuration: configuration)
}
}
@ -479,7 +487,9 @@ private final class LimitSheetContent: CombinedComponent {
textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string
needsSecondParagraph = false
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:
textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string
case .profileColors:

View File

@ -1180,6 +1180,7 @@ private final class LimitSheetContent: CombinedComponent {
if let remaining {
let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1)
let valueString = strings.ChannelBoost_MoreBoosts(remaining)
switch boostSubject {
case .stories:
if level == 0 {
@ -1189,9 +1190,12 @@ private final class LimitSheetContent: CombinedComponent {
titleText = strings.ChannelBoost_IncreaseLimit
string = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string
}
case .nameColors:
case let .nameColors(colors):
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):
titleText = strings.ChannelBoost_CustomReactions
string = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string
@ -1778,7 +1782,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
public enum BoostSubject: Equatable {
case stories
case nameColors
case nameColors(colors: PeerNameColor)
case channelReactions(reactionCount: Int)
}

View File

@ -193,10 +193,14 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode {
}
let string: String?
if let _ = item.themeReference.emoticon {
string = nil
if let themeReference = item.themeReference {
if let _ = themeReference.emoticon {
string = nil
} else {
string = themeDisplayName(strings: item.strings, reference: themeReference)
}
} 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)
@ -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()))
if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedSize {
var themeReference = item.themeReference
if case .builtin = themeReference, item.nightMode {
themeReference = .builtin(.night)
if var themeReference = item.themeReference {
if case .builtin = themeReference, item.nightMode {
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 {
@ -501,7 +506,9 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode {
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
item.updatedTheme(theme)
if let theme {
item.updatedTheme(theme)
}
}, contextAction: nil)
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):
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):
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
arguments.selectTheme(theme)
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
if let theme {
arguments.selectTheme(theme)
}
}, contextAction: { theme, node, gesture in
arguments.themeContextAction(false, theme, node, gesture)
}, 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 {
self.panGestureState = PanGestureState(offsetFraction: panGestureState.offsetFraction < 0.0 ? -1.0 : 1.0)
self.notifyDismissedInteractivelyOnPanGestureApply = true
self.callScreen.beginPictureInPictureIfPossible()
}
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 dark = "d"
case isHidden = "h"
case requiredChannelMinBoostLevel = "rcmb"
}
public let light: ColorOption
public let dark: ColorOption?
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.dark = dark
self.isHidden = isHidden
self.requiredChannelMinBoostLevel = requiredChannelMinBoostLevel
}
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.dark = try container.decodeIfPresent(ColorOption.self, forKey: .dark)
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 {
@ -120,6 +124,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable {
try container.encode(self.light, forKey: .light)
try container.encodeIfPresent(self.dark, forKey: .dark)
try container.encodeIfPresent(self.isHidden, forKey: .isHidden)
try container.encodeIfPresent(self.requiredChannelMinBoostLevel, forKey: .requiredChannelMinBoostLevel)
}
public static func ==(lhs: ColorOptionPack, rhs: ColorOptionPack) -> Bool {
@ -135,6 +140,9 @@ public final class EngineAvailableColorOptions: Codable, Equatable {
if lhs.isHidden != rhs.isHidden {
return false
}
if lhs.requiredChannelMinBoostLevel != rhs.requiredChannelMinBoostLevel {
return false
}
return true
}
}
@ -262,14 +270,14 @@ private extension EngineAvailableColorOptions {
var mappedOptions: [Option] = []
for apiColor in apiColors {
switch apiColor {
case let .peerColorOption(flags, colorId, colors, darkColors, _):
case let .peerColorOption(flags, colorId, colors, darkColors, requiredChannelMinBoostLevel):
let isHidden = (flags & (1 << 0)) != 0
let mappedColors = colors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:))
let mappedDarkColors = darkColors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:))
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 {
let staticMap: [UInt32] = [
0xcc5049,
@ -282,7 +290,7 @@ private extension EngineAvailableColorOptions {
]
let colorPack = MultiColorPack(colors: [staticMap[Int(colorId)]])
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
}
let requiredLevel = requiredBoostSubject.requiredLevel(premiumConfiguration)
let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration)
if let boostLevel = self.boostLevel, requiredLevel > boostLevel {
self.displayBoostLevels(subject: requiredBoostSubject)
return
@ -806,7 +806,7 @@ final class ChannelAppearanceScreenComponent: Component {
return availableSize
}
var requiredBoostSubject: BoostSubject = .nameColors
var requiredBoostSubjects: [BoostSubject] = [.nameColors(colors: resolvedState.nameColor)]
let replyIconLevel = 5
var profileIconLevel = 7
@ -824,34 +824,27 @@ final class ChannelAppearanceScreenComponent: Component {
let replyFileId = resolvedState.replyFileId
if replyFileId != nil {
requiredBoostSubject = .nameIcon
requiredBoostSubjects.append(.nameIcon)
}
let profileColor = resolvedState.profileColor
if profileColor != nil {
requiredBoostSubject = .profileColors
requiredBoostSubjects.append(.profileColors)
}
let backgroundFileId = resolvedState.backgroundFileId
if backgroundFileId != nil {
requiredBoostSubject = .profileIcon
requiredBoostSubjects.append(.profileIcon)
}
let emojiStatus = resolvedState.emojiStatus
if emojiStatus != nil {
requiredBoostSubject = .emojiStatus
requiredBoostSubjects.append(.emojiStatus)
}
let statusFileId = emojiStatus?.fileId
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 }
chatThemes.insert(.builtin(.dayClassic), at: 0)
if !chatThemes.isEmpty {
if self.currentTheme == nil {
self.currentTheme = chatThemes[0]
}
}
let chatThemes = cloudThemes.filter { $0.emoticon != nil }
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()
@ -891,8 +884,8 @@ final class ChannelAppearanceScreenComponent: Component {
}
}
if self.currentTheme != nil && self.currentTheme != chatThemes.first {
requiredBoostSubject = .wallpaper
if self.currentTheme != nil {
requiredBoostSubjects.append(.wallpaper)
}
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
let topInset: CGFloat = 24.0
@ -951,7 +950,7 @@ final class ChannelAppearanceScreenComponent: Component {
)),
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(
strings: environment.strings,
level: replyIconLevel
@ -1047,7 +1046,7 @@ final class ChannelAppearanceScreenComponent: Component {
contentHeight += sectionSpacing
if !chatThemes.isEmpty, let currentTheme {
if !chatThemes.isEmpty {
var wallpaperLogoContents: [AnyComponentWithIdentity<Empty>] = []
wallpaperLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
@ -1057,7 +1056,7 @@ final class ChannelAppearanceScreenComponent: Component {
)),
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(
strings: environment.strings,
level: themeLevel
@ -1085,12 +1084,13 @@ final class ChannelAppearanceScreenComponent: Component {
strings: environment.strings,
sectionId: 0,
themes: chatThemes,
hasNoTheme: true,
animatedEmojiStickers: component.context.animatedEmojiStickers,
themeSpecificAccentColors: [:],
themeSpecificChatWallpapers: [:],
nightMode: environment.theme.overallDarkAppearance,
channelMode: true,
currentTheme: currentTheme,
currentTheme: self.currentTheme,
updatedTheme: { [weak self] value in
guard let self else {
return
@ -1138,7 +1138,7 @@ final class ChannelAppearanceScreenComponent: Component {
)),
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(
strings: environment.strings,
level: profileIconLevel
@ -1249,7 +1249,7 @@ final class ChannelAppearanceScreenComponent: Component {
)),
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(
strings: environment.strings,
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)
)))
let requiredLevel = requiredBoostSubject.requiredLevel(premiumConfiguration)
let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration)
if let boostLevel = self.boostLevel, requiredLevel > boostLevel {
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(PremiumLockButtonSubtitleComponent(
count: Int(requiredLevel),

View File

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

View File

@ -891,7 +891,7 @@ public func PeerNameColorScreen(
}
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
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 }))

View File

@ -24,7 +24,7 @@ import HexColor
private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
let index: Int
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference
let themeReference: PresentationThemeReference?
let nightMode: Bool
let channelMode: Bool
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
@ -45,7 +45,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
if lhs.emojiFile?.fileId != rhs.emojiFile?.fileId {
return false
}
if lhs.themeReference.index != rhs.themeReference.index {
if lhs.themeReference?.index != rhs.themeReference?.index {
return false
}
if lhs.nightMode != rhs.nightMode {
@ -79,7 +79,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
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)
}
}
@ -88,7 +88,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable {
public class ThemeCarouselThemeIconItem: ListViewItem {
public let context: AccountContext
public let emojiFile: TelegramMediaFile?
public let themeReference: PresentationThemeReference
public let themeReference: PresentationThemeReference?
public let nightMode: Bool
public let channelMode: Bool
public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
@ -97,10 +97,10 @@ public class ThemeCarouselThemeIconItem: ListViewItem {
public let theme: PresentationTheme
public let strings: PresentationStrings
public let wallpaper: TelegramWallpaper?
public let action: (PresentationThemeReference) -> Void
public let action: (PresentationThemeReference?) -> 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.emojiFile = emojiFile
self.themeReference = themeReference
@ -336,6 +336,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
}
func asyncLayout() -> (ThemeCarouselThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode)
let makeImageLayout = self.imageNode.asyncLayout()
@ -368,9 +369,14 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
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?
if let _ = item.themeReference.emoticon {
if item.themeReference == nil {
string = ""
} else if let _ = item.themeReference?.emoticon {
} else {
string = "🎨"
}
@ -384,16 +390,19 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode {
strongSelf.item = item
if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedChannelMode {
var themeReference = item.themeReference
if case .builtin = themeReference, item.nightMode {
themeReference = .builtin(.night)
if var themeReference = item.themeReference {
if case .builtin = themeReference, item.nightMode {
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 {
@ -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.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))
if let file = item.emojiFile, currentItem?.emojiFile == nil {
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 }
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 {
strongSelf.activateAreaNode.accessibilityTraits = [.button, .selected]
} else {
@ -525,21 +538,23 @@ public class ThemeCarouselThemeItem: ListViewItem, ItemListItem, ListItemCompone
public let theme: PresentationTheme
public let strings: PresentationStrings
public let themes: [PresentationThemeReference]
public let hasNoTheme: Bool
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 currentTheme: PresentationThemeReference?
public let updatedTheme: (PresentationThemeReference?) -> Void
public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
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.theme = theme
self.strings = strings
self.themes = themes
self.hasNoTheme = hasNoTheme
self.animatedEmojiStickers = animatedEmojiStickers
self.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
@ -634,7 +649,7 @@ private struct ThemeCarouselThemeItemNodeTransition {
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 deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -648,7 +663,7 @@ private func ensureThemeVisible(listNode: ListView, themeReference: Presentation
var resultNode: ThemeCarouselThemeItemIconNode?
listNode.forEachItemNode { node in
if resultNode == nil, let node = node as? ThemeCarouselThemeItemIconNode {
if node.item?.themeReference.index == themeReference.index {
if node.item?.themeReference?.index == themeReference.index {
resultNode = node
}
}
@ -736,7 +751,7 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
var scrollToItem: ListViewScrollToItem?
if !self.initialized || !self.tapping {
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)
self.initialized = true
@ -836,8 +851,16 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
var index: Int = 0
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 {
let selected = item.currentTheme.index == theme.index
let selected = item.currentTheme?.index == theme.index
if selected {
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))
}
let action: (PresentationThemeReference) -> Void = { [weak self] themeReference in
let action: (PresentationThemeReference?) -> Void = { [weak self] themeReference in
if let strongSelf = self {
strongSelf.tapping = true
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) {
strongSelf.tapping = false
}