Various improvements

This commit is contained in:
Ilya Laktyushin 2022-05-03 20:04:31 +04:00
parent 7063b84dcf
commit 93b84ebf24
47 changed files with 2320 additions and 288 deletions

View file

@ -7547,3 +7547,20 @@ Sorry for the inconvenience.";
"Chat.MultipleTypingPair" = "%@ and %@";
"Chat.MultipleTypingMore" = "%@ and %@ others";
"DialogList.ExtendedPinLimitError" = "Sorry, you can pin more than **%1$@** chats to the top. Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **%2$@** chats.";
"DialogList.ExtendedPinLimitIncrease" = "Increase Limit";
"Group.Username.RemoveExistingUsernamesTitle" = "Too Many Public Links";
"Group.Username.RemoveExistingUsernamesOrExtendInfo" = "You have reserved too many public links. Try revoking a link from an older group or channel, or upgrade to **Telegram Premium** to double the limit to **%@** public links.";
"Group.Username.IncreaseLimit" = "Increase Limit";
"OldChannels.TooManyCommunitiesTitle" = "Too Many Communities";
"OldChannels.TooManyCommunitiesText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one or upgrade to **Telegram Premium** to double the limit to **%@** groups and channels.";
"OldChannels.IncreaseLimit" = "Increase Limit";
"OldChannels.LeaveCommunities_1" = "Leave %@ Community";
"OldChannels.LeaveCommunities_any" = "Leave %@ Communities";
"Stickers.FaveLimitReachedInfo" = "Sorry, you can't add more than **%@** stickers to favorites. Replace an older saved sticker or subscribe to **Telegram Premium** to double the limit to **%@** favorites stickers.";
"Stickers.FaveLimitReplaceOlder" = "Replace Older Sticker";
"Stickers.FaveLimitIncrease" = "Increase Limit";

View file

@ -67,6 +67,7 @@ swift_library(
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
"//submodules/Components/ProgressIndicatorComponent:ProgressIndicatorComponent",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/PremiumUI:PremiumUI",
],
visibility = [
"//visibility:public",

View file

@ -311,16 +311,36 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
}
if isPinned || chatListFilter == nil || peerId.namespace != Namespaces.Peer.SecretChat {
items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { c, f in
let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: .peer(peerId))
|> deliverOnMainQueue).start(next: { result in
switch result {
case .done:
break
f(.default)
case .limitExceeded:
break
var subItems: [ContextMenuItem] = []
subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
c.popItems()
})))
subItems.append(.separator)
subItems.append(.action(ContextMenuActionItem(text: strings.DialogList_ExtendedPinLimitError("5", "10").string, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in
return nil
}, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?)))
subItems.append(.action(ContextMenuActionItem(text: strings.DialogList_ExtendedPinLimitIncrease, icon: { _ in
return nil
}, action: { _, f in
f(.default)
})))
c.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
}
f(.default)
})
})))
}

View file

@ -13,6 +13,7 @@ import ContextUI
import ItemListUI
import SearchUI
import ChatListSearchItemHeader
import PremiumUI
public enum ChatListNodeMode {
case chatList
@ -840,14 +841,16 @@ public final class ChatListNode: ListView {
switch result {
case .done:
break
case let .limitExceeded(maxCount):
let text: String
if chatListFilter != nil {
text = strongSelf.currentState.presentationData.strings.DialogList_UnknownPinLimitError
} else {
text = strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").string
}
strongSelf.presentAlert?(text)
case .limitExceeded:
let controller = LimitScreen(context: strongSelf.context, subject: .pins)
strongSelf.present?(controller)
// let text: String
// if chatListFilter != nil {
// text = strongSelf.currentState.presentationData.strings.DialogList_UnknownPinLimitError
// } else {
// text = strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").string
// }
// strongSelf.presentAlert?(text)
}
}
})

View file

@ -3,6 +3,7 @@ import UIKit
import ComponentFlow
import Display
import SolidRoundedButtonNode
import AppBundle
public final class SolidRoundedButtonComponent: Component {
public typealias Theme = SolidRoundedButtonTheme
@ -15,6 +16,8 @@ public final class SolidRoundedButtonComponent: Component {
public let height: CGFloat
public let cornerRadius: CGFloat
public let gloss: Bool
public let iconName: String?
public let iconPosition: SolidRoundedButtonIconPosition
public let action: () -> Void
public init(
@ -26,6 +29,8 @@ public final class SolidRoundedButtonComponent: Component {
height: CGFloat = 48.0,
cornerRadius: CGFloat = 24.0,
gloss: Bool = false,
iconName: String? = nil,
iconPosition: SolidRoundedButtonIconPosition = .left,
action: @escaping () -> Void
) {
self.title = title
@ -36,6 +41,8 @@ public final class SolidRoundedButtonComponent: Component {
self.height = height
self.cornerRadius = cornerRadius
self.gloss = gloss
self.iconName = iconName
self.iconPosition = iconPosition
self.action = action
}
@ -64,6 +71,12 @@ public final class SolidRoundedButtonComponent: Component {
if lhs.gloss != rhs.gloss {
return false
}
if lhs.iconName != rhs.iconName {
return false
}
if lhs.iconPosition != rhs.iconPosition {
return false
}
return true
}
@ -84,6 +97,8 @@ public final class SolidRoundedButtonComponent: Component {
cornerRadius: component.cornerRadius,
gloss: component.gloss
)
button.iconPosition = component.iconPosition
button.icon = component.iconName.flatMap { UIImage(bundleImageName: $0) }
self.button = button
self.addSubview(button)

View file

@ -18,6 +18,7 @@ swift_library(
"//submodules/AppBundle:AppBundle",
"//submodules/AccountContext:AccountContext",
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
"//submodules/Markdown:Markdown",
],
visibility = [
"//visibility:public",

View file

@ -3,6 +3,7 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
import SwiftSignalKit
import Markdown
public enum ContextActionSibling {
case none
@ -53,6 +54,9 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let smallTextFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
let boldTextFont = Font.semibold(presentationData.listsFontSize.baseDisplaySize)
let smallBoldTextFont = Font.semibold(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isAccessibilityElement = false
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
@ -76,18 +80,29 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
}
let titleFont: UIFont
let titleBoldFont: UIFont
switch action.textFont {
case .regular:
titleFont = textFont
titleBoldFont = boldTextFont
case .small:
titleFont = smallTextFont
titleBoldFont = smallBoldTextFont
case let .custom(customFont):
titleFont = customFont
titleBoldFont = customFont
}
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor)
if action.parseMarkdown {
let attributedText = parseMarkdownIntoAttributedString(action.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: textColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: textColor), linkAttribute: { _ in
return nil
}))
self.textNode.attributedText = attributedText
} else {
self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor)
}
switch action.textLayout {
case .singleLine:

View file

@ -92,6 +92,7 @@ public final class ContextMenuActionItem {
public let textColor: ContextMenuActionItemTextColor
public let textFont: ContextMenuActionItemFont
public let textLayout: ContextMenuActionItemTextLayout
public let parseMarkdown: Bool
public let badge: ContextMenuActionBadge?
public let icon: (PresentationTheme) -> UIImage?
public let iconSource: ContextMenuActionItemIconSource?
@ -103,6 +104,7 @@ public final class ContextMenuActionItem {
textColor: ContextMenuActionItemTextColor = .primary,
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
textFont: ContextMenuActionItemFont = .regular,
parseMarkdown: Bool = false,
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
@ -114,6 +116,7 @@ public final class ContextMenuActionItem {
textColor: textColor,
textLayout: textLayout,
textFont: textFont,
parseMarkdown: parseMarkdown,
badge: badge,
icon: icon,
iconSource: iconSource,
@ -131,6 +134,7 @@ public final class ContextMenuActionItem {
textColor: ContextMenuActionItemTextColor = .primary,
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
textFont: ContextMenuActionItemFont = .regular,
parseMarkdown: Bool = false,
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
@ -141,6 +145,7 @@ public final class ContextMenuActionItem {
self.textColor = textColor
self.textFont = textFont
self.textLayout = textLayout
self.parseMarkdown = parseMarkdown
self.badge = badge
self.icon = icon
self.iconSource = iconSource

View file

@ -8,6 +8,7 @@ import TelegramCore
import SwiftSignalKit
import AccountContext
import ReactionSelectionNode
import Markdown
public protocol ContextControllerActionsStackItemNode: ASDisplayNode {
func update(
@ -170,17 +171,22 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
subtitle = subtitleValue
case .multiline:
self.titleLabelNode.maximumNumberOfLines = 0
self.titleLabelNode.lineSpacing = 0.1
}
let titleFont: UIFont
let titleBoldFont: UIFont
switch self.item.textFont {
case let .custom(font):
titleFont = font
titleBoldFont = font
case .small:
let smallTextFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
titleFont = smallTextFont
titleBoldFont = Font.semibold(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
case .regular:
titleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
titleBoldFont = Font.semibold(presentationData.listsFontSize.baseDisplaySize)
}
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
@ -196,11 +202,23 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
titleColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
}
self.titleLabelNode.attributedText = NSAttributedString(
string: self.item.text,
font: titleFont,
textColor: titleColor
)
if self.item.parseMarkdown {
let attributedText = parseMarkdownIntoAttributedString(
self.item.text,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: titleFont, textColor: titleColor),
bold: MarkdownAttributeSet(font: titleBoldFont, textColor: titleColor),
link: MarkdownAttributeSet(font: titleFont, textColor: titleColor),
linkAttribute: { _ in return nil }
)
)
self.titleLabelNode.attributedText = attributedText
} else {
self.titleLabelNode.attributedText = NSAttributedString(
string: self.item.text,
font: titleFont,
textColor: titleColor)
}
self.subtitleNode.attributedText = subtitle.flatMap { subtitle in
return NSAttributedString(

View file

@ -59,6 +59,7 @@ private final class ChannelVisibilityControllerArguments {
private enum ChannelVisibilitySection: Int32 {
case type
case limitInfo
case link
case linkActions
case joinToSend
@ -87,6 +88,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case publicLinkHeader(PresentationTheme, String)
case publicLinkAvailability(PresentationTheme, String, Bool)
case linksLimitInfo(PresentationTheme, String, String, Int)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String)
case privateLinkHeader(PresentationTheme, String)
case privateLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
@ -115,6 +117,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
switch self {
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
return ChannelVisibilitySection.type.rawValue
case .linksLimitInfo:
return ChannelVisibilitySection.limitInfo.rawValue
case .publicLinkHeader, .publicLinkAvailability, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
return ChannelVisibilitySection.link.rawValue
case .privateLinkManage, .privateLinkManageInfo:
@ -144,22 +148,24 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return 4
case .publicLinkAvailability:
return 5
case .privateLinkHeader:
case .linksLimitInfo:
return 6
case .privateLink:
case .privateLinkHeader:
return 7
case .editablePublicLink:
case .privateLink:
return 8
case .privateLinkInfo:
case .editablePublicLink:
return 9
case .publicLinkStatus:
case .privateLinkInfo:
return 10
case .publicLinkInfo:
case .publicLinkStatus:
return 11
case .existingLinksInfo:
case .publicLinkInfo:
return 12
case .existingLinksInfo:
return 13
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
return 13 + index
return 14 + index
case .privateLinkManage:
return 1000
case .privateLinkManageInfo:
@ -221,6 +227,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else {
return false
}
case let .linksLimitInfo(lhsTheme, lhsTitle, lhsText, lhsLimit):
if case let .linksLimitInfo(rhsTheme, rhsTitle, rhsText, rhsLimit) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText, lhsLimit == rhsLimit {
return true
} else {
return false
}
case let .privateLinkHeader(lhsTheme, lhsTitle):
if case let .privateLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
@ -381,6 +393,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor)
attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length))
return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section)
case let .linksLimitInfo(theme, title, text, limit):
return IncreaseLimitHeaderItem(theme: theme, icon: .link, count: limit, title: title, text: text, sectionId: self.section)
case let .privateLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .privateLink(_, invite, peers, importersCount, displayImporters):
@ -715,7 +729,8 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
if displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke {
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10))
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
@ -803,7 +818,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
case .privateChannel:
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
if isGroup {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
} else {
@ -885,6 +900,8 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
if displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke {
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(1000)").string, 500))
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
@ -1338,6 +1355,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|> map { presentationData, state, view, publicChannelsToRevoke, importersContext, importers -> (ItemListControllerState, (ItemListNodeState, Any)) in
let peer = peerViewMainPeer(view)
var footerItem: ItemListControllerFooterItem?
var rightNavigationButton: ItemListNavigationButton?
if let peer = peer as? TelegramChannel {
var doneEnabled = true
@ -1548,6 +1567,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
}
var crossfade: Bool = false
var animateChanges: Bool = false
if let cachedData = view.cachedData as? CachedChannelData {
let invitation = cachedData.exportedInvitation
let previousInvitation = previousInvitation.swap(invitation)
@ -1580,6 +1600,14 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
if selectedType == .publicChannel, let hadNamesToRevoke = hadNamesToRevoke, !crossfade {
crossfade = hadNamesToRevoke != hasNamesToRevoke
}
if hasNamesToRevoke && selectedType == .publicChannel {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Group_Username_IncreaseLimit, colorful: true, action: {})
}
if let hadNamesToRevoke = hadNamesToRevoke {
animateChanges = hadNamesToRevoke != hasNamesToRevoke
}
}
let title: String
@ -1601,7 +1629,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, focusItemTag: focusItemTag, crossfadeState: crossfade, animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, focusItemTag: focusItemTag, footerItem: footerItem, crossfadeState: crossfade, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View file

@ -0,0 +1,141 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import SolidRoundedButtonNode
import AppBundle
final class IncreaseLimitFooterItem: ItemListControllerFooterItem {
let theme: PresentationTheme
let title: String
let colorful: Bool
let action: () -> Void
init(theme: PresentationTheme, title: String, colorful: Bool, action: @escaping () -> Void) {
self.theme = theme
self.title = title
self.colorful = colorful
self.action = action
}
func isEqual(to: ItemListControllerFooterItem) -> Bool {
if let item = to as? IncreaseLimitFooterItem {
return self.theme === item.theme && self.title == item.title
} else {
return false
}
}
func node(current: ItemListControllerFooterItemNode?) -> ItemListControllerFooterItemNode {
if let current = current as? IncreaseLimitFooterItemNode {
current.item = self
return current
} else {
return IncreaseLimitFooterItemNode(item: self)
}
}
}
final class IncreaseLimitFooterItemNode: ItemListControllerFooterItemNode {
private let backgroundNode: NavigationBackgroundNode
private let separatorNode: ASDisplayNode
private let buttonNode: SolidRoundedButtonNode
private var validLayout: ContainerViewLayout?
var item: IncreaseLimitFooterItem {
didSet {
self.updateItem()
if let layout = self.validLayout {
let _ = self.updateLayout(layout: layout, transition: .immediate)
}
}
}
init(item: IncreaseLimitFooterItem) {
self.item = item
self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.tabBar.backgroundColor)
self.separatorNode = ASDisplayNode()
self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0)
self.buttonNode.iconPosition = .right
self.buttonNode.icon = UIImage(bundleImageName: "Premium/X2")
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.buttonNode)
self.updateItem()
}
private func updateItem() {
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor
let backgroundColor = self.item.theme.list.itemCheckColors.fillColor
let backgroundColors: [UIColor]
if self.item.colorful {
backgroundColors = [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)]
self.buttonNode.icon = UIImage(bundleImageName: "Premium/X2")
} else {
backgroundColors = []
self.buttonNode.icon = nil
}
self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: backgroundColor, backgroundColors: backgroundColors, foregroundColor: self.item.theme.list.itemCheckColors.foregroundColor), animated: true)
self.buttonNode.title = self.item.title
self.buttonNode.pressed = { [weak self] in
self?.item.action()
}
}
override func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateAlpha(node: self.backgroundNode, alpha: alpha)
transition.updateAlpha(node: self.separatorNode, alpha: alpha)
}
override func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = layout
let buttonInset: CGFloat = 16.0
let buttonWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - buttonInset * 2.0
let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition)
let inset: CGFloat = 9.0
let insets = layout.insets(options: [.input])
var panelHeight: CGFloat = buttonHeight + inset * 2.0
let totalPanelHeight: CGFloat
if let inputHeight = layout.inputHeight, inputHeight > 0.0 {
totalPanelHeight = panelHeight + insets.bottom
} else {
panelHeight += insets.bottom
totalPanelHeight = panelHeight
}
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - totalPanelHeight), size: CGSize(width: layout.size.width, height: panelHeight))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: panelFrame.minY + inset), size: CGSize(width: buttonWidth, height: buttonHeight)))
transition.updateFrame(node: self.backgroundNode, frame: panelFrame)
self.backgroundNode.update(size: panelFrame.size, transition: transition)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: UIScreenPixel)))
return panelHeight
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if self.backgroundNode.frame.contains(point) {
return true
} else {
return false
}
}
}

