Various improvements

This commit is contained in:
Ilya Laktyushin 2023-10-20 21:56:12 +04:00
parent 56db74612e
commit 2107f94bc3
31 changed files with 2512 additions and 202 deletions

View File

@ -994,7 +994,7 @@ public enum PremiumLimitSubject {
case expiringStories
case storiesWeekly
case storiesMonthly
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, boosted: Bool)
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32)
}
public protocol ComposeController: ViewController {

View File

@ -59,7 +59,10 @@ func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewL
modalStack[modalStack.count - 1].controllers.append(controller)
}
} else if !modalStack.isEmpty {
controller._presentedInModal = true
if modalStack[modalStack.count - 1].isFlat {
} else {
controller._presentedInModal = true
}
if modalStack[modalStack.count - 1].isStandalone {
modalStack.append(ModalContainerLayout(controllers: [controller], isFlat: isFlat, isStandalone: isStandalone))
} else {

View File

@ -107,6 +107,7 @@ swift_library(
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
"//submodules/CountrySelectionUI",
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
],
visibility = [
"//visibility:public",

View File

@ -325,7 +325,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case let .header(_, title, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(title + text), sectionId: self.section)
case let .createGiveaway(_, title, subtitle, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: .blue, name: "Premium/Giveaway"), title: title, subtitle: subtitle, isSelected: isSelected, sectionId: self.section, action: {
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .blue, name: "Premium/Giveaway"), title: title, subtitle: subtitle, isSelected: isSelected, sectionId: self.section, action: {
arguments.updateState { state in
var updatedState = state
updatedState.mode = .giveaway
@ -333,7 +333,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
}
})
case let .awardUsers(_, title, subtitle, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: .violet, name: "Media Editor/Privacy/SelectedUsers"), title: title, subtitle: subtitle, subtitleActive: true, isSelected: isSelected, sectionId: self.section, action: {
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .violet, name: "Media Editor/Privacy/SelectedUsers"), title: title, subtitle: subtitle, subtitleActive: true, isSelected: isSelected, sectionId: self.section, action: {
var openSelection = false
arguments.updateState { state in
var updatedState = state
@ -361,7 +361,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
default:
color = .blue
}
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: nil)
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: nil)
case let .subscriptionsHeader(_, text, additionalText):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
case let .subscriptions(_, value):
@ -1056,7 +1056,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let stateContext = ShareWithPeersScreen.StateContext(
context: context,
subject: .channels(exclude: Set([peerId])),
subject: .channels(exclude: Set([peerId]), searchQuery: nil),
initialPeerIds: Set(state.channels.filter { $0 != peerId })
)
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in

View File

@ -75,21 +75,6 @@ final class CreateGiveawayFooterItemNode: ItemListControllerFooterItemNode {
private func updateItem() {
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor
// if self.item.isLoading != self.currentIsLoading {
// self.currentIsLoading = self.item.isLoading
//
// if self.currentIsLoading {
// self.buttonNode.transitionToProgress()
// } else {
// self.buttonNode.transitionFromProgress()
// }
// }
// self.buttonNode.pressed = { [weak self] in
// self?.item.action()
// }
}
override func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
@ -12,23 +13,16 @@ import AccountContext
import AvatarNode
public final class GiftOptionItem: ListViewItem, ItemListItem {
public struct Icon: Equatable {
public enum Icon: Equatable {
public enum Color {
case blue
case green
case red
case violet
}
public let color: Color
public let name: String
public init(
color: Color,
name: String
) {
self.color = color
self.name = name
}
case peer(EnginePeer)
case image(color: Color, name: String)
}
public enum Font {
@ -60,6 +54,7 @@ public final class GiftOptionItem: ListViewItem, ItemListItem {
let icon: Icon?
let title: String
let titleFont: Font
let titleBadge: String?
let subtitle: String?
let subtitleFont: SubtitleFont
let subtitleActive: Bool
@ -69,12 +64,13 @@ public final class GiftOptionItem: ListViewItem, ItemListItem {
public let sectionId: ItemListSectionId
let action: (() -> Void)?
public init(presentationData: ItemListPresentationData, context: AccountContext, icon: Icon? = nil, title: String, titleFont: Font = .regular, subtitle: String?, subtitleFont: SubtitleFont = .regular, subtitleActive: Bool = false, label: Label? = nil, badge: String? = nil, isSelected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?) {
public init(presentationData: ItemListPresentationData, context: AccountContext, icon: Icon? = nil, title: String, titleFont: Font = .regular, titleBadge: String? = nil, subtitle: String?, subtitleFont: SubtitleFont = .regular, subtitleActive: Bool = false, label: Label? = nil, badge: String? = nil, isSelected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?) {
self.presentationData = presentationData
self.icon = icon
self.context = context
self.title = title
self.titleFont = titleFont
self.titleBadge = titleBadge
self.subtitle = subtitle
self.subtitleFont = subtitleFont
self.subtitleActive = subtitleActive
@ -146,7 +142,9 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
}
fileprivate var iconNode: ASImageNode?
fileprivate var avatarNode: AvatarNode?
private let titleNode: TextNode
private let titleBadge = ComponentView<Empty>()
private let statusNode: TextNode
private var statusArrowNode: ASImageNode?
@ -282,7 +280,10 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
}
let verticalInset: CGFloat = 10.0
let titleSpacing: CGFloat = 2.0
var titleSpacing: CGFloat = 2.0
if case .bold = item.titleFont {
titleSpacing = 0.0
}
let insets = itemListNeighborsGroupedInsets(neighbors, params)
let separatorHeight = UIScreenPixel
@ -331,37 +332,72 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
transition = .immediate
}
let iconUpdated = currentItem?.icon != item.icon
let iconSize = CGSize(width: 40.0, height: 40.0)
if let icon = item.icon {
let iconNode: ASImageNode
if let current = strongSelf.iconNode {
iconNode = current
} else {
iconNode = ASImageNode()
iconNode.displaysAsynchronously = false
strongSelf.addSubnode(iconNode)
strongSelf.iconNode = iconNode
}
let colors: [UIColor]
switch icon.color {
case .blue:
colors = [UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x71d4fc)]
case .green:
colors = [UIColor(rgb: 0x54cb68), UIColor(rgb: 0xa0de7e)]
case .red:
colors = [UIColor(rgb: 0xff516a), UIColor(rgb: 0xff885e)]
case .violet:
colors = [UIColor(rgb: 0xd569ec), UIColor(rgb: 0xe0a2f3)]
}
if iconNode.image == nil {
iconNode.image = generateAvatarImage(size: iconSize, icon: generateTintedImage(image: UIImage(bundleImageName: icon.name), color: .white), iconScale: 1.0, cornerRadius: 20.0, color: .blue, customColors: colors)
}
let iconFrame = CGRect(origin: CGPoint(x: leftInset - 3.0 + editingOffset, y: floorToScreenPixels((layout.contentSize.height - iconSize.height) / 2.0)), size: iconSize)
iconNode.frame = iconFrame
switch icon {
case let .peer(peer):
if let iconNode = strongSelf.iconNode {
strongSelf.iconNode = nil
iconNode.removeFromSupernode()
}
let avatarNode: AvatarNode
if let current = strongSelf.avatarNode {
avatarNode = current
} else {
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0)))
strongSelf.addSubnode(avatarNode)
strongSelf.avatarNode = avatarNode
}
avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer)
avatarNode.frame = iconFrame
case let .image(color, name):
if let avatarNode = strongSelf.avatarNode {
strongSelf.avatarNode = nil
avatarNode.removeFromSupernode()
}
let iconNode: ASImageNode
if let current = strongSelf.iconNode {
iconNode = current
} else {
iconNode = ASImageNode()
iconNode.displaysAsynchronously = false
strongSelf.addSubnode(iconNode)
strongSelf.iconNode = iconNode
}
let colors: [UIColor]
switch color {
case .blue:
colors = [UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x71d4fc)]
case .green:
colors = [UIColor(rgb: 0x54cb68), UIColor(rgb: 0xa0de7e)]
case .red:
colors = [UIColor(rgb: 0xff516a), UIColor(rgb: 0xff885e)]
case .violet:
colors = [UIColor(rgb: 0xd569ec), UIColor(rgb: 0xe0a2f3)]
}
if iconNode.image == nil || iconUpdated {
iconNode.image = generateAvatarImage(size: iconSize, icon: generateTintedImage(image: UIImage(bundleImageName: name), color: .white), iconScale: 1.0, cornerRadius: 20.0, color: .blue, customColors: colors)
}
iconNode.frame = iconFrame
}
} else {
if let avatarNode = strongSelf.avatarNode {
strongSelf.avatarNode = nil
avatarNode.removeFromSupernode()
}
if let iconNode = strongSelf.iconNode {
strongSelf.iconNode = nil
iconNode.removeFromSupernode()
}
}
if let selectableControlSizeAndApply = selectableControlSizeAndApply {
@ -445,7 +481,8 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
} else {
titleVerticalOriginY = floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0)
}
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset, y: titleVerticalOriginY), size: titleLayout.size))
let titleFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset, y: titleVerticalOriginY), size: titleLayout.size)
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
var badgeOffset: CGFloat = 0.0
if badgeLayout.size.width > 0.0 {
@ -528,6 +565,29 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
if let badge = item.titleBadge {
let badgeSize = strongSelf.titleBadge.update(
transition: .immediate,
component: AnyComponent(
BoostIconComponent(text: badge)
),
environment: {},
containerSize: CGSize(width: params.width, height: 100.0)
)
if let view = strongSelf.titleBadge.view {
if view.superview == nil {
strongSelf.view.addSubview(view)
}
let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - badgeSize.height / 2.0) - 1.0), size: badgeSize)
view.frame = badgeFrame
}
} else {
if let view = strongSelf.titleBadge.view {
view.removeFromSuperview()
}
}
}
})
}

