mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
56db74612e
commit
2107f94bc3
@ -994,7 +994,7 @@ public enum PremiumLimitSubject {
|
||||
case expiringStories
|
||||
case storiesWeekly
|
||||
case storiesMonthly
|
||||
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, boosted: Bool)
|
||||
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32)
|
||||
}
|
||||
|
||||
public protocol ComposeController: ViewController {
|
||||
|
@ -59,7 +59,10 @@ func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewL
|
||||
modalStack[modalStack.count - 1].controllers.append(controller)
|
||||
}
|
||||
} else if !modalStack.isEmpty {
|
||||
controller._presentedInModal = true
|
||||
if modalStack[modalStack.count - 1].isFlat {
|
||||
} else {
|
||||
controller._presentedInModal = true
|
||||
}
|
||||
if modalStack[modalStack.count - 1].isStandalone {
|
||||
modalStack.append(ModalContainerLayout(controllers: [controller], isFlat: isFlat, isStandalone: isStandalone))
|
||||
} else {
|
||||
|
@ -107,6 +107,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||
"//submodules/CountrySelectionUI",
|
||||
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -325,7 +325,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
case let .header(_, title, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(title + text), sectionId: self.section)
|
||||
case let .createGiveaway(_, title, subtitle, isSelected):
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: .blue, name: "Premium/Giveaway"), title: title, subtitle: subtitle, isSelected: isSelected, sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .blue, name: "Premium/Giveaway"), title: title, subtitle: subtitle, isSelected: isSelected, sectionId: self.section, action: {
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.mode = .giveaway
|
||||
@ -333,7 +333,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
}
|
||||
})
|
||||
case let .awardUsers(_, title, subtitle, isSelected):
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: .violet, name: "Media Editor/Privacy/SelectedUsers"), title: title, subtitle: subtitle, subtitleActive: true, isSelected: isSelected, sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .violet, name: "Media Editor/Privacy/SelectedUsers"), title: title, subtitle: subtitle, subtitleActive: true, isSelected: isSelected, sectionId: self.section, action: {
|
||||
var openSelection = false
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
@ -361,7 +361,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
default:
|
||||
color = .blue
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: nil)
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: nil)
|
||||
case let .subscriptionsHeader(_, text, additionalText):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
|
||||
case let .subscriptions(_, value):
|
||||
@ -1056,7 +1056,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(
|
||||
context: context,
|
||||
subject: .channels(exclude: Set([peerId])),
|
||||
subject: .channels(exclude: Set([peerId]), searchQuery: nil),
|
||||
initialPeerIds: Set(state.channels.filter { $0 != peerId })
|
||||
)
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in
|
||||
|
@ -75,21 +75,6 @@ final class CreateGiveawayFooterItemNode: ItemListControllerFooterItemNode {
|
||||
private func updateItem() {
|
||||
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||
self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor
|
||||
|
||||
|
||||
// if self.item.isLoading != self.currentIsLoading {
|
||||
// self.currentIsLoading = self.item.isLoading
|
||||
//
|
||||
// if self.currentIsLoading {
|
||||
// self.buttonNode.transitionToProgress()
|
||||
// } else {
|
||||
// self.buttonNode.transitionFromProgress()
|
||||
// }
|
||||
// }
|
||||
|
||||
// self.buttonNode.pressed = { [weak self] in
|
||||
// self?.item.action()
|
||||
// }
|
||||
}
|
||||
|
||||
override func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
@ -12,23 +13,16 @@ import AccountContext
|
||||
import AvatarNode
|
||||
|
||||
public final class GiftOptionItem: ListViewItem, ItemListItem {
|
||||
public struct Icon: Equatable {
|
||||
public enum Icon: Equatable {
|
||||
public enum Color {
|
||||
case blue
|
||||
case green
|
||||
case red
|
||||
case violet
|
||||
}
|
||||
public let color: Color
|
||||
public let name: String
|
||||
|
||||
public init(
|
||||
color: Color,
|
||||
name: String
|
||||
) {
|
||||
self.color = color
|
||||
self.name = name
|
||||
}
|
||||
case peer(EnginePeer)
|
||||
case image(color: Color, name: String)
|
||||
}
|
||||
|
||||
public enum Font {
|
||||
@ -60,6 +54,7 @@ public final class GiftOptionItem: ListViewItem, ItemListItem {
|
||||
let icon: Icon?
|
||||
let title: String
|
||||
let titleFont: Font
|
||||
let titleBadge: String?
|
||||
let subtitle: String?
|
||||
let subtitleFont: SubtitleFont
|
||||
let subtitleActive: Bool
|
||||
@ -69,12 +64,13 @@ public final class GiftOptionItem: ListViewItem, ItemListItem {
|
||||
public let sectionId: ItemListSectionId
|
||||
let action: (() -> Void)?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, context: AccountContext, icon: Icon? = nil, title: String, titleFont: Font = .regular, subtitle: String?, subtitleFont: SubtitleFont = .regular, subtitleActive: Bool = false, label: Label? = nil, badge: String? = nil, isSelected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?) {
|
||||
public init(presentationData: ItemListPresentationData, context: AccountContext, icon: Icon? = nil, title: String, titleFont: Font = .regular, titleBadge: String? = nil, subtitle: String?, subtitleFont: SubtitleFont = .regular, subtitleActive: Bool = false, label: Label? = nil, badge: String? = nil, isSelected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?) {
|
||||
self.presentationData = presentationData
|
||||
self.icon = icon
|
||||
self.context = context
|
||||
self.title = title
|
||||
self.titleFont = titleFont
|
||||
self.titleBadge = titleBadge
|
||||
self.subtitle = subtitle
|
||||
self.subtitleFont = subtitleFont
|
||||
self.subtitleActive = subtitleActive
|
||||
@ -146,7 +142,9 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
fileprivate var iconNode: ASImageNode?
|
||||
fileprivate var avatarNode: AvatarNode?
|
||||
private let titleNode: TextNode
|
||||
private let titleBadge = ComponentView<Empty>()
|
||||
private let statusNode: TextNode
|
||||
private var statusArrowNode: ASImageNode?
|
||||
|
||||
@ -282,7 +280,10 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
let verticalInset: CGFloat = 10.0
|
||||
let titleSpacing: CGFloat = 2.0
|
||||
var titleSpacing: CGFloat = 2.0
|
||||
if case .bold = item.titleFont {
|
||||
titleSpacing = 0.0
|
||||
}
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
let separatorHeight = UIScreenPixel
|
||||
@ -331,37 +332,72 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
let iconUpdated = currentItem?.icon != item.icon
|
||||
|
||||
let iconSize = CGSize(width: 40.0, height: 40.0)
|
||||
if let icon = item.icon {
|
||||
let iconNode: ASImageNode
|
||||
|
||||
if let current = strongSelf.iconNode {
|
||||
iconNode = current
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
iconNode.displaysAsynchronously = false
|
||||
strongSelf.addSubnode(iconNode)
|
||||
|
||||
strongSelf.iconNode = iconNode
|
||||
}
|
||||
|
||||
let colors: [UIColor]
|
||||
switch icon.color {
|
||||
case .blue:
|
||||
colors = [UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x71d4fc)]
|
||||
case .green:
|
||||
colors = [UIColor(rgb: 0x54cb68), UIColor(rgb: 0xa0de7e)]
|
||||
case .red:
|
||||
colors = [UIColor(rgb: 0xff516a), UIColor(rgb: 0xff885e)]
|
||||
case .violet:
|
||||
colors = [UIColor(rgb: 0xd569ec), UIColor(rgb: 0xe0a2f3)]
|
||||
}
|
||||
if iconNode.image == nil {
|
||||
iconNode.image = generateAvatarImage(size: iconSize, icon: generateTintedImage(image: UIImage(bundleImageName: icon.name), color: .white), iconScale: 1.0, cornerRadius: 20.0, color: .blue, customColors: colors)
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: leftInset - 3.0 + editingOffset, y: floorToScreenPixels((layout.contentSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
iconNode.frame = iconFrame
|
||||
|
||||
switch icon {
|
||||
case let .peer(peer):
|
||||
if let iconNode = strongSelf.iconNode {
|
||||
strongSelf.iconNode = nil
|
||||
iconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
let avatarNode: AvatarNode
|
||||
if let current = strongSelf.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0)))
|
||||
strongSelf.addSubnode(avatarNode)
|
||||
|
||||
strongSelf.avatarNode = avatarNode
|
||||
}
|
||||
avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer)
|
||||
avatarNode.frame = iconFrame
|
||||
case let .image(color, name):
|
||||
if let avatarNode = strongSelf.avatarNode {
|
||||
strongSelf.avatarNode = nil
|
||||
avatarNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
let iconNode: ASImageNode
|
||||
if let current = strongSelf.iconNode {
|
||||
iconNode = current
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
iconNode.displaysAsynchronously = false
|
||||
strongSelf.addSubnode(iconNode)
|
||||
|
||||
strongSelf.iconNode = iconNode
|
||||
}
|
||||
|
||||
let colors: [UIColor]
|
||||
switch color {
|
||||
case .blue:
|
||||
colors = [UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x71d4fc)]
|
||||
case .green:
|
||||
colors = [UIColor(rgb: 0x54cb68), UIColor(rgb: 0xa0de7e)]
|
||||
case .red:
|
||||
colors = [UIColor(rgb: 0xff516a), UIColor(rgb: 0xff885e)]
|
||||
case .violet:
|
||||
colors = [UIColor(rgb: 0xd569ec), UIColor(rgb: 0xe0a2f3)]
|
||||
}
|
||||
if iconNode.image == nil || iconUpdated {
|
||||
iconNode.image = generateAvatarImage(size: iconSize, icon: generateTintedImage(image: UIImage(bundleImageName: name), color: .white), iconScale: 1.0, cornerRadius: 20.0, color: .blue, customColors: colors)
|
||||
}
|
||||
iconNode.frame = iconFrame
|
||||
}
|
||||
} else {
|
||||
if let avatarNode = strongSelf.avatarNode {
|
||||
strongSelf.avatarNode = nil
|
||||
avatarNode.removeFromSupernode()
|
||||
}
|
||||
if let iconNode = strongSelf.iconNode {
|
||||
strongSelf.iconNode = nil
|
||||
iconNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let selectableControlSizeAndApply = selectableControlSizeAndApply {
|
||||
@ -445,7 +481,8 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
} else {
|
||||
titleVerticalOriginY = floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0)
|
||||
}
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset, y: titleVerticalOriginY), size: titleLayout.size))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset, y: titleVerticalOriginY), size: titleLayout.size)
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
||||
|
||||
var badgeOffset: CGFloat = 0.0
|
||||
if badgeLayout.size.width > 0.0 {
|
||||
@ -528,6 +565,29 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
|
||||
|
||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||
|
||||
if let badge = item.titleBadge {
|
||||
let badgeSize = strongSelf.titleBadge.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
BoostIconComponent(text: badge)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: params.width, height: 100.0)
|
||||
)
|
||||
if let view = strongSelf.titleBadge.view {
|
||||
if view.superview == nil {
|
||||
strongSelf.view.addSubview(view)
|
||||
}
|
||||
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - badgeSize.height / 2.0) - 1.0), size: badgeSize)
|
||||
view.frame = badgeFrame
|
||||
}
|
||||
} else {
|
||||
if let view = strongSelf.titleBadge.view {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
let dateTimeFormat = environment.dateTimeFormat
|
||||
let accountContext = context.component.context
|
||||
|
||||
let state = context.state
|
||||
let giftCode = component.giftCode
|
||||
@ -239,7 +240,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
Button(
|
||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
|
||||
action: {
|
||||
if let peer = fromPeer {
|
||||
if let peer = fromPeer, peer.id != accountContext.account.peerId {
|
||||
component.openPeer(peer)
|
||||
Queue.mainQueue().after(1.0, {
|
||||
component.cancel(false)
|
||||
@ -258,7 +259,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
Button(
|
||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
|
||||
action: {
|
||||
if let peer = toPeer {
|
||||
if let peer = toPeer, peer.id != accountContext.account.peerId {
|
||||
component.openPeer(peer)
|
||||
Queue.mainQueue().after(1.0, {
|
||||
component.cancel(false)
|
||||
@ -309,7 +310,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
component.openMessage(messageId)
|
||||
}
|
||||
Queue.mainQueue().after(1.0) {
|
||||
component.cancel(true)
|
||||
component.cancel(false)
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -479,11 +480,15 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
||||
giftCode: context.component.giftCode,
|
||||
action: context.component.action,
|
||||
cancel: { animate in
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
if animate {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else if let controller = controller() {
|
||||
controller.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
},
|
||||
openPeer: context.component.openPeer,
|
||||
openMessage: context.component.openMessage,
|
||||
|
@ -723,7 +723,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
var premiumLimits: EngineConfiguration.UserLimits
|
||||
var isPremium = false
|
||||
|
||||
var boosted = false
|
||||
var myBoostCount: Int32 = 0
|
||||
|
||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
@ -1045,7 +1045,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
string = strings.Premium_MaxStoriesMonthlyNoPremiumText("\(limit)").string
|
||||
}
|
||||
buttonAnimationName = nil
|
||||
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, boosted):
|
||||
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount):
|
||||
if link == nil, !isCurrent, state.initialized {
|
||||
peerShortcutChild = peerShortcut.update(
|
||||
component: Button(
|
||||
@ -1054,7 +1054,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
peer: peer,
|
||||
badge: "X2"
|
||||
badge: myBoostCount > 0 ? "\(myBoostCount)" : nil
|
||||
)
|
||||
),
|
||||
action: {
|
||||
@ -1098,11 +1098,11 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
)
|
||||
}
|
||||
|
||||
if boosted && state.boosted != boosted {
|
||||
state.boosted = boosted
|
||||
if myBoostCount > 0 && state.myBoostCount != myBoostCount {
|
||||
state.myBoostCount = myBoostCount
|
||||
boostUpdated = true
|
||||
}
|
||||
useAlternateText = boosted
|
||||
useAlternateText = myBoostCount > 0
|
||||
|
||||
iconName = "Premium/Boost"
|
||||
badgeText = "\(component.count)"
|
||||
@ -1157,7 +1157,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
|
||||
premiumTitle = ""
|
||||
|
||||
if boosted {
|
||||
if myBoostCount > 0 {
|
||||
let prefixString = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText
|
||||
|
||||
let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1)
|
||||
@ -1205,6 +1205,10 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
if case .folders = subject, !state.isPremium {
|
||||
reachedMaximumLimit = false
|
||||
}
|
||||
if case .storiesChannelBoost = component.subject {
|
||||
reachedMaximumLimit = false
|
||||
}
|
||||
|
||||
|
||||
let contentSize: CGSize
|
||||
if state.initialized {
|
||||
@ -1648,7 +1652,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
||||
case storiesWeekly
|
||||
case storiesMonthly
|
||||
|
||||
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, boosted: Bool)
|
||||
case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
@ -1742,8 +1746,7 @@ private final class PeerShortcutComponent: Component {
|
||||
private let avatarNode: AvatarNode
|
||||
private let text = ComponentView<Empty>()
|
||||
|
||||
private let badgeBackground = UIView()
|
||||
private let badgeText = ComponentView<Empty>()
|
||||
private let badge = ComponentView<Empty>()
|
||||
|
||||
private var component: PeerShortcutComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
@ -1756,12 +1759,8 @@ private final class PeerShortcutComponent: Component {
|
||||
self.backgroundView.clipsToBounds = true
|
||||
self.backgroundView.layer.cornerRadius = 16.0
|
||||
|
||||
self.badgeBackground.clipsToBounds = true
|
||||
self.badgeBackground.backgroundColor = UIColor(rgb: 0x9671ff)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubview(self.badgeBackground)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -1803,35 +1802,26 @@ private final class PeerShortcutComponent: Component {
|
||||
}
|
||||
|
||||
if let badge = component.badge {
|
||||
let badgeSize = self.badgeText.update(
|
||||
let badgeSize = self.badge.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: badge, font: Font.with(size: 11.0, design: .round, weight: .bold), textColor: .white, paragraphAlignment: .left))
|
||||
)
|
||||
BoostIconComponent(text: badge)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let view = self.badgeText.view {
|
||||
if let view = self.badge.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - badgeSize.width / 2.0), y: -2.0), size: badgeSize)
|
||||
view.frame = textFrame
|
||||
|
||||
let backgroundFrame = textFrame.insetBy(dx: -5.0, dy: -3.0)
|
||||
self.badgeBackground.frame = backgroundFrame
|
||||
self.badgeBackground.layer.cornerRadius = backgroundFrame.height / 2.0
|
||||
|
||||
self.badgeBackground.isHidden = false
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - badgeSize.width / 2.0), y: -5.0), size: badgeSize)
|
||||
view.frame = badgeFrame
|
||||
}
|
||||
} else {
|
||||
if let view = self.badgeText.view {
|
||||
if let view = self.badge.view {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
self.badgeBackground.isHidden = true
|
||||
}
|
||||
|
||||
self.backgroundView.frame = CGRect(origin: .zero, size: size)
|
||||
@ -1848,3 +1838,94 @@ private final class PeerShortcutComponent: Component {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class BoostIconComponent: Component {
|
||||
let text: String
|
||||
|
||||
public init(text: String) {
|
||||
self.text = text
|
||||
}
|
||||
|
||||
public static func ==(lhs: BoostIconComponent, rhs: BoostIconComponent) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let badgeBackground = UIView()
|
||||
private let badgeIcon: UIImageView
|
||||
private let badgeText = ComponentView<Empty>()
|
||||
|
||||
private var component: BoostIconComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.badgeIcon = UIImageView(image: UIImage(bundleImageName: "Premium/BoostButtonIcon"))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.badgeBackground.clipsToBounds = true
|
||||
self.badgeBackground.backgroundColor = UIColor(rgb: 0x9671ff)
|
||||
|
||||
self.addSubview(self.badgeBackground)
|
||||
self.addSubview(self.badgeIcon)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: BoostIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let textSize = self.badgeText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.text, font: Font.with(size: 10.0, design: .round, weight: .bold), textColor: .white, paragraphAlignment: .left))
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let spacing: CGFloat = 2.0
|
||||
var totalWidth = textSize.width + spacing
|
||||
var iconSize = CGSize()
|
||||
if let icon = self.badgeIcon.image {
|
||||
iconSize = CGSize(width: icon.size.width * 0.9, height: icon.size.height * 0.9)
|
||||
totalWidth += icon.size.width
|
||||
}
|
||||
|
||||
let size = CGSize(width: totalWidth + 8.0, height: 19.0)
|
||||
|
||||
let iconFrame = CGRect(x: floorToScreenPixels((size.width - totalWidth) / 2.0 + 1.0), y: 4.0 + UIScreenPixel, width: iconSize.width, height: iconSize.height)
|
||||
self.badgeIcon.frame = iconFrame
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: 4.0), size: textSize)
|
||||
|
||||
if let view = self.badgeText.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = textFrame
|
||||
}
|
||||
|
||||
self.badgeBackground.frame = CGRect(origin: .zero, size: size)
|
||||
self.badgeBackground.layer.cornerRadius = size.height / 2.0
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
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 openGifts: () -> Void
|
||||
let createPrepaidGiveaway: (PrepaidGiveaway) -> Void
|
||||
let updateGiftsSelected: (Bool) -> Void
|
||||
|
||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void) {
|
||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void) {
|
||||
self.context = context
|
||||
self.loadDetailedGraph = loadDetailedGraph
|
||||
self.openMessageStats = openMessage
|
||||
@ -47,6 +48,7 @@ private final class ChannelStatsControllerArguments {
|
||||
self.expandBoosters = expandBoosters
|
||||
self.openGifts = openGifts
|
||||
self.createPrepaidGiveaway = createPrepaidGiveaway
|
||||
self.updateGiftsSelected = updateGiftsSelected
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +117,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
|
||||
case boostersTitle(PresentationTheme, String)
|
||||
case boostersPlaceholder(PresentationTheme, String)
|
||||
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer?, Int32)
|
||||
case boosterTabs(PresentationTheme, String, String, Bool)
|
||||
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer?, Int32, ChannelBoostersContext.State.Boost.Flags, Int32)
|
||||
case boostersExpand(PresentationTheme, String)
|
||||
case boostersInfo(PresentationTheme, String)
|
||||
|
||||
@ -156,7 +159,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return StatsSection.boostOverview.rawValue
|
||||
case .boostPrepaidTitle, .boostPrepaid, .boostPrepaidInfo:
|
||||
return StatsSection.boostPrepaid.rawValue
|
||||
case .boostersTitle, .boostersPlaceholder, .booster, .boostersExpand, .boostersInfo:
|
||||
case .boostersTitle, .boostersPlaceholder, .boosterTabs, .booster, .boostersExpand, .boostersInfo:
|
||||
return StatsSection.boosters.rawValue
|
||||
case .boostLinkTitle, .boostLink, .boostLinkInfo:
|
||||
return StatsSection.boostLink.rawValue
|
||||
@ -227,8 +230,10 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return 2101
|
||||
case .boostersPlaceholder:
|
||||
return 2102
|
||||
case let .booster(index, _, _, _, _):
|
||||
return 2103 + index
|
||||
case .boosterTabs:
|
||||
return 2103
|
||||
case let .booster(index, _, _, _, _, _, _):
|
||||
return 2104 + index
|
||||
case .boostersExpand:
|
||||
return 10000
|
||||
case .boostersInfo:
|
||||
@ -428,8 +433,14 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .booster(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsExpires):
|
||||
if case let .booster(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsExpires) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsExpires == rhsExpires {
|
||||
case let .boosterTabs(lhsTheme, lhsBoostText, lhsGiftText, lhsGiftSelected):
|
||||
if case let .boosterTabs(rhsTheme, rhsBoostText, rhsGiftText, rhsGiftSelected) = rhs, lhsTheme === rhsTheme, lhsBoostText == rhsBoostText, lhsGiftText == rhsGiftText, lhsGiftSelected == rhsGiftSelected {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .booster(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsCount, lhsFlags, lhsExpires):
|
||||
if case let .booster(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsCount, rhsFlags, rhsExpires) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsCount == rhsCount, lhsFlags == rhsFlags, lhsExpires == rhsExpires {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -533,17 +544,34 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
}, contextAction: { node, gesture in
|
||||
arguments.contextAction(message.id, node, gesture)
|
||||
})
|
||||
case let .booster(_, _, dateTimeFormat, peer, expires):
|
||||
let _ = dateTimeFormat
|
||||
let _ = peer
|
||||
let _ = expires
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("text"), sectionId: self.section)
|
||||
// let expiresValue = stringForMediumDate(timestamp: expires, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
// return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: peer?.id != nil && peer?.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
// if let peer {
|
||||
// arguments.openPeer(peer)
|
||||
// }
|
||||
// }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
||||
case let .boosterTabs(_, boostText, giftText, giftSelected):
|
||||
return BoostsTabsItem(theme: presentationData.theme, boostsText: boostText, giftsText: giftText, selectedTab: giftSelected ? .gifts : .boosts, sectionId: self.section, selectionUpdated: { tab in
|
||||
arguments.updateGiftsSelected(tab == .gifts)
|
||||
})
|
||||
case let .booster(_, _, _, peer, count, flags, expires):
|
||||
let expiresValue = stringForDate(timestamp: expires, strings: presentationData.strings)
|
||||
let expiresString = presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string
|
||||
|
||||
let title: String
|
||||
let icon: GiftOptionItem.Icon
|
||||
if let peer {
|
||||
title = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
icon = .peer(peer)
|
||||
} else {
|
||||
if flags.contains(.isUnclaimed) {
|
||||
title = "Unclaimed"
|
||||
icon = .image(color: .red, name: "Premium/Unclaimed")
|
||||
} else if flags.contains(.isGiveaway) {
|
||||
title = "To be distributed"
|
||||
icon = .image(color: .blue, name: "Premium/ToBeDistributed")
|
||||
} else {
|
||||
title = "Unknown"
|
||||
icon = .image(color: .red, name: "Premium/ToBeDistributed")
|
||||
}
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: icon, title: title, titleFont: .bold, titleBadge: count > 1 ? "\(count)" : nil, subtitle: expiresString, sectionId: self.section, action: peer != nil && peer?.id != arguments.context.account.peerId ? {
|
||||
arguments.openPeer(peer!)
|
||||
} : nil)
|
||||
case let .boostersExpand(theme, title):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandBoosters()
|
||||
@ -579,7 +607,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
default:
|
||||
color = .blue
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity)", subtitle: subtitle, label: nil, sectionId: self.section, action: {
|
||||
arguments.createPrepaidGiveaway(prepaidGiveaway)
|
||||
})
|
||||
}
|
||||
@ -594,15 +622,18 @@ public enum ChannelStatsSection {
|
||||
private struct ChannelStatsControllerState: Equatable {
|
||||
let section: ChannelStatsSection
|
||||
let boostersExpanded: Bool
|
||||
let giftsSelected: Bool
|
||||
|
||||
init() {
|
||||
self.section = .stats
|
||||
self.boostersExpanded = false
|
||||
self.giftsSelected = false
|
||||
}
|
||||
|
||||
init(section: ChannelStatsSection, boostersExpanded: Bool) {
|
||||
init(section: ChannelStatsSection, boostersExpanded: Bool, giftsSelected: Bool) {
|
||||
self.section = section
|
||||
self.boostersExpanded = boostersExpanded
|
||||
self.giftsSelected = giftsSelected
|
||||
}
|
||||
|
||||
static func ==(lhs: ChannelStatsControllerState, rhs: ChannelStatsControllerState) -> Bool {
|
||||
@ -612,20 +643,27 @@ private struct ChannelStatsControllerState: Equatable {
|
||||
if lhs.boostersExpanded != rhs.boostersExpanded {
|
||||
return false
|
||||
}
|
||||
if lhs.giftsSelected != rhs.giftsSelected {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func withUpdatedSection(_ section: ChannelStatsSection) -> ChannelStatsControllerState {
|
||||
return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded)
|
||||
return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded, giftsSelected: self.giftsSelected)
|
||||
}
|
||||
|
||||
func withUpdatedBoostersExpanded(_ boostersExpanded: Bool) -> ChannelStatsControllerState {
|
||||
return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded)
|
||||
return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded, giftsSelected: self.giftsSelected)
|
||||
}
|
||||
|
||||
func withUpdatedGiftsSelected(_ giftsSelected: Bool) -> ChannelStatsControllerState {
|
||||
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, giftsSelected: giftsSelected)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, presentationData: PresentationData) -> [StatsEntry] {
|
||||
private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData) -> [StatsEntry] {
|
||||
var entries: [StatsEntry] = []
|
||||
|
||||
switch state.section {
|
||||
@ -735,6 +773,19 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
entries.append(.boostersPlaceholder(presentationData.theme, boostersPlaceholder))
|
||||
}
|
||||
|
||||
var boostsCount: Int32 = 0
|
||||
if let boostersState {
|
||||
boostsCount = boostersState.count
|
||||
}
|
||||
var giftsCount: Int32 = 0
|
||||
if let giftsState {
|
||||
giftsCount = giftsState.count
|
||||
}
|
||||
|
||||
if boostsCount > 0 && giftsCount > 0 && boostsCount != giftsCount {
|
||||
entries.append(.boosterTabs(presentationData.theme, "\(boostsCount) Boosts", "\(giftsCount) Gifts", state.giftsSelected))
|
||||
}
|
||||
|
||||
if let boostersState {
|
||||
var boosterIndex: Int32 = 0
|
||||
|
||||
@ -747,7 +798,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
}
|
||||
|
||||
for booster in boosters {
|
||||
entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster.peer, booster.expires))
|
||||
entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster.peer, booster.multiplier, booster.flags, booster.expires))
|
||||
boosterIndex += 1
|
||||
}
|
||||
|
||||
@ -773,8 +824,8 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
}
|
||||
|
||||
public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil, statsDatacenterId: Int32?) -> ViewController {
|
||||
let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false))
|
||||
let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, giftsSelected: false))
|
||||
let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
@ -813,7 +864,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
} else {
|
||||
boostData = .single(nil) |> then(context.engine.peers.getChannelBoostStatus(peerId: peerId))
|
||||
}
|
||||
let boostersContext = ChannelBoostersContext(account: context.account, peerId: peerId)
|
||||
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
|
||||
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
|
||||
|
||||
var presentImpl: ((ViewController) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
@ -885,6 +937,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
createPrepaidGiveaway: { prepaidGiveaway in
|
||||
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(prepaidGiveaway))
|
||||
pushImpl?(controller)
|
||||
},
|
||||
updateGiftsSelected: { selected in
|
||||
updateState { $0.withUpdatedGiftsSelected(selected) }
|
||||
})
|
||||
|
||||
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
|
||||
@ -905,11 +960,12 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
dataPromise.get(),
|
||||
messagesPromise.get(),
|
||||
boostData,
|
||||
boostersContext.state,
|
||||
boostsContext.state,
|
||||
giftsContext.state,
|
||||
longLoadingSignal
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, peer, data, messageView, boostData, boostersState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { presentationData, state, peer, data, messageView, boostData, boostersState, giftsState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let previous = previousData.swap(data)
|
||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||||
switch state.section {
|
||||
@ -939,7 +995,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, interactions: interactions, boostData: boostData, boostersState: boostersState, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -959,7 +1015,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
controller.visibleBottomContentOffsetChanged = { offset in
|
||||
let state = stateValue.with { $0 }
|
||||
if case let .known(value) = offset, value < 100.0, case .boosts = state.section, state.boostersExpanded {
|
||||
boostersContext.loadMore()
|
||||
boostsContext.loadMore()
|
||||
}
|
||||
}
|
||||
controller.titleControlValueChanged = { value in
|
||||
|
@ -97,7 +97,7 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer.
|
||||
let accountPeerId = account.peerId
|
||||
|
||||
return account.postbox.transaction { transaction -> Signal<Peer, NoError> in
|
||||
guard let peer = transaction.getPeer(account.peerId) as? TelegramChannel else {
|
||||
guard let peer = transaction.getPeer(peerId) as? TelegramChannel else {
|
||||
return .complete()
|
||||
}
|
||||
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId)], update: { _, updated in
|
||||
|
@ -47,7 +47,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
private var giveaway: TelegramMediaGiveaway?
|
||||
|
||||
private let buttonNode: ChatMessageAttachedContentButtonNode
|
||||
private let channelButton: PeerButtonNode
|
||||
private let channelButtons: PeerButtonsStackNode
|
||||
|
||||
override public var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
@ -95,7 +95,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
self.badgeTextNode = TextNode()
|
||||
|
||||
self.buttonNode = ChatMessageAttachedContentButtonNode()
|
||||
self.channelButton = PeerButtonNode()
|
||||
self.channelButtons = PeerButtonsStackNode()
|
||||
|
||||
super.init()
|
||||
|
||||
@ -107,7 +107,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
self.addSubnode(self.dateTitleNode)
|
||||
self.addSubnode(self.dateTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.channelButton)
|
||||
self.addSubnode(self.channelButtons)
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.badgeBackgroundNode)
|
||||
self.addSubnode(self.badgeTextNode)
|
||||
@ -129,6 +129,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceView, gesture, value)
|
||||
}
|
||||
|
||||
self.channelButtons.openPeer = { [weak self] peer in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
}
|
||||
}
|
||||
|
||||
override public func accessibilityActivate() -> Bool {
|
||||
@ -185,7 +192,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
|
||||
|
||||
let makeChannelLayout = PeerButtonNode.asyncLayout(self.channelButton)
|
||||
let makeChannelsLayout = PeerButtonsStackNode.asyncLayout(self.channelButtons)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
@ -207,12 +214,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
incoming = false
|
||||
}
|
||||
|
||||
let backgroundColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first! : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill.first!
|
||||
let textColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||
let accentColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
|
||||
var updatedBadgeImage: UIImage?
|
||||
if themeUpdated {
|
||||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: .white, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
|
||||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
|
||||
}
|
||||
|
||||
let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 1)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: .white)
|
||||
@ -413,13 +421,18 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
maxContentWidth = max(maxContentWidth, dateTitleLayout.size.width)
|
||||
maxContentWidth = max(maxContentWidth, dateTextLayout.size.width)
|
||||
maxContentWidth = max(maxContentWidth, buttonWidth)
|
||||
maxContentWidth += 30.0
|
||||
|
||||
var channelPeer: EnginePeer?
|
||||
if let channelPeerId = giveaway?.channelPeerIds.first, let peer = item.message.peers[channelPeerId] {
|
||||
channelPeer = EnginePeer(peer)
|
||||
|
||||
var channelPeers: [EnginePeer] = []
|
||||
if let channelPeerIds = giveaway?.channelPeerIds {
|
||||
for peerId in channelPeerIds {
|
||||
if let peer = item.message.peers[peerId] {
|
||||
channelPeers.append(EnginePeer(peer))
|
||||
}
|
||||
}
|
||||
}
|
||||
let (_, continueChannelLayout) = makeChannelLayout(item.context, maxContentWidth - 30.0, channelPeer, accentColor, accentColor.withAlphaComponent(0.1))
|
||||
let (channelsWidth, continueChannelLayout) = makeChannelsLayout(item.context, 250.0, channelPeers, accentColor, accentColor.withAlphaComponent(0.1))
|
||||
maxContentWidth = max(maxContentWidth, channelsWidth)
|
||||
maxContentWidth += 30.0
|
||||
|
||||
let contentWidth = maxContentWidth + layoutConstants.text.bubbleInsets.right * 2.0
|
||||
|
||||
@ -427,7 +440,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0, 33.0)
|
||||
let buttonSpacing: CGFloat = 4.0
|
||||
|
||||
let (channelButtonSize, channelButtonApply) = continueChannelLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
|
||||
let (channelButtonsSize, channelButtonsApply) = continueChannelLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
|
||||
|
||||
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
|
||||
|
||||
@ -436,7 +449,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
if countriesTextLayout.size.height > 0.0 {
|
||||
layoutSize.height += countriesTextLayout.size.height + 7.0
|
||||
}
|
||||
layoutSize.height += channelButtonSize.height
|
||||
layoutSize.height += channelButtonsSize.height
|
||||
|
||||
if let statusSizeAndApply = statusSizeAndApply {
|
||||
layoutSize.height += statusSizeAndApply.0.height - 4.0
|
||||
@ -462,7 +475,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
let _ = countriesTextApply()
|
||||
let _ = dateTitleApply()
|
||||
let _ = dateTextApply()
|
||||
let _ = channelButtonApply()
|
||||
let _ = channelButtonsApply()
|
||||
let _ = buttonApply(animation)
|
||||
|
||||
let smallSpacing: CGFloat = 2.0
|
||||
@ -493,8 +506,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
strongSelf.participantsTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTextLayout.size.width) / 2.0), y: originY), size: participantsTextLayout.size)
|
||||
originY += participantsTextLayout.size.height + smallSpacing * 2.0 + 3.0
|
||||
|
||||
strongSelf.channelButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - channelButtonSize.width) / 2.0), y: originY), size: channelButtonSize)
|
||||
originY += channelButtonSize.height
|
||||
strongSelf.channelButtons.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - channelButtonsSize.width) / 2.0), y: originY), size: channelButtonsSize)
|
||||
originY += channelButtonsSize.height
|
||||
|
||||
if countriesTextLayout.size.height > 0.0 {
|
||||
originY += smallSpacing * 2.0 + 3.0
|
||||
@ -566,6 +579,9 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
}
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.channelButtons.frame.contains(point) {
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
@ -578,10 +594,6 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
@objc private func buttonPressed() {
|
||||
if let item = self.item {
|
||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
||||
self.buttonNode.startShimmering()
|
||||
Queue.mainQueue().after(0.75) {
|
||||
self.buttonNode.stopShimmering()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -600,6 +612,126 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerButtonsStackNode: ASDisplayNode {
|
||||
var buttonNodes: [PeerButtonNode] = []
|
||||
var openPeer: (EnginePeer) -> Void = { _ in }
|
||||
|
||||
static func asyncLayout(_ current: PeerButtonsStackNode) -> (_ context: AccountContext, _ width: CGFloat, _ peers: [EnginePeer], _ titleColor: UIColor, _ backgroundColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> PeerButtonsStackNode)) {
|
||||
let currentChannelButtons = current.buttonNodes.isEmpty ? nil : current.buttonNodes
|
||||
let maybeMakeChannelButtons = current.buttonNodes.map(PeerButtonNode.asyncLayout)
|
||||
|
||||
return { context, width, peers, titleColor, backgroundColor in
|
||||
let targetNode = current
|
||||
|
||||
var buttonNodes: [PeerButtonNode] = []
|
||||
let makeChannelButtonLayouts: [(_ context: AccountContext, _ width: CGFloat, _ peer: EnginePeer?, _ titleColor: UIColor, _ backgroundColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> PeerButtonNode))]
|
||||
if let currentChannelButtons {
|
||||
buttonNodes = currentChannelButtons
|
||||
makeChannelButtonLayouts = maybeMakeChannelButtons
|
||||
} else {
|
||||
for _ in peers {
|
||||
buttonNodes.append(PeerButtonNode())
|
||||
}
|
||||
makeChannelButtonLayouts = buttonNodes.map(PeerButtonNode.asyncLayout)
|
||||
}
|
||||
|
||||
var maxWidth = 0.0
|
||||
let buttonHeight: CGFloat = 24.0
|
||||
let horizontalButtonSpacing: CGFloat = 4.0
|
||||
let verticalButtonSpacing: CGFloat = 6.0
|
||||
|
||||
var sizes: [CGSize] = []
|
||||
var groups: [[Int]] = []
|
||||
var currentGroup: [Int] = []
|
||||
|
||||
var buttonContinues: [(CGFloat) -> (CGSize, () -> PeerButtonNode)] = []
|
||||
for i in 0 ..< makeChannelButtonLayouts.count {
|
||||
let peer = peers[i]
|
||||
let makeChannelButtonLayout = makeChannelButtonLayouts[i]
|
||||
|
||||
let (buttonWidth, buttonContinue) = makeChannelButtonLayout(context, width, peer, titleColor, backgroundColor)
|
||||
sizes.append(CGSize(width: buttonWidth, height: buttonHeight))
|
||||
buttonContinues.append(buttonContinue)
|
||||
|
||||
var itemsWidth: CGFloat = 0.0
|
||||
for j in currentGroup {
|
||||
itemsWidth += sizes[j].width
|
||||
}
|
||||
itemsWidth += buttonWidth
|
||||
itemsWidth += CGFloat(currentGroup.count) * horizontalButtonSpacing
|
||||
|
||||
if itemsWidth > width {
|
||||
groups.append(currentGroup)
|
||||
currentGroup = []
|
||||
}
|
||||
currentGroup.append(i)
|
||||
}
|
||||
if !currentGroup.isEmpty {
|
||||
groups.append(currentGroup)
|
||||
}
|
||||
|
||||
var rowWidths: [CGFloat] = []
|
||||
for group in groups {
|
||||
var rowWidth: CGFloat = 0.0
|
||||
for i in group {
|
||||
let buttonSize = sizes[i]
|
||||
rowWidth += buttonSize.width
|
||||
}
|
||||
rowWidth += CGFloat(currentGroup.count) * horizontalButtonSpacing
|
||||
|
||||
if rowWidth > maxWidth {
|
||||
maxWidth = rowWidth
|
||||
}
|
||||
rowWidths.append(rowWidth)
|
||||
}
|
||||
|
||||
var frames: [CGRect] = []
|
||||
var originY: CGFloat = 0.0
|
||||
for i in 0 ..< groups.count {
|
||||
let rowWidth = rowWidths[i]
|
||||
var originX = floorToScreenPixels((maxWidth - rowWidth) / 2.0)
|
||||
|
||||
for i in groups[i] {
|
||||
let buttonSize = sizes[i]
|
||||
frames.append(CGRect(origin: CGPoint(x: originX, y: originY), size: buttonSize))
|
||||
originX += buttonSize.width + horizontalButtonSpacing
|
||||
}
|
||||
originY += buttonHeight + verticalButtonSpacing
|
||||
}
|
||||
|
||||
return (maxWidth, { _ in
|
||||
var buttonLayoutsAndApply: [(CGSize, () -> PeerButtonNode)] = []
|
||||
for buttonApply in buttonContinues {
|
||||
buttonLayoutsAndApply.append(buttonApply(maxWidth))
|
||||
}
|
||||
|
||||
return (CGSize(width: maxWidth, height: originY - verticalButtonSpacing), {
|
||||
targetNode.buttonNodes = buttonNodes
|
||||
|
||||
for i in 0 ..< buttonNodes.count {
|
||||
let peer = peers[i]
|
||||
let buttonNode = buttonNodes[i]
|
||||
buttonNode.pressed = { [weak targetNode] in
|
||||
targetNode?.openPeer(peer)
|
||||
}
|
||||
if buttonNode.supernode == nil {
|
||||
targetNode.addSubnode(buttonNode)
|
||||
}
|
||||
let frame = frames[i]
|
||||
buttonNode.frame = frame
|
||||
}
|
||||
|
||||
for (_, apply) in buttonLayoutsAndApply {
|
||||
let _ = apply()
|
||||
}
|
||||
|
||||
return targetNode
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerButtonNode: HighlightTrackingButtonNode {
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textNode: TextNode
|
||||
|
@ -5898,7 +5898,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.visibleItemSelectionLayers[itemId] = itemSelectionLayer
|
||||
}
|
||||
|
||||
if case .accent = item.tintMode {
|
||||
if case let .custom(color) = item.tintMode {
|
||||
itemSelectionLayer.backgroundColor = color.withMultipliedAlpha(0.1).cgColor
|
||||
itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor
|
||||
} else if case .accent = item.tintMode {
|
||||
itemSelectionLayer.backgroundColor = keyboardChildEnvironment.theme.list.itemAccentColor.withMultipliedAlpha(0.1).cgColor
|
||||
itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor
|
||||
} else {
|
||||
|
@ -124,6 +124,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let clipContentToTopPanel: Bool
|
||||
public let useExternalSearchContainer: Bool
|
||||
public let hidePanels: Bool
|
||||
public let customTintColor: UIColor?
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
@ -157,7 +158,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
isExpanded: Bool,
|
||||
clipContentToTopPanel: Bool,
|
||||
useExternalSearchContainer: Bool,
|
||||
hidePanels: Bool = false
|
||||
hidePanels: Bool = false,
|
||||
customTintColor: UIColor? = nil
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -191,6 +193,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
self.clipContentToTopPanel = clipContentToTopPanel
|
||||
self.useExternalSearchContainer = useExternalSearchContainer
|
||||
self.hidePanels = hidePanels
|
||||
self.customTintColor = customTintColor
|
||||
}
|
||||
|
||||
public static func ==(lhs: EntityKeyboardComponent, rhs: EntityKeyboardComponent) -> Bool {
|
||||
@ -257,7 +260,9 @@ public final class EntityKeyboardComponent: Component {
|
||||
if lhs.useExternalSearchContainer != rhs.useExternalSearchContainer {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.customTintColor != rhs.customTintColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -341,6 +346,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
icon: icon,
|
||||
theme: component.theme,
|
||||
useAccentColor: false,
|
||||
customTintColor: component.customTintColor,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "masks", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
@ -376,6 +382,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
id: "masks",
|
||||
theme: component.theme,
|
||||
customTintColor: component.customTintColor,
|
||||
items: topMaskItems,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
defaultActiveItemId: maskContent.panelItemGroups.first?.groupId,
|
||||
@ -430,6 +437,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
icon: .featured,
|
||||
theme: component.theme,
|
||||
useAccentColor: false,
|
||||
customTintColor: component.customTintColor,
|
||||
title: component.strings.Stickers_Trending,
|
||||
pressed: { [weak self] in
|
||||
self?.component?.stickerContent?.inputInteractionHolder.inputInteraction?.openFeatured?()
|
||||
@ -475,6 +483,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
icon: icon,
|
||||
theme: component.theme,
|
||||
useAccentColor: false,
|
||||
customTintColor: component.customTintColor,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
@ -511,6 +520,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
id: "stickers",
|
||||
theme: component.theme,
|
||||
customTintColor: component.customTintColor,
|
||||
items: topStickerItems,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
defaultActiveItemId: stickerContent.panelItemGroups.first?.groupId,
|
||||
@ -573,6 +583,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
icon: icon,
|
||||
theme: component.theme,
|
||||
useAccentColor: false,
|
||||
customTintColor: component.customTintColor,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "emoji", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
@ -623,6 +634,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
id: "emoji",
|
||||
theme: component.theme,
|
||||
customTintColor: component.customTintColor,
|
||||
items: topEmojiItems,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
activeContentItemIdUpdated: emojiContentItemIdUpdated,
|
||||
|
@ -281,6 +281,7 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
let icon: Icon
|
||||
let theme: PresentationTheme
|
||||
let useAccentColor: Bool
|
||||
let customTintColor: UIColor?
|
||||
let title: String
|
||||
let pressed: () -> Void
|
||||
|
||||
@ -288,12 +289,14 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
icon: Icon,
|
||||
theme: PresentationTheme,
|
||||
useAccentColor: Bool,
|
||||
customTintColor: UIColor?,
|
||||
title: String,
|
||||
pressed: @escaping () -> Void
|
||||
) {
|
||||
self.icon = icon
|
||||
self.theme = theme
|
||||
self.useAccentColor = useAccentColor
|
||||
self.customTintColor = customTintColor
|
||||
self.title = title
|
||||
self.pressed = pressed
|
||||
}
|
||||
@ -308,6 +311,9 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
if lhs.useAccentColor != rhs.useAccentColor {
|
||||
return false
|
||||
}
|
||||
if lhs.customTintColor != rhs.customTintColor {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
@ -383,14 +389,18 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
self.component = component
|
||||
|
||||
let color: UIColor
|
||||
if itemEnvironment.isHighlighted {
|
||||
if component.useAccentColor {
|
||||
color = component.theme.list.itemAccentColor
|
||||
} else {
|
||||
color = component.theme.chat.inputMediaPanel.panelHighlightedIconColor
|
||||
}
|
||||
if let customTintColor = component.customTintColor {
|
||||
color = customTintColor
|
||||
} else {
|
||||
color = component.theme.chat.inputMediaPanel.panelIconColor
|
||||
if itemEnvironment.isHighlighted {
|
||||
if component.useAccentColor {
|
||||
color = component.theme.list.itemAccentColor
|
||||
} else {
|
||||
color = component.theme.chat.inputMediaPanel.panelHighlightedIconColor
|
||||
}
|
||||
} else {
|
||||
color = component.theme.chat.inputMediaPanel.panelIconColor
|
||||
}
|
||||
}
|
||||
|
||||
if self.iconView.tintColor != color {
|
||||
@ -1192,6 +1202,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
|
||||
let id: AnyHashable
|
||||
let theme: PresentationTheme
|
||||
let customTintColor: UIColor?
|
||||
let items: [Item]
|
||||
let containerSideInset: CGFloat
|
||||
let defaultActiveItemId: AnyHashable?
|
||||
@ -1203,6 +1214,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
init(
|
||||
id: AnyHashable,
|
||||
theme: PresentationTheme,
|
||||
customTintColor: UIColor?,
|
||||
items: [Item],
|
||||
containerSideInset: CGFloat,
|
||||
defaultActiveItemId: AnyHashable? = nil,
|
||||
@ -1213,6 +1225,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
) {
|
||||
self.id = id
|
||||
self.theme = theme
|
||||
self.customTintColor = customTintColor
|
||||
self.items = items
|
||||
self.containerSideInset = containerSideInset
|
||||
self.defaultActiveItemId = defaultActiveItemId
|
||||
@ -1229,6 +1242,9 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.customTintColor != rhs.customTintColor {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
@ -1847,8 +1863,12 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
|
||||
func update(component: EntityKeyboardTopPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
if self.component?.theme !== component.theme {
|
||||
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
|
||||
if self.component?.theme !== component.theme || self.component?.customTintColor != component.customTintColor {
|
||||
if let customTintColor = component.customTintColor {
|
||||
self.highlightedIconBackgroundView.backgroundColor = customTintColor.withAlphaComponent(0.1)
|
||||
} else {
|
||||
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
|
||||
}
|
||||
}
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
@ -67,7 +67,7 @@ final class ApplyColorFooterItemNode: ItemListControllerFooterItemNode {
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
// self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.updateItem()
|
||||
|
@ -186,6 +186,7 @@ final class EmojiPickerItemNode: ListViewItemNode {
|
||||
strings: item.strings,
|
||||
deviceMetrics: .iPhone14ProMax,
|
||||
emojiContent: item.emojiContent,
|
||||
backgroundIconColor: item.backgroundIconColor,
|
||||
backgroundColor: item.theme.list.itemBlocksBackgroundColor,
|
||||
separatorColor: item.theme.list.itemBlocksSeparatorColor
|
||||
)
|
||||
@ -221,6 +222,7 @@ private final class EmojiSelectionComponent: Component {
|
||||
public let strings: PresentationStrings
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let emojiContent: EmojiPagerContentComponent
|
||||
public let backgroundIconColor: UIColor
|
||||
public let backgroundColor: UIColor
|
||||
public let separatorColor: UIColor
|
||||
|
||||
@ -229,6 +231,7 @@ private final class EmojiSelectionComponent: Component {
|
||||
strings: PresentationStrings,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
emojiContent: EmojiPagerContentComponent,
|
||||
backgroundIconColor: UIColor,
|
||||
backgroundColor: UIColor,
|
||||
separatorColor: UIColor
|
||||
) {
|
||||
@ -236,6 +239,7 @@ private final class EmojiSelectionComponent: Component {
|
||||
self.strings = strings
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.emojiContent = emojiContent
|
||||
self.backgroundIconColor = backgroundIconColor
|
||||
self.backgroundColor = backgroundColor
|
||||
self.separatorColor = separatorColor
|
||||
}
|
||||
@ -253,6 +257,9 @@ private final class EmojiSelectionComponent: Component {
|
||||
if lhs.emojiContent != rhs.emojiContent {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundIconColor != rhs.backgroundIconColor {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
}
|
||||
@ -338,7 +345,8 @@ private final class EmojiSelectionComponent: Component {
|
||||
displayBottomPanel: false,
|
||||
isExpanded: true,
|
||||
clipContentToTopPanel: false,
|
||||
useExternalSearchContainer: false
|
||||
useExternalSearchContainer: false,
|
||||
customTintColor: component.backgroundIconColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
|
@ -252,11 +252,11 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||
|
||||
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor {
|
||||
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId {
|
||||
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
|
||||
strongSelf.view.addSubview(snapshot)
|
||||
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.1, removeOnCompletion: false, completion: { _ in
|
||||
snapshot.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
@ -393,13 +393,18 @@ public func PeerNameColorScreen(
|
||||
title: buttonTitle,
|
||||
locked: isLocked,
|
||||
action: {
|
||||
if isPremium {
|
||||
if !isLocked {
|
||||
let state = stateValue.with { $0 }
|
||||
|
||||
let nameColor = state.updatedNameColor ?? peer?.nameColor
|
||||
let backgroundEmojiId = state.updatedBackgroundEmojiId ?? peer?.backgroundEmojiId
|
||||
|
||||
let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone()
|
||||
switch subject {
|
||||
case .account:
|
||||
let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone()
|
||||
case let .channel(peerId):
|
||||
let _ = context.engine.peers.updatePeerNameColorAndEmoji(peerId: peerId, nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone()
|
||||
}
|
||||
|
||||
dismissImpl?()
|
||||
} else {
|
||||
|
@ -836,7 +836,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
guard let stateValue = self.effectiveStateValue else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||
topOffset = max(0.0, topOffset)
|
||||
transition.setTransform(layer: self.backgroundView.layer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
||||
@ -1082,7 +1082,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
self.hapticFeedback.impact(.light)
|
||||
} else {
|
||||
self.postingAvailabilityDisposable.set((component.context.engine.messages.checkStoriesUploadAvailability(target: .peer(peer.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -1106,7 +1106,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
previousController.dismiss()
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, boosted: false), count: Int32(status.boosts), forceDark: true, cancel: {}, action: { [weak navigationController] in
|
||||
let controller = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0), count: Int32(status.boosts), forceDark: true, cancel: {}, action: { [weak navigationController] in
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
if let previousController = navigationController?.viewControllers.reversed().first(where: { $0 is ShareWithPeersScreen}) as? ShareWithPeersScreen {
|
||||
@ -1397,7 +1397,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
subtitle = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let isSelected = self.selectedPeers.contains(peer.id) || self.selectedGroups.contains(peer.id)
|
||||
let _ = visibleItem.update(
|
||||
transition: itemTransition,
|
||||
@ -1671,7 +1671,21 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
}
|
||||
|
||||
let fadeTransition = Transition.easeInOut(duration: 0.25)
|
||||
if let searchStateContext = self.searchStateContext, case let .contactsSearch(query, _) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
|
||||
|
||||
var searchQuery: String?
|
||||
var searchResultsAreEmpty = false
|
||||
if let searchStateContext = self.searchStateContext, let value = searchStateContext.stateValue {
|
||||
if case let .contactsSearch(query, _) = searchStateContext.subject {
|
||||
searchQuery = query
|
||||
} else if case let .members(_, query) = searchStateContext.subject {
|
||||
searchQuery = query
|
||||
} else if case let .channels(_, query) = searchStateContext.subject {
|
||||
searchQuery = query
|
||||
}
|
||||
searchResultsAreEmpty = value.peers.isEmpty
|
||||
}
|
||||
|
||||
if let searchQuery, searchResultsAreEmpty {
|
||||
let sideInset: CGFloat = 44.0
|
||||
let emptyAnimationHeight = 148.0
|
||||
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
|
||||
@ -1695,7 +1709,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: environment.strings.Contacts_Search_NoResultsQueryDescription(query).string, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)),
|
||||
text: .plain(NSAttributedString(string: environment.strings.Contacts_Search_NoResultsQueryDescription(searchQuery).string, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
)
|
||||
@ -2041,15 +2055,28 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
containerSize: CGSize(width: containerWidth, height: 1000.0)
|
||||
)
|
||||
|
||||
if !self.navigationTextFieldState.text.isEmpty {
|
||||
let searchQuery = self.navigationTextFieldState.text
|
||||
if !searchQuery.isEmpty {
|
||||
var onlyContacts = false
|
||||
if component.initialPrivacy.base == .closeFriends || component.initialPrivacy.base == .contacts {
|
||||
onlyContacts = true
|
||||
}
|
||||
if let searchStateContext = self.searchStateContext, searchStateContext.subject == .contactsSearch(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts) {
|
||||
|
||||
let searchSubject: ShareWithPeersScreen.StateContext.Subject
|
||||
switch component.stateContext.subject {
|
||||
case let .channels(exclude, _):
|
||||
searchSubject = .channels(exclude: exclude, searchQuery: searchQuery)
|
||||
case let .members(peerId, _):
|
||||
searchSubject = .members(peerId: peerId, searchQuery: searchQuery)
|
||||
default:
|
||||
searchSubject = .contactsSearch(query: searchQuery, onlyContacts: onlyContacts)
|
||||
}
|
||||
|
||||
|
||||
if let searchStateContext = self.searchStateContext, searchStateContext.subject == searchSubject {
|
||||
} else {
|
||||
self.searchStateDisposable?.dispose()
|
||||
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: .contactsSearch(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts), editing: false)
|
||||
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: searchSubject)
|
||||
var applyState = false
|
||||
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
|
@ -4,6 +4,7 @@ import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramUIPreferences
|
||||
import TemporaryCachedPeerDataManager
|
||||
import Postbox
|
||||
|
||||
public extension ShareWithPeersScreen {
|
||||
final class State {
|
||||
@ -48,7 +49,7 @@ public extension ShareWithPeersScreen {
|
||||
case contacts(base: EngineStoryPrivacy.Base)
|
||||
case contactsSearch(query: String, onlyContacts: Bool)
|
||||
case members(peerId: EnginePeer.Id, searchQuery: String?)
|
||||
case channels(exclude: Set<EnginePeer.Id>)
|
||||
case channels(exclude: Set<EnginePeer.Id>, searchQuery: String?)
|
||||
}
|
||||
|
||||
var stateValue: State?
|
||||
@ -584,7 +585,7 @@ public extension ShareWithPeersScreen {
|
||||
self.stateDisposable = combinedDisposable
|
||||
|
||||
self.listControl = disposableAndLoadMoreControl.1
|
||||
case let .channels(excludePeerIds):
|
||||
case let .channels(excludePeerIds, searchQuery):
|
||||
self.stateDisposable = (combineLatest(
|
||||
context.engine.messages.chatList(group: .root, count: 500) |> take(1),
|
||||
context.engine.data.get(EngineDataMap(Array(self.initialPeerIds).map(TelegramEngine.EngineData.Item.Peer.Peer.init)))
|
||||
@ -627,13 +628,24 @@ public extension ShareWithPeersScreen {
|
||||
existingIds.insert(peerId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let queryTokens = stringIndexTokens(searchQuery ?? "", transliteration: .combined)
|
||||
func peerMatchesTokens(peer: EnginePeer, tokens: [ValueBoxKey]) -> Bool {
|
||||
if matchStringIndexTokens(peer.indexName._asIndexName().indexTokens, with: queryTokens) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var peers: [EnginePeer] = []
|
||||
peers = chatList.items.filter { peer in
|
||||
if let peer = peer.renderedPeer.peer {
|
||||
if excludePeerIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
if let _ = searchQuery, !peerMatchesTokens(peer: peer, tokens: queryTokens) {
|
||||
return false
|
||||
}
|
||||
if self.initialPeerIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
|
@ -43,6 +43,11 @@ public final class PeerListItemComponent: Component {
|
||||
case editing(isSelected: Bool, isTinted: Bool)
|
||||
}
|
||||
|
||||
public enum SelectionPosition: Equatable {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
public enum SubtitleAccessory: Equatable {
|
||||
case none
|
||||
case checks
|
||||
@ -101,6 +106,8 @@ public final class PeerListItemComponent: Component {
|
||||
let rightAccessory: RightAccessory
|
||||
let reaction: Reaction?
|
||||
let selectionState: SelectionState
|
||||
let selectionPosition: SelectionPosition
|
||||
let isEnabled: Bool
|
||||
let hasNext: Bool
|
||||
let action: (EnginePeer) -> Void
|
||||
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
|
||||
@ -121,6 +128,8 @@ public final class PeerListItemComponent: Component {
|
||||
rightAccessory: RightAccessory = .none,
|
||||
reaction: Reaction? = nil,
|
||||
selectionState: SelectionState,
|
||||
selectionPosition: SelectionPosition = .left,
|
||||
isEnabled: Bool = true,
|
||||
hasNext: Bool,
|
||||
action: @escaping (EnginePeer) -> Void,
|
||||
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
|
||||
@ -140,6 +149,8 @@ public final class PeerListItemComponent: Component {
|
||||
self.rightAccessory = rightAccessory
|
||||
self.reaction = reaction
|
||||
self.selectionState = selectionState
|
||||
self.selectionPosition = selectionPosition
|
||||
self.isEnabled = isEnabled
|
||||
self.hasNext = hasNext
|
||||
self.action = action
|
||||
self.contextAction = contextAction
|
||||
@ -189,6 +200,12 @@ public final class PeerListItemComponent: Component {
|
||||
if lhs.selectionState != rhs.selectionState {
|
||||
return false
|
||||
}
|
||||
if lhs.selectionPosition != rhs.selectionPosition {
|
||||
return false
|
||||
}
|
||||
if lhs.isEnabled != rhs.isEnabled {
|
||||
return false
|
||||
}
|
||||
if lhs.hasNext != rhs.hasNext {
|
||||
return false
|
||||
}
|
||||
@ -411,6 +428,8 @@ public final class PeerListItemComponent: Component {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
self.containerButton.alpha = component.isEnabled ? 1.0 : 0.3
|
||||
|
||||
self.avatarButtonView.isUserInteractionEnabled = component.storyStats != nil && component.openStories != nil
|
||||
|
||||
let labelData: (String, Bool)
|
||||
@ -457,9 +476,17 @@ public final class PeerListItemComponent: Component {
|
||||
var avatarLeftInset: CGFloat = component.sideInset + 10.0
|
||||
|
||||
if case let .editing(isSelected, isTinted) = component.selectionState {
|
||||
leftInset += 44.0
|
||||
avatarLeftInset += 44.0
|
||||
let checkSize: CGFloat = 22.0
|
||||
let checkOriginX: CGFloat
|
||||
switch component.selectionPosition {
|
||||
case .left:
|
||||
leftInset += 44.0
|
||||
avatarLeftInset += 44.0
|
||||
checkOriginX = floor((54.0 - checkSize) * 0.5)
|
||||
case .right:
|
||||
rightInset += 44.0
|
||||
checkOriginX = availableSize.width - 11.0 - checkSize
|
||||
}
|
||||
|
||||
let checkLayer: CheckLayer
|
||||
if let current = self.checkLayer {
|
||||
@ -484,7 +511,7 @@ public final class PeerListItemComponent: Component {
|
||||
checkLayer.setSelected(isSelected, animated: false)
|
||||
checkLayer.setNeedsDisplay()
|
||||
}
|
||||
transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: floor((54.0 - checkSize) * 0.5), y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
|
||||
transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: checkOriginX, y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
|
||||
} else {
|
||||
if let checkLayer = self.checkLayer {
|
||||
self.checkLayer = nil
|
||||
|
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
|
||||
})
|
||||
})
|
||||
} else {
|
||||
self.playShakeAnimation()
|
||||
}
|
||||
case let .withBotStartPayload(botStart):
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
|
@ -835,8 +835,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
}), nil)
|
||||
}
|
||||
})
|
||||
case let .boost(peerId, status, myBoostsStatus):
|
||||
let _ = myBoostsStatus
|
||||
case let .boost(peerId, status, myBoostStatus):
|
||||
let _ = myBoostStatus
|
||||
var forceDark = false
|
||||
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
|
||||
forceDark = true
|
||||
@ -847,9 +847,21 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
return
|
||||
}
|
||||
|
||||
var isBoosted = false
|
||||
if status.boostedByMe {
|
||||
isBoosted = true
|
||||
var myBoostCount: Int32 = 0
|
||||
var availableBoosts: [MyBoostStatus.Boost] = []
|
||||
var occupiedBoosts: [MyBoostStatus.Boost] = []
|
||||
if let myBoostStatus {
|
||||
for boost in myBoostStatus.boosts {
|
||||
if let boostPeer = boost.peer {
|
||||
if boostPeer.id == peer.id {
|
||||
myBoostCount += 1
|
||||
} else {
|
||||
occupiedBoosts.append(boost)
|
||||
}
|
||||
} else {
|
||||
availableBoosts.append(boost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isCurrent = false
|
||||
@ -861,22 +873,19 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
var currentLevelBoosts = Int32(status.currentLevelBoosts)
|
||||
var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init)
|
||||
|
||||
if isBoosted && status.boosts == currentLevelBoosts {
|
||||
if myBoostCount > 0 && status.boosts == currentLevelBoosts {
|
||||
currentLevel = max(0, currentLevel - 1)
|
||||
nextLevelBoosts = currentLevelBoosts
|
||||
currentLevelBoosts = max(0, currentLevelBoosts - 1)
|
||||
}
|
||||
|
||||
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, boosted: isBoosted)
|
||||
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, boosted: true)
|
||||
let nextCount = Int32(status.boosts + 1)
|
||||
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount)
|
||||
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount + 1)
|
||||
var nextCount = Int32(status.boosts + 1)
|
||||
|
||||
var updateImpl: (() -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), forceDark: forceDark, action: {
|
||||
if isBoosted {
|
||||
return true
|
||||
}
|
||||
let dismiss = false
|
||||
updateImpl?()
|
||||
|
||||
@ -945,8 +954,23 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
|
||||
updateImpl = { [weak controller] in
|
||||
if let _ = status.nextLevelBoosts {
|
||||
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: []).startStandalone()
|
||||
controller?.updateSubject(nextSubject, count: nextCount)
|
||||
if let availableBoost = availableBoosts.first {
|
||||
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]).startStandalone()
|
||||
controller?.updateSubject(nextSubject, count: nextCount)
|
||||
|
||||
availableBoosts.removeFirst()
|
||||
nextCount += 1
|
||||
} else if !occupiedBoosts.isEmpty, let myBoostStatus {
|
||||
let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in
|
||||
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone()
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "\(slots.count) boosts are reassigned from 1 other channel.", timeout: nil), elevatedLayout: true, position: .bottom, action: { _ in return true })
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(undoController, in: .window(.root))
|
||||
})
|
||||
dismissImpl?()
|
||||
navigationController?.pushViewController(replaceController)
|
||||
}
|
||||
} else {
|
||||
dismissImpl?()
|
||||
}
|
||||
@ -974,7 +998,9 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
let _ = context.engine.payments.applyPremiumGiftCode(slug: slug).startStandalone()
|
||||
},
|
||||
openPeer: { peer in
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
|
||||
if peer.id != context.account.peerId {
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
|
||||
}
|
||||
},
|
||||
openMessage: { messageId in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|
||||
|
@ -8362,7 +8362,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
if let previousController = navigationController.viewControllers.last as? ShareWithPeersScreen {
|
||||
previousController.dismiss()
|
||||
}
|
||||
let controller = PremiumLimitScreen(context: self.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, boosted: false), count: Int32(status.boosts), action: { [weak self] in
|
||||
let controller = PremiumLimitScreen(context: self.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0), count: Int32(status.boosts), action: { [weak self] in
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
if let self {
|
||||
|
@ -1795,8 +1795,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSubject = .storiesWeekly
|
||||
case .storiesMonthly:
|
||||
mappedSubject = .storiesMonthly
|
||||
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, boosted):
|
||||
mappedSubject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: level, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: link, boosted: boosted)
|
||||
case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount):
|
||||
mappedSubject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: level, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: link, myBoostCount: myBoostCount)
|
||||
}
|
||||
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user