View file

@ -0,0 +1,201 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import Markdown
class IncreaseLimitHeaderItem: ListViewItem, ItemListItem {
enum Icon {
case group
case link
}
let theme: PresentationTheme
let icon: Icon
let count: Int
let title: String
let text: String
let sectionId: ItemListSectionId
init(theme: PresentationTheme, icon: Icon, count: Int, title: String, text: String, sectionId: ItemListSectionId) {
self.theme = theme
self.icon = icon
self.count = count
self.title = title
self.text = text
self.sectionId = sectionId
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = IncreaseLimitHeaderItemNode()
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 {
guard let nodeValue = node() as? IncreaseLimitHeaderItemNode else {
assertionFailure()
return
}
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 let titleFont = Font.semibold(17.0)
private let textFont = Font.regular(14.0)
private let boldTextFont = Font.semibold(13.0)
class IncreaseLimitHeaderItemNode: ListViewItemNode {
private var backgroundNode: ASImageNode
private var iconNode: ASImageNode
private var countNode: TextNode
private let titleNode: TextNode
private let textNode: TextNode
private var item: IncreaseLimitHeaderItem?
init() {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale
self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.contentMode = .left
self.textNode.contentsScale = UIScreen.main.scale
self.backgroundNode = ASImageNode()
self.backgroundNode.clipsToBounds = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = generateGradientImage(size: CGSize(width: 100.0, height: 47.0), colors: [UIColor(rgb: 0xa44ece), UIColor(rgb: 0xff7924)], locations: [0.0, 1.0], direction: .horizontal)
self.backgroundNode.cornerRadius = 23.5
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.countNode = TextNode()
self.countNode.isUserInteractionEnabled = false
self.countNode.contentMode = .left
self.countNode.contentsScale = UIScreen.main.scale
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.countNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
}
override func didLoad() {
super.didLoad()
if #available(iOS 13.0, *) {
self.backgroundNode.layer.cornerCurve = .continuous
}
}
func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeCountLayout = TextNode.asyncLayout(self.countNode)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode)
return { item, params, neighbors in
let leftInset: CGFloat = 32.0 + params.leftInset
let topInset: CGFloat = 2.0
let badgeHeight: CGFloat = 47.0
let titleSpacing: CGFloat = 19.0
let textSpacing: CGFloat = 15.0
let bottomInset: CGFloat = 2.0
let countAttributedText = NSAttributedString(string: "\(item.count)", font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []), textColor: .white)
let (countLayout, countApply) = makeCountLayout(TextNodeLayoutArguments(attributedString: countAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let titleAttributedText = NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let textColor = item.theme.list.freeTextColor
let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: textColor), linkAttribute: { _ in
return nil
}))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let contentSize = CGSize(width: params.width, height: topInset + badgeHeight + titleSpacing + titleLayout.size.height + textSpacing + textLayout.size.height + bottomInset)
let insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.accessibilityLabel = attributedText.string
if strongSelf.iconNode.image == nil {
let image: UIImage?
switch item.icon {
case .group:
image = UIImage(bundleImageName: "Premium/Group")
case .link:
image = UIImage(bundleImageName: "Premium/Link")
}
strongSelf.iconNode.image = generateTintedImage(image: image, color: .white)
}
let countBackgroundWidth: CGFloat = countLayout.size.width + 67.0
let countBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - countBackgroundWidth) / 2.0), y: topInset), size: CGSize(width: countBackgroundWidth, height: badgeHeight))
strongSelf.backgroundNode.frame = countBackgroundFrame
let _ = countApply()
strongSelf.countNode.frame = CGRect(origin: CGPoint(x: countBackgroundFrame.maxX - countLayout.size.width - 15.0, y: countBackgroundFrame.minY + floorToScreenPixels((countBackgroundFrame.height - countLayout.size.height) / 2.0)), size: countLayout.size)
if let image = strongSelf.iconNode.image {
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: countBackgroundFrame.minX + 18.0, y: countBackgroundFrame.minY + floorToScreenPixels((countBackgroundFrame.height - image.size.height) / 2.0)), size: image.size)
}
let _ = titleApply()
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleLayout.size.width) / 2.0), y: countBackgroundFrame.maxY + titleSpacing), size: titleLayout.size)
let _ = textApply()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: countBackgroundFrame.maxY + titleSpacing + titleLayout.size.height + textSpacing), size: textLayout.size)
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View file

