mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Name colors improvements
This commit is contained in:
parent
137c3d9101
commit
4f183fa7fe
@ -10123,3 +10123,20 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Channel.AdminLog.MessageChangedNameColorSet" = "%1$@ set name color to %2$@";
|
||||
"Channel.AdminLog.MessageChangedBackgroundEmojiSet" = "%1$@ set background emoji to %2$@";
|
||||
|
||||
"Appearance.NameColor" = "Your Name Color";
|
||||
|
||||
"NameColor.Title.Account" = "Your Name Color";
|
||||
"NameColor.Title.Channel" = "Channel Title Color";
|
||||
|
||||
"NameColor.ChatPreview.Title" = "COLOR PREVIEW";
|
||||
"NameColor.ChatPreview.ReplyText" = "Reply to your message";
|
||||
"NameColor.ChatPreview.MessageText" = "Your name and replies to your messages will be shown in the selected color.";
|
||||
"NameColor.ChatPreview.LinkSite" = "Telegram";
|
||||
"NameColor.ChatPreview.LinkTitle" = "Link Preview";
|
||||
"NameColor.ChatPreview.LinkText" = "Your selected color will also tint the link preview.";
|
||||
"NameColor.ChatPreview.Description.Account" = "You can choose an individual color to tint your name, the links you send, and replies to your messages.";
|
||||
|
||||
"NameColor.ApplyColor" = "Apply Color";
|
||||
|
||||
"NameColor.TooltipPremium.Account" = "Subscribe to [Telegram Premium]() to choose a custom color for your name.";
|
||||
|
@ -29,7 +29,7 @@ open class ListViewItemHeaderNode: ASDisplayNode {
|
||||
private var isFlashingOnScrolling = false
|
||||
weak var attachedToItemNode: ListViewItemNode?
|
||||
|
||||
var item: ListViewItemHeader?
|
||||
public var item: ListViewItemHeader?
|
||||
|
||||
func updateInternalStickLocationDistanceFactor(_ factor: CGFloat, animated: Bool) {
|
||||
self.internalStickLocationDistanceFactor = factor
|
||||
@ -124,7 +124,7 @@ open class ListViewItemHeaderNode: ASDisplayNode {
|
||||
|
||||
private var cachedLayout: (CGSize, CGFloat, CGFloat)?
|
||||
|
||||
func updateLayoutInternal(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
|
||||
public func updateLayoutInternal(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
|
||||
var update = false
|
||||
if let cachedLayout = self.cachedLayout {
|
||||
if cachedLayout.0 != size || cachedLayout.1 != leftInset || cachedLayout.2 != rightInset {
|
||||
|
@ -264,6 +264,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
|
||||
self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
|
@ -233,6 +233,8 @@ public final class ItemListControllerNodeView: UITracingLayerView {
|
||||
}
|
||||
|
||||
open class ItemListControllerNode: ASDisplayNode {
|
||||
private weak var controller: ItemListController?
|
||||
|
||||
private var _ready = ValuePromise<Bool>()
|
||||
open var ready: Signal<Bool, NoError> {
|
||||
return self._ready.get()
|
||||
@ -295,6 +297,7 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||
|
||||
public init(controller: ItemListController?, navigationBar: NavigationBar, state: Signal<(ItemListPresentationData, (ItemListNodeState, Any)), NoError>) {
|
||||
self.controller = controller
|
||||
self.navigationBar = navigationBar
|
||||
|
||||
self.listNode = ListView()
|
||||
@ -585,9 +588,10 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
insets.top += headerHeight
|
||||
}
|
||||
|
||||
var footerHeight: CGFloat = 0.0
|
||||
if let footerItemNode = self.footerItemNode {
|
||||
let footerHeight = footerItemNode.updateLayout(layout: layout, transition: transition)
|
||||
insets.bottom += footerHeight
|
||||
footerHeight = footerItemNode.updateLayout(layout: layout, transition: transition)
|
||||
insets.bottom = footerHeight
|
||||
}
|
||||
|
||||
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||
@ -616,6 +620,12 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
self.dequeueTransitions()
|
||||
}
|
||||
|
||||
var layout = layout
|
||||
layout.intrinsicInsets.left = 4.0
|
||||
layout.intrinsicInsets.right = 4.0
|
||||
layout.intrinsicInsets.bottom = insets.bottom
|
||||
self.controller?.presentationContext.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
if !self.afterLayoutActions.isEmpty {
|
||||
let afterLayoutActions = self.afterLayoutActions
|
||||
self.afterLayoutActions = []
|
||||
|
@ -35,6 +35,7 @@ public enum ItemListDisclosureLabelStyle {
|
||||
case multilineDetailText
|
||||
case badge(UIColor)
|
||||
case color(UIColor)
|
||||
case semitransparentBadge(UIColor)
|
||||
case image(image: UIImage, size: CGSize)
|
||||
}
|
||||
|
||||
@ -125,6 +126,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
|
||||
private let badgeFont = Font.regular(15.0)
|
||||
private let boldBadgeFont = Font.semibold(14.0)
|
||||
|
||||
public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
@ -256,11 +258,21 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
var updatedLabelBadgeImage: UIImage?
|
||||
var updatedLabelImage: UIImage?
|
||||
|
||||
var badgeDiameter: CGFloat = 20.0
|
||||
var badgeColor: UIColor?
|
||||
var badgeColorUpdated = false
|
||||
if case let .badge(color) = item.labelStyle {
|
||||
if item.label.count > 0 {
|
||||
badgeColor = color
|
||||
}
|
||||
} else if case let .semitransparentBadge(color) = item.labelStyle {
|
||||
badgeDiameter = 24.0
|
||||
badgeColor = color.withAlphaComponent(0.1)
|
||||
|
||||
badgeColorUpdated = true
|
||||
if let currentItem = currentItem, case let .semitransparentBadge(previousColor) = currentItem.labelStyle, color.isEqual(previousColor) {
|
||||
badgeColorUpdated = false
|
||||
}
|
||||
}
|
||||
if case let .color(color) = item.labelStyle {
|
||||
var updatedColor = true
|
||||
@ -277,7 +289,6 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
updatedLabelImage = image
|
||||
}
|
||||
|
||||
let badgeDiameter: CGFloat = 20.0
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
switch item.disclosureStyle {
|
||||
@ -289,7 +300,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
if let badgeColor = badgeColor {
|
||||
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor)
|
||||
}
|
||||
} else if let badgeColor = badgeColor, !currentHasBadge {
|
||||
} else if let badgeColor = badgeColor, !currentHasBadge || badgeColorUpdated {
|
||||
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor)
|
||||
}
|
||||
|
||||
@ -313,7 +324,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
var additionalTextRightInset: CGFloat = 0.0
|
||||
switch item.labelStyle {
|
||||
case .badge:
|
||||
case .badge, .semitransparentBadge:
|
||||
additionalTextRightInset += 44.0
|
||||
default:
|
||||
break
|
||||
@ -355,6 +366,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .badge:
|
||||
labelBadgeColor = item.presentationData.theme.list.itemCheckColors.foregroundColor
|
||||
labelFont = badgeFont
|
||||
case let .semitransparentBadge(color):
|
||||
labelBadgeColor = color
|
||||
labelFont = boldBadgeFont
|
||||
case .detailText, .multilineDetailText:
|
||||
labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||
labelFont = detailFont
|
||||
@ -578,14 +592,19 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.labelBadgeNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
let badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0)
|
||||
var badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0)
|
||||
if case .semitransparentBadge = item.labelStyle {
|
||||
badgeWidth += 2.0
|
||||
}
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth, y: floor((contentSize.height - badgeDiameter) / 2.0)), size: CGSize(width: badgeWidth, height: badgeDiameter))
|
||||
strongSelf.labelBadgeNode.frame = badgeFrame
|
||||
|
||||
let labelFrame: CGRect
|
||||
switch item.labelStyle {
|
||||
case .badge:
|
||||
labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: badgeFrame.minY + 1), size: labelLayout.size)
|
||||
labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: badgeFrame.minY + 1.0), size: labelLayout.size)
|
||||
case .semitransparentBadge:
|
||||
labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: badgeFrame.minY + 1.0 - UIScreenPixel + floorToScreenPixels((badgeDiameter - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
case .detailText, .multilineDetailText:
|
||||
labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
|
||||
default:
|
||||
|
@ -114,6 +114,7 @@ swift_library(
|
||||
"//submodules/ImageBlur:ImageBlur",
|
||||
"//submodules/AttachmentUI:AttachmentUI",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -18,6 +18,7 @@ import AccountContext
|
||||
import ContextUI
|
||||
import UndoUI
|
||||
import PremiumUI
|
||||
import PeerNameColorScreen
|
||||
|
||||
func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
|
||||
let name: String
|
||||
@ -50,6 +51,7 @@ private final class ThemeSettingsControllerArguments {
|
||||
let selectTheme: (PresentationThemeReference) -> Void
|
||||
let openThemeSettings: () -> Void
|
||||
let openWallpaperSettings: () -> Void
|
||||
let openNameColorSettings: () -> Void
|
||||
let selectAccentColor: (PresentationThemeAccentColor?) -> Void
|
||||
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
|
||||
let toggleNightTheme: (Bool) -> Void
|
||||
@ -64,11 +66,12 @@ private final class ThemeSettingsControllerArguments {
|
||||
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
|
||||
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, openNameColorSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
self.context = context
|
||||
self.selectTheme = selectTheme
|
||||
self.openThemeSettings = openThemeSettings
|
||||
self.openWallpaperSettings = openWallpaperSettings
|
||||
self.openNameColorSettings = openNameColorSettings
|
||||
self.selectAccentColor = selectAccentColor
|
||||
self.openAccentColorPicker = openAccentColorPicker
|
||||
self.toggleNightTheme = toggleNightTheme
|
||||
@ -119,6 +122,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
case themes(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, Bool, [String: [StickerPackItem]], [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper])
|
||||
case chatTheme(PresentationTheme, String)
|
||||
case wallpaper(PresentationTheme, String)
|
||||
case nameColor(PresentationTheme, String, String, UIColor)
|
||||
case autoNight(PresentationTheme, String, Bool, Bool)
|
||||
case autoNightTheme(PresentationTheme, String, String)
|
||||
case textSize(PresentationTheme, String, String)
|
||||
@ -133,7 +137,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .themeListHeader, .chatPreview, .themes, .chatTheme, .wallpaper:
|
||||
case .themeListHeader, .chatPreview, .themes, .chatTheme, .wallpaper, .nameColor:
|
||||
return ThemeSettingsControllerSection.chatPreview.rawValue
|
||||
case .autoNight, .autoNightTheme:
|
||||
return ThemeSettingsControllerSection.nightMode.rawValue
|
||||
@ -150,38 +154,40 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .themeListHeader:
|
||||
return 0
|
||||
case .chatPreview:
|
||||
return 1
|
||||
case .themes:
|
||||
return 2
|
||||
case .chatTheme:
|
||||
return 3
|
||||
case .wallpaper:
|
||||
return 4
|
||||
case .autoNight:
|
||||
return 5
|
||||
case .autoNightTheme:
|
||||
return 6
|
||||
case .textSize:
|
||||
return 7
|
||||
case .bubbleSettings:
|
||||
return 8
|
||||
case .powerSaving:
|
||||
return 9
|
||||
case .stickersAndEmoji:
|
||||
return 10
|
||||
case .iconHeader:
|
||||
return 11
|
||||
case .iconItem:
|
||||
return 12
|
||||
case .otherHeader:
|
||||
return 13
|
||||
case .showNextMediaOnTap:
|
||||
return 14
|
||||
case .showNextMediaOnTapInfo:
|
||||
return 15
|
||||
case .themeListHeader:
|
||||
return 0
|
||||
case .chatPreview:
|
||||
return 1
|
||||
case .themes:
|
||||
return 2
|
||||
case .chatTheme:
|
||||
return 3
|
||||
case .wallpaper:
|
||||
return 4
|
||||
case .nameColor:
|
||||
return 5
|
||||
case .autoNight:
|
||||
return 6
|
||||
case .autoNightTheme:
|
||||
return 7
|
||||
case .textSize:
|
||||
return 8
|
||||
case .bubbleSettings:
|
||||
return 9
|
||||
case .powerSaving:
|
||||
return 10
|
||||
case .stickersAndEmoji:
|
||||
return 11
|
||||
case .iconHeader:
|
||||
return 12
|
||||
case .iconItem:
|
||||
return 13
|
||||
case .otherHeader:
|
||||
return 14
|
||||
case .showNextMediaOnTap:
|
||||
return 15
|
||||
case .showNextMediaOnTapInfo:
|
||||
return 16
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +217,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .nameColor(lhsTheme, lhsText, lhsName, lhsColor):
|
||||
if case let .nameColor(rhsTheme, rhsText, rhsName, rhsColor) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsName == rhsName, lhsColor == rhsColor {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .autoNight(lhsTheme, lhsText, lhsValue, lhsEnabled):
|
||||
if case let .autoNight(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
@ -297,10 +309,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
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)
|
||||
}, contextAction: { theme, node, gesture in
|
||||
arguments.themeContextAction(false, theme, node, gesture)
|
||||
}, tag: ThemeSettingsEntryTag.theme)
|
||||
arguments.selectTheme(theme)
|
||||
}, contextAction: { theme, node, gesture in
|
||||
arguments.themeContextAction(false, theme, node, gesture)
|
||||
}, tag: ThemeSettingsEntryTag.theme)
|
||||
case let .chatTheme(_, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openThemeSettings()
|
||||
@ -309,6 +321,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openWallpaperSettings()
|
||||
})
|
||||
case let .nameColor(_, text, name, color):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: name, labelStyle: .semitransparentBadge(color), sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openNameColorSettings()
|
||||
})
|
||||
case let .autoNight(_, title, value, enabled):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleNightTheme(value)
|
||||
@ -353,7 +369,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] {
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], accountPeer: EnginePeer?) -> [ThemeSettingsControllerEntry] {
|
||||
var entries: [ThemeSettingsControllerEntry] = []
|
||||
|
||||
let strings = presentationData.strings
|
||||
@ -365,6 +381,10 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
||||
entries.append(.chatTheme(presentationData.theme, strings.Settings_ChatThemes))
|
||||
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
|
||||
|
||||
if let accountPeer, case let .user(user) = accountPeer {
|
||||
entries.append(.nameColor(presentationData.theme, strings.Appearance_NameColor, accountPeer.compactDisplayTitle, (user.nameColor ?? .blue).color))
|
||||
}
|
||||
|
||||
entries.append(.autoNight(presentationData.theme, strings.Appearance_NightTheme, presentationThemeSettings.automaticThemeSwitchSetting.force, !presentationData.autoNightModeTriggered || presentationThemeSettings.automaticThemeSwitchSetting.force))
|
||||
let autoNightMode: String
|
||||
switch presentationThemeSettings.automaticThemeSwitchSetting.trigger {
|
||||
@ -488,6 +508,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
pushControllerImpl?(themePickerController(context: context))
|
||||
}, openWallpaperSettings: {
|
||||
pushControllerImpl?(ThemeGridController(context: context))
|
||||
}, openNameColorSettings: {
|
||||
pushControllerImpl?(PeerNameColorScreen(context: context, subject: .account))
|
||||
}, selectAccentColor: { accentColor in
|
||||
selectAccentColorImpl?(accentColor)
|
||||
}, openAccentColorPicker: { themeReference, create in
|
||||
@ -1000,8 +1022,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
})
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId))
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)))
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
|
||||
let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
|
||||
|
||||
@ -1042,7 +1064,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
chatThemes.insert(.builtin(.dayClassic), at: 0)
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ public struct CachedPremiumGiftOption: Equatable, PostboxCoding {
|
||||
}
|
||||
}
|
||||
|
||||
public enum PeerNameColor: Int32 {
|
||||
public enum PeerNameColor: Int32, CaseIterable {
|
||||
case red
|
||||
case orange
|
||||
case violet
|
||||
@ -122,12 +122,12 @@ public enum PeerNameColor: Int32 {
|
||||
case cyan
|
||||
case blue
|
||||
case pink
|
||||
case other7
|
||||
case other8
|
||||
case other9
|
||||
case other10
|
||||
case other11
|
||||
case other12
|
||||
case redDash
|
||||
case orangeDash
|
||||
case violetDash
|
||||
case greenDash
|
||||
case cyanDash
|
||||
case blueDash
|
||||
case other13
|
||||
case other14
|
||||
case other15
|
||||
|
@ -53,23 +53,24 @@ public enum UpdateNameColorAndEmojiError {
|
||||
|
||||
func _internal_updateNameColorAndEmoji(account: Account, nameColor: PeerNameColor, backgroundEmojiId: Int64?) -> Signal<Void, UpdateNameColorAndEmojiError> {
|
||||
let flags: Int32 = (1 << 0)
|
||||
return account.postbox.loadedPeerWithId(account.peerId)
|
||||
|> castError(UpdateNameColorAndEmojiError.self)
|
||||
|> mapToSignal { accountPeer -> Signal<Void, UpdateNameColorAndEmojiError> in
|
||||
guard let accountPeer = accountPeer as? TelegramUser else {
|
||||
return .fail(.generic)
|
||||
return account.postbox.transaction { transaction -> Signal<Peer, NoError> in
|
||||
guard let peer = transaction.getPeer(account.peerId) as? TelegramUser else {
|
||||
return .complete()
|
||||
}
|
||||
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId)], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
return .single(peer)
|
||||
}
|
||||
|> switchToLatest
|
||||
|> castError(UpdateNameColorAndEmojiError.self)
|
||||
|> mapToSignal { _ -> Signal<Void, UpdateNameColorAndEmojiError> in
|
||||
return account.network.request(Api.functions.account.updateColor(flags: flags, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId ?? 0))
|
||||
|> mapError { _ -> UpdateNameColorAndEmojiError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { apiUser -> Signal<Void, UpdateNameColorAndEmojiError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
updatePeersCustom(transaction: transaction, peers: [accountPeer.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId)], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
|> castError(UpdateNameColorAndEmojiError.self)
|
||||
|> mapToSignal { _ -> Signal<Void, UpdateNameColorAndEmojiError> in
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -505,6 +505,10 @@ public extension EnginePeer {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var nameColor: PeerNameColor? {
|
||||
return self._asPeer().nameColor
|
||||
}
|
||||
}
|
||||
|
||||
public extension EnginePeer {
|
||||
|
@ -216,6 +216,25 @@ public extension Peer {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var nameColor: PeerNameColor? {
|
||||
switch self {
|
||||
case let user as TelegramUser:
|
||||
if let nameColor = user.nameColor {
|
||||
return nameColor
|
||||
} else {
|
||||
return PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7)) ?? .blue
|
||||
}
|
||||
case let channel as TelegramChannel:
|
||||
if let nameColor = channel.nameColor {
|
||||
return nameColor
|
||||
} else {
|
||||
return PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7)) ?? .blue
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension TelegramPeerUsername {
|
||||
|
@ -0,0 +1,46 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import TelegramCore
|
||||
|
||||
public extension PeerNameColor {
|
||||
var color: UIColor {
|
||||
return self.dashColors.0
|
||||
}
|
||||
|
||||
var dashColors: (UIColor, UIColor?) {
|
||||
switch self {
|
||||
case .red:
|
||||
return (UIColor(rgb: 0xCC5049), nil)
|
||||
case .orange:
|
||||
return (UIColor(rgb: 0xD67722), nil)
|
||||
case .violet:
|
||||
return (UIColor(rgb: 0x955CDB), nil)
|
||||
case .green:
|
||||
return (UIColor(rgb: 0x40A920), nil)
|
||||
case .cyan:
|
||||
return (UIColor(rgb: 0x309EBA), nil)
|
||||
case .blue:
|
||||
return (UIColor(rgb: 0x368AD1), nil)
|
||||
case .pink:
|
||||
return (UIColor(rgb: 0xC7508B), nil)
|
||||
case .redDash:
|
||||
return (UIColor(rgb: 0xE15052), UIColor(rgb: 0xF9AE63))
|
||||
case .orangeDash:
|
||||
return (UIColor(rgb: 0xE0802B), UIColor(rgb: 0xFAC534))
|
||||
case .violetDash:
|
||||
return (UIColor(rgb: 0xA05FF3), UIColor(rgb: 0xF48FFF))
|
||||
case .greenDash:
|
||||
return (UIColor(rgb: 0x27A910), UIColor(rgb: 0xA7DC57))
|
||||
case .cyanDash:
|
||||
return (UIColor(rgb: 0x27ACCE), UIColor(rgb: 0x82E8D6))
|
||||
case .blueDash:
|
||||
return (UIColor(rgb: 0x3391D4), UIColor(rgb: 0x7DD3F0))
|
||||
case .other13:
|
||||
return (.black, nil)
|
||||
case .other14:
|
||||
return (.black, nil)
|
||||
case .other15:
|
||||
return (.black, nil)
|
||||
}
|
||||
}
|
||||
}
|
@ -300,6 +300,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
|
||||
case chatReplyBackgroundTemplateImage
|
||||
case chatReplyServiceBackgroundTemplateImage
|
||||
case chatReplyLineDashTemplateImage
|
||||
}
|
||||
|
||||
public enum ChatExpiredStoryIndicatorType: Hashable {
|
||||
|
@ -1316,4 +1316,26 @@ public struct PresentationResourcesChat {
|
||||
})?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)).withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatReplyLineDashTemplateImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatReplyLineDashTemplateImage.rawValue, { theme in
|
||||
let radius: CGFloat = 3.0
|
||||
let offset: CGFloat = 5.0
|
||||
|
||||
return generateImage(CGSize(width: radius, height: radius * 6.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.move(to: CGPoint(x: size.width, y: offset))
|
||||
context.addLine(to: CGPoint(x: size.width, y: offset + radius * 3.0))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: offset + radius * 4.0))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: offset + radius))
|
||||
context.closePath()
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fillPath()
|
||||
})?.resizableImage(withCapInsets: .zero, resizingMode: .tile).withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
//chatReplyLineDashTemplateImage
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ private final class FlashColorComponent: Component {
|
||||
|
||||
func update(component: FlashColorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
let contentSize = CGSize(width: 24.0, height: 24.0)
|
||||
let contentSize = CGSize(width: 30.0, height: 30.0)
|
||||
self.contentView.frame = CGRect(origin: .zero, size: contentSize)
|
||||
|
||||
let bounds = CGRect(origin: .zero, size: contentSize)
|
||||
@ -192,7 +192,7 @@ final class FlashTintControlComponent: Component {
|
||||
let isFirstTime = self.component == nil
|
||||
self.component = component
|
||||
|
||||
let size = CGSize(width: 160.0, height: 40.0)
|
||||
let size = CGSize(width: 184.0, height: 92.0)
|
||||
if isFirstTime {
|
||||
self.maskLayer.path = generateRoundedRectWithTailPath(rectSize: size, cornerRadius: 10.0, tailSize: CGSize(width: 18, height: 7.0), tailRadius: 1.0, tailPosition: 0.8, transformTail: false).cgPath
|
||||
self.maskLayer.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.height + 7.0))
|
||||
@ -264,7 +264,7 @@ final class FlashTintControlComponent: Component {
|
||||
if view.superview == nil {
|
||||
self.containerView.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - swatchesSize.width) / 2.0), y: floorToScreenPixels((size.height - swatchesSize.height) / 2.0)), size: swatchesSize)
|
||||
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - swatchesSize.width) / 2.0), y: 8.0), size: swatchesSize)
|
||||
}
|
||||
|
||||
self.dismissView.frame = CGRect(origin: .zero, size: availableSize)
|
||||
|
@ -53,6 +53,7 @@ public struct ChatMessageAttachedContentNodeMediaFlags: OptionSet {
|
||||
|
||||
public final class ChatMessageAttachedContentNode: HighlightTrackingButtonNode {
|
||||
private var backgroundView: UIImageView?
|
||||
private var lineDashView: UIImageView?
|
||||
private let topTitleNode: TextNode
|
||||
private let textNode: TextNodeWithEntities
|
||||
private let inlineImageNode: TransformImageNode
|
||||
@ -251,13 +252,47 @@ public final class ChatMessageAttachedContentNode: HighlightTrackingButtonNode {
|
||||
let string = NSMutableAttributedString()
|
||||
var notEmpty = false
|
||||
|
||||
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
|
||||
let mainColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
if !incoming {
|
||||
mainColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
} else {
|
||||
var authorNameColor: UIColor?
|
||||
let author = message.author
|
||||
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), author?.id.namespace == Namespaces.Peer.CloudUser {
|
||||
authorNameColor = author?.nameColor?.color
|
||||
secondaryColor = author?.nameColor?.dashColors.1
|
||||
// if let rawAuthorNameColor = authorNameColor {
|
||||
// var dimColors = false
|
||||
// switch presentationData.theme.theme.name {
|
||||
// case .builtin(.nightAccent), .builtin(.night):
|
||||
// dimColors = true
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// if dimColors {
|
||||
// var hue: CGFloat = 0.0
|
||||
// var saturation: CGFloat = 0.0
|
||||
// var brightness: CGFloat = 0.0
|
||||
// rawAuthorNameColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
|
||||
// authorNameColor = UIColor(hue: hue, saturation: saturation * 0.7, brightness: min(1.0, brightness * 1.2), alpha: 1.0)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
} else {
|
||||
mainColor = presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
}
|
||||
}
|
||||
|
||||
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
|
||||
if let title = title, !title.isEmpty {
|
||||
if titleBeforeMedia {
|
||||
topTitleString.append(NSAttributedString(string: title, font: titleFont, textColor: messageTheme.accentTextColor))
|
||||
topTitleString.append(NSAttributedString(string: title, font: titleFont, textColor: incoming ? mainColor : messageTheme.accentTextColor))
|
||||
} else {
|
||||
string.append(NSAttributedString(string: title, font: titleFont, textColor: messageTheme.accentTextColor))
|
||||
string.append(NSAttributedString(string: title, font: titleFont, textColor: incoming ? mainColor : messageTheme.accentTextColor))
|
||||
notEmpty = true
|
||||
}
|
||||
}
|
||||
@ -565,7 +600,6 @@ public final class ChatMessageAttachedContentNode: HighlightTrackingButtonNode {
|
||||
|
||||
let upatedTextCutout = textCutout
|
||||
|
||||
|
||||
let (topTitleLayout, topTitleApply) = topTitleAsyncLayout(TextNodeLayoutArguments(attributedString: topTitleString, backgroundColor: nil, maximumNumberOfLines: 12, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (textLayout, textApply) = textAsyncLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 12, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: upatedTextCutout, insets: UIEdgeInsets()))
|
||||
|
||||
@ -603,40 +637,7 @@ public final class ChatMessageAttachedContentNode: HighlightTrackingButtonNode {
|
||||
var textFrame = CGRect(origin: CGPoint(), size: textLayout.size)
|
||||
|
||||
textFrame = textFrame.offsetBy(dx: insets.left, dy: insets.top)
|
||||
|
||||
let mainColor: UIColor
|
||||
if !incoming {
|
||||
mainColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
} else {
|
||||
var authorNameColor: UIColor?
|
||||
let author = message.author
|
||||
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), author?.id.namespace == Namespaces.Peer.CloudUser {
|
||||
authorNameColor = author.flatMap { chatMessagePeerIdColors[Int(clamping: $0.id.id._internalGetInt64Value() % 7)] }
|
||||
if let rawAuthorNameColor = authorNameColor {
|
||||
var dimColors = false
|
||||
switch presentationData.theme.theme.name {
|
||||
case .builtin(.nightAccent), .builtin(.night):
|
||||
dimColors = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
if dimColors {
|
||||
var hue: CGFloat = 0.0
|
||||
var saturation: CGFloat = 0.0
|
||||
var brightness: CGFloat = 0.0
|
||||
rawAuthorNameColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
|
||||
authorNameColor = UIColor(hue: hue, saturation: saturation * 0.7, brightness: min(1.0, brightness * 1.2), alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
} else {
|
||||
mainColor = presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var boundingSize = textFrame.size
|
||||
if titleBeforeMedia {
|
||||
boundingSize.height += topTitleLayout.size.height + 4.0
|
||||
@ -854,10 +855,29 @@ public final class ChatMessageAttachedContentNode: HighlightTrackingButtonNode {
|
||||
backgroundView.image = PresentationResourcesChat.chatReplyBackgroundTemplateImage(presentationData.theme.theme)
|
||||
}
|
||||
backgroundView.tintColor = mainColor
|
||||
|
||||
animation.animator.updateFrame(layer: backgroundView.layer, frame: CGRect(origin: CGPoint(x: 11.0, y: insets.top - 3.0), size: CGSize(width: adjustedBoundingSize.width - 4.0 - insets.right, height: adjustedBoundingSize.height - insets.top - insets.bottom + 4.0)), completion: nil)
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 11.0, y: insets.top - 3.0), size: CGSize(width: adjustedBoundingSize.width - 4.0 - insets.right, height: adjustedBoundingSize.height - insets.top - insets.bottom + 4.0))
|
||||
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
|
||||
backgroundView.isHidden = !displayLine
|
||||
|
||||
if let secondaryColor {
|
||||
let lineDashView: UIImageView
|
||||
if let current = strongSelf.lineDashView {
|
||||
lineDashView = current
|
||||
} else {
|
||||
lineDashView = UIImageView(image: PresentationResourcesChat.chatReplyLineDashTemplateImage(presentationData.theme.theme))
|
||||
strongSelf.lineDashView = lineDashView
|
||||
strongSelf.view.insertSubview(lineDashView, aboveSubview: backgroundView)
|
||||
}
|
||||
lineDashView.tintColor = secondaryColor
|
||||
lineDashView.frame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: 3.0, height: backgroundFrame.height))
|
||||
} else {
|
||||
if let lineDashView = strongSelf.lineDashView {
|
||||
strongSelf.lineDashView = nil
|
||||
lineDashView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
//strongSelf.borderColor = UIColor.red.cgColor
|
||||
//strongSelf.borderWidth = 2.0
|
||||
|
||||
|
@ -1836,13 +1836,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
if initialDisplayHeader && displayAuthorInfo {
|
||||
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil {
|
||||
authorNameString = EnginePeer(peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||
authorNameColor = chatMessagePeerIdColors[Int(clamping: peer.id.id._internalGetInt64Value() % 7)]
|
||||
authorNameColor = (peer as Peer).nameColor?.color
|
||||
} else if let effectiveAuthor = effectiveAuthor {
|
||||
authorNameString = EnginePeer(effectiveAuthor).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||
|
||||
let nameColor: UIColor
|
||||
if incoming {
|
||||
nameColor = chatMessagePeerIdColors[Int(clamping: effectiveAuthor.id.id._internalGetInt64Value() % 7)]
|
||||
nameColor = (effectiveAuthor.nameColor ?? .blue).color
|
||||
} else {
|
||||
nameColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private let backgroundView: UIImageView
|
||||
private var lineDashView: UIImageView?
|
||||
private var quoteIconView: UIImageView?
|
||||
private let contentNode: ASDisplayNode
|
||||
private var titleNode: TextNode?
|
||||
@ -216,11 +217,13 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
let dustColor: UIColor
|
||||
|
||||
var authorNameColor: UIColor?
|
||||
var dashSecondaryColor: UIColor?
|
||||
|
||||
let author = arguments.message?.effectiveAuthor
|
||||
|
||||
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(arguments.parentMessage.id.peerId.namespace) && author?.id.namespace == Namespaces.Peer.CloudUser {
|
||||
authorNameColor = author.flatMap { chatMessagePeerIdColors[Int(clamping: $0.id.id._internalGetInt64Value() % 7)] }
|
||||
authorNameColor = author?.nameColor?.color
|
||||
dashSecondaryColor = author?.nameColor?.dashColors.1
|
||||
if let rawAuthorNameColor = authorNameColor {
|
||||
var dimColors = false
|
||||
switch arguments.presentationData.theme.theme.name {
|
||||
@ -240,6 +243,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
let mainColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
|
||||
switch arguments.type {
|
||||
case let .bubble(incoming):
|
||||
@ -247,6 +251,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
if incoming {
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
secondaryColor = dashSecondaryColor
|
||||
} else {
|
||||
mainColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
}
|
||||
@ -608,6 +613,24 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
node.backgroundView.tintColor = mainColor
|
||||
node.backgroundView.frame = backgroundFrame
|
||||
|
||||
if let secondaryColor {
|
||||
let lineDashView: UIImageView
|
||||
if let current = node.lineDashView {
|
||||
lineDashView = current
|
||||
} else {
|
||||
lineDashView = UIImageView(image: PresentationResourcesChat.chatReplyLineDashTemplateImage(arguments.presentationData.theme.theme))
|
||||
node.lineDashView = lineDashView
|
||||
node.contentNode.view.addSubview(lineDashView)
|
||||
}
|
||||
lineDashView.tintColor = secondaryColor
|
||||
lineDashView.frame = CGRect(origin: .zero, size: CGSize(width: 3.0, height: backgroundFrame.height))
|
||||
} else {
|
||||
if let lineDashView = node.lineDashView {
|
||||
node.lineDashView = nil
|
||||
lineDashView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if arguments.quote != nil || arguments.replyForward?.quote != nil {
|
||||
let quoteIconView: UIImageView
|
||||
if let current = node.quoteIconView {
|
||||
|
@ -0,0 +1,31 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PeerNameColorScreen",
|
||||
module_name = "PeerNameColorScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/WallpaperBackgroundNode",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/SolidRoundedButtonNode",
|
||||
"//submodules/AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,134 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import SolidRoundedButtonNode
|
||||
import AppBundle
|
||||
|
||||
final class ApplyColorFooterItem: ItemListControllerFooterItem {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let locked: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(theme: PresentationTheme, title: String, locked: Bool, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.locked = locked
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func isEqual(to: ItemListControllerFooterItem) -> Bool {
|
||||
if let item = to as? ApplyColorFooterItem {
|
||||
return self.theme === item.theme && self.title == item.title && self.locked == item.locked
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func node(current: ItemListControllerFooterItemNode?) -> ItemListControllerFooterItemNode {
|
||||
if let current = current as? ApplyColorFooterItemNode {
|
||||
current.item = self
|
||||
return current
|
||||
} else {
|
||||
return ApplyColorFooterItemNode(item: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ApplyColorFooterItemNode: ItemListControllerFooterItemNode {
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
var item: ApplyColorFooterItem {
|
||||
didSet {
|
||||
self.updateItem()
|
||||
if let layout = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(item: ApplyColorFooterItem) {
|
||||
self.item = item
|
||||
|
||||
self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.tabBar.backgroundColor)
|
||||
self.separatorNode = ASDisplayNode()
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0)
|
||||
self.buttonNode.icon = item.locked ? UIImage(bundleImageName: "Chat/Stickers/Lock") : nil
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.updateItem()
|
||||
}
|
||||
|
||||
private func updateItem() {
|
||||
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||
self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor
|
||||
|
||||
let backgroundColor = self.item.theme.list.itemCheckColors.fillColor
|
||||
let textColor = self.item.theme.list.itemCheckColors.foregroundColor
|
||||
|
||||
self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: backgroundColor, backgroundColors: [], foregroundColor: textColor), animated: true)
|
||||
self.buttonNode.title = self.item.title
|
||||
self.buttonNode.icon = self.item.locked ? UIImage(bundleImageName: "Chat/Stickers/Lock") : nil
|
||||
|
||||
self.buttonNode.pressed = { [weak self] in
|
||||
self?.item.action()
|
||||
}
|
||||
}
|
||||
|
||||
override func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: alpha)
|
||||
transition.updateAlpha(node: self.separatorNode, alpha: alpha)
|
||||
}
|
||||
|
||||
override func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = layout
|
||||
|
||||
let buttonInset: CGFloat = 16.0
|
||||
let buttonWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - buttonInset * 2.0
|
||||
let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition)
|
||||
let inset: CGFloat = 9.0
|
||||
|
||||
let insets = layout.insets(options: [.input])
|
||||
|
||||
var panelHeight: CGFloat = buttonHeight + inset * 2.0
|
||||
let totalPanelHeight: CGFloat
|
||||
if let inputHeight = layout.inputHeight, inputHeight > 0.0 {
|
||||
totalPanelHeight = panelHeight + insets.bottom
|
||||
} else {
|
||||
panelHeight += insets.bottom
|
||||
totalPanelHeight = panelHeight
|
||||
}
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - totalPanelHeight), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: panelFrame.minY + inset), size: CGSize(width: buttonWidth, height: buttonHeight)))
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: panelFrame)
|
||||
self.backgroundNode.update(size: panelFrame.size, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: UIScreenPixel)))
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
if self.backgroundNode.frame.contains(point) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,429 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import ItemListUI
|
||||
import EmojiStatusComponent
|
||||
import ComponentFlow
|
||||
import AccountContext
|
||||
|
||||
enum ItemListReactionArrowStyle {
|
||||
case arrow
|
||||
case none
|
||||
}
|
||||
|
||||
final class BackgroundEmojiItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let presentationData: ItemListPresentationData
|
||||
let icon: UIImage?
|
||||
let title: String
|
||||
let arrowStyle: ItemListReactionArrowStyle
|
||||
let reaction: MessageReaction.Reaction
|
||||
let availableReactions: AvailableReactions?
|
||||
public let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let action: (() -> Void)?
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(context: AccountContext, presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, arrowStyle: ItemListReactionArrowStyle = .none, reaction: MessageReaction.Reaction, availableReactions: AvailableReactions?, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, tag: ItemListItemTag? = nil) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
self.arrowStyle = arrowStyle
|
||||
self.reaction = reaction
|
||||
self.availableReactions = availableReactions
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
self.action = action
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
Queue.mainQueue().async {
|
||||
let node = BackgroundEmojiItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? BackgroundEmojiItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var selectable: Bool = true
|
||||
|
||||
public func selected(listView: ListView){
|
||||
listView.clearHighlightAnimated(true)
|
||||
self.action?()
|
||||
}
|
||||
}
|
||||
|
||||
private let badgeFont = Font.regular(15.0)
|
||||
|
||||
final class BackgroundEmojiItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
let iconNode: ASImageNode
|
||||
let titleNode: TextNode
|
||||
let arrowNode: ASImageNode
|
||||
let iconView: ComponentHostView<Empty>
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: BackgroundEmojiItem?
|
||||
private var fileDisposable: Disposable?
|
||||
private var file: TelegramMediaFile?
|
||||
|
||||
override var canBeSelected: Bool {
|
||||
if let item = self.item, let _ = item.action {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.isUserInteractionEnabled = false
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
|
||||
self.iconView = ComponentHostView<Empty>()
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.isLayerBacked = true
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.view.addSubview(self.iconView)
|
||||
self.addSubnode(self.arrowNode)
|
||||
|
||||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.fileDisposable?.dispose()
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: BackgroundEmojiItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
var rightInset: CGFloat
|
||||
rightInset = 34.0 + params.rightInset
|
||||
let _ = rightInset
|
||||
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
let itemBackgroundColor: UIColor
|
||||
let itemSeparatorColor: UIColor
|
||||
|
||||
var updatedTheme: PresentationTheme?
|
||||
var updateArrowImage: UIImage?
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||
}
|
||||
|
||||
var updateIcon = false
|
||||
if currentItem?.icon != item.icon {
|
||||
updateIcon = true
|
||||
}
|
||||
|
||||
var leftInset = 16.0 + params.leftInset
|
||||
if item.icon != nil {
|
||||
leftInset += 43.0
|
||||
}
|
||||
|
||||
var additionalTextRightInset: CGFloat = 0.0
|
||||
additionalTextRightInset += 44.0
|
||||
if item.arrowStyle == .arrow {
|
||||
additionalTextRightInset += 24.0
|
||||
}
|
||||
|
||||
let titleColor: UIColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||
|
||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - additionalTextRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let verticalInset: CGFloat = 11.0
|
||||
|
||||
let height: CGFloat
|
||||
height = verticalInset * 2.0 + titleLayout.size.height
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
contentSize = CGSize(width: params.width, height: height)
|
||||
insets = itemListNeighborsPlainInsets(neighbors)
|
||||
case .blocks:
|
||||
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
contentSize = CGSize(width: params.width, height: height)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
}
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||
strongSelf.activateArea.accessibilityLabel = item.title
|
||||
|
||||
strongSelf.activateArea.accessibilityTraits = []
|
||||
|
||||
if let icon = item.icon {
|
||||
if strongSelf.iconNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.iconNode)
|
||||
}
|
||||
if updateIcon {
|
||||
strongSelf.iconNode.image = icon
|
||||
}
|
||||
let iconY = floor((layout.contentSize.height - icon.size.height) / 2.0)
|
||||
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - icon.size.width) / 2.0), y: iconY), size: icon.size)
|
||||
} else if strongSelf.iconNode.supernode != nil {
|
||||
strongSelf.iconNode.image = nil
|
||||
strongSelf.iconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
|
||||
}
|
||||
|
||||
if let updateArrowImage = updateArrowImage {
|
||||
strongSelf.arrowNode.image = updateArrowImage
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
if strongSelf.backgroundNode.supernode != nil {
|
||||
strongSelf.backgroundNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode != nil {
|
||||
strongSelf.topStripeNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
|
||||
}
|
||||
if strongSelf.maskNode.supernode != nil {
|
||||
strongSelf.maskNode.removeFromSupernode()
|
||||
}
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
||||
case .blocks:
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||
}
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = titleFrame
|
||||
|
||||
var animationContent: EmojiStatusComponent.AnimationContent?
|
||||
switch item.reaction {
|
||||
case .builtin:
|
||||
if let availableReactions = item.availableReactions {
|
||||
for reaction in availableReactions.reactions {
|
||||
if reaction.value == item.reaction {
|
||||
animationContent = .file(file: reaction.selectAnimation)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .custom(fileId):
|
||||
animationContent = .customEmoji(fileId: fileId)
|
||||
}
|
||||
|
||||
|
||||
var rightInset: CGFloat = 0.0
|
||||
if let arrowImage = strongSelf.arrowNode.image, item.arrowStyle == .arrow {
|
||||
let arrowRightOffset: CGFloat = 7.0
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - arrowRightOffset - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
rightInset += arrowRightOffset + arrowImage.size.width
|
||||
strongSelf.arrowNode.isHidden = false
|
||||
} else {
|
||||
strongSelf.arrowNode.isHidden = true
|
||||
}
|
||||
|
||||
if let animationContent = animationContent {
|
||||
let iconBoundingSize = CGSize(width: 28.0, height: 28.0)
|
||||
let iconOffsetX: CGFloat = 0.0
|
||||
let iconSize = strongSelf.iconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: item.context,
|
||||
animationCache: item.context.animationCache,
|
||||
animationRenderer: item.context.animationRenderer,
|
||||
content: .animation(content: animationContent, size: iconBoundingSize, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .forever),
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: iconBoundingSize
|
||||
)
|
||||
strongSelf.iconView.isUserInteractionEnabled = false
|
||||
strongSelf.iconView.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - iconSize.width + iconOffsetX - rightInset, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
|
||||
}
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||
|
||||
if highlighted {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
if self.highlightedBackgroundNode.supernode == nil {
|
||||
var anchorNode: ASDisplayNode?
|
||||
if self.bottomStripeNode.supernode != nil {
|
||||
anchorNode = self.bottomStripeNode
|
||||
} else if self.topStripeNode.supernode != nil {
|
||||
anchorNode = self.topStripeNode
|
||||
} else if self.backgroundNode.supernode != nil {
|
||||
anchorNode = self.backgroundNode
|
||||
}
|
||||
if let anchorNode = anchorNode {
|
||||
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||
} else {
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.highlightedBackgroundNode.supernode != nil {
|
||||
if animated {
|
||||
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
||||
if let strongSelf = self {
|
||||
if completed {
|
||||
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
} else {
|
||||
self.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -0,0 +1,373 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import WallpaperBackgroundNode
|
||||
|
||||
class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem {
|
||||
struct MessageItem: Equatable {
|
||||
static func == (lhs: MessageItem, rhs: MessageItem) -> Bool {
|
||||
if lhs.outgoing != rhs.outgoing {
|
||||
return false
|
||||
}
|
||||
if lhs.peerId != rhs.peerId {
|
||||
return false
|
||||
}
|
||||
if lhs.author != rhs.author {
|
||||
return false
|
||||
}
|
||||
if lhs.photo != rhs.photo {
|
||||
return false
|
||||
}
|
||||
if lhs.nameColor != rhs.nameColor {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundEmojiId != rhs.backgroundEmojiId {
|
||||
return false
|
||||
}
|
||||
if let lhsReply = lhs.reply, let rhsReply = rhs.reply, lhsReply.0 != rhsReply.0 || lhsReply.1 != rhsReply.1 {
|
||||
return false
|
||||
} else if (lhs.reply == nil) != (rhs.reply == nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsLinkPreview = lhs.linkPreview, let rhsLinkPreview = rhs.linkPreview, lhsLinkPreview.0 != rhsLinkPreview.0 || lhsLinkPreview.1 != rhsLinkPreview.1 || lhsLinkPreview.2 != rhsLinkPreview.2 {
|
||||
return false
|
||||
} else if (lhs.linkPreview == nil) != (rhs.linkPreview == nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
let outgoing: Bool
|
||||
let peerId: EnginePeer.Id
|
||||
let author: String
|
||||
let photo: [TelegramMediaImageRepresentation]
|
||||
let nameColor: PeerNameColor
|
||||
let backgroundEmojiId: Int64?
|
||||
let reply: (String, String)?
|
||||
let linkPreview: (String, String, String)?
|
||||
let text: String
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let componentTheme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let sectionId: ItemListSectionId
|
||||
let fontSize: PresentationFontSize
|
||||
let chatBubbleCorners: PresentationChatBubbleCorners
|
||||
let wallpaper: TelegramWallpaper
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let messageItems: [MessageItem]
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [MessageItem]) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.componentTheme = componentTheme
|
||||
self.strings = strings
|
||||
self.sectionId = sectionId
|
||||
self.fontSize = fontSize
|
||||
self.chatBubbleCorners = chatBubbleCorners
|
||||
self.wallpaper = wallpaper
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.messageItems = messageItems
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = PeerNameColorChatPreviewItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? PeerNameColorChatPreviewItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
private var backgroundNode: WallpaperBackgroundNode?
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private var messageNodes: [ListViewItemNode]?
|
||||
private var itemHeaderNodes: [ListViewItemNode.HeaderId: ListViewItemHeaderNode] = [:]
|
||||
|
||||
private var item: PeerNameColorChatPreviewItem?
|
||||
private var finalImage = true
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
init() {
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: PeerNameColorChatPreviewItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentNodes = self.messageNodes
|
||||
|
||||
var currentBackgroundNode = self.backgroundNode
|
||||
|
||||
return { item, params, neighbors in
|
||||
if currentBackgroundNode == nil {
|
||||
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
|
||||
}
|
||||
currentBackgroundNode?.update(wallpaper: item.wallpaper)
|
||||
currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners)
|
||||
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1))
|
||||
|
||||
var items: [ListViewItem] = []
|
||||
for messageItem in item.messageItems.reversed() {
|
||||
let authorPeerId = messageItem.peerId
|
||||
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
var messages = SimpleDictionary<MessageId, Message>()
|
||||
|
||||
peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: messageItem.author, lastName: "", username: nil, phone: nil, photo: messageItem.photo, botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: messageItem.nameColor, backgroundEmojiId: messageItem.backgroundEmojiId)
|
||||
|
||||
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
|
||||
if let (_, text) = messageItem.reply {
|
||||
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[authorPeerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
}
|
||||
|
||||
var media: [Media] = []
|
||||
if let (site, title, text) = messageItem.linkPreview {
|
||||
media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: nil, websiteName: site, title: title, text: text, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil, displayOptions: TelegramMediaWebpageDisplayOptions.default))))
|
||||
}
|
||||
|
||||
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)] : [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, isCentered: false))
|
||||
}
|
||||
|
||||
var nodes: [ListViewItemNode] = []
|
||||
if let messageNodes = currentNodes {
|
||||
nodes = messageNodes
|
||||
for i in 0 ..< items.count {
|
||||
let itemNode = messageNodes[i]
|
||||
items[i].updateNode(async: { $0() }, node: {
|
||||
return itemNode
|
||||
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in
|
||||
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height))
|
||||
|
||||
itemNode.contentSize = layout.contentSize
|
||||
itemNode.insets = layout.insets
|
||||
itemNode.frame = nodeFrame
|
||||
itemNode.isUserInteractionEnabled = false
|
||||
|
||||
apply(ListViewItemApply(isOnScreen: true))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
var messageNodes: [ListViewItemNode] = []
|
||||
for i in 0 ..< items.count {
|
||||
var itemNode: ListViewItemNode?
|
||||
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
|
||||
itemNode = node
|
||||
apply().1(ListViewItemApply(isOnScreen: true))
|
||||
})
|
||||
itemNode!.isUserInteractionEnabled = false
|
||||
messageNodes.append(itemNode!)
|
||||
}
|
||||
nodes = messageNodes
|
||||
}
|
||||
|
||||
var contentSize = CGSize(width: params.width, height: 4.0 + 4.0)
|
||||
for node in nodes {
|
||||
contentSize.height += node.frame.size.height
|
||||
}
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
let leftInset = params.leftInset
|
||||
let rightInset = params.leftInset
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||
|
||||
strongSelf.messageNodes = nodes
|
||||
var topOffset: CGFloat = 4.0
|
||||
for node in nodes {
|
||||
if node.supernode == nil {
|
||||
strongSelf.containerNode.addSubnode(node)
|
||||
}
|
||||
node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: topOffset), size: node.frame.size), within: layoutSize)
|
||||
topOffset += node.frame.size.height
|
||||
|
||||
if let header = node.headers()?.last {
|
||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: 7.0), size: CGSize(width: layoutSize.width, height: header.height))
|
||||
let stickLocationDistanceFactor: CGFloat = 0.0
|
||||
|
||||
let id = header.id
|
||||
let headerNode: ListViewItemHeaderNode
|
||||
if let current = strongSelf.itemHeaderNodes[id] {
|
||||
headerNode = current
|
||||
headerNode.updateFrame(headerFrame, within: layoutSize)
|
||||
|
||||
if headerNode.item !== header {
|
||||
header.updateNode(headerNode, previous: nil, next: nil)
|
||||
headerNode.item = header
|
||||
}
|
||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
|
||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
||||
} else {
|
||||
headerNode = header.node(synchronousLoad: false)
|
||||
if headerNode.item !== header {
|
||||
header.updateNode(headerNode, previous: nil, next: nil)
|
||||
headerNode.item = header
|
||||
}
|
||||
headerNode.frame = headerFrame
|
||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
|
||||
strongSelf.itemHeaderNodes[id] = headerNode
|
||||
|
||||
strongSelf.containerNode.addSubnode(headerNode)
|
||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentBackgroundNode = currentBackgroundNode, strongSelf.backgroundNode !== currentBackgroundNode {
|
||||
strongSelf.backgroundNode = currentBackgroundNode
|
||||
strongSelf.insertSubnode(currentBackgroundNode, at: 0)
|
||||
}
|
||||
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
|
||||
let displayMode: WallpaperDisplayMode
|
||||
if abs(params.availableHeight - params.width) < 100.0, params.availableHeight > 700.0 {
|
||||
displayMode = .halfAspectFill
|
||||
} else {
|
||||
if backgroundFrame.width > backgroundFrame.height * 4.0 {
|
||||
if params.availableHeight < 700.0 {
|
||||
displayMode = .halfAspectFill
|
||||
} else {
|
||||
displayMode = .aspectFill
|
||||
}
|
||||
} else {
|
||||
displayMode = .aspectFill
|
||||
}
|
||||
}
|
||||
|
||||
if let backgroundNode = strongSelf.backgroundNode {
|
||||
backgroundNode.frame = backgroundFrame.insetBy(dx: 0.0, dy: -100.0)
|
||||
backgroundNode.updateLayout(size: backgroundNode.bounds.size, displayMode: displayMode, transition: .immediate)
|
||||
}
|
||||
strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -0,0 +1,538 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
|
||||
private enum PeerNameColorEntryId: Hashable {
|
||||
case color(Int32)
|
||||
}
|
||||
|
||||
private enum PeerNameColorEntry: Comparable, Identifiable {
|
||||
case color(Int, PeerNameColor, Bool)
|
||||
|
||||
var stableId: PeerNameColorEntryId {
|
||||
switch self {
|
||||
case let .color(_, color, _):
|
||||
return .color(color.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerNameColorEntry, rhs: PeerNameColorEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .color(lhsIndex, lhsAccentColor, lhsSelected):
|
||||
if case let .color(rhsIndex, rhsAccentColor, rhsSelected) = rhs, lhsIndex == rhsIndex, lhsAccentColor == rhsAccentColor, lhsSelected == rhsSelected {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: PeerNameColorEntry, rhs: PeerNameColorEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .color(lhsIndex, _, _):
|
||||
switch rhs {
|
||||
case let .color(rhsIndex, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(action: @escaping (PeerNameColor) -> Void) -> ListViewItem {
|
||||
switch self {
|
||||
case let .color(_, color, selected):
|
||||
return PeerNameColorIconItem(color: color, selected: selected, action: action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class PeerNameColorIconItem: ListViewItem {
|
||||
let color: PeerNameColor
|
||||
let selected: Bool
|
||||
let action: (PeerNameColor) -> Void
|
||||
|
||||
public init(color: PeerNameColor, selected: Bool, action: @escaping (PeerNameColor) -> Void) {
|
||||
self.color = color
|
||||
self.selected = selected
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = PeerNameColorIconItemNode()
|
||||
let (nodeLayout, apply) = node.asyncLayout()(self, params)
|
||||
node.insets = nodeLayout.insets
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in
|
||||
apply(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
assert(node() is PeerNameColorIconItemNode)
|
||||
if let nodeValue = node() as? PeerNameColorIconItemNode {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (nodeLayout, apply) = layout(self, params)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, { _ in
|
||||
let animated: Bool
|
||||
if case .Crossfade = animation {
|
||||
animated = true
|
||||
} else {
|
||||
animated = false
|
||||
}
|
||||
apply(animated)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var selectable = true
|
||||
public func selected(listView: ListView) {
|
||||
self.action(self.color)
|
||||
}
|
||||
}
|
||||
|
||||
private func generateRingImage(nameColor: PeerNameColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setStrokeColor(nameColor.color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
|
||||
})
|
||||
}
|
||||
|
||||
private func generateFillImage(nameColor: PeerNameColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let circleBounds = bounds
|
||||
context.addEllipse(in: circleBounds)
|
||||
context.clip()
|
||||
|
||||
let (firstColor, secondColor) = nameColor.dashColors
|
||||
if let secondColor {
|
||||
context.setFillColor(secondColor.cgColor)
|
||||
context.fill(circleBounds)
|
||||
|
||||
context.move(to: .zero)
|
||||
context.addLine(to: CGPoint(x: size.width, y: 0.0))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: size.height))
|
||||
context.closePath()
|
||||
context.setFillColor(firstColor.cgColor)
|
||||
context.fillPath()
|
||||
} else {
|
||||
context.setFillColor(firstColor.cgColor)
|
||||
context.fill(circleBounds)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private final class PeerNameColorIconItemNode : ListViewItemNode {
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let fillNode: ASImageNode
|
||||
private let ringNode: ASImageNode
|
||||
|
||||
var item: PeerNameColorIconItem?
|
||||
|
||||
init() {
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
|
||||
self.fillNode = ASImageNode()
|
||||
self.fillNode.displaysAsynchronously = false
|
||||
self.fillNode.displayWithoutProcessing = true
|
||||
|
||||
self.ringNode = ASImageNode()
|
||||
self.ringNode.displaysAsynchronously = false
|
||||
self.ringNode.displayWithoutProcessing = true
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.ringNode)
|
||||
self.containerNode.addSubnode(self.fillNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate
|
||||
if selected {
|
||||
transition.updateTransformScale(node: self.fillNode, scale: 0.8)
|
||||
} else {
|
||||
transition.updateTransformScale(node: self.fillNode, scale: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (PeerNameColorIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let currentItem = self.item
|
||||
|
||||
return { [weak self] item, params in
|
||||
var updatedAccentColor = false
|
||||
var updatedSelected = false
|
||||
|
||||
if currentItem == nil || currentItem?.color != item.color {
|
||||
updatedAccentColor = true
|
||||
}
|
||||
if currentItem?.selected != item.selected {
|
||||
updatedSelected = true
|
||||
}
|
||||
|
||||
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 60.0, height: 56.0), insets: UIEdgeInsets())
|
||||
return (itemLayout, { animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
if updatedAccentColor {
|
||||
strongSelf.fillNode.image = generateFillImage(nameColor: item.color)
|
||||
strongSelf.ringNode.image = generateRingImage(nameColor: item.color)
|
||||
}
|
||||
|
||||
let center = CGPoint(x: 30.0, y: 28.0)
|
||||
let bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 40.0, height: 40.0))
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.contentSize)
|
||||
|
||||
strongSelf.fillNode.position = center
|
||||
strongSelf.ringNode.position = center
|
||||
|
||||
strongSelf.fillNode.bounds = bounds
|
||||
strongSelf.ringNode.bounds = bounds
|
||||
|
||||
if updatedSelected {
|
||||
strongSelf.setSelected(item.selected, animated: !updatedAccentColor && currentItem != nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
super.animateInsertion(currentTimestamp, duration: duration, short: short)
|
||||
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
super.animateRemoved(currentTimestamp, duration: duration)
|
||||
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||
super.animateAdded(currentTimestamp, duration: duration)
|
||||
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerNameColorItem: ListViewItem, ItemListItem {
|
||||
var sectionId: ItemListSectionId
|
||||
|
||||
let theme: PresentationTheme
|
||||
let colors: [PeerNameColor]
|
||||
let currentColor: PeerNameColor
|
||||
let updated: (PeerNameColor) -> Void
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: PresentationTheme, colors: [PeerNameColor], currentColor: PeerNameColor, updated: @escaping (PeerNameColor) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.colors = colors
|
||||
self.currentColor = currentColor
|
||||
self.updated = updated
|
||||
self.tag = tag
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = PeerNameColorItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? PeerNameColorItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PeerNameColorItemNodeTransition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let updatePosition: Bool
|
||||
}
|
||||
|
||||
private func preparedTransition(action: @escaping (PeerNameColor) -> Void, from fromEntries: [PeerNameColorEntry], to toEntries: [PeerNameColorEntry], updatePosition: Bool) -> PeerNameColorItemNodeTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(action: action), directionHint: .Down) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(action: action), directionHint: nil) }
|
||||
|
||||
return PeerNameColorItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, updatePosition: updatePosition)
|
||||
}
|
||||
|
||||
private func ensureColorVisible(listNode: ListView, color: PeerNameColor, animated: Bool) -> Bool {
|
||||
var resultNode: PeerNameColorIconItemNode?
|
||||
listNode.forEachItemNode { node in
|
||||
if resultNode == nil, let node = node as? PeerNameColorIconItemNode {
|
||||
if node.item?.color == color {
|
||||
resultNode = node
|
||||
}
|
||||
}
|
||||
}
|
||||
if let resultNode = resultNode {
|
||||
listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 24.0)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let listNode: ListView
|
||||
private var entries: [PeerNameColorEntry]?
|
||||
private var enqueuedTransitions: [PeerNameColorItemNodeTransition] = []
|
||||
private var initialized = false
|
||||
|
||||
private var item: PeerNameColorItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
private var tapping = false
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.listNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: PeerNameColorItemNodeTransition) {
|
||||
self.enqueuedTransitions.append(transition)
|
||||
|
||||
if let _ = self.item {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransition() {
|
||||
guard let item = self.item, let transition = self.enqueuedTransitions.first else {
|
||||
return
|
||||
}
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
let options = ListViewDeleteAndInsertOptions()
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if !self.initialized || transition.updatePosition || !self.tapping {
|
||||
if let index = item.colors.firstIndex(where: { $0 == item.currentColor }) {
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-70.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
|
||||
self.initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: PeerNameColorItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
var themeUpdated = false
|
||||
if currentItem?.theme !== item.theme {
|
||||
themeUpdated = true
|
||||
}
|
||||
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
contentSize = CGSize(width: params.width, height: 60.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutParams = params
|
||||
|
||||
if themeUpdated {
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
}
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height)
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
var listInsets = UIEdgeInsets()
|
||||
listInsets.top += params.leftInset + 8.0
|
||||
listInsets.bottom += params.rightInset + 8.0
|
||||
|
||||
strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width)
|
||||
strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0)
|
||||
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
var entries: [PeerNameColorEntry] = []
|
||||
|
||||
var index: Int = 0
|
||||
for color in item.colors {
|
||||
entries.append(.color(index, color, color == item.currentColor))
|
||||
index += 1
|
||||
}
|
||||
|
||||
let action: (PeerNameColor) -> Void = { [weak self] color in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.tapping = true
|
||||
item.updated(color)
|
||||
Queue.mainQueue().after(0.4) {
|
||||
self.tapping = false
|
||||
}
|
||||
let _ = ensureColorVisible(listNode: self.listNode, color: color, animated: true)
|
||||
}
|
||||
|
||||
let previousEntries = strongSelf.entries ?? []
|
||||
let updatePosition = currentItem != nil && previousEntries.count != entries.count
|
||||
let transition = preparedTransition(action: action, from: previousEntries, to: entries, updatePosition: updatePosition)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
|
||||
strongSelf.entries = entries
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -0,0 +1,471 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import UndoUI
|
||||
|
||||
private final class PeerNameColorScreenArguments {
|
||||
let context: AccountContext
|
||||
let updateNameColor: (PeerNameColor) -> Void
|
||||
let openBackgroundEmoji: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
updateNameColor: @escaping (PeerNameColor) -> Void,
|
||||
openBackgroundEmoji: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.updateNameColor = updateNameColor
|
||||
self.openBackgroundEmoji = openBackgroundEmoji
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerNameColorScreenSection: Int32 {
|
||||
case nameColor
|
||||
case backgroundEmoji
|
||||
}
|
||||
|
||||
private enum PeerNameColorScreenEntry: ItemListNodeEntry {
|
||||
enum StableId: Hashable {
|
||||
case colorHeader
|
||||
case colorMessage
|
||||
case colorPicker
|
||||
case colorDescription
|
||||
case backgroundEmoji
|
||||
case backgroundEmojiDescription
|
||||
}
|
||||
|
||||
case colorHeader(String)
|
||||
case colorMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, items: [PeerNameColorChatPreviewItem.MessageItem])
|
||||
case colorPicker(colors: [PeerNameColor], currentColor: PeerNameColor)
|
||||
case colorDescription(String)
|
||||
case backgroundEmoji(String, MessageReaction.Reaction, AvailableReactions)
|
||||
case backgroundEmojiDescription(String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .colorHeader, .colorMessage, .colorPicker, .colorDescription:
|
||||
return PeerNameColorScreenSection.nameColor.rawValue
|
||||
case .backgroundEmoji, .backgroundEmojiDescription:
|
||||
return PeerNameColorScreenSection.backgroundEmoji.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: StableId {
|
||||
switch self {
|
||||
case .colorHeader:
|
||||
return .colorHeader
|
||||
case .colorMessage:
|
||||
return .colorMessage
|
||||
case .colorPicker:
|
||||
return .colorPicker
|
||||
case .colorDescription:
|
||||
return .colorDescription
|
||||
case .backgroundEmoji:
|
||||
return .backgroundEmoji
|
||||
case .backgroundEmojiDescription:
|
||||
return .backgroundEmojiDescription
|
||||
}
|
||||
}
|
||||
|
||||
var sortId: Int {
|
||||
switch self {
|
||||
case .colorHeader:
|
||||
return 0
|
||||
case .colorMessage:
|
||||
return 1
|
||||
case .colorPicker:
|
||||
return 2
|
||||
case .colorDescription:
|
||||
return 3
|
||||
case .backgroundEmoji:
|
||||
return 4
|
||||
case .backgroundEmojiDescription:
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerNameColorScreenEntry, rhs: PeerNameColorScreenEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .colorHeader(text):
|
||||
if case .colorHeader(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .colorMessage(lhsWallpaper, lhsFontSize, lhsBubbleCorners, lhsDateTimeFormat, lhsNameDisplayOrder, lhsItems):
|
||||
if case let .colorMessage(rhsWallpaper, rhsFontSize, rhsBubbleCorners, rhsDateTimeFormat, rhsNameDisplayOrder, rhsItems) = rhs, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsBubbleCorners == rhsBubbleCorners, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, lhsItems == rhsItems {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .colorPicker(lhsColors, lhsCurrentColor):
|
||||
if case let .colorPicker(rhsColors, rhsCurrentColor) = rhs, lhsColors == rhsColors, lhsCurrentColor == rhsCurrentColor {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .colorDescription(text):
|
||||
if case .colorDescription(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .backgroundEmoji(lhsText, lhsReaction, lhsAvailableReactions):
|
||||
if case let .backgroundEmoji(rhsText, rhsReaction, rhsAvailableReactions) = rhs, lhsText == rhsText, lhsReaction == rhsReaction, lhsAvailableReactions == rhsAvailableReactions {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .backgroundEmojiDescription(text):
|
||||
if case .backgroundEmojiDescription(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: PeerNameColorScreenEntry, rhs: PeerNameColorScreenEntry) -> Bool {
|
||||
return lhs.sortId < rhs.sortId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! PeerNameColorScreenArguments
|
||||
switch self {
|
||||
case let .colorHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .colorMessage(wallpaper, fontSize, chatBubbleCorners, dateTimeFormat, nameDisplayOrder, items):
|
||||
return PeerNameColorChatPreviewItem(
|
||||
context: arguments.context,
|
||||
theme: presentationData.theme,
|
||||
componentTheme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
sectionId: self.section,
|
||||
fontSize: fontSize,
|
||||
chatBubbleCorners: chatBubbleCorners,
|
||||
wallpaper: wallpaper,
|
||||
dateTimeFormat: dateTimeFormat,
|
||||
nameDisplayOrder: nameDisplayOrder,
|
||||
messageItems: items)
|
||||
case let .colorPicker(colors, currentColor):
|
||||
return PeerNameColorItem(
|
||||
theme: presentationData.theme,
|
||||
colors: colors,
|
||||
currentColor: currentColor,
|
||||
updated: { color in
|
||||
arguments.updateNameColor(color)
|
||||
},
|
||||
sectionId: self.section
|
||||
)
|
||||
case let .colorDescription(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .backgroundEmoji(title, reaction, availableReactions):
|
||||
return BackgroundEmojiItem(
|
||||
context: arguments.context,
|
||||
presentationData: presentationData,
|
||||
title: title,
|
||||
reaction: reaction,
|
||||
availableReactions: availableReactions,
|
||||
sectionId: self.section,
|
||||
style: .blocks,
|
||||
action: {
|
||||
arguments.openBackgroundEmoji()
|
||||
})
|
||||
case let .backgroundEmojiDescription(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PeerNameColorScreenState: Equatable {
|
||||
var updatedNameColor: PeerNameColor?
|
||||
}
|
||||
|
||||
private func peerNameColorScreenEntries(
|
||||
presentationData: PresentationData,
|
||||
state: PeerNameColorScreenState,
|
||||
peer: EnginePeer?,
|
||||
isPremium: Bool
|
||||
) -> [PeerNameColorScreenEntry] {
|
||||
var entries: [PeerNameColorScreenEntry] = []
|
||||
|
||||
if let peer {
|
||||
var allColors: [PeerNameColor] = [
|
||||
.blue
|
||||
]
|
||||
allColors.append(contentsOf: PeerNameColor.allCases.filter { $0 != .blue})
|
||||
allColors.removeLast(3)
|
||||
|
||||
let nameColor: PeerNameColor
|
||||
if let updatedNameColor = state.updatedNameColor {
|
||||
nameColor = updatedNameColor
|
||||
} else if let peerNameColor = peer.nameColor {
|
||||
nameColor = peerNameColor
|
||||
} else {
|
||||
nameColor = .blue
|
||||
}
|
||||
let messageItem = PeerNameColorChatPreviewItem.MessageItem(
|
||||
outgoing: false,
|
||||
peerId: peer.id,
|
||||
author: peer.compactDisplayTitle,
|
||||
photo: peer.profileImageRepresentations,
|
||||
nameColor: nameColor,
|
||||
backgroundEmojiId: nil,
|
||||
reply: (peer.compactDisplayTitle, presentationData.strings.NameColor_ChatPreview_ReplyText),
|
||||
linkPreview: (presentationData.strings.NameColor_ChatPreview_LinkSite, presentationData.strings.NameColor_ChatPreview_LinkTitle, presentationData.strings.NameColor_ChatPreview_LinkText),
|
||||
text: presentationData.strings.NameColor_ChatPreview_MessageText
|
||||
)
|
||||
|
||||
entries.append(.colorHeader(presentationData.strings.NameColor_ChatPreview_Title))
|
||||
entries.append(.colorMessage(
|
||||
wallpaper: presentationData.chatWallpaper,
|
||||
fontSize: presentationData.chatFontSize,
|
||||
bubbleCorners: presentationData.chatBubbleCorners,
|
||||
dateTimeFormat: presentationData.dateTimeFormat,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
items: [messageItem]
|
||||
))
|
||||
entries.append(.colorPicker(
|
||||
colors: allColors,
|
||||
currentColor: nameColor
|
||||
))
|
||||
entries.append(.colorDescription(presentationData.strings.NameColor_ChatPreview_Description_Account))
|
||||
}
|
||||
|
||||
// entries.append(.backgroundEmoji(presentationData.strings.Settings_QuickReactionSetup_ChooseQuickReaction, reactionSettings.quickReaction, availableReactions))
|
||||
// entries.append(.backgroundEmojiDescription(presentationData.strings.Settings_QuickReactionSetup_ChooseQuickReactionInfo))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public enum PeerNameColorScreenSubject {
|
||||
case account
|
||||
case channel(EnginePeer.Id)
|
||||
}
|
||||
|
||||
public func PeerNameColorScreen(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||
subject: PeerNameColorScreenSubject
|
||||
) -> ViewController {
|
||||
let statePromise = ValuePromise(PeerNameColorScreenState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: PeerNameColorScreenState())
|
||||
let updateState: ((PeerNameColorScreenState) -> PeerNameColorScreenState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var presentImpl: ((ViewController) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
// var openQuickReactionImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let arguments = PeerNameColorScreenArguments(
|
||||
context: context,
|
||||
updateNameColor: { color in
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updatedNameColor = color
|
||||
return updatedState
|
||||
}
|
||||
},
|
||||
openBackgroundEmoji: {
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
let peerId: EnginePeer.Id
|
||||
switch subject {
|
||||
case .account:
|
||||
peerId = context.account.peerId
|
||||
case let .channel(channelId):
|
||||
peerId = channelId
|
||||
}
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
presentationData,
|
||||
statePromise.get(),
|
||||
context.engine.stickers.availableReactions(),
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, availableReactions, peer -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let isPremium = peer?.isPremium ?? false
|
||||
let title: String
|
||||
switch subject {
|
||||
case .account:
|
||||
title = presentationData.strings.NameColor_Title_Account
|
||||
case .channel:
|
||||
title = presentationData.strings.NameColor_Title_Channel
|
||||
}
|
||||
|
||||
let footerItem = ApplyColorFooterItem(
|
||||
theme: presentationData.theme,
|
||||
title: presentationData.strings.NameColor_ApplyColor,
|
||||
locked: !isPremium,
|
||||
action: {
|
||||
if isPremium {
|
||||
let state = stateValue.with { $0 }
|
||||
if let nameColor = state.updatedNameColor {
|
||||
let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor, backgroundEmojiId: nil).startStandalone()
|
||||
}
|
||||
dismissImpl?()
|
||||
} else {
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .premiumPaywall(
|
||||
title: nil,
|
||||
text: presentationData.strings.NameColor_TooltipPremium_Account,
|
||||
customUndoText: nil,
|
||||
timeout: nil,
|
||||
linkAction: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { action in
|
||||
if case .info = action {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesSuggestedReactions, forceDark: false, dismissed: nil)
|
||||
pushImpl?(controller)
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
presentImpl?(controller)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let entries = peerNameColorScreenEntries(
|
||||
presentationData: presentationData,
|
||||
state: state,
|
||||
peer: peer,
|
||||
isPremium: isPremium
|
||||
)
|
||||
|
||||
let controllerState = ItemListControllerState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
title: .text(title),
|
||||
leftNavigationButton: nil,
|
||||
rightNavigationButton: nil,
|
||||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
||||
animateChanges: false
|
||||
)
|
||||
let listState = ItemListNodeState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
entries: entries,
|
||||
style: .blocks,
|
||||
footerItem: footerItem,
|
||||
animateChanges: true
|
||||
)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
// openQuickReactionImpl = { [weak controller] in
|
||||
// let _ = (combineLatest(queue: .mainQueue(),
|
||||
// settings,
|
||||
// context.engine.stickers.availableReactions()
|
||||
// )
|
||||
// |> take(1)
|
||||
// |> deliverOnMainQueue).start(next: { settings, availableReactions in
|
||||
// var currentSelectedFileId: MediaId?
|
||||
// switch settings.quickReaction {
|
||||
// case .builtin:
|
||||
// if let availableReactions = availableReactions {
|
||||
// if let reaction = availableReactions.reactions.first(where: { $0.value == settings.quickReaction }) {
|
||||
// currentSelectedFileId = reaction.selectAnimation.fileId
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// case let .custom(fileId):
|
||||
// currentSelectedFileId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
// }
|
||||
//
|
||||
// var selectedItems = Set<MediaId>()
|
||||
// if let currentSelectedFileId = currentSelectedFileId {
|
||||
// selectedItems.insert(currentSelectedFileId)
|
||||
// }
|
||||
//
|
||||
// guard let controller = controller else {
|
||||
// return
|
||||
// }
|
||||
// var sourceItemNode: ItemListReactionItemNode?
|
||||
// controller.forEachItemNode { itemNode in
|
||||
// if let itemNode = itemNode as? ItemListReactionItemNode {
|
||||
// sourceItemNode = itemNode
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if let sourceItemNode = sourceItemNode {
|
||||
// controller.present(EmojiStatusSelectionController(
|
||||
// context: context,
|
||||
// mode: .quickReactionSelection(completion: {
|
||||
// updateState { state in
|
||||
// var state = state
|
||||
// state.hasReaction = false
|
||||
// return state
|
||||
// }
|
||||
// }),
|
||||
// sourceView: sourceItemNode.iconView,
|
||||
// emojiContent: EmojiPagerContentComponent.emojiInputData(
|
||||
// context: context,
|
||||
// animationCache: context.animationCache,
|
||||
// animationRenderer: context.animationRenderer,
|
||||
// isStandalone: false,
|
||||
// isStatusSelection: false,
|
||||
// isReactionSelection: true,
|
||||
// isEmojiSelection: false,
|
||||
// hasTrending: false,
|
||||
// isQuickReactionSelection: true,
|
||||
// topReactionItems: [],
|
||||
// areUnicodeEmojiEnabled: false,
|
||||
// areCustomEmojiEnabled: true,
|
||||
// chatPeerId: context.account.peerId,
|
||||
// selectedItems: selectedItems
|
||||
// ),
|
||||
// currentSelection: nil,
|
||||
// destinationItemView: { [weak sourceItemNode] in
|
||||
// return sourceItemNode?.iconView
|
||||
// }
|
||||
// ), in: .window(.root))
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
presentImpl = { [weak controller] c in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.present(c, in: .current)
|
||||
}
|
||||
pushImpl = { [weak controller] c in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -1312,7 +1312,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
var contentHeight: CGFloat = 20.0
|
||||
|
||||
let margin: CGFloat = 12.0
|
||||
let leftMargin = 12.0 + layout.insets(options: []).left
|
||||
let leftMargin = margin + layout.insets(options: []).left
|
||||
|
||||
let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
let buttonMinX: CGFloat
|
||||
|
Loading…
x
Reference in New Issue
Block a user