View File

@ -130,6 +130,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
let theme = environment.theme
let strings = environment.strings
let dateTimeFormat = environment.dateTimeFormat
let accountContext = context.component.context
let state = context.state
let giftCode = component.giftCode
@ -239,7 +240,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
Button(
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
action: {
if let peer = fromPeer {
if let peer = fromPeer, peer.id != accountContext.account.peerId {
component.openPeer(peer)
Queue.mainQueue().after(1.0, {
component.cancel(false)
@ -258,7 +259,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
Button(
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
action: {
if let peer = toPeer {
if let peer = toPeer, peer.id != accountContext.account.peerId {
component.openPeer(peer)
Queue.mainQueue().after(1.0, {
component.cancel(false)
@ -309,7 +310,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
component.openMessage(messageId)
}
Queue.mainQueue().after(1.0) {
component.cancel(true)
component.cancel(false)
}
}
)
@ -479,11 +480,15 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
giftCode: context.component.giftCode,
action: context.component.action,
cancel: { animate in
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
if animate {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
} else if let controller = controller() {
controller.dismiss(animated: false, completion: nil)
}
},
openPeer: context.component.openPeer,
openMessage: context.component.openMessage,

View File

@ -723,7 +723,7 @@ private final class LimitSheetContent: CombinedComponent {
var premiumLimits: EngineConfiguration.UserLimits
var isPremium = false
var boosted = false
var myBoostCount: Int32 = 0
var cachedCloseImage: (UIImage, PresentationTheme)?
var cachedChevronImage: (UIImage, PresentationTheme)?
@ -1045,7 +1045,7 @@ private final class LimitSheetContent: CombinedComponent {
string = strings.Premium_MaxStoriesMonthlyNoPremiumText("\(limit)").string
}
buttonAnimationName = nil
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, boosted):
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount):
if link == nil, !isCurrent, state.initialized {
peerShortcutChild = peerShortcut.update(
component: Button(
@ -1054,7 +1054,7 @@ private final class LimitSheetContent: CombinedComponent {
context: component.context,
theme: environment.theme,
peer: peer,
badge: "X2"
badge: myBoostCount > 0 ? "\(myBoostCount)" : nil
)
),
action: {
@ -1098,11 +1098,11 @@ private final class LimitSheetContent: CombinedComponent {
)
}
if boosted && state.boosted != boosted {
state.boosted = boosted
if myBoostCount > 0 && state.myBoostCount != myBoostCount {
state.myBoostCount = myBoostCount
boostUpdated = true
}
useAlternateText = boosted
useAlternateText = myBoostCount > 0
iconName = "Premium/Boost"
badgeText = "\(component.count)"
@ -1157,7 +1157,7 @@ private final class LimitSheetContent: CombinedComponent {
premiumTitle = ""
if boosted {
if myBoostCount > 0 {
let prefixString = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText
let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1)
@ -1205,6 +1205,10 @@ private final class LimitSheetContent: CombinedComponent {
if case .folders = subject, !state.isPremium {
reachedMaximumLimit = false
}
if case .storiesChannelBoost = component.subject {
reachedMaximumLimit = false
}
let contentSize: CGSize
if state.initialized {
@ -1648,7 +1652,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
case storiesWeekly
case storiesMonthly
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, boosted: Bool)
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32)
}
private let context: AccountContext
@ -1742,8 +1746,7 @@ private final class PeerShortcutComponent: Component {
private let avatarNode: AvatarNode
private let text = ComponentView<Empty>()
private let badgeBackground = UIView()
private let badgeText = ComponentView<Empty>()
private let badge = ComponentView<Empty>()
private var component: PeerShortcutComponent?
private weak var state: EmptyComponentState?
@ -1756,12 +1759,8 @@ private final class PeerShortcutComponent: Component {
self.backgroundView.clipsToBounds = true
self.backgroundView.layer.cornerRadius = 16.0
self.badgeBackground.clipsToBounds = true
self.badgeBackground.backgroundColor = UIColor(rgb: 0x9671ff)
self.addSubview(self.backgroundView)
self.addSubnode(self.avatarNode)
self.addSubview(self.badgeBackground)
}
required init?(coder: NSCoder) {
@ -1803,35 +1802,26 @@ private final class PeerShortcutComponent: Component {
}
if let badge = component.badge {
let badgeSize = self.badgeText.update(
let badgeSize = self.badge.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: badge, font: Font.with(size: 11.0, design: .round, weight: .bold), textColor: .white, paragraphAlignment: .left))
)
BoostIconComponent(text: badge)
),
environment: {},
containerSize: availableSize
)
if let view = self.badgeText.view {
if let view = self.badge.view {
if view.superview == nil {
self.addSubview(view)
}
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - badgeSize.width / 2.0), y: -2.0), size: badgeSize)
view.frame = textFrame
let backgroundFrame = textFrame.insetBy(dx: -5.0, dy: -3.0)
self.badgeBackground.frame = backgroundFrame
self.badgeBackground.layer.cornerRadius = backgroundFrame.height / 2.0
self.badgeBackground.isHidden = false
let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - badgeSize.width / 2.0), y: -5.0), size: badgeSize)
view.frame = badgeFrame
}
} else {
if let view = self.badgeText.view {
if let view = self.badge.view {
view.removeFromSuperview()
}
self.badgeBackground.isHidden = true
}
self.backgroundView.frame = CGRect(origin: .zero, size: size)
@ -1848,3 +1838,94 @@ private final class PeerShortcutComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class BoostIconComponent: Component {
let text: String
public init(text: String) {
self.text = text
}
public static func ==(lhs: BoostIconComponent, rhs: BoostIconComponent) -> Bool {
if lhs.text != rhs.text {
return false
}
return true
}
public final class View: UIView {
private let badgeBackground = UIView()
private let badgeIcon: UIImageView
private let badgeText = ComponentView<Empty>()
private var component: BoostIconComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.badgeIcon = UIImageView(image: UIImage(bundleImageName: "Premium/BoostButtonIcon"))
super.init(frame: frame)
self.badgeBackground.clipsToBounds = true
self.badgeBackground.backgroundColor = UIColor(rgb: 0x9671ff)
self.addSubview(self.badgeBackground)
self.addSubview(self.badgeIcon)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: BoostIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
self.state = state
let textSize = self.badgeText.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: component.text, font: Font.with(size: 10.0, design: .round, weight: .bold), textColor: .white, paragraphAlignment: .left))
)
),
environment: {},
containerSize: availableSize
)
let spacing: CGFloat = 2.0
var totalWidth = textSize.width + spacing
var iconSize = CGSize()
if let icon = self.badgeIcon.image {
iconSize = CGSize(width: icon.size.width * 0.9, height: icon.size.height * 0.9)
totalWidth += icon.size.width
}
let size = CGSize(width: totalWidth + 8.0, height: 19.0)
let iconFrame = CGRect(x: floorToScreenPixels((size.width - totalWidth) / 2.0 + 1.0), y: 4.0 + UIScreenPixel, width: iconSize.width, height: iconSize.height)
self.badgeIcon.frame = iconFrame
let textFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: 4.0), size: textSize)
if let view = self.badgeText.view {
if view.superview == nil {
self.addSubview(view)
}
view.frame = textFrame
}
self.badgeBackground.frame = CGRect(origin: .zero, size: size)
self.badgeBackground.layer.cornerRadius = size.height / 2.0
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,267 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import LegacyComponents
import ItemListUI
import PresentationDataUtils
final class BoostsTabsItem: ListViewItem, ItemListItem {
enum Tab {
case boosts
case gifts
}
let theme: PresentationTheme
let boostsText: String
let giftsText: String
let selectedTab: Tab
let sectionId: ItemListSectionId
let selectionUpdated: (Tab) -> Void
init(theme: PresentationTheme, boostsText: String, giftsText: String, selectedTab: Tab, sectionId: ItemListSectionId, selectionUpdated: @escaping (Tab) -> Void) {
self.theme = theme
self.boostsText = boostsText
self.giftsText = giftsText
self.selectedTab = selectedTab
self.sectionId = sectionId
self.selectionUpdated = selectionUpdated
}
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 = BoostsTabsItemNode()
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? BoostsTabsItemNode {
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 final class BoostsTabsItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let boostsButton: HighlightTrackingButtonNode
private let boostsTextNode: TextNode
private let giftsButton: HighlightTrackingButtonNode
private let giftsTextNode: TextNode
private let selectionNode: ASImageNode
private var item: BoostsTabsItem?
private var layoutParams: ListViewItemLayoutParams?
init() {
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.boostsButton = HighlightTrackingButtonNode()
self.boostsTextNode = TextNode()
self.boostsTextNode.isUserInteractionEnabled = false
self.boostsTextNode.displaysAsynchronously = false
self.giftsButton = HighlightTrackingButtonNode()
self.giftsTextNode = TextNode()
self.giftsTextNode.isUserInteractionEnabled = false
self.giftsTextNode.displaysAsynchronously = false
self.selectionNode = ASImageNode()
self.selectionNode.displaysAsynchronously = false
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.boostsTextNode)
self.addSubnode(self.giftsTextNode)
self.addSubnode(self.selectionNode)
self.addSubnode(self.boostsButton)
self.addSubnode(self.giftsButton)
self.boostsButton.addTarget(self, action: #selector(self.boostsPressed), forControlEvents: .touchUpInside)
self.giftsButton.addTarget(self, action: #selector(self.giftsPressed), forControlEvents: .touchUpInside)
}
@objc private func boostsPressed() {
if let item = self.item {
item.selectionUpdated(.boosts)
}
}
@objc private func giftsPressed() {
if let item = self.item {
item.selectionUpdated(.gifts)
}
}
func asyncLayout() -> (_ item: BoostsTabsItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
let makeBoostsTextLayout = TextNode.asyncLayout(self.boostsTextNode)
let makeGiftsTextLayout = TextNode.asyncLayout(self.giftsTextNode)
return { item, params, neighbors in
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let accentColor = item.theme.list.itemAccentColor
let secondaryColor = item.theme.list.itemSecondaryTextColor
let (boostsTextLayout, boostsTextApply) = makeBoostsTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.boostsText, font: Font.medium(14.0), textColor: item.selectedTab == .boosts ? accentColor : secondaryColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let (giftsTextLayout, giftsTextApply) = makeGiftsTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.giftsText, font: Font.medium(14.0), textColor: item.selectedTab == .gifts ? accentColor : secondaryColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
contentSize = CGSize(width: params.width, height: 48.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
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
if themeUpdated {
strongSelf.selectionNode.image = generateImage(CGSize(width: 4.0, height: 3.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(item.theme.list.itemAccentColor.cgColor)
let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: size.width, height: 4.0)), cornerRadius: 2.0)
context.addPath(path.cgPath)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 2, topCapHeight: 0)
}
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
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.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))
let _ = boostsTextApply()
let _ = giftsTextApply()
strongSelf.boostsTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 16.0, y: 16.0), size: boostsTextLayout.size)
strongSelf.giftsTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 16.0 + boostsTextLayout.size.width + 27.0, y: 16.0), size: giftsTextLayout.size)
strongSelf.boostsButton.frame = strongSelf.boostsTextNode.frame.insetBy(dx: -10.0, dy: -10.0)
strongSelf.giftsButton.frame = strongSelf.giftsTextNode.frame.insetBy(dx: -10.0, dy: -10.0)
let selectionHeight: CGFloat = 3.0
let selectionFrame: CGRect
switch item.selectedTab {
case .boosts:
selectionFrame = CGRect(x: strongSelf.boostsTextNode.frame.minX, y: layoutSize.height - selectionHeight, width: strongSelf.boostsTextNode.frame.width, height: selectionHeight)
case .gifts:
selectionFrame = CGRect(x: strongSelf.giftsTextNode.frame.minX, y: layoutSize.height - selectionHeight, width: strongSelf.giftsTextNode.frame.width, height: selectionHeight)
}
var transition: ContainedViewLayoutTransition = .immediate
if let currentItem, currentItem.selectedTab != item.selectedTab {
transition = .animated(duration: 0.3, curve: .spring)
}
transition.updateFrame(node: strongSelf.selectionNode, frame: selectionFrame)
}
})
}
}
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)
}
}