@ -47,9 +47,9 @@ func localizedOldChannelDate(peer: InactiveChannel, strings: PresentationStrings
if let channel = peer.peer as? TelegramChannel, case .group = channel.info {
if let participantsCount = peer.participantsCount, participantsCount != 0 {
string = strings.OldChannels_GroupFormat(participantsCount) + string
string = strings.OldChannels_GroupFormat(participantsCount) + ", " + string
} else {
string = strings.OldChannels_GroupEmptyFormat + string
string = strings.OldChannels_GroupEmptyFormat + ", " + string
}
} else {
string = strings.OldChannels_ChannelFormat + string
@ -83,7 +83,7 @@ private enum OldChannelsEntryId: Hashable {
}
private enum OldChannelsEntry: ItemListNodeEntry {
case info(String, String)
case info(Int, String, String)
case peersHeader(String)
case peer(Int, InactiveChannel, Bool)
@ -109,8 +109,8 @@ private enum OldChannelsEntry: ItemListNodeEntry {
static func ==(lhs: OldChannelsEntry, rhs: OldChannelsEntry) -> Bool {
switch lhs {
case let .info(title, text):
if case .info(title, text) = rhs {
case let .info(count, title, text):
if case .info(count, title, text) = rhs {
return true
} else {
return false
@ -167,8 +167,8 @@ private enum OldChannelsEntry: ItemListNodeEntry {
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! OldChannelsItemArguments
switch self {
case let .info(title, text):
return ItemListInfoItem(presentationData: presentationData, title: title, text: .plain(text), style: .blocks, sectionId: self.section, closeAction: nil)
case let .info(count, title, text):
return IncreaseLimitHeaderItem(theme: presentationData.theme, icon: .group, count: count, title: title, text: text, sectionId: self.section)
case let .peersHeader(title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .peer(_, peer, selected):
@ -184,19 +184,10 @@ private struct OldChannelsState: Equatable {
var isSearching: Bool = false
}
private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] {
private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, limit: Int, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] {
var entries: [OldChannelsEntry] = []
let noticeText: String
switch intent {
case .join:
noticeText = presentationData.strings.OldChannels_NoticeText
case .create:
noticeText = presentationData.strings.OldChannels_NoticeCreateText
case .upgrade:
noticeText = presentationData.strings.OldChannels_NoticeUpgradeText
}
entries.append(.info(presentationData.strings.OldChannels_NoticeTitle, noticeText))
entries.append(.info(limit, presentationData.strings.OldChannels_TooManyCommunitiesTitle, presentationData.strings.OldChannels_TooManyCommunitiesText("\(limit)", "\(limit * 2)").string))
if let peers = peers, !peers.isEmpty {
entries.append(.peersHeader(presentationData.strings.OldChannels_ChannelsHeader))
@ -209,127 +200,6 @@ private func oldChannelsEntries(presentationData: PresentationData, state: OldCh
return entries
}
private final class OldChannelsActionPanelNode: ASDisplayNode {
private let separatorNode: ASDisplayNode
let buttonNode: SolidRoundedButtonNode
init(presentationData: ItemListPresentationData, leaveAction: @escaping () -> Void) {
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.buttonNode = SolidRoundedButtonNode(title: "", icon: nil, theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 11.0, gloss: false)
super.init()
self.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
self.addSubnode(self.separatorNode)
self.addSubnode(self.buttonNode)
self.buttonNode.pressed = {
leaveAction()
}
}
func updatePresentationData(_ presentationData: ItemListPresentationData) {
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
}
func updateLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
let sideInset: CGFloat = 16.0
let verticalInset: CGFloat = 16.0
let buttonHeight: CGFloat = 50.0
let insets = layout.insets(options: [.input])
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
let _ = self.buttonNode.updateLayout(width: layout.size.width - sideInset * 2.0, transition: transition)
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: CGSize(width: layout.size.width, height: buttonHeight)))
return buttonHeight + verticalInset * 2.0 + insets.bottom
}
}
private final class OldChannelsControllerImpl: ItemListController {
private let panelNode: OldChannelsActionPanelNode
private var displayPanel: Bool = false
private var validLayout: ContainerViewLayout?
private var presentationData: ItemListPresentationData
private var presentationDataDisposable: Disposable?
var leaveAction: (() -> Void)?
override init<ItemGenerationArguments>(presentationData: ItemListPresentationData, updatedPresentationData: Signal<ItemListPresentationData, NoError>, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>?) {
self.presentationData = presentationData
var leaveActionImpl: (() -> Void)?
self.panelNode = OldChannelsActionPanelNode(presentationData: presentationData, leaveAction: {
leaveActionImpl?()
})
super.init(presentationData: presentationData, updatedPresentationData: updatedPresentationData, state: state, tabBarItem: tabBarItem)
self.presentationDataDisposable = (updatedPresentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
guard let strongSelf = self else {
return
}
strongSelf.presentationData = presentationData
strongSelf.panelNode.updatePresentationData(presentationData)
})
leaveActionImpl = { [weak self] in
self?.leaveAction?()
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
override var navigationBarRequiresEntireLayoutUpdate: Bool {
return false
}
override func loadDisplayNode() {
super.loadDisplayNode()
self.displayNode.addSubnode(self.panelNode)
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
let panelHeight = self.panelNode.updateLayout(layout, transition: transition)
var additionalInsets = UIEdgeInsets()
additionalInsets.bottom = max(layout.intrinsicInsets.bottom, panelHeight)
self.additionalInsets = additionalInsets
super.containerLayoutUpdated(layout, transition: transition)
transition.updateFrame(node: self.panelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.displayPanel ? (layout.size.height - panelHeight) : layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)), beginWithCurrentState: true)
}
func updatePanelPeerCount(_ value: Int) {
self.panelNode.buttonNode.title = self.presentationData.strings.OldChannels_Leave(Int32(value))
if self.displayPanel != (value != 0) {
self.displayPanel = (value != 0)
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
}
}
}
}
public enum OldChannelsControllerIntent {
case join
@ -344,20 +214,19 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
let updateState: ((OldChannelsState) -> OldChannelsState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var updateSelectedPeersImpl: ((Int) -> Void)?
var dismissImpl: (() -> Void)?
var setDisplayNavigationBarImpl: ((Bool) -> Void)?
var ensurePeerVisibleImpl: ((PeerId) -> Void)?
var leaveActionImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
let arguments = OldChannelsItemArguments(
context: context,
togglePeer: { peerId, ensureVisible in
var selectedPeerCount = 0
var didSelect = false
updateState { state in
var state = state
@ -367,10 +236,8 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
state.selectedPeers.insert(peerId)
didSelect = true
}
selectedPeerCount = state.selectedPeers.count
return state
}
updateSelectedPeersImpl?(selectedPeerCount)
if didSelect && ensureVisible {
ensurePeerVisibleImpl?(peerId)
}
@ -436,7 +303,20 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false)
let buttonText: String
let colorful: Bool
if state.selectedPeers.count > 0 {
buttonText = presentationData.strings.OldChannels_LeaveCommunities(Int32(state.selectedPeers.count))
colorful = false
} else {
buttonText = presentationData.strings.OldChannels_IncreaseLimit
colorful = true
}
let footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: buttonText, colorful: colorful, action: {
leaveActionImpl?()
})
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, limit: 500, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, footerItem: footerItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false)
return (controllerState, (listState, arguments))
}
@ -444,14 +324,10 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
actionsDisposable.dispose()
}
let controller = OldChannelsControllerImpl(context: context, state: signal)
let controller = ItemListController(context: context, state: signal)
controller.navigationPresentation = .modal
updateSelectedPeersImpl = { [weak controller] value in
controller?.updatePanelPeerCount(value)
}
controller.leaveAction = {
leaveActionImpl = {
let state = stateValue.with { $0 }
let _ = (peersPromise.get()
|> take(1)

View file

@ -25,6 +25,11 @@ swift_library(
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
"//submodules/Components/BundleIconComponent:BundleIconComponent",
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",
],
visibility = [
"//visibility:public",

View file

@ -0,0 +1,754 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import ComponentFlow
import ViewControllerComponent
import MultilineTextComponent
import BundleIconComponent
import SolidRoundedButtonComponent
import Markdown
private final class LimitScreenComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let subject: LimitScreen.Subject
let proceed: () -> Void
init(context: AccountContext, subject: LimitScreen.Subject, proceed: @escaping () -> Void) {
self.context = context
self.subject = subject
self.proceed = proceed
}
static func ==(lhs: LimitScreenComponent, rhs: LimitScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.subject != rhs.subject {
return false
}
return true
}
final class State: ComponentState {
private let context: AccountContext
init(context: AccountContext) {
self.context = context
super.init()
}
}
func makeState() -> State {
return State(context: self.context)
}
static var body: Body {
let icon = Child(BundleIconComponent.self)
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let button = Child(SolidRoundedButtonComponent.self)
let cancel = Child(Button.self)
return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let component = context.component
let theme = environment.theme
let strings = environment.strings
let topInset: CGFloat = 34.0 + 38.0
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 24.0 + environment.safeInsets.left
let icon = icon.update(
component: BundleIconComponent(
name: "Premium/Tmp",
tintColor: nil
),
availableSize: CGSize(width: context.availableSize.width, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let title = title.update(
component: MultilineTextComponent(
text: NSAttributedString(string: "Limit Reached", font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = theme.actionSheet.secondaryTextColor
let string: String
switch component.subject {
case .chatsInFolder:
string = ""
case .folders:
string = ""
case .pins:
string = strings.DialogList_ExtendedPinLimitError("\(5)", "\(10)").string
}
let attributedText = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in
return nil
}))
let text = text.update(
component: MultilineTextComponent(
text: attributedText,
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.1
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
let button = button.update(
component: SolidRoundedButtonComponent(
title: "Increase Limit",
theme: SolidRoundedButtonComponent.Theme(
backgroundColor: .black,
backgroundColors: [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)],
foregroundColor: .white
),
font: .bold,
fontSize: 17.0,
height: 50.0,
cornerRadius: 10.0,
gloss: false,
iconName: "Premium/X2",
iconPosition: .right,
action: { [weak component] in
guard let component = component else {
return
}
component.proceed()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition
)
let cancel = cancel.update(component: Button(
content: AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)),
action: {
}
),
availableSize: context.availableSize,
transition: context.transition)
let width = context.availableSize.width
context.add(icon
.position(CGPoint(x: width / 2.0, y: 57.0))
)
context.add(title
.position(CGPoint(x: width / 2.0, y: topInset + 39.0))
)
context.add(text
.position(CGPoint(x: width / 2.0, y: topInset + 101.0))
)
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 76.0 + text.size.height + 27.0), size: button.size)
context.add(button
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
)
context.add(cancel
.position(CGPoint(x: width / 2.0, y: topInset + 76.0 + text.size.height + 20.0 + button.size.height + 40.0))
)
let contentSize = CGSize(width: context.availableSize.width, height: topInset + title.size.height + text.size.height)
return contentSize
}
}
}
public class LimitScreen: ViewController {
final class Node: ViewControllerTracingNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
private var presentationData: PresentationData
private weak var controller: LimitScreen?
private let component: AnyComponent<ViewControllerComponentContainer.Environment>
private let theme: PresentationTheme?
let dim: ASDisplayNode
let wrappingView: UIView
let containerView: UIView
let scrollView: UIScrollView
let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
private(set) var isExpanded = false
private var panGestureRecognizer: UIPanGestureRecognizer?
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)?
private var currentIsVisible: Bool = false
private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
fileprivate var temporaryDismiss = false
init(context: AccountContext, controller: LimitScreen, component: AnyComponent<ViewControllerComponentContainer.Environment>, theme: PresentationTheme?) {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.controller = controller
self.component = component
self.theme = theme
self.dim = ASDisplayNode()
self.dim.alpha = 0.0
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
self.wrappingView = UIView()
self.containerView = UIView()
self.scrollView = UIScrollView()
self.hostView = ComponentHostView()
super.init()
self.scrollView.delegate = self
self.scrollView.showsVerticalScrollIndicator = false
self.containerView.clipsToBounds = true
self.containerView.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
self.addSubnode(self.dim)
self.view.addSubview(self.wrappingView)
self.wrappingView.addSubview(self.containerView)
self.containerView.addSubview(self.scrollView)
self.scrollView.addSubview(self.hostView)
}
override func didLoad() {
super.didLoad()
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
panRecognizer.delegate = self
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
self.panGestureRecognizer = panRecognizer
self.wrappingView.addGestureRecognizer(panRecognizer)
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
}
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.controller?.dismiss(animated: true)
}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let (layout, _) = self.currentLayout {
if case .regular = layout.metrics.widthClass {
return false
}
}
return true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = self.scrollView.contentOffset.y
self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer {
return true
}
return false
}
private var isDismissing = false
func animateIn() {
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
let targetPosition = self.containerView.center
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height)
self.containerView.center = startPosition
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
transition.animateView(allowUserInteraction: true, {
self.containerView.center = targetPosition
}, completion: { _ in
})
}
func animateOut(completion: @escaping () -> Void = {}) {
self.isDismissing = true
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in
self?.controller?.dismiss(animated: false, completion: completion)
})
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
if !self.temporaryDismiss {
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) {
self.currentLayout = (layout, navigationHeight)
if let controller = self.controller, let navigationBar = controller.navigationBar, navigationBar.view.superview !== self.wrappingView {
self.containerView.addSubview(navigationBar.view)
}
self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0))
var effectiveExpanded = self.isExpanded
if case .regular = layout.metrics.widthClass {
effectiveExpanded = true
}
let isLandscape = layout.orientation == .landscape
let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset
let topInset: CGFloat
if let (panInitialTopInset, panOffset, _, _) = self.panGestureArguments {
if effectiveExpanded {
topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset))
} else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
}
} else {
topInset = effectiveExpanded ? 0.0 : edgeTopInset
}
transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size), completion: nil)
let modalProgress = isLandscape ? 0.0 : (1.0 - topInset / self.defaultTopInset)
self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition)
let clipFrame: CGRect
if layout.metrics.widthClass == .compact {
self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25)
if isLandscape {
self.containerView.layer.cornerRadius = 0.0
} else {
self.containerView.layer.cornerRadius = 10.0
}
if #available(iOS 11.0, *) {
if layout.safeInsets.bottom.isZero {
self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else {
self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
}
if isLandscape {
clipFrame = CGRect(origin: CGPoint(), size: layout.size)
} else {
let coveredByModalTransition: CGFloat = 0.0
var containerTopInset: CGFloat = 10.0
if let statusBarHeight = layout.statusBarHeight {
containerTopInset += statusBarHeight
}
let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset))
let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width
let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition
let maxScaledTopInset: CGFloat = containerTopInset - 10.0
let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height)
}
} else {
self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4)
self.containerView.layer.cornerRadius = 10.0
let verticalInset: CGFloat = 44.0
let maxSide = max(layout.size.width, layout.size.height)
let minSide = min(layout.size.width, layout.size.height)
let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0)
clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize)
}
transition.setFrame(view: self.containerView, frame: clipFrame)
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil)
let environment = ViewControllerComponentContainer.Environment(
statusBarHeight: 0.0,
navigationHeight: navigationHeight,
safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right),
isVisible: self.currentIsVisible,
theme: self.theme ?? self.presentationData.theme,
strings: self.presentationData.strings,
controller: { [weak self] in
return self?.controller
}
)
var contentSize = self.hostView.update(
transition: transition,
component: self.component,
environment: {
environment
},
forceUpdate: true,
containerSize: CGSize(width: clipFrame.size.width, height: 10000.0)
)
contentSize.height = max(layout.size.height - navigationHeight, contentSize.height)
transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil)
self.scrollView.contentSize = contentSize
}
private var didPlayAppearAnimation = false
func updateIsVisible(isVisible: Bool) {
if self.currentIsVisible == isVisible {
return
}
self.currentIsVisible = isVisible
guard let currentLayout = self.currentLayout else {
return
}
self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: .immediate)
if !self.didPlayAppearAnimation {
self.didPlayAppearAnimation = true
self.animateIn()
}
}
private var defaultTopInset: CGFloat {
return 390.0
// guard let (layout, _) = self.currentLayout else{
// return 210.0
// }
// if case .compact = layout.metrics.widthClass {
// var factor: CGFloat = 0.2488
// if layout.size.width <= 320.0 {
// factor = 0.15
// }
// return floor(max(layout.size.width, layout.size.height) * factor)
// } else {
// return 210.0
// }
}
private func findScrollView(view: UIView?) -> (UIScrollView, ListView?)? {
if let view = view {
if let view = view as? UIScrollView {
return (view, nil)
}
if let node = view.asyncdisplaykit_node as? ListView {
return (node.scroller, node)
}
return findScrollView(view: view.superview)
} else {
return nil
}
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
guard let (layout, navigationHeight) = self.currentLayout else {
return
}
let isLandscape = layout.orientation == .landscape
let edgeTopInset = isLandscape ? 0.0 : defaultTopInset
switch recognizer.state {
case .began:
let point = recognizer.location(in: self.view)
let currentHitView = self.hitTest(point, with: nil)
var scrollViewAndListNode = self.findScrollView(view: currentHitView)
if scrollViewAndListNode?.0.frame.height == self.frame.width {
scrollViewAndListNode = nil
}
let scrollView = scrollViewAndListNode?.0
let listNode = scrollViewAndListNode?.1
let topInset: CGFloat
if self.isExpanded {
topInset = 0.0
} else {
topInset = edgeTopInset
}
self.panGestureArguments = (topInset, 0.0, scrollView, listNode)
case .changed:
guard let (topInset, panOffset, scrollView, listNode) = self.panGestureArguments else {
return
}
let visibleContentOffset = listNode?.visibleContentOffset()
let contentOffset = scrollView?.contentOffset.y ?? 0.0
var translation = recognizer.translation(in: self.view).y
var currentOffset = topInset + translation
let epsilon = 1.0
if case let .known(value) = visibleContentOffset, value <= epsilon {
if let scrollView = scrollView {
scrollView.bounces = false
scrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: false)
}
} else if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon {
scrollView.bounces = false
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
} else if let scrollView = scrollView {
translation = panOffset
currentOffset = topInset + translation
if self.isExpanded {
recognizer.setTranslation(CGPoint(), in: self.view)
} else if currentOffset > 0.0 {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
}
}
self.panGestureArguments = (topInset, translation, scrollView, listNode)
if !self.isExpanded {
if currentOffset > 0.0, let scrollView = scrollView {
scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView)
}
}
var bounds = self.bounds
if self.isExpanded {
bounds.origin.y = -max(0.0, translation - edgeTopInset)
} else {
bounds.origin.y = -translation
}
bounds.origin.y = min(0.0, bounds.origin.y)
self.bounds = bounds
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
case .ended:
guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else {
return
}
self.panGestureArguments = nil
let visibleContentOffset = listNode?.visibleContentOffset()
let contentOffset = scrollView?.contentOffset.y ?? 0.0
let translation = recognizer.translation(in: self.view).y
var velocity = recognizer.velocity(in: self.view)
if self.isExpanded {
if case let .known(value) = visibleContentOffset, value > 0.1 {
velocity = CGPoint()
} else if case .unknown = visibleContentOffset {
velocity = CGPoint()
} else if contentOffset > 0.1 {
velocity = CGPoint()
}
}
var bounds = self.bounds
if self.isExpanded {
bounds.origin.y = -max(0.0, translation - edgeTopInset)
} else {
bounds.origin.y = -translation
}
bounds.origin.y = min(0.0, bounds.origin.y)
scrollView?.bounces = true
let offset = currentTopInset + panOffset
let topInset: CGFloat = edgeTopInset
var dismissing = false
if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 1800.0) {
self.controller?.dismiss(animated: true, completion: nil)
dismissing = true
} else if self.isExpanded {
if velocity.y > 300.0 || offset > topInset / 2.0 {
self.isExpanded = false
if let listNode = listNode {
listNode.scroller.setContentOffset(CGPoint(), animated: false)
} else if let scrollView = scrollView {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
}
let distance = topInset - offset
let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance)
let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition))
} else {
self.isExpanded = true
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut)))
}
} else if (velocity.y < -300.0 || offset < topInset / 2.0) {
if velocity.y > -2200.0 && velocity.y < -300.0, let listNode = listNode {
DispatchQueue.main.async {
listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
}
}
let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset)
let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
self.isExpanded = true
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition))
} else {
if let listNode = listNode {
listNode.scroller.setContentOffset(CGPoint(), animated: false)
} else if let scrollView = scrollView {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
}
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut)))
}
if !dismissing {
var bounds = self.bounds
let previousBounds = bounds
bounds.origin.y = 0.0
self.bounds = bounds
self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
}
case .cancelled:
self.panGestureArguments = nil
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut)))
default:
break
}
}
func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) {
guard isExpanded != self.isExpanded else {
return
}
self.isExpanded = isExpanded
guard let (layout, navigationHeight) = self.currentLayout else {
return
}
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition))
}
}
var node: Node {
return self.displayNode as! Node
}
private let context: AccountContext
private let theme: PresentationTheme?
private let component: AnyComponent<ViewControllerComponentContainer.Environment>
private var isInitiallyExpanded = false
private var currentLayout: ContainerViewLayout?
public var pushController: (ViewController) -> Void = { _ in }
public var presentController: (ViewController) -> Void = { _ in }
public enum Subject {
case folders
case chatsInFolder
case pins
}
public convenience init(context: AccountContext, subject: Subject) {
self.init(context: context, component: LimitScreenComponent(context: context, subject: subject, proceed: {}))
}
private init<C: Component>(context: AccountContext, component: C, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.context = context
self.component = AnyComponent(component)
self.theme = nil
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 }))
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func cancelPressed() {
self.dismiss(animated: true, completion: nil)
}
override open func loadDisplayNode() {
self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme)
self.displayNodeDidLoad()
}
public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
self.view.endEditing(true)
if flag {
self.node.animateOut(completion: {
super.dismiss(animated: false, completion: {})
completion?()
})
} else {
super.dismiss(animated: false, completion: {})
completion?()
}
}
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.node.updateIsVisible(isVisible: true)
}
override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.node.updateIsVisible(isVisible: false)
}
override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
var navigationLayout = self.navigationLayout(layout: layout)
var navigationFrame = navigationLayout.navigationFrame
var layout = layout
if case .regular = layout.metrics.widthClass {
let verticalInset: CGFloat = 44.0
let maxSide = max(layout.size.width, layout.size.height)
let minSide = min(layout.size.width, layout.size.height)
let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0)
let clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize)
navigationFrame.size.width = clipFrame.width
layout.size = clipFrame.size
}
navigationFrame.size.height = 56.0
navigationLayout.navigationFrame = navigationFrame
navigationLayout.defaultContentHeight = 56.0
layout.statusBarHeight = nil
self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition)
}
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.currentLayout = layout
super.containerLayoutUpdated(layout, transition: transition)
let navigationHeight: CGFloat = 56.0
self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition))
}
}

