mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
7063b84dcf
commit
93b84ebf24
47 changed files with 2320 additions and 288 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ swift_library(
|
|||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||
"//submodules/Components/ProgressIndicatorComponent:ProgressIndicatorComponent",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ swift_library(
|
|||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
|
||||
"//submodules/Markdown:Markdown",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
141
submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift
Normal file
141
submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
201
submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift
Normal file
201
submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
754
submodules/PremiumUI/Sources/LimitScreen.swift
Normal file
754
submodules/PremiumUI/Sources/LimitScreen.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)!
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Premium/Chat.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Chat.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "badgechat_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
76
submodules/TelegramUI/Images.xcassets/Premium/Chat.imageset/badgechat_24.pdf
vendored
Normal file
76
submodules/TelegramUI/Images.xcassets/Premium/Chat.imageset/badgechat_24.pdf
vendored
Normal 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
|
||||
11
submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json
vendored
Normal file
11
submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "badgefolder_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
108
submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/badgefolder_24.pdf
vendored
Normal file
108
submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/badgefolder_24.pdf
vendored
Normal 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
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "badgegroup_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
96
submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/badgegroup_24.pdf
vendored
Normal file
96
submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/badgegroup_24.pdf
vendored
Normal 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
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "badgelink_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
101
submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/badgelink_24.pdf
vendored
Normal file
101
submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/badgelink_24.pdf
vendored
Normal 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
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "badgepin_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
108
submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/badgepin_24.pdf
vendored
Normal file
108
submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/badgepin_24.pdf
vendored
Normal 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
|
||||
21
submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
21
submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Tmp2.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Tmp2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
12
submodules/TelegramUI/Images.xcassets/Premium/X2.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/X2.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "x2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
129
submodules/TelegramUI/Images.xcassets/Premium/X2.imageset/x2.pdf
vendored
Normal file
129
submodules/TelegramUI/Images.xcassets/Premium/X2.imageset/x2.pdf
vendored
Normal 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
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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)!
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ import DatePickerNode
|
|||
import DebugSettingsUI
|
||||
import TabBarUI
|
||||
|
||||
import TelegramCallsUI
|
||||
|
||||
public final class TelegramRootController: NavigationController {
|
||||
private let context: AccountContext
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue