mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +00:00
Various improvements
This commit is contained in:
parent
56db74612e
commit
2107f94bc3
@ -994,7 +994,7 @@ public enum PremiumLimitSubject {
|
|||||||
case expiringStories
|
case expiringStories
|
||||||
case storiesWeekly
|
case storiesWeekly
|
||||||
case storiesMonthly
|
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 {
|
public protocol ComposeController: ViewController {
|
||||||
|
|||||||
@ -59,7 +59,10 @@ func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewL
|
|||||||
modalStack[modalStack.count - 1].controllers.append(controller)
|
modalStack[modalStack.count - 1].controllers.append(controller)
|
||||||
}
|
}
|
||||||
} else if !modalStack.isEmpty {
|
} else if !modalStack.isEmpty {
|
||||||
controller._presentedInModal = true
|
if modalStack[modalStack.count - 1].isFlat {
|
||||||
|
} else {
|
||||||
|
controller._presentedInModal = true
|
||||||
|
}
|
||||||
if modalStack[modalStack.count - 1].isStandalone {
|
if modalStack[modalStack.count - 1].isStandalone {
|
||||||
modalStack.append(ModalContainerLayout(controllers: [controller], isFlat: isFlat, isStandalone: isStandalone))
|
modalStack.append(ModalContainerLayout(controllers: [controller], isFlat: isFlat, isStandalone: isStandalone))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -107,6 +107,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||||
"//submodules/CountrySelectionUI",
|
"//submodules/CountrySelectionUI",
|
||||||
|
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -325,7 +325,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||||||
case let .header(_, title, text):
|
case let .header(_, title, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(title + text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(title + text), sectionId: self.section)
|
||||||
case let .createGiveaway(_, title, subtitle, isSelected):
|
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
|
arguments.updateState { state in
|
||||||
var updatedState = state
|
var updatedState = state
|
||||||
updatedState.mode = .giveaway
|
updatedState.mode = .giveaway
|
||||||
@ -333,7 +333,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
case let .awardUsers(_, title, subtitle, isSelected):
|
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
|
var openSelection = false
|
||||||
arguments.updateState { state in
|
arguments.updateState { state in
|
||||||
var updatedState = state
|
var updatedState = state
|
||||||
@ -361,7 +361,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||||||
default:
|
default:
|
||||||
color = .blue
|
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):
|
case let .subscriptionsHeader(_, text, additionalText):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
|
||||||
case let .subscriptions(_, value):
|
case let .subscriptions(_, value):
|
||||||
@ -1056,7 +1056,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
|||||||
|
|
||||||
let stateContext = ShareWithPeersScreen.StateContext(
|
let stateContext = ShareWithPeersScreen.StateContext(
|
||||||
context: context,
|
context: context,
|
||||||
subject: .channels(exclude: Set([peerId])),
|
subject: .channels(exclude: Set([peerId]), searchQuery: nil),
|
||||||
initialPeerIds: Set(state.channels.filter { $0 != peerId })
|
initialPeerIds: Set(state.channels.filter { $0 != peerId })
|
||||||
)
|
)
|
||||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in
|
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in
|
||||||
|
|||||||
@ -75,21 +75,6 @@ final class CreateGiveawayFooterItemNode: ItemListControllerFooterItemNode {
|
|||||||
private func updateItem() {
|
private func updateItem() {
|
||||||
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||||
self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor
|
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) {
|
override func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import ComponentFlow
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
@ -12,23 +13,16 @@ import AccountContext
|
|||||||
import AvatarNode
|
import AvatarNode
|
||||||
|
|
||||||
public final class GiftOptionItem: ListViewItem, ItemListItem {
|
public final class GiftOptionItem: ListViewItem, ItemListItem {
|
||||||
public struct Icon: Equatable {
|
public enum Icon: Equatable {
|
||||||
public enum Color {
|
public enum Color {
|
||||||
case blue
|
case blue
|
||||||
case green
|
case green
|
||||||
case red
|
case red
|
||||||
case violet
|
case violet
|
||||||
}
|
}
|
||||||
public let color: Color
|
|
||||||
public let name: String
|
|
||||||
|
|
||||||
public init(
|
case peer(EnginePeer)
|
||||||
color: Color,
|
case image(color: Color, name: String)
|
||||||
name: String
|
|
||||||
) {
|
|
||||||
self.color = color
|
|
||||||
self.name = name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Font {
|
public enum Font {
|
||||||
@ -60,6 +54,7 @@ public final class GiftOptionItem: ListViewItem, ItemListItem {
|
|||||||
let icon: Icon?
|
let icon: Icon?
|
||||||
let title: String
|
let title: String
|
||||||
let titleFont: Font
|
let titleFont: Font
|
||||||
|
let titleBadge: String?
|
||||||
let subtitle: String?
|
let subtitle: String?
|
||||||
let subtitleFont: SubtitleFont
|
let subtitleFont: SubtitleFont
|
||||||
let subtitleActive: Bool
|
let subtitleActive: Bool
|
||||||
@ -69,12 +64,13 @@ public final class GiftOptionItem: ListViewItem, ItemListItem {
|
|||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let action: (() -> Void)?
|
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.presentationData = presentationData
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.context = context
|
self.context = context
|
||||||
self.title = title
|
self.title = title
|
||||||
self.titleFont = titleFont
|
self.titleFont = titleFont
|
||||||
|
self.titleBadge = titleBadge
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
self.subtitleFont = subtitleFont
|
self.subtitleFont = subtitleFont
|
||||||
self.subtitleActive = subtitleActive
|
self.subtitleActive = subtitleActive
|
||||||
@ -146,7 +142,9 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var iconNode: ASImageNode?
|
fileprivate var iconNode: ASImageNode?
|
||||||
|
fileprivate var avatarNode: AvatarNode?
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
|
private let titleBadge = ComponentView<Empty>()
|
||||||
private let statusNode: TextNode
|
private let statusNode: TextNode
|
||||||
private var statusArrowNode: ASImageNode?
|
private var statusArrowNode: ASImageNode?
|
||||||
|
|
||||||
@ -282,7 +280,10 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let verticalInset: CGFloat = 10.0
|
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 insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||||
let separatorHeight = UIScreenPixel
|
let separatorHeight = UIScreenPixel
|
||||||
@ -331,37 +332,72 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
|||||||
transition = .immediate
|
transition = .immediate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let iconUpdated = currentItem?.icon != item.icon
|
||||||
|
|
||||||
let iconSize = CGSize(width: 40.0, height: 40.0)
|
let iconSize = CGSize(width: 40.0, height: 40.0)
|
||||||
if let icon = item.icon {
|
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)
|
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 {
|
if let selectableControlSizeAndApply = selectableControlSizeAndApply {
|
||||||
@ -445,7 +481,8 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else {
|
} else {
|
||||||
titleVerticalOriginY = floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0)
|
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
|
var badgeOffset: CGFloat = 0.0
|
||||||
if badgeLayout.size.width > 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.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)
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,6 +130,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
|||||||
let theme = environment.theme
|
let theme = environment.theme
|
||||||
let strings = environment.strings
|
let strings = environment.strings
|
||||||
let dateTimeFormat = environment.dateTimeFormat
|
let dateTimeFormat = environment.dateTimeFormat
|
||||||
|
let accountContext = context.component.context
|
||||||
|
|
||||||
let state = context.state
|
let state = context.state
|
||||||
let giftCode = component.giftCode
|
let giftCode = component.giftCode
|
||||||
@ -239,7 +240,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
|
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
|
||||||
action: {
|
action: {
|
||||||
if let peer = fromPeer {
|
if let peer = fromPeer, peer.id != accountContext.account.peerId {
|
||||||
component.openPeer(peer)
|
component.openPeer(peer)
|
||||||
Queue.mainQueue().after(1.0, {
|
Queue.mainQueue().after(1.0, {
|
||||||
component.cancel(false)
|
component.cancel(false)
|
||||||
@ -258,7 +259,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
|
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
|
||||||
action: {
|
action: {
|
||||||
if let peer = toPeer {
|
if let peer = toPeer, peer.id != accountContext.account.peerId {
|
||||||
component.openPeer(peer)
|
component.openPeer(peer)
|
||||||
Queue.mainQueue().after(1.0, {
|
Queue.mainQueue().after(1.0, {
|
||||||
component.cancel(false)
|
component.cancel(false)
|
||||||
@ -309,7 +310,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
|||||||
component.openMessage(messageId)
|
component.openMessage(messageId)
|
||||||
}
|
}
|
||||||
Queue.mainQueue().after(1.0) {
|
Queue.mainQueue().after(1.0) {
|
||||||
component.cancel(true)
|
component.cancel(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -479,11 +480,15 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
|||||||
giftCode: context.component.giftCode,
|
giftCode: context.component.giftCode,
|
||||||
action: context.component.action,
|
action: context.component.action,
|
||||||
cancel: { animate in
|
cancel: { animate in
|
||||||
animateOut.invoke(Action { _ in
|
if animate {
|
||||||
if let controller = controller() {
|
animateOut.invoke(Action { _ in
|
||||||
controller.dismiss(completion: nil)
|
if let controller = controller() {
|
||||||
}
|
controller.dismiss(completion: nil)
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
} else if let controller = controller() {
|
||||||
|
controller.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
openPeer: context.component.openPeer,
|
openPeer: context.component.openPeer,
|
||||||
openMessage: context.component.openMessage,
|
openMessage: context.component.openMessage,
|
||||||
|
|||||||
@ -723,7 +723,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
var premiumLimits: EngineConfiguration.UserLimits
|
var premiumLimits: EngineConfiguration.UserLimits
|
||||||
var isPremium = false
|
var isPremium = false
|
||||||
|
|
||||||
var boosted = false
|
var myBoostCount: Int32 = 0
|
||||||
|
|
||||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||||
var cachedChevronImage: (UIImage, PresentationTheme)?
|
var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||||
@ -1045,7 +1045,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
string = strings.Premium_MaxStoriesMonthlyNoPremiumText("\(limit)").string
|
string = strings.Premium_MaxStoriesMonthlyNoPremiumText("\(limit)").string
|
||||||
}
|
}
|
||||||
buttonAnimationName = nil
|
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 {
|
if link == nil, !isCurrent, state.initialized {
|
||||||
peerShortcutChild = peerShortcut.update(
|
peerShortcutChild = peerShortcut.update(
|
||||||
component: Button(
|
component: Button(
|
||||||
@ -1054,7 +1054,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
context: component.context,
|
context: component.context,
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
peer: peer,
|
peer: peer,
|
||||||
badge: "X2"
|
badge: myBoostCount > 0 ? "\(myBoostCount)" : nil
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
action: {
|
action: {
|
||||||
@ -1098,11 +1098,11 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if boosted && state.boosted != boosted {
|
if myBoostCount > 0 && state.myBoostCount != myBoostCount {
|
||||||
state.boosted = boosted
|
state.myBoostCount = myBoostCount
|
||||||
boostUpdated = true
|
boostUpdated = true
|
||||||
}
|
}
|
||||||
useAlternateText = boosted
|
useAlternateText = myBoostCount > 0
|
||||||
|
|
||||||
iconName = "Premium/Boost"
|
iconName = "Premium/Boost"
|
||||||
badgeText = "\(component.count)"
|
badgeText = "\(component.count)"
|
||||||
@ -1157,7 +1157,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
|
|
||||||
premiumTitle = ""
|
premiumTitle = ""
|
||||||
|
|
||||||
if boosted {
|
if myBoostCount > 0 {
|
||||||
let prefixString = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText
|
let prefixString = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText
|
||||||
|
|
||||||
let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1)
|
let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1)
|
||||||
@ -1205,6 +1205,10 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
if case .folders = subject, !state.isPremium {
|
if case .folders = subject, !state.isPremium {
|
||||||
reachedMaximumLimit = false
|
reachedMaximumLimit = false
|
||||||
}
|
}
|
||||||
|
if case .storiesChannelBoost = component.subject {
|
||||||
|
reachedMaximumLimit = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
if state.initialized {
|
if state.initialized {
|
||||||
@ -1648,7 +1652,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
|||||||
case storiesWeekly
|
case storiesWeekly
|
||||||
case storiesMonthly
|
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
|
private let context: AccountContext
|
||||||
@ -1742,8 +1746,7 @@ private final class PeerShortcutComponent: Component {
|
|||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let text = ComponentView<Empty>()
|
private let text = ComponentView<Empty>()
|
||||||
|
|
||||||
private let badgeBackground = UIView()
|
private let badge = ComponentView<Empty>()
|
||||||
private let badgeText = ComponentView<Empty>()
|
|
||||||
|
|
||||||
private var component: PeerShortcutComponent?
|
private var component: PeerShortcutComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
@ -1756,12 +1759,8 @@ private final class PeerShortcutComponent: Component {
|
|||||||
self.backgroundView.clipsToBounds = true
|
self.backgroundView.clipsToBounds = true
|
||||||
self.backgroundView.layer.cornerRadius = 16.0
|
self.backgroundView.layer.cornerRadius = 16.0
|
||||||
|
|
||||||
self.badgeBackground.clipsToBounds = true
|
|
||||||
self.badgeBackground.backgroundColor = UIColor(rgb: 0x9671ff)
|
|
||||||
|
|
||||||
self.addSubview(self.backgroundView)
|
self.addSubview(self.backgroundView)
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
self.addSubview(self.badgeBackground)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -1803,35 +1802,26 @@ private final class PeerShortcutComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let badge = component.badge {
|
if let badge = component.badge {
|
||||||
let badgeSize = self.badgeText.update(
|
let badgeSize = self.badge.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
MultilineTextComponent(
|
BoostIconComponent(text: badge)
|
||||||
text: .plain(NSAttributedString(string: badge, font: Font.with(size: 11.0, design: .round, weight: .bold), textColor: .white, paragraphAlignment: .left))
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
)
|
)
|
||||||
if let view = self.badgeText.view {
|
if let view = self.badge.view {
|
||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
self.addSubview(view)
|
self.addSubview(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - badgeSize.width / 2.0), y: -2.0), size: badgeSize)
|
let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - badgeSize.width / 2.0), y: -5.0), size: badgeSize)
|
||||||
view.frame = textFrame
|
view.frame = badgeFrame
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let view = self.badgeText.view {
|
if let view = self.badge.view {
|
||||||
view.removeFromSuperview()
|
view.removeFromSuperview()
|
||||||
}
|
}
|
||||||
self.badgeBackground.isHidden = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundView.frame = CGRect(origin: .zero, size: size)
|
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)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1201
submodules/PremiumUI/Sources/ReplaceBoostScreen.swift
Normal file
1201
submodules/PremiumUI/Sources/ReplaceBoostScreen.swift
Normal file
File diff suppressed because it is too large
Load Diff
267
submodules/StatisticsUI/Sources/BoostsTabsItem.swift
Normal file
267
submodules/StatisticsUI/Sources/BoostsTabsItem.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,8 +35,9 @@ private final class ChannelStatsControllerArguments {
|
|||||||
let expandBoosters: () -> Void
|
let expandBoosters: () -> Void
|
||||||
let openGifts: () -> Void
|
let openGifts: () -> Void
|
||||||
let createPrepaidGiveaway: (PrepaidGiveaway) -> 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.context = context
|
||||||
self.loadDetailedGraph = loadDetailedGraph
|
self.loadDetailedGraph = loadDetailedGraph
|
||||||
self.openMessageStats = openMessage
|
self.openMessageStats = openMessage
|
||||||
@ -47,6 +48,7 @@ private final class ChannelStatsControllerArguments {
|
|||||||
self.expandBoosters = expandBoosters
|
self.expandBoosters = expandBoosters
|
||||||
self.openGifts = openGifts
|
self.openGifts = openGifts
|
||||||
self.createPrepaidGiveaway = createPrepaidGiveaway
|
self.createPrepaidGiveaway = createPrepaidGiveaway
|
||||||
|
self.updateGiftsSelected = updateGiftsSelected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +117,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
case boostersTitle(PresentationTheme, String)
|
case boostersTitle(PresentationTheme, String)
|
||||||
case boostersPlaceholder(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 boostersExpand(PresentationTheme, String)
|
||||||
case boostersInfo(PresentationTheme, String)
|
case boostersInfo(PresentationTheme, String)
|
||||||
|
|
||||||
@ -156,7 +159,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
return StatsSection.boostOverview.rawValue
|
return StatsSection.boostOverview.rawValue
|
||||||
case .boostPrepaidTitle, .boostPrepaid, .boostPrepaidInfo:
|
case .boostPrepaidTitle, .boostPrepaid, .boostPrepaidInfo:
|
||||||
return StatsSection.boostPrepaid.rawValue
|
return StatsSection.boostPrepaid.rawValue
|
||||||
case .boostersTitle, .boostersPlaceholder, .booster, .boostersExpand, .boostersInfo:
|
case .boostersTitle, .boostersPlaceholder, .boosterTabs, .booster, .boostersExpand, .boostersInfo:
|
||||||
return StatsSection.boosters.rawValue
|
return StatsSection.boosters.rawValue
|
||||||
case .boostLinkTitle, .boostLink, .boostLinkInfo:
|
case .boostLinkTitle, .boostLink, .boostLinkInfo:
|
||||||
return StatsSection.boostLink.rawValue
|
return StatsSection.boostLink.rawValue
|
||||||
@ -227,8 +230,10 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
return 2101
|
return 2101
|
||||||
case .boostersPlaceholder:
|
case .boostersPlaceholder:
|
||||||
return 2102
|
return 2102
|
||||||
case let .booster(index, _, _, _, _):
|
case .boosterTabs:
|
||||||
return 2103 + index
|
return 2103
|
||||||
|
case let .booster(index, _, _, _, _, _, _):
|
||||||
|
return 2104 + index
|
||||||
case .boostersExpand:
|
case .boostersExpand:
|
||||||
return 10000
|
return 10000
|
||||||
case .boostersInfo:
|
case .boostersInfo:
|
||||||
@ -428,8 +433,14 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .booster(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsExpires):
|
case let .boosterTabs(lhsTheme, lhsBoostText, lhsGiftText, lhsGiftSelected):
|
||||||
if case let .booster(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsExpires) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsExpires == rhsExpires {
|
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
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -533,17 +544,34 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
}, contextAction: { node, gesture in
|
}, contextAction: { node, gesture in
|
||||||
arguments.contextAction(message.id, node, gesture)
|
arguments.contextAction(message.id, node, gesture)
|
||||||
})
|
})
|
||||||
case let .booster(_, _, dateTimeFormat, peer, expires):
|
case let .boosterTabs(_, boostText, giftText, giftSelected):
|
||||||
let _ = dateTimeFormat
|
return BoostsTabsItem(theme: presentationData.theme, boostsText: boostText, giftsText: giftText, selectedTab: giftSelected ? .gifts : .boosts, sectionId: self.section, selectionUpdated: { tab in
|
||||||
let _ = peer
|
arguments.updateGiftsSelected(tab == .gifts)
|
||||||
let _ = expires
|
})
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("text"), sectionId: self.section)
|
case let .booster(_, _, _, peer, count, flags, expires):
|
||||||
// let expiresValue = stringForMediumDate(timestamp: expires, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
let expiresValue = stringForDate(timestamp: expires, strings: presentationData.strings)
|
||||||
// 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: {
|
let expiresString = presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string
|
||||||
// if let peer {
|
|
||||||
// arguments.openPeer(peer)
|
let title: String
|
||||||
// }
|
let icon: GiftOptionItem.Icon
|
||||||
// }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
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):
|
case let .boostersExpand(theme, title):
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||||
arguments.expandBoosters()
|
arguments.expandBoosters()
|
||||||
@ -579,7 +607,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
default:
|
default:
|
||||||
color = .blue
|
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)
|
arguments.createPrepaidGiveaway(prepaidGiveaway)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -594,15 +622,18 @@ public enum ChannelStatsSection {
|
|||||||
private struct ChannelStatsControllerState: Equatable {
|
private struct ChannelStatsControllerState: Equatable {
|
||||||
let section: ChannelStatsSection
|
let section: ChannelStatsSection
|
||||||
let boostersExpanded: Bool
|
let boostersExpanded: Bool
|
||||||
|
let giftsSelected: Bool
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.section = .stats
|
self.section = .stats
|
||||||
self.boostersExpanded = false
|
self.boostersExpanded = false
|
||||||
|
self.giftsSelected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
init(section: ChannelStatsSection, boostersExpanded: Bool) {
|
init(section: ChannelStatsSection, boostersExpanded: Bool, giftsSelected: Bool) {
|
||||||
self.section = section
|
self.section = section
|
||||||
self.boostersExpanded = boostersExpanded
|
self.boostersExpanded = boostersExpanded
|
||||||
|
self.giftsSelected = giftsSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ChannelStatsControllerState, rhs: ChannelStatsControllerState) -> Bool {
|
static func ==(lhs: ChannelStatsControllerState, rhs: ChannelStatsControllerState) -> Bool {
|
||||||
@ -612,20 +643,27 @@ private struct ChannelStatsControllerState: Equatable {
|
|||||||
if lhs.boostersExpanded != rhs.boostersExpanded {
|
if lhs.boostersExpanded != rhs.boostersExpanded {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.giftsSelected != rhs.giftsSelected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedSection(_ section: ChannelStatsSection) -> ChannelStatsControllerState {
|
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 {
|
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] = []
|
var entries: [StatsEntry] = []
|
||||||
|
|
||||||
switch state.section {
|
switch state.section {
|
||||||
@ -735,6 +773,19 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
|||||||
entries.append(.boostersPlaceholder(presentationData.theme, boostersPlaceholder))
|
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 {
|
if let boostersState {
|
||||||
var boosterIndex: Int32 = 0
|
var boosterIndex: Int32 = 0
|
||||||
|
|
||||||
@ -747,7 +798,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
for booster in boosters {
|
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
|
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 {
|
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 statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false))
|
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false))
|
||||||
let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in
|
let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
@ -813,7 +864,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
} else {
|
} else {
|
||||||
boostData = .single(nil) |> then(context.engine.peers.getChannelBoostStatus(peerId: peerId))
|
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 presentImpl: ((ViewController) -> Void)?
|
||||||
var pushImpl: ((ViewController) -> Void)?
|
var pushImpl: ((ViewController) -> Void)?
|
||||||
@ -885,6 +937,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
createPrepaidGiveaway: { prepaidGiveaway in
|
createPrepaidGiveaway: { prepaidGiveaway in
|
||||||
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(prepaidGiveaway))
|
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(prepaidGiveaway))
|
||||||
pushImpl?(controller)
|
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)
|
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(),
|
dataPromise.get(),
|
||||||
messagesPromise.get(),
|
messagesPromise.get(),
|
||||||
boostData,
|
boostData,
|
||||||
boostersContext.state,
|
boostsContext.state,
|
||||||
|
giftsContext.state,
|
||||||
longLoadingSignal
|
longLoadingSignal
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue
|
|> 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)
|
let previous = previousData.swap(data)
|
||||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||||||
switch state.section {
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -959,7 +1015,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
controller.visibleBottomContentOffsetChanged = { offset in
|
controller.visibleBottomContentOffsetChanged = { offset in
|
||||||
let state = stateValue.with { $0 }
|
let state = stateValue.with { $0 }
|
||||||
if case let .known(value) = offset, value < 100.0, case .boosts = state.section, state.boostersExpanded {
|
if case let .known(value) = offset, value < 100.0, case .boosts = state.section, state.boostersExpanded {
|
||||||
boostersContext.loadMore()
|
boostsContext.loadMore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.titleControlValueChanged = { value in
|
controller.titleControlValueChanged = { value in
|
||||||
|
|||||||
@ -97,7 +97,7 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer.
|
|||||||
let accountPeerId = account.peerId
|
let accountPeerId = account.peerId
|
||||||
|
|
||||||
return account.postbox.transaction { transaction -> Signal<Peer, NoError> in
|
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()
|
return .complete()
|
||||||
}
|
}
|
||||||
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId)], update: { _, updated in
|
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId)], update: { _, updated in
|
||||||
|
|||||||
@ -47,7 +47,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
private var giveaway: TelegramMediaGiveaway?
|
private var giveaway: TelegramMediaGiveaway?
|
||||||
|
|
||||||
private let buttonNode: ChatMessageAttachedContentButtonNode
|
private let buttonNode: ChatMessageAttachedContentButtonNode
|
||||||
private let channelButton: PeerButtonNode
|
private let channelButtons: PeerButtonsStackNode
|
||||||
|
|
||||||
override public var visibility: ListViewItemNodeVisibility {
|
override public var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
@ -95,7 +95,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
self.badgeTextNode = TextNode()
|
self.badgeTextNode = TextNode()
|
||||||
|
|
||||||
self.buttonNode = ChatMessageAttachedContentButtonNode()
|
self.buttonNode = ChatMessageAttachedContentButtonNode()
|
||||||
self.channelButton = PeerButtonNode()
|
self.channelButtons = PeerButtonsStackNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
self.addSubnode(self.dateTitleNode)
|
self.addSubnode(self.dateTitleNode)
|
||||||
self.addSubnode(self.dateTextNode)
|
self.addSubnode(self.dateTextNode)
|
||||||
self.addSubnode(self.buttonNode)
|
self.addSubnode(self.buttonNode)
|
||||||
self.addSubnode(self.channelButton)
|
self.addSubnode(self.channelButtons)
|
||||||
self.addSubnode(self.animationNode)
|
self.addSubnode(self.animationNode)
|
||||||
self.addSubnode(self.badgeBackgroundNode)
|
self.addSubnode(self.badgeBackgroundNode)
|
||||||
self.addSubnode(self.badgeTextNode)
|
self.addSubnode(self.badgeTextNode)
|
||||||
@ -129,6 +129,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
|
|
||||||
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceView, gesture, value)
|
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 {
|
override public func accessibilityActivate() -> Bool {
|
||||||
@ -185,7 +192,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
|
|
||||||
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
|
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
|
||||||
|
|
||||||
let makeChannelLayout = PeerButtonNode.asyncLayout(self.channelButton)
|
let makeChannelsLayout = PeerButtonsStackNode.asyncLayout(self.channelButtons)
|
||||||
|
|
||||||
let currentItem = self.item
|
let currentItem = self.item
|
||||||
|
|
||||||
@ -207,12 +214,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
incoming = false
|
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 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
|
let accentColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||||
|
|
||||||
var updatedBadgeImage: UIImage?
|
var updatedBadgeImage: UIImage?
|
||||||
if themeUpdated {
|
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)
|
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, dateTitleLayout.size.width)
|
||||||
maxContentWidth = max(maxContentWidth, dateTextLayout.size.width)
|
maxContentWidth = max(maxContentWidth, dateTextLayout.size.width)
|
||||||
maxContentWidth = max(maxContentWidth, buttonWidth)
|
maxContentWidth = max(maxContentWidth, buttonWidth)
|
||||||
maxContentWidth += 30.0
|
|
||||||
|
var channelPeers: [EnginePeer] = []
|
||||||
var channelPeer: EnginePeer?
|
if let channelPeerIds = giveaway?.channelPeerIds {
|
||||||
if let channelPeerId = giveaway?.channelPeerIds.first, let peer = item.message.peers[channelPeerId] {
|
for peerId in channelPeerIds {
|
||||||
channelPeer = EnginePeer(peer)
|
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
|
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 (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0, 33.0)
|
||||||
let buttonSpacing: CGFloat = 4.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)
|
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
|
||||||
|
|
||||||
@ -436,7 +449,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
if countriesTextLayout.size.height > 0.0 {
|
if countriesTextLayout.size.height > 0.0 {
|
||||||
layoutSize.height += countriesTextLayout.size.height + 7.0
|
layoutSize.height += countriesTextLayout.size.height + 7.0
|
||||||
}
|
}
|
||||||
layoutSize.height += channelButtonSize.height
|
layoutSize.height += channelButtonsSize.height
|
||||||
|
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
layoutSize.height += statusSizeAndApply.0.height - 4.0
|
layoutSize.height += statusSizeAndApply.0.height - 4.0
|
||||||
@ -462,7 +475,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
let _ = countriesTextApply()
|
let _ = countriesTextApply()
|
||||||
let _ = dateTitleApply()
|
let _ = dateTitleApply()
|
||||||
let _ = dateTextApply()
|
let _ = dateTextApply()
|
||||||
let _ = channelButtonApply()
|
let _ = channelButtonsApply()
|
||||||
let _ = buttonApply(animation)
|
let _ = buttonApply(animation)
|
||||||
|
|
||||||
let smallSpacing: CGFloat = 2.0
|
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)
|
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
|
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)
|
strongSelf.channelButtons.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - channelButtonsSize.width) / 2.0), y: originY), size: channelButtonsSize)
|
||||||
originY += channelButtonSize.height
|
originY += channelButtonsSize.height
|
||||||
|
|
||||||
if countriesTextLayout.size.height > 0.0 {
|
if countriesTextLayout.size.height > 0.0 {
|
||||||
originY += smallSpacing * 2.0 + 3.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 {
|
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) {
|
if self.buttonNode.frame.contains(point) {
|
||||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||||
}
|
}
|
||||||
@ -578,10 +594,6 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
@objc private func buttonPressed() {
|
@objc private func buttonPressed() {
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
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 final class PeerButtonNode: HighlightTrackingButtonNode {
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
private let textNode: TextNode
|
private let textNode: TextNode
|
||||||
|
|||||||
@ -5898,7 +5898,10 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.visibleItemSelectionLayers[itemId] = itemSelectionLayer
|
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.backgroundColor = keyboardChildEnvironment.theme.list.itemAccentColor.withMultipliedAlpha(0.1).cgColor
|
||||||
itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor
|
itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -124,6 +124,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
public let clipContentToTopPanel: Bool
|
public let clipContentToTopPanel: Bool
|
||||||
public let useExternalSearchContainer: Bool
|
public let useExternalSearchContainer: Bool
|
||||||
public let hidePanels: Bool
|
public let hidePanels: Bool
|
||||||
|
public let customTintColor: UIColor?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
@ -157,7 +158,8 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
isExpanded: Bool,
|
isExpanded: Bool,
|
||||||
clipContentToTopPanel: Bool,
|
clipContentToTopPanel: Bool,
|
||||||
useExternalSearchContainer: Bool,
|
useExternalSearchContainer: Bool,
|
||||||
hidePanels: Bool = false
|
hidePanels: Bool = false,
|
||||||
|
customTintColor: UIColor? = nil
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
@ -191,6 +193,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
self.clipContentToTopPanel = clipContentToTopPanel
|
self.clipContentToTopPanel = clipContentToTopPanel
|
||||||
self.useExternalSearchContainer = useExternalSearchContainer
|
self.useExternalSearchContainer = useExternalSearchContainer
|
||||||
self.hidePanels = hidePanels
|
self.hidePanels = hidePanels
|
||||||
|
self.customTintColor = customTintColor
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
||||||
@ -257,7 +260,9 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
if lhs.useExternalSearchContainer != rhs.useExternalSearchContainer {
|
if lhs.useExternalSearchContainer != rhs.useExternalSearchContainer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.customTintColor != rhs.customTintColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +346,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
icon: icon,
|
icon: icon,
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
useAccentColor: false,
|
useAccentColor: false,
|
||||||
|
customTintColor: component.customTintColor,
|
||||||
title: title,
|
title: title,
|
||||||
pressed: { [weak self] in
|
pressed: { [weak self] in
|
||||||
self?.scrollToItemGroup(contentId: "masks", groupId: itemGroup.supergroupId, subgroupId: nil)
|
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(
|
contentTopPanels.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||||
id: "masks",
|
id: "masks",
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
|
customTintColor: component.customTintColor,
|
||||||
items: topMaskItems,
|
items: topMaskItems,
|
||||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||||
defaultActiveItemId: maskContent.panelItemGroups.first?.groupId,
|
defaultActiveItemId: maskContent.panelItemGroups.first?.groupId,
|
||||||
@ -430,6 +437,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
icon: .featured,
|
icon: .featured,
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
useAccentColor: false,
|
useAccentColor: false,
|
||||||
|
customTintColor: component.customTintColor,
|
||||||
title: component.strings.Stickers_Trending,
|
title: component.strings.Stickers_Trending,
|
||||||
pressed: { [weak self] in
|
pressed: { [weak self] in
|
||||||
self?.component?.stickerContent?.inputInteractionHolder.inputInteraction?.openFeatured?()
|
self?.component?.stickerContent?.inputInteractionHolder.inputInteraction?.openFeatured?()
|
||||||
@ -475,6 +483,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
icon: icon,
|
icon: icon,
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
useAccentColor: false,
|
useAccentColor: false,
|
||||||
|
customTintColor: component.customTintColor,
|
||||||
title: title,
|
title: title,
|
||||||
pressed: { [weak self] in
|
pressed: { [weak self] in
|
||||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.supergroupId, subgroupId: nil)
|
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(
|
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||||
id: "stickers",
|
id: "stickers",
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
|
customTintColor: component.customTintColor,
|
||||||
items: topStickerItems,
|
items: topStickerItems,
|
||||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||||
defaultActiveItemId: stickerContent.panelItemGroups.first?.groupId,
|
defaultActiveItemId: stickerContent.panelItemGroups.first?.groupId,
|
||||||
@ -573,6 +583,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
icon: icon,
|
icon: icon,
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
useAccentColor: false,
|
useAccentColor: false,
|
||||||
|
customTintColor: component.customTintColor,
|
||||||
title: title,
|
title: title,
|
||||||
pressed: { [weak self] in
|
pressed: { [weak self] in
|
||||||
self?.scrollToItemGroup(contentId: "emoji", groupId: itemGroup.supergroupId, subgroupId: nil)
|
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(
|
contentTopPanels.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||||
id: "emoji",
|
id: "emoji",
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
|
customTintColor: component.customTintColor,
|
||||||
items: topEmojiItems,
|
items: topEmojiItems,
|
||||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||||
activeContentItemIdUpdated: emojiContentItemIdUpdated,
|
activeContentItemIdUpdated: emojiContentItemIdUpdated,
|
||||||
|
|||||||
@ -281,6 +281,7 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
|||||||
let icon: Icon
|
let icon: Icon
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let useAccentColor: Bool
|
let useAccentColor: Bool
|
||||||
|
let customTintColor: UIColor?
|
||||||
let title: String
|
let title: String
|
||||||
let pressed: () -> Void
|
let pressed: () -> Void
|
||||||
|
|
||||||
@ -288,12 +289,14 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
|||||||
icon: Icon,
|
icon: Icon,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
useAccentColor: Bool,
|
useAccentColor: Bool,
|
||||||
|
customTintColor: UIColor?,
|
||||||
title: String,
|
title: String,
|
||||||
pressed: @escaping () -> Void
|
pressed: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.useAccentColor = useAccentColor
|
self.useAccentColor = useAccentColor
|
||||||
|
self.customTintColor = customTintColor
|
||||||
self.title = title
|
self.title = title
|
||||||
self.pressed = pressed
|
self.pressed = pressed
|
||||||
}
|
}
|
||||||
@ -308,6 +311,9 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
|||||||
if lhs.useAccentColor != rhs.useAccentColor {
|
if lhs.useAccentColor != rhs.useAccentColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.customTintColor != rhs.customTintColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.title != rhs.title {
|
if lhs.title != rhs.title {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -383,14 +389,18 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
if itemEnvironment.isHighlighted {
|
if let customTintColor = component.customTintColor {
|
||||||
if component.useAccentColor {
|
color = customTintColor
|
||||||
color = component.theme.list.itemAccentColor
|
|
||||||
} else {
|
|
||||||
color = component.theme.chat.inputMediaPanel.panelHighlightedIconColor
|
|
||||||
}
|
|
||||||
} else {
|
} 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 {
|
if self.iconView.tintColor != color {
|
||||||
@ -1192,6 +1202,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
|||||||
|
|
||||||
let id: AnyHashable
|
let id: AnyHashable
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let customTintColor: UIColor?
|
||||||
let items: [Item]
|
let items: [Item]
|
||||||
let containerSideInset: CGFloat
|
let containerSideInset: CGFloat
|
||||||
let defaultActiveItemId: AnyHashable?
|
let defaultActiveItemId: AnyHashable?
|
||||||
@ -1203,6 +1214,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
|||||||
init(
|
init(
|
||||||
id: AnyHashable,
|
id: AnyHashable,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
|
customTintColor: UIColor?,
|
||||||
items: [Item],
|
items: [Item],
|
||||||
containerSideInset: CGFloat,
|
containerSideInset: CGFloat,
|
||||||
defaultActiveItemId: AnyHashable? = nil,
|
defaultActiveItemId: AnyHashable? = nil,
|
||||||
@ -1213,6 +1225,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
|||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.customTintColor = customTintColor
|
||||||
self.items = items
|
self.items = items
|
||||||
self.containerSideInset = containerSideInset
|
self.containerSideInset = containerSideInset
|
||||||
self.defaultActiveItemId = defaultActiveItemId
|
self.defaultActiveItemId = defaultActiveItemId
|
||||||
@ -1229,6 +1242,9 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
|||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme !== rhs.theme {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.customTintColor != rhs.customTintColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.items != rhs.items {
|
if lhs.items != rhs.items {
|
||||||
return false
|
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 {
|
func update(component: EntityKeyboardTopPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
if self.component?.theme !== component.theme {
|
if self.component?.theme !== component.theme || self.component?.customTintColor != component.customTintColor {
|
||||||
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
|
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.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|||||||
@ -67,7 +67,7 @@ final class ApplyColorFooterItemNode: ItemListControllerFooterItemNode {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.separatorNode)
|
// self.addSubnode(self.separatorNode)
|
||||||
self.addSubnode(self.buttonNode)
|
self.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
self.updateItem()
|
self.updateItem()
|
||||||
|
|||||||
@ -186,6 +186,7 @@ final class EmojiPickerItemNode: ListViewItemNode {
|
|||||||
strings: item.strings,
|
strings: item.strings,
|
||||||
deviceMetrics: .iPhone14ProMax,
|
deviceMetrics: .iPhone14ProMax,
|
||||||
emojiContent: item.emojiContent,
|
emojiContent: item.emojiContent,
|
||||||
|
backgroundIconColor: item.backgroundIconColor,
|
||||||
backgroundColor: item.theme.list.itemBlocksBackgroundColor,
|
backgroundColor: item.theme.list.itemBlocksBackgroundColor,
|
||||||
separatorColor: item.theme.list.itemBlocksSeparatorColor
|
separatorColor: item.theme.list.itemBlocksSeparatorColor
|
||||||
)
|
)
|
||||||
@ -221,6 +222,7 @@ private final class EmojiSelectionComponent: Component {
|
|||||||
public let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
public let deviceMetrics: DeviceMetrics
|
public let deviceMetrics: DeviceMetrics
|
||||||
public let emojiContent: EmojiPagerContentComponent
|
public let emojiContent: EmojiPagerContentComponent
|
||||||
|
public let backgroundIconColor: UIColor
|
||||||
public let backgroundColor: UIColor
|
public let backgroundColor: UIColor
|
||||||
public let separatorColor: UIColor
|
public let separatorColor: UIColor
|
||||||
|
|
||||||
@ -229,6 +231,7 @@ private final class EmojiSelectionComponent: Component {
|
|||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
deviceMetrics: DeviceMetrics,
|
deviceMetrics: DeviceMetrics,
|
||||||
emojiContent: EmojiPagerContentComponent,
|
emojiContent: EmojiPagerContentComponent,
|
||||||
|
backgroundIconColor: UIColor,
|
||||||
backgroundColor: UIColor,
|
backgroundColor: UIColor,
|
||||||
separatorColor: UIColor
|
separatorColor: UIColor
|
||||||
) {
|
) {
|
||||||
@ -236,6 +239,7 @@ private final class EmojiSelectionComponent: Component {
|
|||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.deviceMetrics = deviceMetrics
|
self.deviceMetrics = deviceMetrics
|
||||||
self.emojiContent = emojiContent
|
self.emojiContent = emojiContent
|
||||||
|
self.backgroundIconColor = backgroundIconColor
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.separatorColor = separatorColor
|
self.separatorColor = separatorColor
|
||||||
}
|
}
|
||||||
@ -253,6 +257,9 @@ private final class EmojiSelectionComponent: Component {
|
|||||||
if lhs.emojiContent != rhs.emojiContent {
|
if lhs.emojiContent != rhs.emojiContent {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.backgroundIconColor != rhs.backgroundIconColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.backgroundColor != rhs.backgroundColor {
|
if lhs.backgroundColor != rhs.backgroundColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -338,7 +345,8 @@ private final class EmojiSelectionComponent: Component {
|
|||||||
displayBottomPanel: false,
|
displayBottomPanel: false,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
clipContentToTopPanel: false,
|
clipContentToTopPanel: false,
|
||||||
useExternalSearchContainer: false
|
useExternalSearchContainer: false,
|
||||||
|
customTintColor: component.backgroundIconColor
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
|
|||||||
@ -252,11 +252,11 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
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) {
|
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
|
||||||
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
|
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
|
||||||
strongSelf.view.addSubview(snapshot)
|
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()
|
snapshot.removeFromSuperview()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -393,13 +393,18 @@ public func PeerNameColorScreen(
|
|||||||
title: buttonTitle,
|
title: buttonTitle,
|
||||||
locked: isLocked,
|
locked: isLocked,
|
||||||
action: {
|
action: {
|
||||||
if isPremium {
|
if !isLocked {
|
||||||
let state = stateValue.with { $0 }
|
let state = stateValue.with { $0 }
|
||||||
|
|
||||||
let nameColor = state.updatedNameColor ?? peer?.nameColor
|
let nameColor = state.updatedNameColor ?? peer?.nameColor
|
||||||
let backgroundEmojiId = state.updatedBackgroundEmojiId ?? peer?.backgroundEmojiId
|
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?()
|
dismissImpl?()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -836,7 +836,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
guard let stateValue = self.effectiveStateValue else {
|
guard let stateValue = self.effectiveStateValue else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||||
topOffset = max(0.0, topOffset)
|
topOffset = max(0.0, topOffset)
|
||||||
transition.setTransform(layer: self.backgroundView.layer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
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)
|
self.hapticFeedback.impact(.light)
|
||||||
} else {
|
} else {
|
||||||
self.postingAvailabilityDisposable.set((component.context.engine.messages.checkStoriesUploadAvailability(target: .peer(peer.id))
|
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 {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1106,7 +1106,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
previousController.dismiss()
|
previousController.dismiss()
|
||||||
}
|
}
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
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
|
UIPasteboard.general.string = link
|
||||||
|
|
||||||
if let previousController = navigationController?.viewControllers.reversed().first(where: { $0 is ShareWithPeersScreen}) as? ShareWithPeersScreen {
|
if let previousController = navigationController?.viewControllers.reversed().first(where: { $0 is ShareWithPeersScreen}) as? ShareWithPeersScreen {
|
||||||
@ -1397,7 +1397,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
subtitle = nil
|
subtitle = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let isSelected = self.selectedPeers.contains(peer.id) || self.selectedGroups.contains(peer.id)
|
let isSelected = self.selectedPeers.contains(peer.id) || self.selectedGroups.contains(peer.id)
|
||||||
let _ = visibleItem.update(
|
let _ = visibleItem.update(
|
||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
@ -1671,7 +1671,21 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fadeTransition = Transition.easeInOut(duration: 0.25)
|
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 sideInset: CGFloat = 44.0
|
||||||
let emptyAnimationHeight = 148.0
|
let emptyAnimationHeight = 148.0
|
||||||
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
|
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
|
||||||
@ -1695,7 +1709,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
MultilineTextComponent(
|
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,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
)
|
)
|
||||||
@ -2041,15 +2055,28 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
containerSize: CGSize(width: containerWidth, height: 1000.0)
|
containerSize: CGSize(width: containerWidth, height: 1000.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
if !self.navigationTextFieldState.text.isEmpty {
|
let searchQuery = self.navigationTextFieldState.text
|
||||||
|
if !searchQuery.isEmpty {
|
||||||
var onlyContacts = false
|
var onlyContacts = false
|
||||||
if component.initialPrivacy.base == .closeFriends || component.initialPrivacy.base == .contacts {
|
if component.initialPrivacy.base == .closeFriends || component.initialPrivacy.base == .contacts {
|
||||||
onlyContacts = true
|
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 {
|
} else {
|
||||||
self.searchStateDisposable?.dispose()
|
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
|
var applyState = false
|
||||||
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import TelegramCore
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import TemporaryCachedPeerDataManager
|
import TemporaryCachedPeerDataManager
|
||||||
|
import Postbox
|
||||||
|
|
||||||
public extension ShareWithPeersScreen {
|
public extension ShareWithPeersScreen {
|
||||||
final class State {
|
final class State {
|
||||||
@ -48,7 +49,7 @@ public extension ShareWithPeersScreen {
|
|||||||
case contacts(base: EngineStoryPrivacy.Base)
|
case contacts(base: EngineStoryPrivacy.Base)
|
||||||
case contactsSearch(query: String, onlyContacts: Bool)
|
case contactsSearch(query: String, onlyContacts: Bool)
|
||||||
case members(peerId: EnginePeer.Id, searchQuery: String?)
|
case members(peerId: EnginePeer.Id, searchQuery: String?)
|
||||||
case channels(exclude: Set<EnginePeer.Id>)
|
case channels(exclude: Set<EnginePeer.Id>, searchQuery: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateValue: State?
|
var stateValue: State?
|
||||||
@ -584,7 +585,7 @@ public extension ShareWithPeersScreen {
|
|||||||
self.stateDisposable = combinedDisposable
|
self.stateDisposable = combinedDisposable
|
||||||
|
|
||||||
self.listControl = disposableAndLoadMoreControl.1
|
self.listControl = disposableAndLoadMoreControl.1
|
||||||
case let .channels(excludePeerIds):
|
case let .channels(excludePeerIds, searchQuery):
|
||||||
self.stateDisposable = (combineLatest(
|
self.stateDisposable = (combineLatest(
|
||||||
context.engine.messages.chatList(group: .root, count: 500) |> take(1),
|
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)))
|
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)
|
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] = []
|
var peers: [EnginePeer] = []
|
||||||
peers = chatList.items.filter { peer in
|
peers = chatList.items.filter { peer in
|
||||||
if let peer = peer.renderedPeer.peer {
|
if let peer = peer.renderedPeer.peer {
|
||||||
if excludePeerIds.contains(peer.id) {
|
if excludePeerIds.contains(peer.id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let _ = searchQuery, !peerMatchesTokens(peer: peer, tokens: queryTokens) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if self.initialPeerIds.contains(peer.id) {
|
if self.initialPeerIds.contains(peer.id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,11 @@ public final class PeerListItemComponent: Component {
|
|||||||
case editing(isSelected: Bool, isTinted: Bool)
|
case editing(isSelected: Bool, isTinted: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SelectionPosition: Equatable {
|
||||||
|
case left
|
||||||
|
case right
|
||||||
|
}
|
||||||
|
|
||||||
public enum SubtitleAccessory: Equatable {
|
public enum SubtitleAccessory: Equatable {
|
||||||
case none
|
case none
|
||||||
case checks
|
case checks
|
||||||
@ -101,6 +106,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
let rightAccessory: RightAccessory
|
let rightAccessory: RightAccessory
|
||||||
let reaction: Reaction?
|
let reaction: Reaction?
|
||||||
let selectionState: SelectionState
|
let selectionState: SelectionState
|
||||||
|
let selectionPosition: SelectionPosition
|
||||||
|
let isEnabled: Bool
|
||||||
let hasNext: Bool
|
let hasNext: Bool
|
||||||
let action: (EnginePeer) -> Void
|
let action: (EnginePeer) -> Void
|
||||||
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
|
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
|
||||||
@ -121,6 +128,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
rightAccessory: RightAccessory = .none,
|
rightAccessory: RightAccessory = .none,
|
||||||
reaction: Reaction? = nil,
|
reaction: Reaction? = nil,
|
||||||
selectionState: SelectionState,
|
selectionState: SelectionState,
|
||||||
|
selectionPosition: SelectionPosition = .left,
|
||||||
|
isEnabled: Bool = true,
|
||||||
hasNext: Bool,
|
hasNext: Bool,
|
||||||
action: @escaping (EnginePeer) -> Void,
|
action: @escaping (EnginePeer) -> Void,
|
||||||
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
|
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
|
||||||
@ -140,6 +149,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
self.rightAccessory = rightAccessory
|
self.rightAccessory = rightAccessory
|
||||||
self.reaction = reaction
|
self.reaction = reaction
|
||||||
self.selectionState = selectionState
|
self.selectionState = selectionState
|
||||||
|
self.selectionPosition = selectionPosition
|
||||||
|
self.isEnabled = isEnabled
|
||||||
self.hasNext = hasNext
|
self.hasNext = hasNext
|
||||||
self.action = action
|
self.action = action
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
@ -189,6 +200,12 @@ public final class PeerListItemComponent: Component {
|
|||||||
if lhs.selectionState != rhs.selectionState {
|
if lhs.selectionState != rhs.selectionState {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.selectionPosition != rhs.selectionPosition {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isEnabled != rhs.isEnabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.hasNext != rhs.hasNext {
|
if lhs.hasNext != rhs.hasNext {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -411,6 +428,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
self.containerButton.alpha = component.isEnabled ? 1.0 : 0.3
|
||||||
|
|
||||||
self.avatarButtonView.isUserInteractionEnabled = component.storyStats != nil && component.openStories != nil
|
self.avatarButtonView.isUserInteractionEnabled = component.storyStats != nil && component.openStories != nil
|
||||||
|
|
||||||
let labelData: (String, Bool)
|
let labelData: (String, Bool)
|
||||||
@ -457,9 +476,17 @@ public final class PeerListItemComponent: Component {
|
|||||||
var avatarLeftInset: CGFloat = component.sideInset + 10.0
|
var avatarLeftInset: CGFloat = component.sideInset + 10.0
|
||||||
|
|
||||||
if case let .editing(isSelected, isTinted) = component.selectionState {
|
if case let .editing(isSelected, isTinted) = component.selectionState {
|
||||||
leftInset += 44.0
|
|
||||||
avatarLeftInset += 44.0
|
|
||||||
let checkSize: CGFloat = 22.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
|
let checkLayer: CheckLayer
|
||||||
if let current = self.checkLayer {
|
if let current = self.checkLayer {
|
||||||
@ -484,7 +511,7 @@ public final class PeerListItemComponent: Component {
|
|||||||
checkLayer.setSelected(isSelected, animated: false)
|
checkLayer.setSelected(isSelected, animated: false)
|
||||||
checkLayer.setNeedsDisplay()
|
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 {
|
} else {
|
||||||
if let checkLayer = self.checkLayer {
|
if let checkLayer = self.checkLayer {
|
||||||
self.checkLayer = nil
|
self.checkLayer = nil
|
||||||
|
|||||||
12
submodules/TelegramUI/Images.xcassets/Premium/ToBeDistributed.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/ToBeDistributed.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "avatar_tobedistributed.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
247
submodules/TelegramUI/Images.xcassets/Premium/ToBeDistributed.imageset/avatar_tobedistributed.pdf
vendored
Normal file
247
submodules/TelegramUI/Images.xcassets/Premium/ToBeDistributed.imageset/avatar_tobedistributed.pdf
vendored
Normal 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
|
||||||
12
submodules/TelegramUI/Images.xcassets/Premium/Unclaimed.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Unclaimed.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "avatar_unclaimed.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
106
submodules/TelegramUI/Images.xcassets/Premium/Unclaimed.imageset/avatar_unclaimed.pdf
vendored
Normal file
106
submodules/TelegramUI/Images.xcassets/Premium/Unclaimed.imageset/avatar_unclaimed.pdf
vendored
Normal 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
|
||||||
@ -17413,6 +17413,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return .text
|
return .text
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
self.playShakeAnimation()
|
||||||
}
|
}
|
||||||
case let .withBotStartPayload(botStart):
|
case let .withBotStartPayload(botStart):
|
||||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||||
|
|||||||
@ -835,8 +835,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
}), nil)
|
}), nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case let .boost(peerId, status, myBoostsStatus):
|
case let .boost(peerId, status, myBoostStatus):
|
||||||
let _ = myBoostsStatus
|
let _ = myBoostStatus
|
||||||
var forceDark = false
|
var forceDark = false
|
||||||
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
|
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
|
||||||
forceDark = true
|
forceDark = true
|
||||||
@ -847,9 +847,21 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var isBoosted = false
|
var myBoostCount: Int32 = 0
|
||||||
if status.boostedByMe {
|
var availableBoosts: [MyBoostStatus.Boost] = []
|
||||||
isBoosted = true
|
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
|
var isCurrent = false
|
||||||
@ -861,22 +873,19 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
var currentLevelBoosts = Int32(status.currentLevelBoosts)
|
var currentLevelBoosts = Int32(status.currentLevelBoosts)
|
||||||
var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init)
|
var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init)
|
||||||
|
|
||||||
if isBoosted && status.boosts == currentLevelBoosts {
|
if myBoostCount > 0 && status.boosts == currentLevelBoosts {
|
||||||
currentLevel = max(0, currentLevel - 1)
|
currentLevel = max(0, currentLevel - 1)
|
||||||
nextLevelBoosts = currentLevelBoosts
|
nextLevelBoosts = currentLevelBoosts
|
||||||
currentLevelBoosts = max(0, currentLevelBoosts - 1)
|
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 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, boosted: true)
|
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount + 1)
|
||||||
let nextCount = Int32(status.boosts + 1)
|
var nextCount = Int32(status.boosts + 1)
|
||||||
|
|
||||||
var updateImpl: (() -> Void)?
|
var updateImpl: (() -> Void)?
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), forceDark: forceDark, action: {
|
let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), forceDark: forceDark, action: {
|
||||||
if isBoosted {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
let dismiss = false
|
let dismiss = false
|
||||||
updateImpl?()
|
updateImpl?()
|
||||||
|
|
||||||
@ -945,8 +954,23 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
|
|
||||||
updateImpl = { [weak controller] in
|
updateImpl = { [weak controller] in
|
||||||
if let _ = status.nextLevelBoosts {
|
if let _ = status.nextLevelBoosts {
|
||||||
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: []).startStandalone()
|
if let availableBoost = availableBoosts.first {
|
||||||
controller?.updateSubject(nextSubject, count: nextCount)
|
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 {
|
} else {
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}
|
}
|
||||||
@ -974,7 +998,9 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
let _ = context.engine.payments.applyPremiumGiftCode(slug: slug).startStandalone()
|
let _ = context.engine.payments.applyPremiumGiftCode(slug: slug).startStandalone()
|
||||||
},
|
},
|
||||||
openPeer: { peer in
|
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
|
openMessage: { messageId in
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|
||||||
|
|||||||
@ -8362,7 +8362,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
if let previousController = navigationController.viewControllers.last as? ShareWithPeersScreen {
|
if let previousController = navigationController.viewControllers.last as? ShareWithPeersScreen {
|
||||||
previousController.dismiss()
|
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
|
UIPasteboard.general.string = link
|
||||||
|
|
||||||
if let self {
|
if let self {
|
||||||
|
|||||||
@ -1795,8 +1795,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
mappedSubject = .storiesWeekly
|
mappedSubject = .storiesWeekly
|
||||||
case .storiesMonthly:
|
case .storiesMonthly:
|
||||||
mappedSubject = .storiesMonthly
|
mappedSubject = .storiesMonthly
|
||||||
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, 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, boosted: boosted)
|
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)
|
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user