View file

@ -14,6 +14,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
private let context: AccountContext
private let theme: PresentationTheme
private let reactions: [AvailableReactions.Reaction]
private var itemContainerNodes: [ASDisplayNode] = []
private var itemNodes: [ReactionNode] = []
private let scrollNode: ASScrollNode
private let tapNode: ASDisplayNode
@ -21,9 +22,14 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
private var standaloneReactionAnimation: StandaloneReactionAnimation?
private var animator: DisplayLinkAnimator?
private var currentPosition: CGFloat = 0.0
private var currentIndex: Int = 0
private var validLayout: CGSize?
private var playingIndices = Set<Int>()
private let positionDelta: Double
init(context: AccountContext, theme: PresentationTheme, reactions: [AvailableReactions.Reaction]) {
self.context = context
self.theme = theme
@ -32,6 +38,8 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollNode = ASScrollNode()
self.tapNode = ASDisplayNode()
self.positionDelta = 1.0 / CGFloat(self.reactions.count)
super.init()
self.addSubnode(self.scrollNode)
@ -51,12 +59,12 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
}
@objc private func reactionTapped(_ gestureRecognizer: UITapGestureRecognizer) {
guard self.animator == nil, self.standaloneReactionAnimation == nil, self.scrollStartPosition == nil else {
guard self.animator == nil, self.scrollStartPosition == nil else {
return
}
let point = gestureRecognizer.location(in: self.view)
guard let index = self.itemNodes.firstIndex(where: { $0.frame.contains(point) }) else {
guard let index = self.itemContainerNodes.firstIndex(where: { $0.frame.contains(point) }) else {
return
}
@ -77,7 +85,8 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
guard index >= 0 && index < self.itemNodes.count else {
return
}
let delta = 1.0 / CGFloat(self.itemNodes.count)
self.currentIndex = index
let delta = self.positionDelta
let startPosition = self.currentPosition
let newPosition = delta * CGFloat(index)
@ -129,6 +138,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
let containerNode = ASDisplayNode()
let itemNode = ReactionNode(context: self.context, theme: self.theme, item: ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
@ -138,9 +148,11 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
), hasAppearAnimation: false)
itemNode.isUserInteractionEnabled = false
self.addSubnode(itemNode)
containerNode.isUserInteractionEnabled = false
containerNode.addSubnode(itemNode)
self.addSubnode(containerNode)
self.itemContainerNodes.append(containerNode)
self.itemNodes.append(itemNode)
}
}
@ -154,22 +166,17 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
}
func playReaction() {
guard self.standaloneReactionAnimation == nil else {
return
}
let delta = 1.0 / CGFloat(self.itemNodes.count)
let delta = self.positionDelta
let index = max(0, min(self.itemNodes.count - 1, Int(round(self.currentPosition / delta))))
let reaction = self.reactions[index]
let targetView = self.itemNodes[index].view
let standaloneReactionAnimation = StandaloneReactionAnimation()
self.standaloneReactionAnimation = standaloneReactionAnimation
guard let supernode = self.supernode else {
guard !self.playingIndices.contains(index) else {
return
}
let reaction = self.reactions[index]
let targetContainerNode = self.itemContainerNodes[index]
let targetView = self.itemNodes[index].view
guard let centerAnimation = reaction.centerAnimation else {
return
}
@ -177,10 +184,15 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
return
}
self.scrollNode.view.isScrollEnabled = false
self.playingIndices.insert(index)
supernode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = supernode.bounds
targetContainerNode.view.superview?.bringSubviewToFront(targetContainerNode.view)
let standaloneReactionAnimation = StandaloneReactionAnimation()
self.standaloneReactionAnimation = standaloneReactionAnimation
targetContainerNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = targetContainerNode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: self.context, theme: self.theme, reaction: ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
@ -200,7 +212,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
completion: { [weak standaloneReactionAnimation, weak self] in
standaloneReactionAnimation?.removeFromSupernode()
self?.standaloneReactionAnimation = nil
self?.scrollNode.view.isScrollEnabled = true
self?.playingIndices.remove(index)
}
)
}
@ -211,7 +223,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollStartPosition = (scrollView.contentOffset.x, self.currentPosition)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition) = self.scrollStartPosition else {
return
@ -227,6 +239,14 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
updatedPosition += 1.0
}
self.currentPosition = updatedPosition
let indexDelta = self.positionDelta
let index = max(0, min(self.itemNodes.count - 1, Int(round(self.currentPosition / indexDelta))))
if index != self.currentIndex {
self.currentIndex = index
print(index)
}
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
@ -237,7 +257,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
return
}
let delta = 1.0 / CGFloat(self.itemNodes.count)
let delta = self.positionDelta
let scrollDelta = targetContentOffset.pointee.x - startContentOffset
let positionDelta = scrollDelta * -0.001
let positionCounts = round(positionDelta / delta)
@ -251,7 +271,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
if !decelerate {
self.resetScrollPosition()
let delta = 1.0 / CGFloat(self.itemNodes.count)
let delta = self.positionDelta
let index = max(0, min(self.itemNodes.count - 1, Int(round(self.currentPosition / delta))))
self.scrollTo(index, playReaction: true, duration: 0.2)
}
@ -272,12 +292,14 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.resetScrollPosition()
}
let delta = 1.0 / CGFloat(self.itemNodes.count)
let delta = self.positionDelta
let areaSize = CGSize(width: floor(size.width * 0.7), height: size.height * 0.5)
var i = 0
for itemNode in self.itemNodes {
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
let containerNode = self.itemContainerNodes[i]
var angle = CGFloat.pi * 0.5 + CGFloat(i) * delta * CGFloat.pi * 2.0 - self.currentPosition * CGFloat.pi * 2.0
if angle < 0.0 {
angle = CGFloat.pi * 2.0 + angle
@ -303,12 +325,13 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
)
let itemFrame = CGRect(origin: CGPoint(x: size.width * 0.5 + point.x * areaSize.width * 0.5 - itemSize.width * 0.5, y: size.height * 0.5 + point.y * areaSize.height * 0.5 - itemSize.height * 0.5), size: itemSize)
itemNode.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
itemNode.position = itemFrame.center
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition)
transition.updateTransformScale(node: itemNode, scale: 1.0 - distance * 0.45)
containerNode.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
containerNode.position = itemFrame.center
transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.45)
itemNode.frame = CGRect(origin: CGPoint(), size: itemFrame.size)
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition)
i += 1
}
}
}

