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
ab0ec36112
commit
eedfedbfc3
21 changed files with 371 additions and 211 deletions
|
|
@ -7550,10 +7550,8 @@ Sorry for the inconvenience.";
|
|||
|
||||
"DialogList.ExtendedPinLimitError" = "Sorry, you can't 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.";
|
||||
|
||||
"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.";
|
||||
|
||||
"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.LeaveCommunities_1" = "Leave %@ Community";
|
||||
"OldChannels.LeaveCommunities_any" = "Leave %@ Communities";
|
||||
|
|
@ -7565,6 +7563,11 @@ Sorry for the inconvenience.";
|
|||
"Premium.MaxChatsInFolderCountText" = "Sorry, you can't add more than **%@** chats to a folder. You can increase this limit to **%@** by upgrading to **Telegram Premium**.";
|
||||
"Premium.MaxFileSizeText" = "Double this limit to %@ per file by subscribing to **Telegram Premium**.";
|
||||
"Premium.MaxPinsText" = "Sorry, you can't 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.";
|
||||
"Premium.MaxFavedStickersTitle" = "The Limit of %@ Stickers Reached";
|
||||
"Premium.MaxFavedStickersText" = "An older sticker was replaced with this one. You can [increase the limit]() to %@ stickers.";
|
||||
|
||||
"Premium.Free" = "Free";
|
||||
"Premium.Premium" = "Premium";
|
||||
|
||||
"Premium.Title" = "Telegram Premium";
|
||||
"Premium.Description" = "Go **beyond the limits**, get **exclusive features** and support us by subscribing to **Telegram Premium**.";
|
||||
|
|
@ -7596,3 +7599,5 @@ Sorry for the inconvenience.";
|
|||
"Premium.HelpUs" = "Help us maintain Premium Features while keeping Telegram free for everyone.";
|
||||
|
||||
"Premium.SubscribeFor" = "Subscribe for %@ per month";
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -321,17 +321,16 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||
case .limitExceeded:
|
||||
f(.default)
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pins, action: {
|
||||
dismissImpl?()
|
||||
let premiumScreen = PremiumIntroScreen(context: context, action: {
|
||||
|
||||
})
|
||||
chatListController?.push(premiumScreen)
|
||||
replaceImpl?(premiumScreen)
|
||||
})
|
||||
chatListController?.push(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import FetchManagerImpl
|
|||
import ComponentFlow
|
||||
import LottieAnimationComponent
|
||||
import ProgressIndicatorComponent
|
||||
import PremiumUI
|
||||
|
||||
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||
if listNode.scroller.isDragging {
|
||||
|
|
@ -1312,14 +1313,49 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.engine.peers.currentChatListFilters()
|
||||
|> deliverOnMainQueue).start(next: { presetList in
|
||||
|
||||
let _ = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
|
||||
),
|
||||
strongSelf.context.engine.peers.currentChatListFilters()
|
||||
).start(next: { result, presetList in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var found = false
|
||||
for filter in presetList {
|
||||
if filter.id == id {
|
||||
let (accountPeer, limits, premiumLimits) = result
|
||||
let limit = limits.maxFolderChatsCount
|
||||
let premiumLimit = premiumLimits.maxFolderChatsCount
|
||||
|
||||
if let accountPeer = accountPeer, accountPeer.isPremium {
|
||||
if filter.data.includePeers.peers.count >= premiumLimit {
|
||||
//printPremiumError
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if filter.data.includePeers.peers.count >= limit {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .chatsInFolder, action: {
|
||||
let controller = PremiumIntroScreen(context: context, action: {
|
||||
|
||||
})
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
f(.dismissWithoutContent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (strongSelf.context.engine.peers.currentChatListFilters()
|
||||
|> deliverOnMainQueue).start(next: { filters in
|
||||
guard let strongSelf = self else {
|
||||
|
|
|
|||
|
|
@ -290,18 +290,17 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||
}
|
||||
} else {
|
||||
if filters.count >= limit {
|
||||
var dismissImpl: (() -> Void)?
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .folders, action: {
|
||||
dismissImpl?()
|
||||
let controller = PremiumIntroScreen(context: context, action: {
|
||||
|
||||
})
|
||||
pushControllerImpl?(controller)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
pushControllerImpl?(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
pushControllerImpl?(controller)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -843,15 +843,15 @@ public final class ChatListNode: ListView {
|
|||
case .done:
|
||||
break
|
||||
case .limitExceeded:
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pins, action: { [weak self] in
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pins, action: {
|
||||
let premiumScreen = PremiumIntroScreen(context: context, action: {
|
||||
dismissImpl?()
|
||||
|
||||
})
|
||||
self?.push?(premiumScreen)
|
||||
replaceImpl?(premiumScreen)
|
||||
})
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
strongSelf.push?(controller)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,6 +155,8 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||
if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) {
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.scrollView.layer.animatePosition(from: CGPoint(x: 0.0, y: availableSize.height - self.scrollView.contentInset.top), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: nil)
|
||||
} else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) {
|
||||
self.animateOut(completion: {})
|
||||
}
|
||||
self.previousIsDisplaying = environment[SheetComponentEnvironment.self].value.isDisplaying
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ open class ViewControllerComponentContainer: ViewController {
|
|||
public final class AnimateInTransition {
|
||||
}
|
||||
|
||||
public final class AnimateOutTransition {
|
||||
}
|
||||
|
||||
public final class Node: ViewControllerTracingNode {
|
||||
private var presentationData: PresentationData
|
||||
private weak var controller: ViewControllerComponentContainer?
|
||||
|
|
@ -183,7 +186,7 @@ open class ViewControllerComponentContainer: ViewController {
|
|||
guard let currentLayout = self.currentLayout else {
|
||||
return
|
||||
}
|
||||
self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: animated ? Transition(animation: .none).withUserData(AnimateInTransition()) : .immediate)
|
||||
self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: animated ? Transition(animation: .none).withUserData(isVisible ? AnimateInTransition() : AnimateOutTransition()) : .immediate)
|
||||
}
|
||||
|
||||
func updateComponent(component: AnyComponent<ViewControllerComponentContainer.Environment>, transition: Transition) {
|
||||
|
|
@ -240,7 +243,11 @@ open class ViewControllerComponentContainer: ViewController {
|
|||
override open func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
self.node.updateIsVisible(isVisible: false, animated: false)
|
||||
self.node.updateIsVisible(isVisible: false, animated: animated)
|
||||
}
|
||||
|
||||
open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
|
|
|||
|
|
@ -554,6 +554,15 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
|
|||
(self.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
}
|
||||
|
||||
public func replace(with controller: ViewController) {
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers.removeAll(where: { $0 === self })
|
||||
controllers.append(controller)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
open func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) {
|
||||
if !(controller is StandalonePresentableController), case .window = context, let arguments = arguments as? ViewControllerPresentationArguments, case .modalSheet = arguments.presentationAnimation, self.navigationController != nil {
|
||||
controller.navigationPresentation = .modal
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ swift_library(
|
|||
"//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||
|
||||
case publicLinkHeader(PresentationTheme, String)
|
||||
case publicLinkAvailability(PresentationTheme, String, Bool)
|
||||
case linksLimitInfo(PresentationTheme, String, String, Int)
|
||||
case linksLimitInfo(PresentationTheme, String, Int32, Int32)
|
||||
case editablePublicLink(PresentationTheme, PresentationStrings, String, String)
|
||||
case privateLinkHeader(PresentationTheme, String)
|
||||
case privateLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
|
||||
|
|
@ -227,8 +227,8 @@ 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 {
|
||||
case let .linksLimitInfo(lhsTheme, lhsText, lhsLimit, lhsPremiumLimit):
|
||||
if case let .linksLimitInfo(rhsTheme, rhsText, rhsLimit, rhsPremiumLimit) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLimit == rhsLimit, lhsPremiumLimit == rhsPremiumLimit {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
@ -393,8 +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 .linksLimitInfo(theme, text, limit, premiumLimit):
|
||||
return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: limit, premiumCount: premiumLimit, 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):
|
||||
|
|
@ -729,7 +729,7 @@ 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("\(20)").string, 10))
|
||||
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20))
|
||||
|
||||
var index: Int32 = 0
|
||||
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
|
||||
|
|
@ -858,7 +858,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||
switch mode {
|
||||
case .revokeNames:
|
||||
if let publicChannelsToRevoke = publicChannelsToRevoke {
|
||||
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(1000)").string, 500))
|
||||
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20))
|
||||
|
||||
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
|
||||
var index: Int32 = 0
|
||||
|
|
@ -921,7 +921,7 @@ 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(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20))
|
||||
|
||||
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
|
||||
var index: Int32 = 0
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import TelegramPresentationData
|
|||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import Markdown
|
||||
import PremiumUI
|
||||
import ComponentFlow
|
||||
|
||||
class IncreaseLimitHeaderItem: ListViewItem, ItemListItem {
|
||||
enum Icon {
|
||||
|
|
@ -15,17 +17,19 @@ class IncreaseLimitHeaderItem: ListViewItem, ItemListItem {
|
|||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let icon: Icon
|
||||
let count: Int
|
||||
let title: String
|
||||
let count: Int32
|
||||
let premiumCount: Int32
|
||||
let text: String
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
init(theme: PresentationTheme, icon: Icon, count: Int, title: String, text: String, sectionId: ItemListSectionId) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, icon: Icon, count: Int32, premiumCount: Int32, text: String, sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.icon = icon
|
||||
self.count = count
|
||||
self.title = title
|
||||
self.premiumCount = premiumCount
|
||||
self.text = text
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
|
@ -68,13 +72,11 @@ class IncreaseLimitHeaderItem: ListViewItem, ItemListItem {
|
|||
}
|
||||
|
||||
private let titleFont = Font.semibold(17.0)
|
||||
private let textFont = Font.regular(14.0)
|
||||
private let boldTextFont = Font.semibold(13.0)
|
||||
private let textFont = Font.regular(15.0)
|
||||
private let boldTextFont = Font.semibold(15.0)
|
||||
|
||||
class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
||||
private var backgroundNode: ASImageNode
|
||||
private var iconNode: ASImageNode
|
||||
private var countNode: TextNode
|
||||
private var hostView: ComponentHostView<Empty>
|
||||
private let titleNode: TextNode
|
||||
private let textNode: TextNode
|
||||
|
||||
|
|
@ -91,25 +93,10 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
|||
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
|
||||
self.hostView = ComponentHostView<Empty>()
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -117,39 +104,27 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
|||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
self.backgroundNode.layer.cornerCurve = .continuous
|
||||
}
|
||||
self.view.addSubview(self.hostView)
|
||||
}
|
||||
|
||||
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 badgeHeight: CGFloat = 200.0
|
||||
let textSpacing: CGFloat = -6.0
|
||||
let bottomInset: CGFloat = -86.0
|
||||
|
||||
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 (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: topInset + badgeHeight + titleSpacing + titleLayout.size.height + textSpacing + textLayout.size.height + bottomInset)
|
||||
let contentSize = CGSize(width: params.width, height: topInset + badgeHeight + textSpacing + textLayout.size.height + bottomInset)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
|
@ -159,33 +134,39 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
|||
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 badgeIconName: String
|
||||
switch item.icon {
|
||||
case .group:
|
||||
badgeIconName = "Premium/Group"
|
||||
case .link:
|
||||
badgeIconName = "Premium/Link"
|
||||
}
|
||||
|
||||
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 size = strongSelf.hostView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PremiumLimitDisplayComponent(
|
||||
inactiveColor: UIColor(rgb: 0xE9E9EA),
|
||||
activeColors: [
|
||||
UIColor(rgb: 0x0077ff),
|
||||
UIColor(rgb: 0x6b93ff),
|
||||
UIColor(rgb: 0x8878ff),
|
||||
UIColor(rgb: 0xe46ace)
|
||||
],
|
||||
inactiveTitle: item.strings.Premium_Free,
|
||||
inactiveTitleColor: .black,
|
||||
activeTitle: item.strings.Premium_Premium,
|
||||
activeValue: "\(item.premiumCount)",
|
||||
activeTitleColor: .white,
|
||||
badgeIconName: badgeIconName,
|
||||
badgeText: "\(item.count)"
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
|
||||
)
|
||||
strongSelf.hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: 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)
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ private enum OldChannelsEntryId: Hashable {
|
|||
}
|
||||
|
||||
private enum OldChannelsEntry: ItemListNodeEntry {
|
||||
case info(Int, String, String)
|
||||
case info(Int32, Int32, 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(count, title, text):
|
||||
if case .info(count, title, text) = rhs {
|
||||
case let .info(count, premiumCount, text):
|
||||
if case .info(count, premiumCount, 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(count, title, text):
|
||||
return IncreaseLimitHeaderItem(theme: presentationData.theme, icon: .group, count: count, title: title, text: text, sectionId: self.section)
|
||||
case let .info(count, premiumCount, text):
|
||||
return IncreaseLimitHeaderItem(theme: presentationData.theme, strings: presentationData.strings, icon: .group, count: count, premiumCount: premiumCount, text: text, sectionId: self.section)
|
||||
case let .peersHeader(title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .peer(_, peer, selected):
|
||||
|
|
@ -184,10 +184,10 @@ private struct OldChannelsState: Equatable {
|
|||
var isSearching: Bool = false
|
||||
}
|
||||
|
||||
private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, limit: Int, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] {
|
||||
private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, limit: Int32, premiumLimit: Int32, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] {
|
||||
var entries: [OldChannelsEntry] = []
|
||||
|
||||
entries.append(.info(limit, presentationData.strings.OldChannels_TooManyCommunitiesTitle, presentationData.strings.OldChannels_TooManyCommunitiesText("\(limit)", "\(limit * 2)").string))
|
||||
entries.append(.info(limit, premiumLimit, presentationData.strings.OldChannels_TooManyCommunitiesText("\(limit)", "\(premiumLimit)").string))
|
||||
|
||||
if let peers = peers, !peers.isEmpty {
|
||||
entries.append(.peersHeader(presentationData.strings.OldChannels_ChannelsHeader))
|
||||
|
|
@ -263,17 +263,25 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
|
|||
|
||||
var previousPeersWereEmpty = true
|
||||
|
||||
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
let signal = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
presentationData,
|
||||
statePromise.get(),
|
||||
peersPromise.get()
|
||||
peersPromise.get(),
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
|
||||
)
|
||||
)
|
||||
|> map { presentationData, state, peers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { presentationData, state, peers, limits -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
let (_, limits, premiumLimits) = limits
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.OldChannels_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
|
||||
|
|
@ -316,7 +324,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
|
|||
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)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, limit: limits.maxChannelsCount, premiumLimit: premiumLimits.maxChannelsCount, 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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,11 @@ private class StarComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if node.animationKeys.contains("tapRotate") {
|
||||
self.playAppearanceAnimation(velocity: nil, mirror: left, boom: true)
|
||||
return
|
||||
}
|
||||
|
||||
let initial = node.rotation
|
||||
let target = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: left ? -0.6 : 0.6)
|
||||
|
||||
|
|
@ -93,7 +98,7 @@ private class StarComponent: Component {
|
|||
animation.duration = 0.25
|
||||
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||
animation.fillMode = .forwards
|
||||
node.addAnimation(animation, forKey: "rotate")
|
||||
node.addAnimation(animation, forKey: "tapRotate")
|
||||
|
||||
node.rotation = target
|
||||
|
||||
|
|
@ -106,7 +111,7 @@ private class StarComponent: Component {
|
|||
springAnimation.stiffness = 21.0
|
||||
springAnimation.damping = 5.8
|
||||
springAnimation.duration = springAnimation.settlingDuration * 0.8
|
||||
node.addAnimation(springAnimation, forKey: "rotate")
|
||||
node.addAnimation(springAnimation, forKey: "tapRotate")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +121,13 @@ private class StarComponent: Component {
|
|||
return
|
||||
}
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
node.removeAnimation(forKey: "rotate", blendOutDuration: 0.1)
|
||||
node.removeAnimation(forKey: "tapRotate", blendOutDuration: 0.1)
|
||||
} else {
|
||||
node.removeAllAnimations()
|
||||
}
|
||||
|
||||
switch gesture.state {
|
||||
case .began:
|
||||
self.previousAngle = 0.0
|
||||
|
|
@ -128,12 +140,12 @@ private class StarComponent: Component {
|
|||
case .ended:
|
||||
let velocity = gesture.velocity(in: gesture.view)
|
||||
|
||||
var small = false
|
||||
var smallAngle = false
|
||||
if (self.previousAngle < .pi / 2 && self.previousAngle > -.pi / 2) && abs(velocity.x) < 200 {
|
||||
small = true
|
||||
smallAngle = true
|
||||
}
|
||||
|
||||
self.playAppearanceAnimation(velocity: velocity.x, small: small)
|
||||
self.playAppearanceAnimation(velocity: velocity.x, smallAngle: smallAngle)
|
||||
node.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)
|
||||
default:
|
||||
break
|
||||
|
|
@ -211,12 +223,12 @@ private class StarComponent: Component {
|
|||
node.geometry?.materials.first?.emission.addAnimation(group, forKey: "shimmer")
|
||||
}
|
||||
|
||||
private func playAppearanceAnimation(velocity: CGFloat? = nil, small: Bool = false, boom: Bool = false) {
|
||||
private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) {
|
||||
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
|
||||
return
|
||||
}
|
||||
|
||||
if boom, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) {
|
||||
if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) {
|
||||
node.physicsField?.isActive = true
|
||||
Queue.mainQueue().after(1.0) {
|
||||
node.physicsField?.isActive = false
|
||||
|
|
@ -224,9 +236,14 @@ private class StarComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
let from = node.rotation
|
||||
var toValue: Float = small ? 0.0 : .pi * 2.0
|
||||
if let velocity = velocity, !small && abs(velocity) > 200 && velocity < 0.0 {
|
||||
let from = node.presentation.rotation
|
||||
node.removeAnimation(forKey: "tapRotate")
|
||||
|
||||
var toValue: Float = smallAngle ? 0.0 : .pi * 2.0
|
||||
if let velocity = velocity, !smallAngle && abs(velocity) > 200 && velocity < 0.0 {
|
||||
toValue *= -1
|
||||
}
|
||||
if mirror {
|
||||
toValue *= -1
|
||||
}
|
||||
let to = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: toValue)
|
||||
|
|
|
|||
|
|
@ -21,17 +21,20 @@ private class PremiumLimitAnimationComponent: Component {
|
|||
private let inactiveColor: UIColor
|
||||
private let activeColors: [UIColor]
|
||||
private let textColor: UIColor
|
||||
private let badgeText: String?
|
||||
|
||||
init(
|
||||
iconName: String,
|
||||
inactiveColor: UIColor,
|
||||
activeColors: [UIColor],
|
||||
textColor: UIColor
|
||||
textColor: UIColor,
|
||||
badgeText: String?
|
||||
) {
|
||||
self.iconName = iconName
|
||||
self.inactiveColor = inactiveColor
|
||||
self.activeColors = activeColors
|
||||
self.textColor = textColor
|
||||
self.badgeText = badgeText
|
||||
}
|
||||
|
||||
static func ==(lhs: PremiumLimitAnimationComponent, rhs: PremiumLimitAnimationComponent) -> Bool {
|
||||
|
|
@ -47,6 +50,9 @@ private class PremiumLimitAnimationComponent: Component {
|
|||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
if lhs.badgeText != rhs.badgeText {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +134,7 @@ private class PremiumLimitAnimationComponent: Component {
|
|||
}
|
||||
|
||||
private var didPlayAppearanceAnimation = false
|
||||
func playAppearanceAnimation(availableSize: CGSize) {
|
||||
func playAppearanceAnimation(component: PremiumLimitAnimationComponent, availableSize: CGSize) {
|
||||
self.badgeView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
|
||||
let now = self.badgeView.layer.convertTime(CACurrentMediaTime(), from: nil)
|
||||
|
|
@ -163,7 +169,9 @@ private class PremiumLimitAnimationComponent: Component {
|
|||
self.badgeView.layer.add(rotateAnimation, forKey: "appearance2")
|
||||
self.badgeView.layer.add(returnAnimation, forKey: "appearance3")
|
||||
|
||||
self.badgeCountLabel.text(num: 4)
|
||||
if let badgeText = component.badgeText, let num = Int(badgeText) {
|
||||
self.badgeCountLabel.text(num: num)
|
||||
}
|
||||
}
|
||||
|
||||
var previousAvailableSize: CGSize?
|
||||
|
|
@ -186,7 +194,25 @@ private class PremiumLimitAnimationComponent: Component {
|
|||
self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0, y: lineHeight / 2.0)
|
||||
}
|
||||
|
||||
let badgeSize = CGSize(width: 82.0, height: 48.0 + 12.0)
|
||||
|
||||
let countWidth: CGFloat
|
||||
if let badgeText = component.badgeText {
|
||||
switch badgeText.count {
|
||||
case 1:
|
||||
countWidth = 20.0
|
||||
case 2:
|
||||
countWidth = 35.0
|
||||
case 3:
|
||||
countWidth = 51.0
|
||||
default:
|
||||
countWidth = 51.0
|
||||
}
|
||||
} else {
|
||||
countWidth = 51.0
|
||||
}
|
||||
let badgeWidth: CGFloat = countWidth + 62.0
|
||||
|
||||
let badgeSize = CGSize(width: badgeWidth, height: 48.0 + 12.0)
|
||||
self.badgeMaskView.frame = CGRect(origin: .zero, size: badgeSize)
|
||||
self.badgeMaskBackgroundView.frame = CGRect(origin: .zero, size: CGSize(width: badgeSize.width, height: 48.0))
|
||||
self.badgeMaskArrowView.frame = CGRect(origin: CGPoint(x: (badgeSize.width - 44.0) / 2.0, y: badgeSize.height - 12.0), size: CGSize(width: 44.0, height: 12.0))
|
||||
|
|
@ -197,14 +223,13 @@ private class PremiumLimitAnimationComponent: Component {
|
|||
self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0, y: badgeSize.height / 2.0)
|
||||
}
|
||||
self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height))
|
||||
|
||||
|
||||
self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0)
|
||||
|
||||
self.badgeCountLabel.frame = CGRect(x: badgeSize.width - 36.0, y: 10.0, width: 30.0, height: 48.0)
|
||||
self.badgeCountLabel.frame = CGRect(x: badgeSize.width - countWidth - 11.0, y: 10.0, width: countWidth, height: 48.0)
|
||||
|
||||
if !self.didPlayAppearanceAnimation {
|
||||
self.didPlayAppearanceAnimation = true
|
||||
self.playAppearanceAnimation(availableSize: availableSize)
|
||||
self.playAppearanceAnimation(component: component, availableSize: availableSize)
|
||||
}
|
||||
|
||||
if self.previousAvailableSize != availableSize {
|
||||
|
|
@ -291,7 +316,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||
public let activeValue: String
|
||||
public let activeTitleColor: UIColor
|
||||
public let badgeIconName: String
|
||||
public let badgeValue: String
|
||||
public let badgeText: String?
|
||||
|
||||
public init(
|
||||
inactiveColor: UIColor,
|
||||
|
|
@ -302,7 +327,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||
activeValue: String,
|
||||
activeTitleColor: UIColor,
|
||||
badgeIconName: String,
|
||||
badgeValue: String
|
||||
badgeText: String?
|
||||
) {
|
||||
self.inactiveColor = inactiveColor
|
||||
self.activeColors = activeColors
|
||||
|
|
@ -312,7 +337,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||
self.activeValue = activeValue
|
||||
self.activeTitleColor = activeTitleColor
|
||||
self.badgeIconName = badgeIconName
|
||||
self.badgeValue = badgeValue
|
||||
self.badgeText = badgeText
|
||||
}
|
||||
|
||||
public static func ==(lhs: PremiumLimitDisplayComponent, rhs: PremiumLimitDisplayComponent) -> Bool {
|
||||
|
|
@ -340,16 +365,16 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||
if lhs.badgeIconName != rhs.badgeIconName {
|
||||
return false
|
||||
}
|
||||
if lhs.badgeValue != rhs.badgeValue {
|
||||
if lhs.badgeText != rhs.badgeText {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static var body: Body {
|
||||
let inactiveTitle = Child(Text.self)
|
||||
let activeTitle = Child(Text.self)
|
||||
let activeValue = Child(Text.self)
|
||||
let inactiveTitle = Child(MultilineTextComponent.self)
|
||||
let activeTitle = Child(MultilineTextComponent.self)
|
||||
let activeValue = Child(MultilineTextComponent.self)
|
||||
let animation = Child(PremiumLimitAnimationComponent.self)
|
||||
|
||||
return { context in
|
||||
|
|
@ -359,30 +384,42 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||
let lineHeight: CGFloat = 30.0
|
||||
|
||||
let inactiveTitle = inactiveTitle.update(
|
||||
component: Text(
|
||||
text: component.inactiveTitle,
|
||||
font: Font.semibold(15.0),
|
||||
color: component.inactiveTitleColor
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(
|
||||
string: component.inactiveTitle,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.inactiveTitleColor
|
||||
)
|
||||
)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let activeTitle = activeTitle.update(
|
||||
component: Text(
|
||||
text: component.activeTitle,
|
||||
font: Font.semibold(15.0),
|
||||
color: component.activeTitleColor
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(
|
||||
string: component.activeTitle,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.activeTitleColor
|
||||
)
|
||||
)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let activeValue = activeValue.update(
|
||||
component: Text(
|
||||
text: component.activeValue,
|
||||
font: Font.semibold(15.0),
|
||||
color: component.activeTitleColor
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(
|
||||
string: component.activeValue,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.activeTitleColor
|
||||
)
|
||||
)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
|
|
@ -393,7 +430,8 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||
iconName: component.badgeIconName,
|
||||
inactiveColor: component.inactiveColor,
|
||||
activeColors: component.activeColors,
|
||||
textColor: component.activeTitleColor
|
||||
textColor: component.activeTitleColor,
|
||||
badgeText: component.badgeText
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: height),
|
||||
transition: context.transition
|
||||
|
|
@ -449,6 +487,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
private let context: AccountContext
|
||||
|
||||
private var disposable: Disposable?
|
||||
var initialized = false
|
||||
var limits: EngineConfiguration.UserLimits
|
||||
var premiumLimits: EngineConfiguration.UserLimits
|
||||
|
||||
|
|
@ -465,6 +504,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
let (limits, premiumLimits) = result
|
||||
strongSelf.initialized = true
|
||||
strongSelf.limits = limits
|
||||
strongSelf.premiumLimits = premiumLimits
|
||||
strongSelf.updated(transition: .immediate)
|
||||
|
|
@ -500,7 +540,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
let textSideInset: CGFloat = 24.0 + environment.safeInsets.left
|
||||
|
||||
let iconName: String
|
||||
let badgeValue: String
|
||||
let badgeText: String
|
||||
let string: String
|
||||
let premiumValue: String
|
||||
switch subject {
|
||||
|
|
@ -508,28 +548,28 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
let limit = state.limits.maxFoldersCount
|
||||
let premiumLimit = state.premiumLimits.maxFoldersCount
|
||||
iconName = "Premium/Folder"
|
||||
badgeValue = "\(limit)"
|
||||
badgeText = "\(limit)"
|
||||
string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string
|
||||
premiumValue = "\(premiumLimit)"
|
||||
case .chatsInFolder:
|
||||
let limit = state.limits.maxFolderChatsCount
|
||||
let premiumLimit = state.premiumLimits.maxFolderChatsCount
|
||||
iconName = "Premium/Chat"
|
||||
badgeValue = "\(limit)"
|
||||
badgeText = "\(limit)"
|
||||
string = strings.Premium_MaxChatsInFolderCountText("\(limit)", "\(premiumLimit)").string
|
||||
premiumValue = "\(premiumLimit)"
|
||||
case .pins:
|
||||
let limit = 4//state.limits.maxPinnedChatCount
|
||||
let premiumLimit = 6//state.premiumLimits.maxPinnedChatCount
|
||||
let limit = state.limits.maxPinnedChatCount
|
||||
let premiumLimit = state.premiumLimits.maxPinnedChatCount
|
||||
iconName = "Premium/Pin"
|
||||
badgeValue = "\(limit)"
|
||||
badgeText = "\(limit)"
|
||||
string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string
|
||||
premiumValue = "\(premiumLimit)"
|
||||
case .files:
|
||||
let limit = 2048 * 1024 * 1024 //state.limits.maxPinnedChatCount
|
||||
let premiumLimit = 4096 * 1024 * 1024 //state.premiumLimits.maxPinnedChatCount
|
||||
let limit: Int64 = 2048 * 1024 * 1024 * Int64(state.limits.maxUploadFileParts)
|
||||
let premiumLimit: Int64 = 4096 * 1024 * 1024 * Int64(state.limits.maxUploadFileParts)
|
||||
iconName = "Premium/File"
|
||||
badgeValue = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
|
||||
badgeText = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
|
||||
string = strings.Premium_MaxFileSizeText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string
|
||||
premiumValue = dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
|
||||
}
|
||||
|
|
@ -567,26 +607,31 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
transition: .immediate
|
||||
)
|
||||
|
||||
let limit = limit.update(
|
||||
component: PremiumLimitDisplayComponent(
|
||||
inactiveColor: UIColor(rgb: 0xE9E9EA),
|
||||
activeColors: [
|
||||
UIColor(rgb: 0x0077ff),
|
||||
UIColor(rgb: 0x6b93ff),
|
||||
UIColor(rgb: 0x8878ff),
|
||||
UIColor(rgb: 0xe46ace)
|
||||
],
|
||||
inactiveTitle: "Free",
|
||||
inactiveTitleColor: .black,
|
||||
activeTitle: "Premium",
|
||||
activeValue: premiumValue,
|
||||
activeTitleColor: .white,
|
||||
badgeIconName: iconName,
|
||||
badgeValue: badgeValue
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
if state.initialized {
|
||||
let limit = limit.update(
|
||||
component: PremiumLimitDisplayComponent(
|
||||
inactiveColor: UIColor(rgb: 0xE9E9EA),
|
||||
activeColors: [
|
||||
UIColor(rgb: 0x0077ff),
|
||||
UIColor(rgb: 0x6b93ff),
|
||||
UIColor(rgb: 0x8878ff),
|
||||
UIColor(rgb: 0xe46ace)
|
||||
],
|
||||
inactiveTitle: strings.Premium_Free,
|
||||
inactiveTitleColor: .black,
|
||||
activeTitle: strings.Premium_Premium,
|
||||
activeValue: premiumValue,
|
||||
activeTitleColor: .white,
|
||||
badgeIconName: iconName,
|
||||
badgeText: badgeText
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(limit
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: limit.size.height / 2.0 + 44.0))
|
||||
)
|
||||
}
|
||||
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
|
|
@ -620,17 +665,11 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
transition: context.transition
|
||||
)
|
||||
|
||||
let width = context.availableSize.width
|
||||
|
||||
context.add(limit
|
||||
.position(CGPoint(x: width / 2.0, y: limit.size.height / 2.0 + 44.0))
|
||||
)
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: width / 2.0, y: 28.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 28.0))
|
||||
)
|
||||
context.add(text
|
||||
.position(CGPoint(x: width / 2.0, y: 228.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 228.0))
|
||||
)
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: 228.0 + ceil(text.size.height / 2.0) + 38.0), size: button.size)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ open class RollingLabel: UILabel {
|
|||
self.text = fullText
|
||||
}
|
||||
let w = UILabel.textWidth(font: self.font, text: self.text ?? "")
|
||||
self.text = "" // 초기화
|
||||
self.text = ""
|
||||
x = -(w / 2)
|
||||
} else if self.textAlignment == .right {
|
||||
if showSymbol {
|
||||
|
|
@ -82,7 +82,7 @@ open class RollingLabel: UILabel {
|
|||
self.text = fullText
|
||||
}
|
||||
let w = UILabel.textWidth(font: self.font, text: self.text ?? "")
|
||||
self.text = "" // 초기화
|
||||
self.text = ""
|
||||
x = -w
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -230,11 +230,9 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
|||
f(.default)
|
||||
|
||||
if let strongSelf = self {
|
||||
if isStarred {
|
||||
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
|
||||
} else {
|
||||
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
|
||||
}
|
||||
let _ = strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred).start(next: { result in
|
||||
|
||||
})
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -363,11 +363,9 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
f(.default)
|
||||
|
||||
if let strongSelf = self {
|
||||
if isStarred {
|
||||
let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
|
||||
} else {
|
||||
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
|
||||
}
|
||||
let _ = strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred).start(next: { _ in
|
||||
|
||||
})
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
import SwiftSignalKit
|
||||
|
||||
public enum SavedStickerResult {
|
||||
case generic
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
func _internal_toggleStickerSaved(postbox: Postbox, network: Network, accountPeerId: PeerId, file: TelegramMediaFile, saved: Bool) -> Signal<SavedStickerResult, AddSavedStickerError> {
|
||||
if saved {
|
||||
return postbox.transaction { transaction -> Signal<SavedStickerResult, AddSavedStickerError> in
|
||||
let isPremium = transaction.getPeer(accountPeerId)?.isPremium ?? false
|
||||
let items = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers)
|
||||
|
||||
let appConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue
|
||||
let userLimitsConfiguration = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: isPremium)
|
||||
|
||||
return addSavedSticker(postbox: postbox, network: network, file: file)
|
||||
|> map { _ -> SavedStickerResult in
|
||||
return .generic
|
||||
}
|
||||
|> then(
|
||||
.single(items.count == userLimitsConfiguration.maxFavedStickerCount ? .limitExceeded : .generic)
|
||||
)
|
||||
}
|
||||
|> castError(AddSavedStickerError.self)
|
||||
|> switchToLatest
|
||||
} else {
|
||||
return removeSavedSticker(postbox: postbox, mediaId: file.fileId)
|
||||
|> map { _ -> SavedStickerResult in
|
||||
return .generic
|
||||
}
|
||||
|> castError(AddSavedStickerError.self)
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +81,10 @@ public extension TelegramEngine {
|
|||
return _internal_getStickerSetShortNameSuggestion(account: self.account, title: title)
|
||||
}
|
||||
|
||||
public func toggleStickerSaved(file: TelegramMediaFile, saved: Bool) -> Signal<SavedStickerResult, AddSavedStickerError> {
|
||||
return _internal_toggleStickerSaved(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, file: file, saved: saved)
|
||||
}
|
||||
|
||||
public func validateStickerSetShortNameInteractive(shortName: String) -> Signal<AddressNameValidationStatus, NoError> {
|
||||
if let error = _internal_checkAddressNameFormat(shortName) {
|
||||
return .single(.invalidFormat(error))
|
||||
|
|
|
|||
|
|
@ -7925,28 +7925,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
}
|
||||
if let stickerFile = stickerFile {
|
||||
let postbox = strongSelf.context.account.postbox
|
||||
let network = strongSelf.context.account.network
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Signal<Bool, NoError> in
|
||||
let context = strongSelf.context
|
||||
let _ = (context.account.postbox.transaction { transaction -> Signal<(SavedStickerResult, Bool), AddSavedStickerError> in
|
||||
let isSaved: Bool
|
||||
if getIsStickerSaved(transaction: transaction, fileId: stickerFile.fileId) {
|
||||
removeSavedSticker(transaction: transaction, mediaId: stickerFile.fileId)
|
||||
return .single(false)
|
||||
isSaved = true
|
||||
} else {
|
||||
return addSavedSticker(postbox: postbox, network: network, file: stickerFile)
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> map { _ -> Bool in
|
||||
return true
|
||||
}
|
||||
|> then(.single(true))
|
||||
isSaved = false
|
||||
}
|
||||
return context.engine.stickers.toggleStickerSaved(file: stickerFile, saved: !isSaved)
|
||||
|> map { result -> (SavedStickerResult, Bool) in
|
||||
return (result, !isSaved)
|
||||
}
|
||||
}
|
||||
|> castError(AddSavedStickerError.self)
|
||||
|> switchToLatest
|
||||
|> deliverOnMainQueue).start(next: { [weak self] added in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result, added in
|
||||
if let strongSelf = self {
|
||||
// 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)
|
||||
switch result {
|
||||
case .generic:
|
||||
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)
|
||||
case .limitExceeded:
|
||||
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: strongSelf.presentationData.strings.Premium_MaxFavedStickersTitle("5").string, text: strongSelf.presentationData.strings.Premium_MaxFavedStickersText("10").string), elevatedLayout: true, action: { [weak self] action in
|
||||
if let strongSelf = self {
|
||||
if case .info = action {
|
||||
let controller = PremiumIntroScreen(context: strongSelf.context, action: {
|
||||
|
||||
})
|
||||
strongSelf.push(controller)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}), with: nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
self.animationBackgroundColor = UIColor(rgb: 0x474747)
|
||||
}
|
||||
|
||||
var isUserInteractionEnabled = false
|
||||
switch content {
|
||||
case let .removedChat(text):
|
||||
self.avatarNode = nil
|
||||
|
|
@ -696,7 +697,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
}), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
|
||||
|
||||
if text.contains("](") {
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
|
||||
|
|
@ -879,6 +884,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
}
|
||||
if isUserInteractionEnabled {
|
||||
self.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.textNode.isUserInteractionEnabled = self.textNode.tapAttributeAction != nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue