Various improvements

This commit is contained in:
Ilya Laktyushin 2022-05-11 19:13:58 +04:00
parent ab0ec36112
commit eedfedbfc3
21 changed files with 371 additions and 211 deletions

View file

@ -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";

View file

@ -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)
}
}
})

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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",
],

View file

@ -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

View file

@ -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)
}
})
}

View file

@ -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))
}

View file

@ -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)

View file

@ -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)

View file

@ -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
}

View file

@ -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
})
}
})))
}

View file

@ -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
})
}
})))
}

View file

@ -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)
}
}

View file

@ -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))

View file

@ -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)
}
}
})
}

View file

@ -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