View file

@ -5,6 +5,6 @@ import TelegramPresentationData
public extension SolidRoundedButtonTheme {
convenience init(theme: PresentationTheme) {
self.init(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor)
self.init(backgroundColor: theme.list.itemCheckColors.fillColor, backgroundColors: [], foregroundColor: theme.list.itemCheckColors.foregroundColor)
}
}

View file

@ -299,6 +299,37 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
}
}
private func generatePremiumReactionIcon() -> UIImage? {
return generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let backgroundImage = UIImage(bundleImageName: "Premium/BackgroundIcon"), let foregroundImage = UIImage(bundleImageName: "Premium/ForegroundIcon") {
context.saveGState()
if let cgImage = backgroundImage.cgImage {
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
}
let colorsArray: [CGColor] = [
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xff7923).cgColor,
UIColor(rgb: 0xff7923).cgColor
]
var locations: [CGFloat] = [0.0, 0.15, 0.85, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
context.restoreGState()
if let cgImage = foregroundImage.cgImage {
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
}
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
})
}
final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
var isExtracted: Bool = false
@ -309,23 +340,11 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
self.imageNode.contentMode = .center
self.imageNode.displaysAsynchronously = false
self.imageNode.isUserInteractionEnabled = false
self.imageNode.alpha = 0.5
self.imageNode.image = generatePremiumReactionIcon()
super.init()
self.addSubnode(self.imageNode)
if theme.overallDarkAppearance {
self.imageNode.image = generateTintedImage(image: UIImage(bundleImageName: "Premium/BackgroundIcon"), color: .white)
} else {
self.imageNode.image = UIImage(bundleImageName: "Premium/BackgroundIcon")
}
}
override func didLoad() {
super.didLoad()
self.imageNode.layer.compositingFilter = "softLightBlendMode"
}
func appear(animated: Bool) {

View file

@ -19,12 +19,12 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter:
public final class SolidRoundedButtonTheme: Equatable {
public let backgroundColor: UIColor
public let gradientBackgroundColor: UIColor?
public let backgroundColors: [UIColor]
public let foregroundColor: UIColor
public init(backgroundColor: UIColor, gradientBackgroundColor: UIColor? = nil, foregroundColor: UIColor) {
public init(backgroundColor: UIColor, backgroundColors: [UIColor] = [], foregroundColor: UIColor) {
self.backgroundColor = backgroundColor
self.gradientBackgroundColor = gradientBackgroundColor
self.backgroundColors = backgroundColors
self.foregroundColor = foregroundColor
}
@ -32,7 +32,7 @@ public final class SolidRoundedButtonTheme: Equatable {
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.gradientBackgroundColor != rhs.gradientBackgroundColor {
if lhs.backgroundColors != rhs.backgroundColors {
return false
}
if lhs.foregroundColor != rhs.foregroundColor {
@ -47,13 +47,18 @@ public enum SolidRoundedButtonFont {
case regular
}
public enum SolidRoundedButtonIconPosition {
case left
case right
}
public final class SolidRoundedButtonNode: ASDisplayNode {
private var theme: SolidRoundedButtonTheme
private var font: SolidRoundedButtonFont
private var fontSize: CGFloat
private let gloss: Bool
private let buttonBackgroundNode: ASDisplayNode
private let buttonBackgroundNode: ASImageNode
private var shimmerView: ShimmerEffectForegroundView?
private var borderView: UIView?
@ -101,6 +106,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
}
}
}
public var iconPosition: SolidRoundedButtonIconPosition = .left {
didSet {
if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate)
}
}
}
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
self.theme = theme
@ -111,9 +124,21 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.title = title
self.gloss = gloss
self.buttonBackgroundNode = ASDisplayNode()
self.buttonBackgroundNode = ASImageNode()
self.buttonBackgroundNode.displaysAsynchronously = false
self.buttonBackgroundNode.clipsToBounds = true
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
if theme.backgroundColors.count > 1 {
self.buttonBackgroundNode.backgroundColor = nil
var locations: [CGFloat] = []
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
for i in 0 ..< theme.backgroundColors.count {
locations.append(delta * CGFloat(i))
}
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
} else {
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
}
self.buttonBackgroundNode.cornerRadius = cornerRadius
self.buttonNode = HighlightTrackingButtonNode()
@ -230,13 +255,34 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
borderShimmerView.layer.compositingFilter = compositingFilter
}
public func updateTheme(_ theme: SolidRoundedButtonTheme) {
public func updateTheme(_ theme: SolidRoundedButtonTheme, animated: Bool = false) {
guard theme !== self.theme else {
return
}
self.theme = theme
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
if animated {
if let snapshotView = self.buttonBackgroundNode.view.snapshotView(afterScreenUpdates: false) {
self.view.insertSubview(snapshotView, aboveSubview: self.buttonBackgroundNode.view)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
}
if theme.backgroundColors.count > 1 {
self.buttonBackgroundNode.backgroundColor = nil
var locations: [CGFloat] = []
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
for i in 0 ..< theme.backgroundColors.count {
locations.append(delta * CGFloat(i))
}
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
} else {
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
self.buttonBackgroundNode.image = nil
}
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor)
@ -284,6 +330,9 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
let iconSize = self.iconNode.image?.size ?? CGSize()
let titleSize = self.titleNode.updateLayout(buttonSize)
let spacingOffset: CGFloat = 9.0
let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset
let iconSpacing: CGFloat = self.iconSpacing
var contentWidth: CGFloat = titleSize.width
@ -291,15 +340,25 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
contentWidth += iconSize.width + iconSpacing
}
var nextContentOrigin = floor((buttonFrame.width - contentWidth) / 2.0)
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize))
if !iconSize.width.isZero {
nextContentOrigin += iconSize.width + iconSpacing
let iconFrame: CGRect
let titleFrame: CGRect
switch self.iconPosition {
case .left:
iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize)
if !iconSize.width.isZero {
nextContentOrigin += iconSize.width + iconSpacing
}
titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize)
case .right:
titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize)
if !iconSize.width.isZero {
nextContentOrigin += titleFrame.width + iconSpacing
}
iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize)
}
let spacingOffset: CGFloat = 9.0
let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset
let titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize)
transition.updateFrame(node: self.iconNode, frame: iconFrame)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
if self.subtitle != self.subtitleNode.attributedText?.string {
@ -374,7 +433,7 @@ public final class SolidRoundedButtonView: UIView {
private var font: SolidRoundedButtonFont
private var fontSize: CGFloat
private let buttonBackgroundNode: UIView
private let buttonBackgroundNode: UIImageView
private let buttonGlossView: SolidRoundedButtonGlossView?
private let buttonNode: HighlightTrackingButton
private let titleNode: ImmediateTextView
@ -418,6 +477,14 @@ public final class SolidRoundedButtonView: UIView {
}
}
public var iconPosition: SolidRoundedButtonIconPosition = .left {
didSet {
if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate)
}
}
}
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
self.theme = theme
self.font = font
@ -426,11 +493,23 @@ public final class SolidRoundedButtonView: UIView {
self.buttonCornerRadius = cornerRadius
self.title = title
self.buttonBackgroundNode = UIView()
self.buttonBackgroundNode = UIImageView()
self.buttonBackgroundNode.clipsToBounds = true
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
self.buttonBackgroundNode.layer.cornerRadius = cornerRadius
if theme.backgroundColors.count > 1 {
self.buttonBackgroundNode.backgroundColor = nil
var locations: [CGFloat] = []
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
for i in 0 ..< theme.backgroundColors.count {
locations.append(delta * CGFloat(i))
}
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
} else {
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
}
if gloss {
self.buttonGlossView = SolidRoundedButtonGlossView(color: theme.foregroundColor, cornerRadius: cornerRadius)
} else {
@ -545,7 +624,20 @@ public final class SolidRoundedButtonView: UIView {
}
self.theme = theme
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
if theme.backgroundColors.count > 1 {
self.buttonBackgroundNode.backgroundColor = nil
var locations: [CGFloat] = []
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
for i in 0 ..< theme.backgroundColors.count {
locations.append(delta * CGFloat(i))
}
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
} else {
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
self.buttonBackgroundNode.image = nil
}
self.buttonGlossView?.color = theme.foregroundColor
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor)
@ -579,6 +671,8 @@ public final class SolidRoundedButtonView: UIView {
let iconSize = self.iconNode.image?.size ?? CGSize()
let titleSize = self.titleNode.updateLayout(buttonSize)
let spacingOffset: CGFloat = 9.0
let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset
let iconSpacing: CGFloat = self.iconSpacing
var contentWidth: CGFloat = titleSize.width
@ -586,15 +680,25 @@ public final class SolidRoundedButtonView: UIView {
contentWidth += iconSize.width + iconSpacing
}
var nextContentOrigin = floor((buttonFrame.width - contentWidth) / 2.0)
transition.updateFrame(view: self.iconNode, frame: CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize))
if !iconSize.width.isZero {
nextContentOrigin += iconSize.width + iconSpacing
let iconFrame: CGRect
let titleFrame: CGRect
switch self.iconPosition {
case .left:
iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize)
if !iconSize.width.isZero {
nextContentOrigin += iconSize.width + iconSpacing
}
titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize)
case .right:
titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize)
if !iconSize.width.isZero {
nextContentOrigin += titleFrame.width + iconSpacing
}
iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize)
}
let spacingOffset: CGFloat = 9.0
let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset
let titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize)
transition.updateFrame(view: self.iconNode, frame: iconFrame)
transition.updateFrame(view: self.titleNode, frame: titleFrame)
if self.subtitle != self.subtitleNode.attributedText?.string {

View file

@ -12,7 +12,7 @@ func managedConfigurationUpdates(accountManager: AccountManager<TelegramAccountM
|> mapToSignal { result -> Signal<Void, NoError> in
return postbox.transaction { transaction -> Signal<Void, NoError> in
switch result {
case let .config(flags, _, _, _, _, dcOptions, _, chatSizeMax, megagroupSizeMax, forwardedCountMax, _, _, _, _, _, _, _, _, savedGifsLimit, editTimeLimit, revokeTimeLimit, revokePmTimeLimit, _, stickersRecentLimit, _, _, _, pinnedDialogsCountMax, pinnedInfolderCountMax, _, _, _, _, _, autoupdateUrlPrefix, gifSearchUsername, venueSearchUsername, imgSearchUsername, _, captionLengthMax, _, webfileDcId, suggestedLangCode, langPackVersion, baseLangPackVersion):
case let .config(flags, _, _, _, _, dcOptions, _, chatSizeMax, megagroupSizeMax, forwardedCountMax, _, _, _, _, _, _, _, _, savedGifsLimit, editTimeLimit, revokeTimeLimit, revokePmTimeLimit, _, stickersRecentLimit, stickersFavedLimit, _, _, pinnedDialogsCountMax, pinnedInfolderCountMax, _, _, _, _, _, autoupdateUrlPrefix, gifSearchUsername, venueSearchUsername, imgSearchUsername, _, captionLengthMax, _, webfileDcId, suggestedLangCode, langPackVersion, baseLangPackVersion):
var addressList: [Int: [MTDatacenterAddress]] = [:]
for option in dcOptions {
switch option {
@ -59,7 +59,7 @@ func managedConfigurationUpdates(accountManager: AccountManager<TelegramAccountM
return entry
})
updateLimitsConfiguration(transaction: transaction, configuration: LimitsConfiguration(maxPinnedChatCount: pinnedDialogsCountMax, maxArchivedPinnedChatCount: pinnedInfolderCountMax, maxGroupMemberCount: chatSizeMax, maxSupergroupMemberCount: megagroupSizeMax, maxMessageForwardBatchSize: forwardedCountMax, maxSavedGifCount: savedGifsLimit, maxRecentStickerCount: stickersRecentLimit, maxMessageEditingInterval: editTimeLimit, maxMediaCaptionLength: captionLengthMax, canRemoveIncomingMessagesInPrivateChats: (flags & (1 << 6)) != 0, maxMessageRevokeInterval: revokeTimeLimit, maxMessageRevokeIntervalInPrivateChats: revokePmTimeLimit))
updateLimitsConfiguration(transaction: transaction, configuration: LimitsConfiguration(maxPinnedChatCount: pinnedDialogsCountMax, maxArchivedPinnedChatCount: pinnedInfolderCountMax, maxGroupMemberCount: chatSizeMax, maxSupergroupMemberCount: megagroupSizeMax, maxMessageForwardBatchSize: forwardedCountMax, maxSavedGifCount: savedGifsLimit, maxRecentStickerCount: stickersRecentLimit, maxFavedStickerCount: stickersFavedLimit, maxMessageEditingInterval: editTimeLimit, maxMediaCaptionLength: captionLengthMax, canRemoveIncomingMessagesInPrivateChats: (flags & (1 << 6)) != 0, maxMessageRevokeInterval: revokeTimeLimit, maxMessageRevokeIntervalInPrivateChats: revokePmTimeLimit))
updateSearchBotsConfiguration(transaction: transaction, configuration: SearchBotsConfiguration(imageBotUsername: imgSearchUsername, gifBotUsername: gifSearchUsername, venueBotUsername: venueSearchUsername))

View file

@ -10,6 +10,7 @@ public struct LimitsConfiguration: Codable, Equatable {
public var maxMessageForwardBatchSize: Int32
public var maxSavedGifCount: Int32
public var maxRecentStickerCount: Int32
public var maxFavedStickerCount: Int32
public var maxMessageEditingInterval: Int32
public var maxMediaCaptionLength: Int32
public var canRemoveIncomingMessagesInPrivateChats: Bool
@ -17,10 +18,10 @@ public struct LimitsConfiguration: Codable, Equatable {
public var maxMessageRevokeIntervalInPrivateChats: Int32
public static var defaultValue: LimitsConfiguration {
return LimitsConfiguration(maxPinnedChatCount: 5, maxArchivedPinnedChatCount: 20, maxGroupMemberCount: 200, maxSupergroupMemberCount: 200000, maxMessageForwardBatchSize: 50, maxSavedGifCount: 200, maxRecentStickerCount: 20, maxMessageEditingInterval: 2 * 24 * 60 * 60, maxMediaCaptionLength: 1000, canRemoveIncomingMessagesInPrivateChats: false, maxMessageRevokeInterval: 2 * 24 * 60 * 60, maxMessageRevokeIntervalInPrivateChats: 2 * 24 * 60 * 60)
return LimitsConfiguration(maxPinnedChatCount: 5, maxArchivedPinnedChatCount: 20, maxGroupMemberCount: 200, maxSupergroupMemberCount: 200000, maxMessageForwardBatchSize: 50, maxSavedGifCount: 200, maxRecentStickerCount: 20, maxFavedStickerCount: 5, maxMessageEditingInterval: 2 * 24 * 60 * 60, maxMediaCaptionLength: 1000, canRemoveIncomingMessagesInPrivateChats: false, maxMessageRevokeInterval: 2 * 24 * 60 * 60, maxMessageRevokeIntervalInPrivateChats: 2 * 24 * 60 * 60)
}
public init(maxPinnedChatCount: Int32, maxArchivedPinnedChatCount: Int32, maxGroupMemberCount: Int32, maxSupergroupMemberCount: Int32, maxMessageForwardBatchSize: Int32, maxSavedGifCount: Int32, maxRecentStickerCount: Int32, maxMessageEditingInterval: Int32, maxMediaCaptionLength: Int32, canRemoveIncomingMessagesInPrivateChats: Bool, maxMessageRevokeInterval: Int32, maxMessageRevokeIntervalInPrivateChats: Int32) {
public init(maxPinnedChatCount: Int32, maxArchivedPinnedChatCount: Int32, maxGroupMemberCount: Int32, maxSupergroupMemberCount: Int32, maxMessageForwardBatchSize: Int32, maxSavedGifCount: Int32, maxRecentStickerCount: Int32, maxFavedStickerCount: Int32, maxMessageEditingInterval: Int32, maxMediaCaptionLength: Int32, canRemoveIncomingMessagesInPrivateChats: Bool, maxMessageRevokeInterval: Int32, maxMessageRevokeIntervalInPrivateChats: Int32) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
self.maxGroupMemberCount = maxGroupMemberCount
@ -28,6 +29,7 @@ public struct LimitsConfiguration: Codable, Equatable {
self.maxMessageForwardBatchSize = maxMessageForwardBatchSize
self.maxSavedGifCount = maxSavedGifCount
self.maxRecentStickerCount = maxRecentStickerCount
self.maxFavedStickerCount = maxFavedStickerCount
self.maxMessageEditingInterval = maxMessageEditingInterval
self.maxMediaCaptionLength = maxMediaCaptionLength
self.canRemoveIncomingMessagesInPrivateChats = canRemoveIncomingMessagesInPrivateChats
@ -45,6 +47,7 @@ public struct LimitsConfiguration: Codable, Equatable {
self.maxMessageForwardBatchSize = (try? container.decodeIfPresent(Int32.self, forKey: "maxMessageForwardBatchSize")) ?? 50
self.maxSavedGifCount = (try? container.decodeIfPresent(Int32.self, forKey: "maxSavedGifCount")) ?? 200
self.maxRecentStickerCount = (try? container.decodeIfPresent(Int32.self, forKey: "maxRecentStickerCount")) ?? 20
self.maxFavedStickerCount = (try? container.decodeIfPresent(Int32.self, forKey: "maxFavedStickerCount")) ?? 5
self.maxMessageEditingInterval = (try? container.decodeIfPresent(Int32.self, forKey: "maxMessageEditingInterval")) ?? (2 * 24 * 60 * 60)
self.maxMediaCaptionLength = (try? container.decodeIfPresent(Int32.self, forKey: "maxMediaCaptionLength")) ?? 1000
self.canRemoveIncomingMessagesInPrivateChats = (try? container.decodeIfPresent(Int32.self, forKey: "canRemoveIncomingMessagesInPrivateChats") ?? 0) != 0
@ -62,6 +65,7 @@ public struct LimitsConfiguration: Codable, Equatable {
try container.encode(self.maxMessageForwardBatchSize, forKey: "maxMessageForwardBatchSize")
try container.encode(self.maxSavedGifCount, forKey: "maxSavedGifCount")
try container.encode(self.maxRecentStickerCount, forKey: "maxRecentStickerCount")
try container.encode(self.maxFavedStickerCount, forKey: "maxFavedStickerCount")
try container.encode(self.maxMessageEditingInterval, forKey: "maxMessageEditingInterval")
try container.encode(self.maxMediaCaptionLength, forKey: "maxMediaCaptionLength")
try container.encode((self.canRemoveIncomingMessagesInPrivateChats ? 1 : 0) as Int32, forKey: "canRemoveIncomingMessagesInPrivateChats")

View file

@ -300,10 +300,10 @@ public struct PresentationResourcesChat {
}
let colorsArray: [CGColor] = [
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xff7923).cgColor,
UIColor(rgb: 0xff7923).cgColor
]
var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
@ -325,12 +325,12 @@ public struct PresentationResourcesChat {
}
let colorsArray: [CGColor] = [
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xff7923).cgColor,
UIColor(rgb: 0xff7923).cgColor
]
var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0]
var locations: [CGFloat] = [0.0, 0.15, 0.85, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())

View file

@ -238,10 +238,10 @@ public struct PresentationResourcesChatList {
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
let colorsArray: [CGColor] = [
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xff7923).cgColor,
UIColor(rgb: 0xff7923).cgColor
]
var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!

View file

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

View file

@ -0,0 +1,76 @@
%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 2.000000 0.864380 cm
0.000000 0.000000 0.000000 scn
20.000000 11.044711 m
20.000000 16.065481 15.522848 20.135620 10.000000 20.135620 c
4.477152 20.135620 0.000000 16.065481 0.000000 11.044711 c
0.000000 8.181209 1.337573 5.834022 3.613619 4.167677 c
3.904685 3.954580 4.172771 2.770550 3.523984 1.775995 c
2.875197 0.781441 2.066323 0.326941 2.471971 0.156790 c
2.722059 0.051889 4.199766 -0.000002 5.266314 0.598131 c
6.791368 1.453400 7.217727 2.304844 7.545889 2.229574 c
8.331102 2.049473 9.153261 1.953802 10.000000 1.953802 c
15.522848 1.953802 20.000000 6.023941 20.000000 11.044711 c
h
f
n
Q
endstream
endobj
3 0 obj
668
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
0000000758 00000 n
0000000780 00000 n
0000000953 00000 n
0000001027 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1086
%%EOF

View file

@ -0,0 +1,11 @@
{
"images" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

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

View file

@ -0,0 +1,108 @@
%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 2.500000 3.500000 cm
0.000000 0.000000 0.000000 scn
0.326980 15.361972 m
0.000000 14.720236 0.000000 13.880157 0.000000 12.199999 c
0.000000 12.130000 l
0.000000 11.849974 0.000000 11.709961 0.054497 11.603004 c
0.102433 11.508924 0.178924 11.432433 0.273005 11.384497 c
0.379961 11.330000 0.519973 11.330000 0.799999 11.330000 c
18.266001 11.330000 l
18.483961 11.330000 18.592939 11.330000 18.679672 11.363398 c
18.811571 11.414187 18.915813 11.518429 18.966602 11.650328 c
19.000000 11.737060 19.000000 11.846040 19.000000 12.064000 c
19.000000 12.935841 19.000000 13.371761 18.866409 13.718690 c
18.663250 14.246285 18.246286 14.663251 17.718691 14.866409 c
17.371761 15.000000 16.935841 15.000000 16.064001 15.000000 c
10.414214 15.000000 l
10.245598 15.000000 10.161290 15.000000 10.080059 15.004409 c
9.397568 15.041451 8.748186 15.310432 8.239400 15.766834 c
8.178847 15.821153 8.119237 15.880764 8.000020 15.999980 c
8.000000 16.000000 l
7.880771 16.119228 7.821156 16.178844 7.760600 16.233166 c
7.251813 16.689568 6.602432 16.958549 5.919941 16.995592 c
5.838710 17.000000 5.754402 17.000000 5.585786 17.000000 c
4.800000 17.000000 l
3.119843 17.000000 2.279764 17.000000 1.638029 16.673019 c
1.073542 16.385399 0.614601 15.926457 0.326980 15.361972 c
h
0.163490 9.180986 m
0.000000 8.860118 0.000000 8.440079 0.000000 7.600000 c
0.000000 6.400000 l
0.000000 4.159790 0.000000 3.039685 0.435974 2.184038 c
0.819467 1.431390 1.431390 0.819468 2.184038 0.435974 c
3.039685 0.000000 4.159790 0.000000 6.399999 0.000000 c
12.599999 0.000000 l
14.840210 0.000000 15.960315 0.000000 16.815962 0.435974 c
17.568609 0.819468 18.180532 1.431390 18.564026 2.184038 c
19.000000 3.039685 19.000000 4.159790 19.000000 6.400001 c
19.000000 7.600000 l
19.000000 8.440079 19.000000 8.860118 18.836510 9.180986 c
18.692699 9.463228 18.463228 9.692699 18.180986 9.836510 c
17.860119 10.000000 17.440079 10.000000 16.600000 10.000000 c
2.400000 10.000000 l
1.559921 10.000000 1.139882 10.000000 0.819014 9.836510 c
0.536771 9.692699 0.307300 9.463228 0.163490 9.180986 c
h
f*
n
Q
endstream
endobj
3 0 obj
2139
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
0000002229 00000 n
0000002252 00000 n
0000002425 00000 n
0000002499 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2558
%%EOF

View file

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

View file

@ -0,0 +1,96 @@
%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 1.000000 3.500000 cm
0.000000 0.000000 0.000000 scn
7.798214 10.500000 m
9.731211 10.500000 11.298214 12.067003 11.298214 14.000000 c
11.298214 15.932997 9.731211 17.500000 7.798214 17.500000 c
5.865218 17.500000 4.298214 15.932997 4.298214 14.000000 c
4.298214 12.067003 5.865218 10.500000 7.798214 10.500000 c
h
15.800003 10.500000 m
17.456858 10.500000 18.800003 11.843145 18.800003 13.500000 c
18.800003 15.156855 17.456858 16.500000 15.800003 16.500000 c
14.143148 16.500000 12.800003 15.156855 12.800003 13.500000 c
12.800003 11.843145 14.143148 10.500000 15.800003 10.500000 c
h
12.332236 6.972357 m
13.807444 6.168993 14.733925 4.998592 15.315786 3.845357 c
15.642039 3.198733 15.667471 2.562775 15.476417 1.999998 c
15.084723 0.846209 13.783087 0.000000 12.298215 0.000000 c
3.298214 0.000000 l
1.089075 0.000000 -0.714483 1.873044 0.280643 3.845357 c
1.315064 5.895553 3.438652 8.000000 7.798214 8.000000 c
9.529486 8.000000 10.908134 7.668119 12.005980 7.139552 c
12.117610 7.085807 12.226336 7.030027 12.332236 6.972357 c
h
13.528140 7.807037 m
14.969814 6.871095 15.903218 5.633628 16.503208 4.444468 c
16.923948 3.610569 17.012365 2.769079 16.852047 1.999998 c
19.298889 1.999998 l
20.955744 1.999998 22.306608 3.401897 21.573879 4.887923 c
20.792742 6.472124 19.169998 8.117645 15.798890 8.117645 c
14.925295 8.117645 14.169111 8.007141 13.514557 7.815837 c
13.528140 7.807037 l
h
f*
n
Q
endstream
endobj
3 0 obj
1470
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
0000001560 00000 n
0000001583 00000 n
0000001756 00000 n
0000001830 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1889
%%EOF

View file

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

View file

@ -0,0 +1,101 @@
%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 1.760010 0.617157 cm
0.000000 0.000000 0.000000 scn
11.862618 18.488392 m
13.375206 20.000980 15.827595 20.000980 17.340183 18.488392 c
18.852770 16.975805 18.852770 14.523417 17.340183 13.010829 c
15.042923 10.713570 l
13.530336 9.200982 11.077947 9.200982 9.565359 10.713570 c
9.363936 10.914992 9.189997 11.132182 9.042994 11.360771 c
8.744267 11.825293 8.125531 11.959696 7.661010 11.660969 c
7.196488 11.362242 7.062085 10.743505 7.360812 10.278984 c
7.584530 9.931103 7.848157 9.602345 8.151146 9.299356 c
10.444782 7.005720 14.163501 7.005720 16.457138 9.299356 c
18.754395 11.596616 l
21.048031 13.890251 21.048031 17.608971 18.754395 19.902607 c
16.460758 22.196241 12.742041 22.196241 10.448404 19.902607 c
8.151146 17.605347 l
7.760622 17.214823 7.760622 16.581657 8.151146 16.191133 c
8.541670 15.800610 9.174835 15.800610 9.565359 16.191133 c
11.862618 18.488392 l
h
8.612004 4.281246 m
7.099417 2.768658 4.647028 2.768658 3.134441 4.281246 c
1.621853 5.793833 1.621853 8.246222 3.134441 9.758809 c
5.431700 12.056068 l
6.944287 13.568655 9.396676 13.568655 10.909264 12.056068 c
11.110686 11.854645 11.284627 11.637455 11.431628 11.408867 c
11.730356 10.944345 12.349092 10.809942 12.813613 11.108670 c
13.278135 11.407397 13.412538 12.026133 13.113811 12.490655 c
12.890092 12.838536 12.626466 13.167293 12.323477 13.470282 c
10.029840 15.763918 6.311122 15.763918 4.017486 13.470282 c
1.720227 11.173022 l
-0.573409 8.879387 -0.573409 5.160667 1.720227 2.867031 c
4.013863 0.573397 7.732582 0.573397 10.026217 2.867031 c
12.323477 5.164291 l
12.714001 5.554815 12.714001 6.187981 12.323477 6.578505 c
11.932953 6.969028 11.299788 6.969028 10.909264 6.578505 c
8.612004 4.281246 l
h
f*
n
Q
endstream
endobj
3 0 obj
1772
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
0000001862 00000 n
0000001885 00000 n
0000002058 00000 n
0000002132 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2191
%%EOF

View file

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

View file

@ -0,0 +1,108 @@
%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 2.398682 1.870453 cm
0.000000 0.000000 0.000000 scn
10.791250 19.146320 m
10.723915 18.838600 10.918332 18.158140 11.307166 16.797222 c
11.307172 16.797207 l
11.385918 16.521591 11.425291 16.383781 11.434118 16.251194 c
11.453884 15.954321 11.367108 15.660133 11.189390 15.421513 c
11.110017 15.314939 11.002151 15.220556 10.786422 15.031794 c
9.300981 13.732033 l
9.153894 13.603332 9.080351 13.538980 9.000536 13.488085 c
8.840524 13.386050 8.659379 13.321789 8.470838 13.300173 c
8.376793 13.289391 8.279138 13.293007 8.083828 13.300241 c
5.466913 13.397163 l
5.466902 13.397163 l
3.994872 13.451683 3.258856 13.478943 2.872828 13.296442 c
2.081267 12.922220 1.688092 12.020587 1.952483 11.185896 c
2.081421 10.778834 2.602221 10.258035 3.643819 9.216436 c
3.643824 9.216431 l
5.596159 7.264095 l
0.292893 1.960831 l
-0.097631 1.570305 -0.097631 0.937141 0.292893 0.546616 c
0.683418 0.156092 1.316582 0.156092 1.707107 0.546616 c
7.010372 5.849882 l
8.962662 3.897593 l
10.004263 2.855991 10.525064 2.335190 10.932127 2.206251 c
11.766818 1.941860 12.668451 2.335035 13.042673 3.126596 c
13.225174 3.512627 13.197914 4.248645 13.143394 5.720681 c
13.046472 8.337596 l
13.046472 8.337616 l
13.039238 8.532912 13.035621 8.630565 13.046403 8.724607 c
13.068019 8.913148 13.132281 9.094293 13.234317 9.254305 c
13.285213 9.334120 13.349563 9.407663 13.478264 9.554749 c
14.778025 11.040191 l
14.778039 11.040205 l
14.966792 11.255926 15.061172 11.363788 15.167744 11.443159 c
15.406365 11.620877 15.700552 11.707653 15.997424 11.687887 c
16.130014 11.679060 16.267828 11.639685 16.543453 11.560935 c
17.904371 11.172101 18.584831 10.977684 18.892551 11.045019 c
19.603607 11.200610 20.031862 11.928437 19.822556 12.625574 c
19.731974 12.927271 19.231562 13.427683 18.230738 14.428506 c
14.174737 18.484509 l
14.174723 18.484522 l
13.173909 19.485336 12.673501 19.985743 12.371805 20.076324 c
11.674668 20.285631 10.946841 19.857376 10.791250 19.146320 c
h
f*
n
Q
endstream
endobj
3 0 obj
2030
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
0000002120 00000 n
0000002143 00000 n
0000002316 00000 n
0000002390 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2449
%%EOF

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Tmp.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Tmp2.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

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

View file

@ -0,0 +1,129 @@
%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 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.435974 15.815962 m
0.000000 14.960315 0.000000 13.840210 0.000000 11.600000 c
0.000000 6.400001 l
0.000000 4.159790 0.000000 3.039685 0.435974 2.184038 c
0.819467 1.431389 1.431390 0.819468 2.184038 0.435974 c
3.039685 0.000000 4.159790 0.000000 6.400000 0.000000 c
17.600000 0.000000 l
19.840210 0.000000 20.960316 0.000000 21.815962 0.435974 c
22.568611 0.819468 23.180532 1.431389 23.564026 2.184038 c
24.000000 3.039685 24.000000 4.159790 24.000000 6.400000 c
24.000000 11.600000 l
24.000000 13.840210 24.000000 14.960315 23.564026 15.815962 c
23.180532 16.568611 22.568611 17.180532 21.815962 17.564026 c
20.960316 18.000000 19.840210 18.000000 17.600000 18.000000 c
6.400001 18.000000 l
4.159790 18.000000 3.039685 18.000000 2.184038 17.564026 c
1.431390 17.180532 0.819467 16.568611 0.435974 15.815962 c
h
5.505859 4.375977 m
5.246094 4.000000 5.027344 3.870117 4.637695 3.870117 c
4.090820 3.870117 3.660156 4.266602 3.660156 4.765625 c
3.660156 5.018555 3.742188 5.237305 3.940430 5.510742 c
6.401367 8.942383 l
6.401367 8.997070 l
4.022461 12.237305 l
3.796875 12.538086 3.721680 12.763672 3.721680 13.030273 c
3.721680 13.590820 4.166016 14.000977 4.767578 14.000977 c
5.157227 14.000977 5.430664 13.830078 5.697266 13.426758 c
7.809570 10.309570 l
7.864258 10.309570 l
10.044922 13.495117 l
10.284180 13.850586 10.489258 14.000977 10.885742 14.000977 c
11.425781 14.000977 11.890625 13.611328 11.890625 13.119141 c
11.890625 12.852539 11.815430 12.633789 11.617188 12.380859 c
9.053711 8.956055 l
9.053711 8.908203 l
11.541992 5.551758 l
11.733398 5.305664 11.815430 5.073242 11.815430 4.799805 c
11.815430 4.259766 11.398438 3.870117 10.824219 3.870117 c
10.448242 3.870117 10.195312 4.020508 9.928711 4.382812 c
7.727539 7.452148 l
7.672852 7.452148 l
5.505859 4.375977 l
h
19.690430 4.000000 m
14.228516 4.000000 l
13.579102 4.000000 13.257812 4.362305 13.257812 4.881836 c
13.257812 5.271484 13.408203 5.531250 13.825195 5.914062 c
16.764648 8.723633 l
17.981445 9.892578 18.295898 10.371094 18.295898 11.082031 c
18.295898 11.895508 17.666992 12.476562 16.785156 12.476562 c
15.992188 12.476562 15.445312 12.080078 15.089844 11.287109 c
14.857422 10.876953 14.618164 10.685547 14.173828 10.685547 c
13.620117 10.685547 13.291992 11.013672 13.291992 11.526367 c
13.291992 11.683594 13.319336 11.827148 13.367188 11.970703 c
13.702148 13.064453 14.939453 14.083008 16.805664 14.083008 c
18.897461 14.083008 20.305664 12.907227 20.305664 11.211914 c
20.305664 10.008789 19.717773 9.256836 18.104492 7.732422 c
15.971680 5.681641 l
15.971680 5.640625 l
19.690430 5.640625 l
20.223633 5.640625 20.544922 5.319336 20.544922 4.820312 c
20.544922 4.334961 20.223633 4.000000 19.690430 4.000000 c
h
f*
n
Q
endstream
endobj
3 0 obj
2835
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 18.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
0000002925 00000 n
0000002948 00000 n
0000003121 00000 n
0000003195 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3254
%%EOF

View file

@ -3358,7 +3358,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(false))).start(next: { [weak self] responded in
if let strongSelf = self {
if !responded {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(EnginePeer(peer).compactDisplayTitle).string), elevatedLayout: false, action: { _ in return false }), in: .current)
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(EnginePeer(peer).compactDisplayTitle).string), elevatedLayout: false, action: { _ in return false }), in: .current)
let _ = ApplicationSpecificNotice.incrementInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start()
}
@ -7945,7 +7945,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|> switchToLatest
|> deliverOnMainQueue).start(next: { [weak self] added in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil)
// strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: added ? "The Limit of 5 Stickers Reached" : nil, text: added ? "An older sticker was replaced with this one. You can [increase the limit]() to 10 stickers." : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil)
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: nil, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil)
}
})
}