View File

@ -35,8 +35,9 @@ private final class ChannelStatsControllerArguments {
let expandBoosters: () -> Void
let openGifts: () -> Void
let createPrepaidGiveaway: (PrepaidGiveaway) -> Void
let updateGiftsSelected: (Bool) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void) {
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void) {
self.context = context
self.loadDetailedGraph = loadDetailedGraph
self.openMessageStats = openMessage
@ -47,6 +48,7 @@ private final class ChannelStatsControllerArguments {
self.expandBoosters = expandBoosters
self.openGifts = openGifts
self.createPrepaidGiveaway = createPrepaidGiveaway
self.updateGiftsSelected = updateGiftsSelected
}
}
@ -115,7 +117,8 @@ private enum StatsEntry: ItemListNodeEntry {
case boostersTitle(PresentationTheme, String)
case boostersPlaceholder(PresentationTheme, String)
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer?, Int32)
case boosterTabs(PresentationTheme, String, String, Bool)
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer?, Int32, ChannelBoostersContext.State.Boost.Flags, Int32)
case boostersExpand(PresentationTheme, String)
case boostersInfo(PresentationTheme, String)
@ -156,7 +159,7 @@ private enum StatsEntry: ItemListNodeEntry {
return StatsSection.boostOverview.rawValue
case .boostPrepaidTitle, .boostPrepaid, .boostPrepaidInfo:
return StatsSection.boostPrepaid.rawValue
case .boostersTitle, .boostersPlaceholder, .booster, .boostersExpand, .boostersInfo:
case .boostersTitle, .boostersPlaceholder, .boosterTabs, .booster, .boostersExpand, .boostersInfo:
return StatsSection.boosters.rawValue
case .boostLinkTitle, .boostLink, .boostLinkInfo:
return StatsSection.boostLink.rawValue
@ -227,8 +230,10 @@ private enum StatsEntry: ItemListNodeEntry {
return 2101
case .boostersPlaceholder:
return 2102
case let .booster(index, _, _, _, _):
return 2103 + index
case .boosterTabs:
return 2103
case let .booster(index, _, _, _, _, _, _):
return 2104 + index
case .boostersExpand:
return 10000
case .boostersInfo:
@ -428,8 +433,14 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .booster(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsExpires):
if case let .booster(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsExpires) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsExpires == rhsExpires {
case let .boosterTabs(lhsTheme, lhsBoostText, lhsGiftText, lhsGiftSelected):
if case let .boosterTabs(rhsTheme, rhsBoostText, rhsGiftText, rhsGiftSelected) = rhs, lhsTheme === rhsTheme, lhsBoostText == rhsBoostText, lhsGiftText == rhsGiftText, lhsGiftSelected == rhsGiftSelected {
return true
} else {
return false
}
case let .booster(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsCount, lhsFlags, lhsExpires):
if case let .booster(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsCount, rhsFlags, rhsExpires) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsCount == rhsCount, lhsFlags == rhsFlags, lhsExpires == rhsExpires {
return true
} else {
return false
@ -533,17 +544,34 @@ private enum StatsEntry: ItemListNodeEntry {
}, contextAction: { node, gesture in
arguments.contextAction(message.id, node, gesture)
})
case let .booster(_, _, dateTimeFormat, peer, expires):
let _ = dateTimeFormat
let _ = peer
let _ = expires
return ItemListTextItem(presentationData: presentationData, text: .markdown("text"), sectionId: self.section)
// let expiresValue = stringForMediumDate(timestamp: expires, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
// return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: peer?.id != nil && peer?.id != arguments.context.account.peerId, sectionId: self.section, action: {
// if let peer {
// arguments.openPeer(peer)
// }
// }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .boosterTabs(_, boostText, giftText, giftSelected):
return BoostsTabsItem(theme: presentationData.theme, boostsText: boostText, giftsText: giftText, selectedTab: giftSelected ? .gifts : .boosts, sectionId: self.section, selectionUpdated: { tab in
arguments.updateGiftsSelected(tab == .gifts)
})
case let .booster(_, _, _, peer, count, flags, expires):
let expiresValue = stringForDate(timestamp: expires, strings: presentationData.strings)
let expiresString = presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string
let title: String
let icon: GiftOptionItem.Icon
if let peer {
title = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
icon = .peer(peer)
} else {
if flags.contains(.isUnclaimed) {
title = "Unclaimed"
icon = .image(color: .red, name: "Premium/Unclaimed")
} else if flags.contains(.isGiveaway) {
title = "To be distributed"
icon = .image(color: .blue, name: "Premium/ToBeDistributed")
} else {
title = "Unknown"
icon = .image(color: .red, name: "Premium/ToBeDistributed")
}
}
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: icon, title: title, titleFont: .bold, titleBadge: count > 1 ? "\(count)" : nil, subtitle: expiresString, sectionId: self.section, action: peer != nil && peer?.id != arguments.context.account.peerId ? {
arguments.openPeer(peer!)
} : nil)
case let .boostersExpand(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandBoosters()
@ -579,7 +607,7 @@ private enum StatsEntry: ItemListNodeEntry {
default:
color = .blue
}
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: {
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity)", subtitle: subtitle, label: nil, sectionId: self.section, action: {
arguments.createPrepaidGiveaway(prepaidGiveaway)
})
}
@ -594,15 +622,18 @@ public enum ChannelStatsSection {
private struct ChannelStatsControllerState: Equatable {
let section: ChannelStatsSection
let boostersExpanded: Bool
let giftsSelected: Bool
init() {
self.section = .stats
self.boostersExpanded = false
self.giftsSelected = false
}
init(section: ChannelStatsSection, boostersExpanded: Bool) {
init(section: ChannelStatsSection, boostersExpanded: Bool, giftsSelected: Bool) {
self.section = section
self.boostersExpanded = boostersExpanded
self.giftsSelected = giftsSelected
}
static func ==(lhs: ChannelStatsControllerState, rhs: ChannelStatsControllerState) -> Bool {
@ -612,20 +643,27 @@ private struct ChannelStatsControllerState: Equatable {
if lhs.boostersExpanded != rhs.boostersExpanded {
return false
}
if lhs.giftsSelected != rhs.giftsSelected {
return false
}
return true
}
func withUpdatedSection(_ section: ChannelStatsSection) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded)
return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded, giftsSelected: self.giftsSelected)
}
func withUpdatedBoostersExpanded(_ boostersExpanded: Bool) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded)
return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded, giftsSelected: self.giftsSelected)
}
func withUpdatedGiftsSelected(_ giftsSelected: Bool) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, giftsSelected: giftsSelected)
}
}
private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, presentationData: PresentationData) -> [StatsEntry] {
private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData) -> [StatsEntry] {
var entries: [StatsEntry] = []
switch state.section {
@ -735,6 +773,19 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
entries.append(.boostersPlaceholder(presentationData.theme, boostersPlaceholder))
}
var boostsCount: Int32 = 0
if let boostersState {
boostsCount = boostersState.count
}
var giftsCount: Int32 = 0
if let giftsState {
giftsCount = giftsState.count
}
if boostsCount > 0 && giftsCount > 0 && boostsCount != giftsCount {
entries.append(.boosterTabs(presentationData.theme, "\(boostsCount) Boosts", "\(giftsCount) Gifts", state.giftsSelected))
}
if let boostersState {
var boosterIndex: Int32 = 0
@ -747,7 +798,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
}
for booster in boosters {
entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster.peer, booster.expires))
entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster.peer, booster.multiplier, booster.flags, booster.expires))
boosterIndex += 1
}
@ -773,8 +824,8 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
}
public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil, statsDatacenterId: Int32?) -> ViewController {
let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false))
let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false))
let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
@ -813,7 +864,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
} else {
boostData = .single(nil) |> then(context.engine.peers.getChannelBoostStatus(peerId: peerId))
}
let boostersContext = ChannelBoostersContext(account: context.account, peerId: peerId)
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
var presentImpl: ((ViewController) -> Void)?
var pushImpl: ((ViewController) -> Void)?
@ -885,6 +937,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
createPrepaidGiveaway: { prepaidGiveaway in
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(prepaidGiveaway))
pushImpl?(controller)
},
updateGiftsSelected: { selected in
updateState { $0.withUpdatedGiftsSelected(selected) }
})
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
@ -905,11 +960,12 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
dataPromise.get(),
messagesPromise.get(),
boostData,
boostersContext.state,
boostsContext.state,
giftsContext.state,
longLoadingSignal
)
|> deliverOnMainQueue
|> map { presentationData, state, peer, data, messageView, boostData, boostersState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|> map { presentationData, state, peer, data, messageView, boostData, boostersState, giftsState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previous = previousData.swap(data)
var emptyStateItem: ItemListControllerEmptyStateItem?
switch state.section {
@ -939,7 +995,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, interactions: interactions, boostData: boostData, boostersState: boostersState, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments))
}
@ -959,7 +1015,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
controller.visibleBottomContentOffsetChanged = { offset in
let state = stateValue.with { $0 }
if case let .known(value) = offset, value < 100.0, case .boosts = state.section, state.boostersExpanded {
boostersContext.loadMore()
boostsContext.loadMore()
}
}
controller.titleControlValueChanged = { value in

View File

@ -97,7 +97,7 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer.
let accountPeerId = account.peerId
return account.postbox.transaction { transaction -> Signal<Peer, NoError> in
guard let peer = transaction.getPeer(account.peerId) as? TelegramChannel else {
guard let peer = transaction.getPeer(peerId) as? TelegramChannel else {
return .complete()
}
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId)], update: { _, updated in

View File

@ -47,7 +47,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
private var giveaway: TelegramMediaGiveaway?
private let buttonNode: ChatMessageAttachedContentButtonNode
private let channelButton: PeerButtonNode
private let channelButtons: PeerButtonsStackNode
override public var visibility: ListViewItemNodeVisibility {
didSet {
@ -95,7 +95,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
self.badgeTextNode = TextNode()
self.buttonNode = ChatMessageAttachedContentButtonNode()
self.channelButton = PeerButtonNode()
self.channelButtons = PeerButtonsStackNode()
super.init()
@ -107,7 +107,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
self.addSubnode(self.dateTitleNode)
self.addSubnode(self.dateTextNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.channelButton)
self.addSubnode(self.channelButtons)
self.addSubnode(self.animationNode)
self.addSubnode(self.badgeBackgroundNode)
self.addSubnode(self.badgeTextNode)
@ -129,6 +129,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceView, gesture, value)
}
self.channelButtons.openPeer = { [weak self] peer in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
}
}
override public func accessibilityActivate() -> Bool {
@ -185,7 +192,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
let makeChannelLayout = PeerButtonNode.asyncLayout(self.channelButton)
let makeChannelsLayout = PeerButtonsStackNode.asyncLayout(self.channelButtons)
let currentItem = self.item
@ -207,12 +214,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
incoming = false
}
let backgroundColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first! : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill.first!
let textColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
let accentColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
var updatedBadgeImage: UIImage?
if themeUpdated {
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: .white, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
}
let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 1)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: .white)
@ -413,13 +421,18 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
maxContentWidth = max(maxContentWidth, dateTitleLayout.size.width)
maxContentWidth = max(maxContentWidth, dateTextLayout.size.width)
maxContentWidth = max(maxContentWidth, buttonWidth)
maxContentWidth += 30.0
var channelPeer: EnginePeer?
if let channelPeerId = giveaway?.channelPeerIds.first, let peer = item.message.peers[channelPeerId] {
channelPeer = EnginePeer(peer)
var channelPeers: [EnginePeer] = []
if let channelPeerIds = giveaway?.channelPeerIds {
for peerId in channelPeerIds {
if let peer = item.message.peers[peerId] {
channelPeers.append(EnginePeer(peer))
}
}
}
let (_, continueChannelLayout) = makeChannelLayout(item.context, maxContentWidth - 30.0, channelPeer, accentColor, accentColor.withAlphaComponent(0.1))
let (channelsWidth, continueChannelLayout) = makeChannelsLayout(item.context, 250.0, channelPeers, accentColor, accentColor.withAlphaComponent(0.1))
maxContentWidth = max(maxContentWidth, channelsWidth)
maxContentWidth += 30.0
let contentWidth = maxContentWidth + layoutConstants.text.bubbleInsets.right * 2.0
@ -427,7 +440,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0, 33.0)
let buttonSpacing: CGFloat = 4.0
let (channelButtonSize, channelButtonApply) = continueChannelLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
let (channelButtonsSize, channelButtonsApply) = continueChannelLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
@ -436,7 +449,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
if countriesTextLayout.size.height > 0.0 {
layoutSize.height += countriesTextLayout.size.height + 7.0
}
layoutSize.height += channelButtonSize.height
layoutSize.height += channelButtonsSize.height
if let statusSizeAndApply = statusSizeAndApply {
layoutSize.height += statusSizeAndApply.0.height - 4.0
@ -462,7 +475,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let _ = countriesTextApply()
let _ = dateTitleApply()
let _ = dateTextApply()
let _ = channelButtonApply()
let _ = channelButtonsApply()
let _ = buttonApply(animation)
let smallSpacing: CGFloat = 2.0
@ -493,8 +506,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
strongSelf.participantsTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTextLayout.size.width) / 2.0), y: originY), size: participantsTextLayout.size)
originY += participantsTextLayout.size.height + smallSpacing * 2.0 + 3.0
strongSelf.channelButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - channelButtonSize.width) / 2.0), y: originY), size: channelButtonSize)
originY += channelButtonSize.height
strongSelf.channelButtons.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - channelButtonsSize.width) / 2.0), y: originY), size: channelButtonsSize)
originY += channelButtonsSize.height
if countriesTextLayout.size.height > 0.0 {
originY += smallSpacing * 2.0 + 3.0
@ -566,6 +579,9 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
}
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.channelButtons.frame.contains(point) {
return ChatMessageBubbleContentTapAction(content: .ignore)
}
if self.buttonNode.frame.contains(point) {
return ChatMessageBubbleContentTapAction(content: .ignore)
}
@ -578,10 +594,6 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
@objc private func buttonPressed() {
if let item = self.item {
let _ = item.controllerInteraction.openMessage(item.message, .default)
self.buttonNode.startShimmering()
Queue.mainQueue().after(0.75) {
self.buttonNode.stopShimmering()
}
}
}
@ -600,6 +612,126 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
}
}
private final class PeerButtonsStackNode: ASDisplayNode {
var buttonNodes: [PeerButtonNode] = []
var openPeer: (EnginePeer) -> Void = { _ in }
static func asyncLayout(_ current: PeerButtonsStackNode) -> (_ context: AccountContext, _ width: CGFloat, _ peers: [EnginePeer], _ titleColor: UIColor, _ backgroundColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> PeerButtonsStackNode)) {
let currentChannelButtons = current.buttonNodes.isEmpty ? nil : current.buttonNodes
let maybeMakeChannelButtons = current.buttonNodes.map(PeerButtonNode.asyncLayout)
return { context, width, peers, titleColor, backgroundColor in
let targetNode = current
var buttonNodes: [PeerButtonNode] = []
let makeChannelButtonLayouts: [(_ context: AccountContext, _ width: CGFloat, _ peer: EnginePeer?, _ titleColor: UIColor, _ backgroundColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> PeerButtonNode))]
if let currentChannelButtons {
buttonNodes = currentChannelButtons
makeChannelButtonLayouts = maybeMakeChannelButtons
} else {
for _ in peers {
buttonNodes.append(PeerButtonNode())
}
makeChannelButtonLayouts = buttonNodes.map(PeerButtonNode.asyncLayout)
}
var maxWidth = 0.0
let buttonHeight: CGFloat = 24.0
let horizontalButtonSpacing: CGFloat = 4.0
let verticalButtonSpacing: CGFloat = 6.0
var sizes: [CGSize] = []
var groups: [[Int]] = []
var currentGroup: [Int] = []
var buttonContinues: [(CGFloat) -> (CGSize, () -> PeerButtonNode)] = []
for i in 0 ..< makeChannelButtonLayouts.count {
let peer = peers[i]
let makeChannelButtonLayout = makeChannelButtonLayouts[i]
let (buttonWidth, buttonContinue) = makeChannelButtonLayout(context, width, peer, titleColor, backgroundColor)
sizes.append(CGSize(width: buttonWidth, height: buttonHeight))
buttonContinues.append(buttonContinue)
var itemsWidth: CGFloat = 0.0
for j in currentGroup {
itemsWidth += sizes[j].width
}
itemsWidth += buttonWidth
itemsWidth += CGFloat(currentGroup.count) * horizontalButtonSpacing
if itemsWidth > width {
groups.append(currentGroup)
currentGroup = []
}
currentGroup.append(i)
}
if !currentGroup.isEmpty {
groups.append(currentGroup)
}
var rowWidths: [CGFloat] = []
for group in groups {
var rowWidth: CGFloat = 0.0
for i in group {
let buttonSize = sizes[i]
rowWidth += buttonSize.width
}
rowWidth += CGFloat(currentGroup.count) * horizontalButtonSpacing
if rowWidth > maxWidth {
maxWidth = rowWidth
}
rowWidths.append(rowWidth)
}
var frames: [CGRect] = []
var originY: CGFloat = 0.0
for i in 0 ..< groups.count {
let rowWidth = rowWidths[i]
var originX = floorToScreenPixels((maxWidth - rowWidth) / 2.0)
for i in groups[i] {
let buttonSize = sizes[i]
frames.append(CGRect(origin: CGPoint(x: originX, y: originY), size: buttonSize))
originX += buttonSize.width + horizontalButtonSpacing
}
originY += buttonHeight + verticalButtonSpacing
}
return (maxWidth, { _ in
var buttonLayoutsAndApply: [(CGSize, () -> PeerButtonNode)] = []
for buttonApply in buttonContinues {
buttonLayoutsAndApply.append(buttonApply(maxWidth))
}
return (CGSize(width: maxWidth, height: originY - verticalButtonSpacing), {
targetNode.buttonNodes = buttonNodes
for i in 0 ..< buttonNodes.count {
let peer = peers[i]
let buttonNode = buttonNodes[i]
buttonNode.pressed = { [weak targetNode] in
targetNode?.openPeer(peer)
}
if buttonNode.supernode == nil {
targetNode.addSubnode(buttonNode)
}
let frame = frames[i]
buttonNode.frame = frame
}
for (_, apply) in buttonLayoutsAndApply {
let _ = apply()
}
return targetNode
})
})
}
}
}
private final class PeerButtonNode: HighlightTrackingButtonNode {
private let backgroundNode: ASImageNode
private let textNode: TextNode

View File

@ -5898,7 +5898,10 @@ public final class EmojiPagerContentComponent: Component {
self.visibleItemSelectionLayers[itemId] = itemSelectionLayer
}
if case .accent = item.tintMode {
if case let .custom(color) = item.tintMode {
itemSelectionLayer.backgroundColor = color.withMultipliedAlpha(0.1).cgColor
itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor
} else if case .accent = item.tintMode {
itemSelectionLayer.backgroundColor = keyboardChildEnvironment.theme.list.itemAccentColor.withMultipliedAlpha(0.1).cgColor
itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor
} else {

View File

@ -124,6 +124,7 @@ public final class EntityKeyboardComponent: Component {
public let clipContentToTopPanel: Bool
public let useExternalSearchContainer: Bool
public let hidePanels: Bool
public let customTintColor: UIColor?
public init(
theme: PresentationTheme,
@ -157,7 +158,8 @@ public final class EntityKeyboardComponent: Component {
isExpanded: Bool,
clipContentToTopPanel: Bool,
useExternalSearchContainer: Bool,
hidePanels: Bool = false
hidePanels: Bool = false,
customTintColor: UIColor? = nil
) {
self.theme = theme
self.strings = strings
@ -191,6 +193,7 @@ public final class EntityKeyboardComponent: Component {
self.clipContentToTopPanel = clipContentToTopPanel
self.useExternalSearchContainer = useExternalSearchContainer
self.hidePanels = hidePanels
self.customTintColor = customTintColor
}
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
@ -257,7 +260,9 @@ public final class EntityKeyboardComponent: Component {
if lhs.useExternalSearchContainer != rhs.useExternalSearchContainer {
return false
}
if lhs.customTintColor != rhs.customTintColor {
return false
}
return true
}
@ -341,6 +346,7 @@ public final class EntityKeyboardComponent: Component {
icon: icon,
theme: component.theme,
useAccentColor: false,
customTintColor: component.customTintColor,
title: title,
pressed: { [weak self] in
self?.scrollToItemGroup(contentId: "masks", groupId: itemGroup.supergroupId, subgroupId: nil)
@ -376,6 +382,7 @@ public final class EntityKeyboardComponent: Component {
contentTopPanels.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(EntityKeyboardTopPanelComponent(
id: "masks",
theme: component.theme,
customTintColor: component.customTintColor,
items: topMaskItems,
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
defaultActiveItemId: maskContent.panelItemGroups.first?.groupId,
@ -430,6 +437,7 @@ public final class EntityKeyboardComponent: Component {
icon: .featured,
theme: component.theme,
useAccentColor: false,
customTintColor: component.customTintColor,
title: component.strings.Stickers_Trending,
pressed: { [weak self] in
self?.component?.stickerContent?.inputInteractionHolder.inputInteraction?.openFeatured?()
@ -475,6 +483,7 @@ public final class EntityKeyboardComponent: Component {
icon: icon,
theme: component.theme,
useAccentColor: false,
customTintColor: component.customTintColor,
title: title,
pressed: { [weak self] in
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.supergroupId, subgroupId: nil)
@ -511,6 +520,7 @@ public final class EntityKeyboardComponent: Component {
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
id: "stickers",
theme: component.theme,
customTintColor: component.customTintColor,
items: topStickerItems,
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
defaultActiveItemId: stickerContent.panelItemGroups.first?.groupId,
@ -573,6 +583,7 @@ public final class EntityKeyboardComponent: Component {
icon: icon,
theme: component.theme,
useAccentColor: false,
customTintColor: component.customTintColor,
title: title,
pressed: { [weak self] in
self?.scrollToItemGroup(contentId: "emoji", groupId: itemGroup.supergroupId, subgroupId: nil)
@ -623,6 +634,7 @@ public final class EntityKeyboardComponent: Component {
contentTopPanels.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardTopPanelComponent(
id: "emoji",
theme: component.theme,
customTintColor: component.customTintColor,
items: topEmojiItems,
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
activeContentItemIdUpdated: emojiContentItemIdUpdated,

View File

@ -281,6 +281,7 @@ final class EntityKeyboardIconTopPanelComponent: Component {
let icon: Icon
let theme: PresentationTheme
let useAccentColor: Bool
let customTintColor: UIColor?
let title: String
let pressed: () -> Void
@ -288,12 +289,14 @@ final class EntityKeyboardIconTopPanelComponent: Component {
icon: Icon,
theme: PresentationTheme,
useAccentColor: Bool,
customTintColor: UIColor?,
title: String,
pressed: @escaping () -> Void
) {
self.icon = icon
self.theme = theme
self.useAccentColor = useAccentColor
self.customTintColor = customTintColor
self.title = title
self.pressed = pressed
}
@ -308,6 +311,9 @@ final class EntityKeyboardIconTopPanelComponent: Component {
if lhs.useAccentColor != rhs.useAccentColor {
return false
}
if lhs.customTintColor != rhs.customTintColor {
return false
}
if lhs.title != rhs.title {
return false
}
@ -383,14 +389,18 @@ final class EntityKeyboardIconTopPanelComponent: Component {
self.component = component
let color: UIColor
if itemEnvironment.isHighlighted {
if component.useAccentColor {
color = component.theme.list.itemAccentColor
} else {
color = component.theme.chat.inputMediaPanel.panelHighlightedIconColor
}
if let customTintColor = component.customTintColor {
color = customTintColor
} else {
color = component.theme.chat.inputMediaPanel.panelIconColor
if itemEnvironment.isHighlighted {
if component.useAccentColor {
color = component.theme.list.itemAccentColor
} else {
color = component.theme.chat.inputMediaPanel.panelHighlightedIconColor
}
} else {
color = component.theme.chat.inputMediaPanel.panelIconColor
}
}
if self.iconView.tintColor != color {
@ -1192,6 +1202,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
let id: AnyHashable
let theme: PresentationTheme
let customTintColor: UIColor?
let items: [Item]
let containerSideInset: CGFloat
let defaultActiveItemId: AnyHashable?
@ -1203,6 +1214,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
init(
id: AnyHashable,
theme: PresentationTheme,
customTintColor: UIColor?,
items: [Item],
containerSideInset: CGFloat,
defaultActiveItemId: AnyHashable? = nil,
@ -1213,6 +1225,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
) {
self.id = id
self.theme = theme
self.customTintColor = customTintColor
self.items = items
self.containerSideInset = containerSideInset
self.defaultActiveItemId = defaultActiveItemId
@ -1229,6 +1242,9 @@ public final class EntityKeyboardTopPanelComponent: Component {
if lhs.theme !== rhs.theme {
return false
}
if lhs.customTintColor != rhs.customTintColor {
return false
}
if lhs.items != rhs.items {
return false
}
@ -1847,8 +1863,12 @@ public final class EntityKeyboardTopPanelComponent: Component {
}
func update(component: EntityKeyboardTopPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
if self.component?.theme !== component.theme {
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
if self.component?.theme !== component.theme || self.component?.customTintColor != component.customTintColor {
if let customTintColor = component.customTintColor {
self.highlightedIconBackgroundView.backgroundColor = customTintColor.withAlphaComponent(0.1)
} else {
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
}
}
self.component = component
self.state = state

View File

@ -67,7 +67,7 @@ final class ApplyColorFooterItemNode: ItemListControllerFooterItemNode {
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
// self.addSubnode(self.separatorNode)
self.addSubnode(self.buttonNode)
self.updateItem()

View File

@ -186,6 +186,7 @@ final class EmojiPickerItemNode: ListViewItemNode {
strings: item.strings,
deviceMetrics: .iPhone14ProMax,
emojiContent: item.emojiContent,
backgroundIconColor: item.backgroundIconColor,
backgroundColor: item.theme.list.itemBlocksBackgroundColor,
separatorColor: item.theme.list.itemBlocksSeparatorColor
)
@ -221,6 +222,7 @@ private final class EmojiSelectionComponent: Component {
public let strings: PresentationStrings
public let deviceMetrics: DeviceMetrics
public let emojiContent: EmojiPagerContentComponent
public let backgroundIconColor: UIColor
public let backgroundColor: UIColor
public let separatorColor: UIColor
@ -229,6 +231,7 @@ private final class EmojiSelectionComponent: Component {
strings: PresentationStrings,
deviceMetrics: DeviceMetrics,
emojiContent: EmojiPagerContentComponent,
backgroundIconColor: UIColor,
backgroundColor: UIColor,
separatorColor: UIColor
) {
@ -236,6 +239,7 @@ private final class EmojiSelectionComponent: Component {
self.strings = strings
self.deviceMetrics = deviceMetrics
self.emojiContent = emojiContent
self.backgroundIconColor = backgroundIconColor
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
}
@ -253,6 +257,9 @@ private final class EmojiSelectionComponent: Component {
if lhs.emojiContent != rhs.emojiContent {
return false
}
if lhs.backgroundIconColor != rhs.backgroundIconColor {
return false
}
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
@ -338,7 +345,8 @@ private final class EmojiSelectionComponent: Component {
displayBottomPanel: false,
isExpanded: true,
clipContentToTopPanel: false,
useExternalSearchContainer: false
useExternalSearchContainer: false,
customTintColor: component.backgroundIconColor
)),
environment: {},
containerSize: availableSize

View File

@ -252,11 +252,11 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor {
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId {
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
strongSelf.view.addSubview(snapshot)
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.1, removeOnCompletion: false, completion: { _ in
snapshot.removeFromSuperview()
})
}

View File

@ -393,13 +393,18 @@ public func PeerNameColorScreen(
title: buttonTitle,
locked: isLocked,
action: {
if isPremium {
if !isLocked {
let state = stateValue.with { $0 }
let nameColor = state.updatedNameColor ?? peer?.nameColor
let backgroundEmojiId = state.updatedBackgroundEmojiId ?? peer?.backgroundEmojiId
let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone()
switch subject {
case .account:
let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone()
case let .channel(peerId):
let _ = context.engine.peers.updatePeerNameColorAndEmoji(peerId: peerId, nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone()
}
dismissImpl?()
} else {

View File

@ -836,7 +836,7 @@ final class ShareWithPeersScreenComponent: Component {
guard let stateValue = self.effectiveStateValue else {
return
}
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
topOffset = max(0.0, topOffset)
transition.setTransform(layer: self.backgroundView.layer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
@ -1082,7 +1082,7 @@ final class ShareWithPeersScreenComponent: Component {
self.hapticFeedback.impact(.light)
} else {
self.postingAvailabilityDisposable.set((component.context.engine.messages.checkStoriesUploadAvailability(target: .peer(peer.id))
|> deliverOnMainQueue).start(next: { [weak self] status in
|> deliverOnMainQueue).start(next: { [weak self] status in
guard let self, let component = self.component else {
return
}
@ -1106,7 +1106,7 @@ final class ShareWithPeersScreenComponent: Component {
previousController.dismiss()
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let controller = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, boosted: false), count: Int32(status.boosts), forceDark: true, cancel: {}, action: { [weak navigationController] in
let controller = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0), count: Int32(status.boosts), forceDark: true, cancel: {}, action: { [weak navigationController] in
UIPasteboard.general.string = link
if let previousController = navigationController?.viewControllers.reversed().first(where: { $0 is ShareWithPeersScreen}) as? ShareWithPeersScreen {
@ -1397,7 +1397,7 @@ final class ShareWithPeersScreenComponent: Component {
subtitle = nil
}
}
let isSelected = self.selectedPeers.contains(peer.id) || self.selectedGroups.contains(peer.id)
let _ = visibleItem.update(
transition: itemTransition,
@ -1671,7 +1671,21 @@ final class ShareWithPeersScreenComponent: Component {
}
let fadeTransition = Transition.easeInOut(duration: 0.25)
if let searchStateContext = self.searchStateContext, case let .contactsSearch(query, _) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
var searchQuery: String?
var searchResultsAreEmpty = false
if let searchStateContext = self.searchStateContext, let value = searchStateContext.stateValue {
if case let .contactsSearch(query, _) = searchStateContext.subject {
searchQuery = query
} else if case let .members(_, query) = searchStateContext.subject {
searchQuery = query
} else if case let .channels(_, query) = searchStateContext.subject {
searchQuery = query
}
searchResultsAreEmpty = value.peers.isEmpty
}
if let searchQuery, searchResultsAreEmpty {
let sideInset: CGFloat = 44.0
let emptyAnimationHeight = 148.0
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
@ -1695,7 +1709,7 @@ final class ShareWithPeersScreenComponent: Component {
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.Contacts_Search_NoResultsQueryDescription(query).string, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)),
text: .plain(NSAttributedString(string: environment.strings.Contacts_Search_NoResultsQueryDescription(searchQuery).string, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)
@ -2041,15 +2055,28 @@ final class ShareWithPeersScreenComponent: Component {
containerSize: CGSize(width: containerWidth, height: 1000.0)
)
if !self.navigationTextFieldState.text.isEmpty {
let searchQuery = self.navigationTextFieldState.text
if !searchQuery.isEmpty {
var onlyContacts = false
if component.initialPrivacy.base == .closeFriends || component.initialPrivacy.base == .contacts {
onlyContacts = true
}
if let searchStateContext = self.searchStateContext, searchStateContext.subject == .contactsSearch(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts) {
let searchSubject: ShareWithPeersScreen.StateContext.Subject
switch component.stateContext.subject {
case let .channels(exclude, _):
searchSubject = .channels(exclude: exclude, searchQuery: searchQuery)
case let .members(peerId, _):
searchSubject = .members(peerId: peerId, searchQuery: searchQuery)
default:
searchSubject = .contactsSearch(query: searchQuery, onlyContacts: onlyContacts)
}
if let searchStateContext = self.searchStateContext, searchStateContext.subject == searchSubject {
} else {
self.searchStateDisposable?.dispose()
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: .contactsSearch(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts), editing: false)
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: searchSubject)
var applyState = false
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {

View File

@ -4,6 +4,7 @@ import TelegramCore
import AccountContext
import TelegramUIPreferences
import TemporaryCachedPeerDataManager
import Postbox
public extension ShareWithPeersScreen {
final class State {
@ -48,7 +49,7 @@ public extension ShareWithPeersScreen {
case contacts(base: EngineStoryPrivacy.Base)
case contactsSearch(query: String, onlyContacts: Bool)
case members(peerId: EnginePeer.Id, searchQuery: String?)
case channels(exclude: Set<EnginePeer.Id>)
case channels(exclude: Set<EnginePeer.Id>, searchQuery: String?)
}
var stateValue: State?
@ -584,7 +585,7 @@ public extension ShareWithPeersScreen {
self.stateDisposable = combinedDisposable
self.listControl = disposableAndLoadMoreControl.1
case let .channels(excludePeerIds):
case let .channels(excludePeerIds, searchQuery):
self.stateDisposable = (combineLatest(
context.engine.messages.chatList(group: .root, count: 500) |> take(1),
context.engine.data.get(EngineDataMap(Array(self.initialPeerIds).map(TelegramEngine.EngineData.Item.Peer.Peer.init)))
@ -627,13 +628,24 @@ public extension ShareWithPeersScreen {
existingIds.insert(peerId)
}
}
let queryTokens = stringIndexTokens(searchQuery ?? "", transliteration: .combined)
func peerMatchesTokens(peer: EnginePeer, tokens: [ValueBoxKey]) -> Bool {
if matchStringIndexTokens(peer.indexName._asIndexName().indexTokens, with: queryTokens) {
return true
}
return false
}
var peers: [EnginePeer] = []
peers = chatList.items.filter { peer in
if let peer = peer.renderedPeer.peer {
if excludePeerIds.contains(peer.id) {
return false
}
if let _ = searchQuery, !peerMatchesTokens(peer: peer, tokens: queryTokens) {
return false
}
if self.initialPeerIds.contains(peer.id) {
return false
}

View File

@ -43,6 +43,11 @@ public final class PeerListItemComponent: Component {
case editing(isSelected: Bool, isTinted: Bool)
}
public enum SelectionPosition: Equatable {
case left
case right
}
public enum SubtitleAccessory: Equatable {
case none
case checks
@ -101,6 +106,8 @@ public final class PeerListItemComponent: Component {
let rightAccessory: RightAccessory
let reaction: Reaction?
let selectionState: SelectionState
let selectionPosition: SelectionPosition
let isEnabled: Bool
let hasNext: Bool
let action: (EnginePeer) -> Void
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
@ -121,6 +128,8 @@ public final class PeerListItemComponent: Component {
rightAccessory: RightAccessory = .none,
reaction: Reaction? = nil,
selectionState: SelectionState,
selectionPosition: SelectionPosition = .left,
isEnabled: Bool = true,
hasNext: Bool,
action: @escaping (EnginePeer) -> Void,
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
@ -140,6 +149,8 @@ public final class PeerListItemComponent: Component {
self.rightAccessory = rightAccessory
self.reaction = reaction
self.selectionState = selectionState
self.selectionPosition = selectionPosition
self.isEnabled = isEnabled
self.hasNext = hasNext
self.action = action
self.contextAction = contextAction
@ -189,6 +200,12 @@ public final class PeerListItemComponent: Component {
if lhs.selectionState != rhs.selectionState {
return false
}
if lhs.selectionPosition != rhs.selectionPosition {
return false
}
if lhs.isEnabled != rhs.isEnabled {
return false
}
if lhs.hasNext != rhs.hasNext {
return false
}
@ -411,6 +428,8 @@ public final class PeerListItemComponent: Component {
self.component = component
self.state = state
self.containerButton.alpha = component.isEnabled ? 1.0 : 0.3
self.avatarButtonView.isUserInteractionEnabled = component.storyStats != nil && component.openStories != nil
let labelData: (String, Bool)
@ -457,9 +476,17 @@ public final class PeerListItemComponent: Component {
var avatarLeftInset: CGFloat = component.sideInset + 10.0
if case let .editing(isSelected, isTinted) = component.selectionState {
leftInset += 44.0
avatarLeftInset += 44.0
let checkSize: CGFloat = 22.0
let checkOriginX: CGFloat
switch component.selectionPosition {
case .left:
leftInset += 44.0
avatarLeftInset += 44.0
checkOriginX = floor((54.0 - checkSize) * 0.5)
case .right:
rightInset += 44.0
checkOriginX = availableSize.width - 11.0 - checkSize
}
let checkLayer: CheckLayer
if let current = self.checkLayer {
@ -484,7 +511,7 @@ public final class PeerListItemComponent: Component {
checkLayer.setSelected(isSelected, animated: false)
checkLayer.setNeedsDisplay()
}
transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: floor((54.0 - checkSize) * 0.5), y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: checkOriginX, y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
} else {
if let checkLayer = self.checkLayer {
self.checkLayer = nil

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "avatar_tobedistributed.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,247 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 11.730469 11.000000 cm
0.000000 0.000000 0.000000 scn
12.269731 14.500000 m
12.269731 12.290861 10.478869 10.500000 8.269731 10.500000 c
6.060592 10.500000 4.269731 12.290861 4.269731 14.500000 c
4.269731 16.709139 6.060592 18.500000 8.269731 18.500000 c
10.478869 18.500000 12.269731 16.709139 12.269731 14.500000 c
h
11.296218 7.641908 m
10.421675 7.869528 9.419095 8.000000 8.269734 8.000000 c
3.641746 8.000000 1.393565 5.884617 0.301445 3.829397 c
-0.735194 1.878584 1.060591 0.000000 3.269730 0.000000 c
13.181631 0.000000 l
13.023307 0.328161 12.934570 0.696198 12.934570 1.084961 c
12.934570 1.251627 l
12.934570 1.649643 12.994987 2.011902 13.098507 2.340250 c
13.044313 2.336742 12.989650 2.334961 12.934570 2.334961 c
11.553859 2.334961 10.434570 3.454249 10.434570 4.834961 c
10.434570 5.875430 10.752380 6.841672 11.296218 7.641908 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 24.665039 10.424805 cm
0.000000 0.000000 0.000000 scn
-0.830000 5.410156 m
-0.830000 4.951760 -0.458396 4.580156 0.000000 4.580156 c
0.458396 4.580156 0.830000 4.951760 0.830000 5.410156 c
-0.830000 5.410156 l
h
1.670000 1.660156 m
1.670000 1.201760 2.041604 0.830156 2.500000 0.830156 c
2.958396 0.830156 3.330000 1.201760 3.330000 1.660156 c
1.670000 1.660156 l
h
3.549973 3.140671 m
3.200984 3.893736 l
3.549973 3.140671 l
h
4.170000 5.410156 m
4.170000 4.739607 3.774784 4.159650 3.200984 3.893736 c
3.898962 2.387607 l
5.037511 2.915239 5.830000 4.069291 5.830000 5.410156 c
4.170000 5.410156 l
h
0.830000 5.410156 m
0.830000 6.332472 1.577684 7.080156 2.500000 7.080156 c
2.500000 8.740156 l
0.660892 8.740156 -0.830000 7.249265 -0.830000 5.410156 c
0.830000 5.410156 l
h
2.500000 7.080156 m
3.422316 7.080156 4.170000 6.332472 4.170000 5.410156 c
5.830000 5.410156 l
5.830000 7.249265 4.339108 8.740156 2.500000 8.740156 c
2.500000 7.080156 l
h
1.670000 1.826823 m
1.670000 1.660156 l
3.330000 1.660156 l
3.330000 1.826823 l
1.670000 1.826823 l
h
3.200984 3.893736 m
2.565116 3.599059 1.670000 2.922441 1.670000 1.826823 c
3.330000 1.826823 l
3.330000 1.876959 3.348376 1.955688 3.445795 2.066916 c
3.547248 2.182750 3.706832 2.298569 3.898962 2.387607 c
3.200984 3.893736 l
h
f
n
Q
q
q
1.000000 0.000000 -0.000000 1.000000 26.332031 8.697510 cm
0.000000 0.000000 0.000000 scn
1.566667 1.033366 m
1.566667 0.628357 1.238342 0.300033 0.833333 0.300033 c
0.833333 0.100033 l
1.348799 0.100033 1.766667 0.517900 1.766667 1.033366 c
1.566667 1.033366 l
h
0.833333 0.300033 m
0.428325 0.300033 0.100000 0.628357 0.100000 1.033366 c
-0.100000 1.033366 l
-0.100000 0.517900 0.317868 0.100033 0.833333 0.100033 c
0.833333 0.300033 l
h
0.100000 1.033366 m
0.100000 1.438375 0.428325 1.766699 0.833333 1.766699 c
0.833333 1.966699 l
0.317868 1.966699 -0.100000 1.548832 -0.100000 1.033366 c
0.100000 1.033366 l
h
0.833333 1.766699 m
1.238342 1.766699 1.566667 1.438375 1.566667 1.033366 c
1.766667 1.033366 l
1.766667 1.548832 1.348799 1.966699 0.833333 1.966699 c
0.833333 1.766699 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 26.332031 8.697510 cm
0.000000 0.000000 0.000000 scn
1.666667 1.033366 m
1.666667 0.573129 1.293571 0.200033 0.833333 0.200033 c
0.373096 0.200033 0.000000 0.573129 0.000000 1.033366 c
0.000000 1.493603 0.373096 1.866699 0.833333 1.866699 c
1.293571 1.866699 1.666667 1.493603 1.666667 1.033366 c
h
f
n
Q
Q
endstream
endobj
2 0 obj
3398
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.850980 0.850980 0.850980 scn
40.000000 20.000000 m
40.000000 8.954306 31.045694 0.000000 20.000000 0.000000 c
8.954305 0.000000 0.000000 8.954306 0.000000 20.000000 c
0.000000 31.045696 8.954305 40.000000 20.000000 40.000000 c
31.045694 40.000000 40.000000 31.045696 40.000000 20.000000 c
h
f
n
Q
endstream
endobj
4 0 obj
387
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 40.000000 40.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000003656 00000 n
0000003679 00000 n
0000004314 00000 n
0000004336 00000 n
0000004634 00000 n
0000004736 00000 n
0000004757 00000 n
0000004930 00000 n
0000005004 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
5064
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "avatar_unclaimed.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,106 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 11.730469 11.000000 cm
0.000000 0.000000 0.000000 scn
12.269731 14.500000 m
12.269731 12.290861 10.478869 10.500000 8.269731 10.500000 c
6.060592 10.500000 4.269731 12.290861 4.269731 14.500000 c
4.269731 16.709139 6.060592 18.500000 8.269731 18.500000 c
10.478869 18.500000 12.269731 16.709139 12.269731 14.500000 c
h
10.542313 7.807499 m
9.856975 7.931618 9.101819 8.000000 8.269734 8.000000 c
3.641746 8.000000 1.393565 5.884617 0.301445 3.829397 c
-0.735194 1.878584 1.060591 0.000000 3.269730 0.000000 c
9.769492 0.000000 l
9.769482 0.639818 10.013559 1.279642 10.501725 1.767807 c
11.733958 3.000039 l
10.501725 4.232272 l
9.525414 5.208583 9.525414 6.791495 10.501725 7.767806 c
10.515141 7.781221 10.528671 7.794453 10.542313 7.807499 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 23.169922 10.008057 cm
0.000000 0.000000 0.000000 scn
6.243101 7.578920 m
6.567236 7.903055 7.092763 7.903055 7.416899 7.578920 c
7.741034 7.254785 7.741034 6.729258 7.416899 6.405123 c
5.003798 3.992022 l
7.416899 1.578920 l
7.741034 1.254785 7.741034 0.729258 7.416899 0.405123 c
7.092763 0.080988 6.567236 0.080988 6.243101 0.405123 c
3.830000 2.818224 l
1.416899 0.405123 l
1.092763 0.080988 0.567236 0.080988 0.243101 0.405123 c
-0.081034 0.729258 -0.081034 1.254785 0.243101 1.578920 c
2.656203 3.992022 l
0.243101 6.405123 l
-0.081034 6.729258 -0.081034 7.254785 0.243101 7.578920 c
0.567237 7.903055 1.092763 7.903055 1.416899 7.578920 c
3.830000 5.165819 l
6.243101 7.578920 l
h
f*
n
Q
endstream
endobj
3 0 obj
1555
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 40.000000 40.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001645 00000 n
0000001668 00000 n
0000001841 00000 n
0000001915 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1974
%%EOF

View File

@ -17413,6 +17413,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return .text
})
})
} else {
self.playShakeAnimation()
}
case let .withBotStartPayload(botStart):
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {

View File

@ -835,8 +835,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}), nil)
}
})
case let .boost(peerId, status, myBoostsStatus):
let _ = myBoostsStatus
case let .boost(peerId, status, myBoostStatus):
let _ = myBoostStatus
var forceDark = false
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
forceDark = true
@ -847,9 +847,21 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
return
}
var isBoosted = false
if status.boostedByMe {
isBoosted = true
var myBoostCount: Int32 = 0
var availableBoosts: [MyBoostStatus.Boost] = []
var occupiedBoosts: [MyBoostStatus.Boost] = []
if let myBoostStatus {
for boost in myBoostStatus.boosts {
if let boostPeer = boost.peer {
if boostPeer.id == peer.id {
myBoostCount += 1
} else {
occupiedBoosts.append(boost)
}
} else {
availableBoosts.append(boost)
}
}
}
var isCurrent = false
@ -861,22 +873,19 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
var currentLevelBoosts = Int32(status.currentLevelBoosts)
var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init)
if isBoosted && status.boosts == currentLevelBoosts {
if myBoostCount > 0 && status.boosts == currentLevelBoosts {
currentLevel = max(0, currentLevel - 1)
nextLevelBoosts = currentLevelBoosts
currentLevelBoosts = max(0, currentLevelBoosts - 1)
}
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, boosted: isBoosted)
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, boosted: true)
let nextCount = Int32(status.boosts + 1)
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount)
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount + 1)
var nextCount = Int32(status.boosts + 1)
var updateImpl: (() -> Void)?
var dismissImpl: (() -> Void)?
let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), forceDark: forceDark, action: {
if isBoosted {
return true
}
let dismiss = false
updateImpl?()
@ -945,8 +954,23 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
updateImpl = { [weak controller] in
if let _ = status.nextLevelBoosts {
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: []).startStandalone()
controller?.updateSubject(nextSubject, count: nextCount)
if let availableBoost = availableBoosts.first {
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]).startStandalone()
controller?.updateSubject(nextSubject, count: nextCount)
availableBoosts.removeFirst()
nextCount += 1
} else if !occupiedBoosts.isEmpty, let myBoostStatus {
let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "\(slots.count) boosts are reassigned from 1 other channel.", timeout: nil), elevatedLayout: true, position: .bottom, action: { _ in return true })
(navigationController?.viewControllers.last as? ViewController)?.present(undoController, in: .window(.root))
})
dismissImpl?()
navigationController?.pushViewController(replaceController)
}
} else {
dismissImpl?()
}
@ -974,7 +998,9 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
let _ = context.engine.payments.applyPremiumGiftCode(slug: slug).startStandalone()
},
openPeer: { peer in
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
if peer.id != context.account.peerId {
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
}
},
openMessage: { messageId in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))

View File

@ -8362,7 +8362,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if let previousController = navigationController.viewControllers.last as? ShareWithPeersScreen {
previousController.dismiss()
}
let controller = PremiumLimitScreen(context: self.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, boosted: false), count: Int32(status.boosts), action: { [weak self] in
let controller = PremiumLimitScreen(context: self.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0), count: Int32(status.boosts), action: { [weak self] in
UIPasteboard.general.string = link
if let self {

View File

@ -1795,8 +1795,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .storiesWeekly
case .storiesMonthly:
mappedSubject = .storiesMonthly
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, boosted):
mappedSubject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: level, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: link, boosted: boosted)
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount):
mappedSubject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: level, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: link, myBoostCount: myBoostCount)
}
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action)
}