View file

@ -618,9 +618,39 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if let starStatus = data.starStatus {
actions.append(.action(ContextMenuActionItem(text: starStatus ? chatPresentationInterfaceState.strings.Stickers_RemoveFromFavorites : chatPresentationInterfaceState.strings.Stickers_AddToFavorites, icon: { theme in
return generateTintedImage(image: starStatus ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
interfaceInteraction.toggleMessageStickerStarred(messages[0].id)
f(.default)
}, action: { c, f in
// interfaceInteraction.toggleMessageStickerStarred(messages[0].id)
var subItems: [ContextMenuItem] = []
subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
c.popItems()
})))
subItems.append(.separator)
subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Stickers_FaveLimitReachedInfo("5", "10").string, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in
return nil
}, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?)))
subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Stickers_FaveLimitReplaceOlder, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
interfaceInteraction.toggleMessageStickerStarred(messages[0].id)
})))
subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Stickers_FaveLimitIncrease, icon: { _ in
return UIImage(bundleImageName: "Premium/Tmp2")
}, action: { _, f in
f(.default)
})))
c.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
//
// f(.default)
})))
}

View file

@ -1087,7 +1087,7 @@ final class ChatMediaInputNode: ChatInputNode {
return false
}
})
strongSelf.controllerInteraction.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
strongSelf.controllerInteraction.presentController(controller, nil)
}, getItemIsPreviewed: { item in
return getItemIsPreviewedImpl?(item) ?? false
}, openSearch: {

View file

@ -2306,10 +2306,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
let colorsArray: [CGColor] = [
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0x418eff).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor,
UIColor(rgb: 0xfc7ebd).cgColor
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xa34ecf).cgColor,
UIColor(rgb: 0xff7923).cgColor,
UIColor(rgb: 0xff7923).cgColor
]
var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!

View file

@ -16,8 +16,6 @@ import DatePickerNode
import DebugSettingsUI
import TabBarUI
import TelegramCallsUI
public final class TelegramRootController: NavigationController {
private let context: AccountContext

View file

@ -34,7 +34,7 @@ public enum UndoOverlayContent {
case voiceChatRecording(text: String)
case voiceChatFlag(text: String)
case voiceChatCanSpeak(text: String)
case sticker(context: AccountContext, file: TelegramMediaFile, text: String)
case sticker(context: AccountContext, file: TelegramMediaFile, title: String?, text: String)
case copy(text: String)
case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String)

View file

@ -408,7 +408,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = undo
@ -626,7 +627,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false
self.originalRemainingSeconds = 3
case let .sticker(context, file, text):
case let .sticker(context, file, title, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
@ -680,10 +681,19 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
updatedImageSignal = .single({ _ in return nil })
updatedFetchSignal = .complete()
}
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2