Various improvements

This commit is contained in:
Ilya Laktyushin 2023-03-28 20:30:04 +04:00
parent 9abee7dc1f
commit 4ac9d1cb57
54 changed files with 2403 additions and 848 deletions

View File

@ -9054,6 +9054,8 @@ Sorry for the inconvenience.";
"Login.Email.WillBeResetIn" = "Email will be reset %@";
"Login.Email.PremiumRequiredTitle" = "Telegram Premium Required";
"Login.Email.PremiumRequiredText" = "Due to high cost of SMS in your country, you need to have a **Telegram Premium** account to reset this email via an SMS code.\n\nYou can ask a friend to gift a Premium subscription for your account\n**%@**";
"Login.Email.ElapsedTime" = "in %@";
"Login.Email.ResetingNow" = "Please wait...";
"ChatList.StartMessaging" = "Select a chat to start messaging";
@ -9092,3 +9094,11 @@ Sorry for the inconvenience.";
"StickerPacksSettings.SuggestAnimatedEmojiInfo" = "Each time you enter an emoji you can replace it with an animated emoji.";
"DialogList.DeleteBotClearHistory" = "Clear Chat History";
"TextFormat.EditLinkTitle" = "Edit Link";
"PeerInfo.Username" = "Username";
"Username.BotLinksOrderInfo" = "Drag and drop links to change the order in which they will be displayed on the bot info page.";
"Wallpaper.ApplyForAll" = "Apply For All Chats";
"Wallpaper.ApplyForChat" = "Apply For This Chat";

View File

@ -804,7 +804,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
strongSelf.animationNode = animationNode
strongSelf.addSubnode(animationNode)
animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: resource, isVideo: isVideo), width: 80, height: 80, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id)
animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: resource, isVideo: isVideo), width: 80, height: 80, playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
}
animationNode.visibility = strongSelf.visibility != .none && item.playAnimatedStickers
animationNode.isHidden = !item.playAnimatedStickers

View File

@ -822,6 +822,8 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
SettingsSearchableItem(id: .appearance(4), title: strings.Wallpaper_SetCustomBackground, alternate: synonyms(strings.SettingsSearch_Synonyms_Appearance_ChatBackground_Custom), icon: icon, breadcrumbs: [strings.Settings_Appearance, strings.Settings_ChatBackground], present: { context, _, present in
presentCustomWallpaperPicker(context: context, present: { controller in
present(.immediate, controller)
}, push: { controller in
present(.push, controller)
})
}),
SettingsSearchableItem(id: .appearance(5), title: strings.Appearance_AutoNightTheme, alternate: synonyms(strings.SettingsSearch_Synonyms_Appearance_AutoNightTheme), icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, _, present in

View File

@ -12,7 +12,7 @@ import LegacyUI
import LegacyMediaPickerUI
import LocalMediaResources
func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (ViewController) -> Void) {
func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = legacyWallpaperPicker(context: context, presentationData: presentationData).start(next: { generator in
let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: presentationData.theme)
@ -34,7 +34,7 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V
})
}
}
present(controller)
push(controller)
}
}
controller.dismissalBlock = { [weak legacyController] in

View File

@ -1180,12 +1180,12 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}
transition.updateFrame(node: self.colorPanelNode, frame: colorPanelFrame)
self.colorPanelNode.updateLayout(size: colorPanelFrame.size, transition: transition)
self.colorPanelNode.updateLayout(size: colorPanelFrame.size, bottomInset: 0.0, transition: transition)
let patternPanelAlpha: CGFloat = self.state.displayPatternPanel ? 1.0 : 0.0
let patternPanelFrame = colorPanelFrame
transition.updateFrame(node: self.patternPanelNode, frame: patternPanelFrame)
self.patternPanelNode.updateLayout(size: patternPanelFrame.size, transition: transition)
self.patternPanelNode.updateLayout(size: patternPanelFrame.size, bottomInset: 0.0, transition: transition)
self.patternPanelNode.isUserInteractionEnabled = self.state.displayPatternPanel
transition.updateAlpha(node: self.patternPanelNode, alpha: patternPanelAlpha)

View File

@ -15,13 +15,13 @@ import SearchUI
import HexColor
import PresentationDataUtils
final class ThemeGridController: ViewController {
public final class ThemeGridController: ViewController {
private var controllerNode: ThemeGridControllerNode {
return self.displayNode as! ThemeGridControllerNode
}
private let _ready = Promise<Bool>()
override var ready: Promise<Bool> {
public override var ready: Promise<Bool> {
return self._ready
}
@ -38,13 +38,13 @@ final class ThemeGridController: ViewController {
private var validLayout: ContainerViewLayout?
override var navigationBarRequiresEntireLayoutUpdate: Bool {
public override var navigationBarRequiresEntireLayoutUpdate: Bool {
return false
}
private var previousContentOffset: GridNodeVisibleContentOffset?
init(context: AccountContext) {
public init(context: AccountContext) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationDataPromise.set(.single(self.presentationData))
@ -115,7 +115,7 @@ final class ThemeGridController: ViewController {
}
}
override func loadDisplayNode() {
public override func loadDisplayNode() {
self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, presentPreviewController: { [weak self] source in
if let strongSelf = self {
let controller = WallpaperGalleryController(context: strongSelf.context, source: source)
@ -137,12 +137,14 @@ final class ThemeGridController: ViewController {
})
}
}
self?.present(controller, in: .window(.root), with: nil, blockInteraction: true)
self?.push(controller)
}
}, presentGallery: { [weak self] in
if let strongSelf = self {
presentCustomWallpaperPicker(context: strongSelf.context, present: { [weak self] controller in
self?.present(controller, in: .window(.root), with: nil, blockInteraction: true)
}, push: { [weak self] controller in
self?.push(controller)
})
}
}, presentColors: { [weak self] in
@ -408,7 +410,7 @@ final class ThemeGridController: ViewController {
self.donePressed()
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, transition: transition)

View File

@ -401,7 +401,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
var colorAdded: (() -> Void)?
var colorRemoved: (() -> Void)?
private var validLayout: CGSize?
private var validLayout: (CGSize, CGFloat)?
init(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
@ -439,6 +439,8 @@ final class WallpaperColorPanelNode: ASDisplayNode {
super.init()
self.backgroundColor = .white
self.addSubnode(self.backgroundNode)
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.bottomSeparatorNode)
@ -537,8 +539,8 @@ final class WallpaperColorPanelNode: ASDisplayNode {
}
}
if updateLayout, let size = self.validLayout {
self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
if updateLayout, let (size, bottomInset) = self.validLayout {
self.updateLayout(size: size, bottomInset: bottomInset, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
}
if let index = self.state.selection {
@ -558,8 +560,8 @@ final class WallpaperColorPanelNode: ASDisplayNode {
}
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, bottomInset)
let condensedLayout = size.width < 375.0
let separatorHeight = UIScreenPixel

View File

@ -100,6 +100,14 @@ class WallpaperGalleryControllerNode: GalleryControllerNode {
override func didLoad() {
super.didLoad()
self.scrollView.isScrollEnabled = false
self.view.interactiveTransitionGestureRecognizerTest = { point in
if point.x < 44.0 {
return false
}
return true
}
//self.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:))))
}
@ -208,10 +216,12 @@ public class WallpaperGalleryController: ViewController {
self.source = source
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .modal
self.title = self.presentationData.strings.WallpaperPreview_Title
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
//self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
var entries: [WallpaperGalleryEntry] = []
@ -323,11 +333,11 @@ public class WallpaperGalleryController: ViewController {
}
func dismiss(forceAway: Bool) {
let completion: () -> Void = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.galleryNode.modalAnimateOut(completion: completion)
// let completion: () -> Void = { [weak self] in
// self?.presentingViewController?.dismiss(animated: false, completion: nil)
// }
self.presentingViewController?.dismiss(animated: true, completion: nil)
//self.galleryNode.modalAnimateOut(completion: completion)
}
private func updateTransaction(entries: [WallpaperGalleryEntry], arguments: WallpaperGalleryItemArguments) -> GalleryPagerTransaction {
@ -603,7 +613,7 @@ public class WallpaperGalleryController: ViewController {
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.galleryNode.modalAnimateIn()
//self.galleryNode.modalAnimateIn()
self.bindCentralItemNode(animated: false, updated: false)
if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
@ -642,7 +652,6 @@ public class WallpaperGalleryController: ViewController {
}
strongSelf.patternPanelNode?.serviceBackgroundColor = serviceColor(for: (initialWallpaper, nil))
strongSelf.patternPanelEnabled = enabled
strongSelf.galleryNode.scrollView.isScrollEnabled = !enabled
if enabled {
strongSelf.patternPanelNode?.updateWallpapers()
strongSelf.patternPanelNode?.didAppear(initialWallpaper: strongSelf.savedPatternWallpaper, intensity: strongSelf.savedPatternIntensity)
@ -670,7 +679,6 @@ public class WallpaperGalleryController: ViewController {
if let strongSelf = self, let (layout, _) = strongSelf.validLayout, let colors = colors, let itemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
strongSelf.patternPanelEnabled = false
strongSelf.colorsPanelEnabled = !strongSelf.colorsPanelEnabled
strongSelf.galleryNode.scrollView.isScrollEnabled = !strongSelf.colorsPanelEnabled
if !strongSelf.colorsPanelEnabled {
strongSelf.colorsPanelNode?.view.endEditing(true)
}
@ -804,8 +812,6 @@ public class WallpaperGalleryController: ViewController {
self.galleryNode.pager.transaction(self.updateTransaction(entries: updatedEntries, arguments: WallpaperGalleryItemArguments(colorPreview: preview, isColorsList: true, patternEnabled: self.patternPanelEnabled)))
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let hadLayout = self.validLayout != nil
@ -823,10 +829,11 @@ public class WallpaperGalleryController: ViewController {
self.galleryNode.containerLayoutUpdated(pagerLayout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
self.overlayNode?.frame = self.galleryNode.bounds
transition.updateFrame(node: self.toolbarNode!, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: 49.0 + layout.intrinsicInsets.bottom)))
self.toolbarNode!.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition)
let toolbarHeight: CGFloat = 66.0
transition.updateFrame(node: self.toolbarNode!, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: toolbarHeight + layout.intrinsicInsets.bottom)))
self.toolbarNode!.updateLayout(size: CGSize(width: layout.size.width, height: toolbarHeight), layout: layout, transition: transition)
var bottomInset = layout.intrinsicInsets.bottom + 49.0
var bottomInset = toolbarHeight + layout.intrinsicInsets.bottom
let currentPatternPanelNode: WallpaperPatternPanelNode
if let patternPanelNode = self.patternPanelNode {
@ -899,6 +906,7 @@ public class WallpaperGalleryController: ViewController {
}
}
let originalBottomInset = bottomInset
var patternPanelFrame = CGRect(x: 0.0, y: layout.size.height, width: layout.size.width, height: panelHeight)
if self.patternPanelEnabled {
patternPanelFrame.origin = CGPoint(x: 0.0, y: layout.size.height - max((layout.inputHeight ?? 0.0) - panelHeight + 44.0, bottomInset) - panelHeight)
@ -906,7 +914,7 @@ public class WallpaperGalleryController: ViewController {
}
transition.updateFrame(node: currentPatternPanelNode, frame: patternPanelFrame)
currentPatternPanelNode.updateLayout(size: patternPanelFrame.size, transition: transition)
currentPatternPanelNode.updateLayout(size: patternPanelFrame.size, bottomInset: originalBottomInset, transition: transition)
var colorsPanelFrame = CGRect(x: 0.0, y: layout.size.height, width: layout.size.width, height: panelHeight)
if self.colorsPanelEnabled {
@ -914,8 +922,10 @@ public class WallpaperGalleryController: ViewController {
bottomInset += panelHeight
}
transition.updateFrame(node: currentColorsPanelNode, frame: colorsPanelFrame)
currentColorsPanelNode.updateLayout(size: colorsPanelFrame.size, transition: transition)
transition.updateFrame(node: currentColorsPanelNode, frame: CGRect(origin: colorsPanelFrame.origin, size: CGSize(width: colorsPanelFrame.width, height: colorsPanelFrame.height + originalBottomInset)))
currentColorsPanelNode.updateLayout(size: colorsPanelFrame.size, bottomInset: originalBottomInset, transition: transition)
self.toolbarNode?.setDoneIsSolid(self.patternPanelEnabled || self.colorsPanelEnabled, transition: transition)
bottomInset += 66.0

View File

@ -95,6 +95,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
private let blurredNode: BlurredImageNode
let cropNode: WallpaperCropNode
private let cancelButtonBackgroundNode: NavigationBackgroundNode
private var cancelButtonNode: HighlightableButtonNode
private let shareButtonBackgroundNode: NavigationBackgroundNode
private var shareButtonNode: HighlightableButtonNode
private var blurButtonNode: WallpaperOptionButtonNode
private var motionButtonNode: WallpaperOptionButtonNode
private var patternButtonNode: WallpaperOptionButtonNode
@ -157,6 +163,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.colorsButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_WallpaperColors, value: .colors(false, [.clear]))
self.cancelButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(white: 0.0, alpha: 0.3))
self.cancelButtonNode = HighlightableButtonNode()
self.cancelButtonNode.insertSubnode(self.cancelButtonBackgroundNode, at: 0)
self.cancelButtonNode.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Common_Cancel, font: Font.semibold(15.0), textColor: .white), for: .normal)
self.cancelButtonNode.titleNode.textShadowColor = UIColor(rgb: 0x000000, alpha: 0.1)
self.shareButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(white: 0.0, alpha: 0.3))
self.shareButtonNode = HighlightableButtonNode()
self.shareButtonNode.insertSubnode(self.shareButtonBackgroundNode, at: 0)
self.shareButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Share"), color: .white), for: .normal)
self.playButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(white: 0.0, alpha: 0.3))
self.playButtonNode = HighlightableButtonNode()
self.playButtonNode.insertSubnode(self.playButtonBackgroundNode, at: 0)
@ -219,12 +236,16 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.addSubnode(self.patternButtonNode)
self.addSubnode(self.colorsButtonNode)
self.addSubnode(self.playButtonNode)
self.addSubnode(self.cancelButtonNode)
self.addSubnode(self.shareButtonNode)
self.blurButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside)
self.motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside)
self.patternButtonNode.addTarget(self, action: #selector(self.togglePattern), forControlEvents: .touchUpInside)
self.colorsButtonNode.addTarget(self, action: #selector(self.toggleColors), forControlEvents: .touchUpInside)
self.playButtonNode.addTarget(self, action: #selector(self.togglePlay), forControlEvents: .touchUpInside)
self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.shareButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
}
deinit {
@ -254,6 +275,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.action?()
}
@objc private func cancelPressed() {
self.dismiss()
}
func setEntry(_ entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource) {
let previousArguments = self.arguments
self.arguments = arguments
@ -443,6 +468,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
} else {
actionSignal = .single(defaultAction)
}
colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3))
case let .image(representations, _):
if let largestSize = largestImageRepresentation(representations) {
contentSize = largestSize.dimensions.cgSize
@ -494,6 +520,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
statusSignal = .single(.Local)
subtitleSignal = .single(nil)
}
colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3))
}
self.cropNode.removeFromSupernode()
case let .asset(asset):
@ -504,6 +531,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
fetchSignal = .complete()
statusSignal = .single(.Local)
subtitleSignal = .single(nil)
colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3))
self.wrapperNode.addSubnode(self.cropNode)
case let .contextResult(result):
var imageDimensions: CGSize?
@ -556,6 +584,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
fetchSignal = .complete()
statusSignal = .single(.Local)
}
colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3))
subtitleSignal = .single(nil)
self.wrapperNode.addSubnode(self.cropNode)
}
@ -628,7 +657,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
strongSelf.motionButtonNode.buttonColor = color
strongSelf.colorsButtonNode.buttonColor = color
strongSelf.playButtonBackgroundNode.updateColor(color: color, transition: .immediate)
if color == UIColor(rgb: 0x000000, alpha: 0.3) {
strongSelf.playButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate)
strongSelf.cancelButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate)
strongSelf.shareButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate)
} else {
strongSelf.playButtonBackgroundNode.updateColor(color: color, transition: .immediate)
strongSelf.cancelButtonBackgroundNode.updateColor(color: color, transition: .immediate)
strongSelf.shareButtonBackgroundNode.updateColor(color: color, transition: .immediate)
}
}))
} else if self.arguments.patternEnabled != previousArguments.patternEnabled {
self.patternButtonNode.isSelected = self.arguments.patternEnabled
@ -901,9 +938,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let buttonSpacing: CGFloat = 18.0
let leftButtonFrame = CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0 - buttonSize.width - buttonSpacing) + offset.x, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize)
let centerButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0) + offset.x, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize)
let rightButtonFrame = CGRect(origin: CGPoint(x: ceil(layout.size.width / 2.0 + buttonSpacing) + offset.x, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize)
let toolbarHeight: CGFloat = 66.0
let leftButtonFrame = CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0 - buttonSize.width - buttonSpacing) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize)
let centerButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize)
let rightButtonFrame = CGRect(origin: CGPoint(x: ceil(layout.size.width / 2.0 + buttonSpacing) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize)
var patternAlpha: CGFloat = 0.0
var patternFrame = centerButtonFrame
@ -919,7 +958,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let playFrame = CGRect(origin: CGPoint(x: centerButtonFrame.midX - playButtonSize.width / 2.0, y: centerButtonFrame.midY - playButtonSize.height / 2.0), size: playButtonSize)
var playAlpha: CGFloat = 0.0
var cancelSize = self.cancelButtonNode.measure(layout.size)
cancelSize.width += 16.0
cancelSize.height = 28.0
let cancelFrame = CGRect(origin: CGPoint(x: 16.0 + offset.x, y: 16.0), size: cancelSize)
let shareFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - 28.0 + offset.x, y: 16.0), size: CGSize(width: 28.0, height: 28.0))
let centerOffset: CGFloat = 32.0
if let entry = self.entry {
@ -1019,10 +1065,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.playButtonBackgroundNode.update(size: playFrame.size, cornerRadius: playFrame.size.height / 2.0, transition: transition)
transition.updateAlpha(node: self.playButtonNode, alpha: playAlpha * alpha)
transition.updateSublayerTransformScale(node: self.playButtonNode, scale: max(0.1, playAlpha))
transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame)
transition.updateFrame(node: self.cancelButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: cancelFrame.size))
self.cancelButtonBackgroundNode.update(size: cancelFrame.size, cornerRadius: cancelFrame.size.height / 2.0, transition: transition)
transition.updateFrame(node: self.shareButtonNode, frame: shareFrame)
transition.updateFrame(node: self.shareButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: shareFrame.size))
self.shareButtonBackgroundNode.update(size: shareFrame.size, cornerRadius: shareFrame.size.height / 2.0, transition: transition)
}
private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) {
let bottomInset: CGFloat = 115.0
let bottomInset: CGFloat = 132.0
if self.patternButtonNode.isSelected || self.colorsButtonNode.isSelected {
//bottomInset = 350.0

View File

@ -31,13 +31,13 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
}
}
private let cancelButton = HighlightTrackingButtonNode()
private let cancelHighlightBackgroundNode = ASDisplayNode()
private let doneButton = HighlightTrackingButtonNode()
private let doneHighlightBackgroundNode = ASDisplayNode()
private let backgroundNode = NavigationBackgroundNode(color: .clear)
private let separatorNode = ASDisplayNode()
private let topSeparatorNode = ASDisplayNode()
private let doneButtonBackgroundNode: NavigationBackgroundNode
private let doneButtonBackgroundView: UIVisualEffectView
private let doneButtonTitleNode: ImmediateTextNode
private let doneButtonSolidBackgroundNode: ASDisplayNode
private let doneButtonSolidTitleNode: ImmediateTextNode
var cancel: (() -> Void)?
var done: (() -> Void)?
@ -48,46 +48,82 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
self.cancelButtonType = cancelButtonType
self.doneButtonType = doneButtonType
self.cancelHighlightBackgroundNode.alpha = 0.0
self.doneHighlightBackgroundNode.alpha = 0.0
self.doneButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45))
self.doneButtonBackgroundNode.cornerRadius = 14.0
let blurEffect: UIBlurEffect
if #available(iOS 13.0, *) {
blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
} else {
blurEffect = UIBlurEffect(style: .light)
}
self.doneButtonBackgroundView = UIVisualEffectView(effect: blurEffect)
self.doneButtonBackgroundView.clipsToBounds = true
self.doneButtonBackgroundView.layer.cornerRadius = 14.0
self.doneButtonBackgroundView.isUserInteractionEnabled = false
self.doneButtonTitleNode = ImmediateTextNode()
self.doneButtonTitleNode.displaysAsynchronously = false
self.doneButtonTitleNode.textShadowColor = UIColor(rgb: 0x000000, alpha: 0.1)
self.doneButtonTitleNode.isUserInteractionEnabled = false
self.doneButtonSolidBackgroundNode = ASDisplayNode()
self.doneButtonSolidBackgroundNode.alpha = 0.0
self.doneButtonSolidBackgroundNode.clipsToBounds = true
self.doneButtonSolidBackgroundNode.layer.cornerRadius = 14.0
if #available(iOS 13.0, *) {
self.doneButtonSolidBackgroundNode.layer.cornerCurve = .continuous
}
self.doneButtonSolidBackgroundNode.isUserInteractionEnabled = false
self.doneButtonSolidTitleNode = ImmediateTextNode()
self.doneButtonSolidTitleNode.alpha = 0.0
self.doneButtonSolidTitleNode.displaysAsynchronously = false
self.doneButtonSolidTitleNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.cancelHighlightBackgroundNode)
self.addSubnode(self.cancelButton)
self.addSubnode(self.doneHighlightBackgroundNode)
self.addSubnode(self.doneButtonBackgroundNode)
self.addSubnode(self.doneButtonTitleNode)
self.addSubnode(self.doneButtonSolidBackgroundNode)
self.addSubnode(self.doneButtonSolidTitleNode)
self.addSubnode(self.doneButton)
self.addSubnode(self.separatorNode)
self.addSubnode(self.topSeparatorNode)
self.updateThemeAndStrings(theme: theme, strings: strings)
self.cancelButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.cancelHighlightBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.cancelHighlightBackgroundNode.alpha = 1.0
} else {
strongSelf.cancelHighlightBackgroundNode.alpha = 0.0
strongSelf.cancelHighlightBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
}
self.doneButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.doneHighlightBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.doneHighlightBackgroundNode.alpha = 1.0
if strongSelf.isSolid {
strongSelf.doneButtonSolidBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.doneButtonSolidBackgroundNode.alpha = 0.55
strongSelf.doneButtonSolidTitleNode.layer.removeAnimation(forKey: "opacity")
strongSelf.doneButtonSolidTitleNode.alpha = 0.55
} else {
strongSelf.doneButtonBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.doneButtonBackgroundNode.alpha = 0.55
strongSelf.doneButtonTitleNode.layer.removeAnimation(forKey: "opacity")
strongSelf.doneButtonTitleNode.alpha = 0.55
}
} else {
strongSelf.doneHighlightBackgroundNode.alpha = 0.0
strongSelf.doneHighlightBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
if strongSelf.isSolid {
strongSelf.doneButtonSolidBackgroundNode.alpha = 1.0
strongSelf.doneButtonSolidBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.doneButtonSolidTitleNode.alpha = 1.0
strongSelf.doneButtonSolidTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
} else {
strongSelf.doneButtonBackgroundNode.alpha = 1.0
strongSelf.doneButtonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
strongSelf.doneButtonTitleNode.alpha = 1.0
strongSelf.doneButtonTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
}
}
}
}
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside)
}
@ -96,25 +132,26 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
self.doneButton.isUserInteractionEnabled = enabled
}
private var isSolid = false
func setDoneIsSolid(_ isSolid: Bool, transition: ContainedViewLayoutTransition) {
guard self.isSolid != isSolid else {
return
}
self.isSolid = isSolid
transition.updateAlpha(node: self.doneButtonBackgroundNode, alpha: isSolid ? 0.0 : 1.0)
transition.updateAlpha(node: self.doneButtonSolidBackgroundNode, alpha: isSolid ? 1.0 : 0.0)
transition.updateAlpha(node: self.doneButtonTitleNode, alpha: isSolid ? 0.0 : 1.0)
transition.updateAlpha(node: self.doneButtonSolidTitleNode, alpha: isSolid ? 1.0 : 0.0)
}
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.backgroundNode.updateColor(color: theme.rootController.tabBar.backgroundColor, transition: .immediate)
self.separatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
self.topSeparatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
self.cancelHighlightBackgroundNode.backgroundColor = theme.list.itemHighlightedBackgroundColor
self.doneHighlightBackgroundNode.backgroundColor = theme.list.itemHighlightedBackgroundColor
let cancelTitle: String
switch self.cancelButtonType {
case .cancel:
cancelTitle = strings.Common_Cancel
case .discard:
cancelTitle = strings.WallpaperPreview_PatternPaternDiscard
}
let doneTitle: String
switch self.doneButtonType {
case .set:
doneTitle = strings.Wallpaper_Set
doneTitle = strings.Wallpaper_ApplyForAll
case .proceed:
doneTitle = strings.Theme_Colors_Proceed
case .apply:
@ -123,19 +160,30 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
doneTitle = ""
self.doneButton.isUserInteractionEnabled = false
}
self.cancelButton.setTitle(cancelTitle, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
self.doneButton.setTitle(doneTitle, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
self.doneButtonTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: .white)
self.doneButtonSolidBackgroundNode.backgroundColor = theme.list.itemCheckColors.fillColor
self.doneButtonSolidTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor)
}
func updateLayout(size: CGSize, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.cancelButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: floor(size.width / 2.0), height: size.height))
self.cancelHighlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: floor(size.width / 2.0), height: size.height))
self.doneButton.frame = CGRect(origin: CGPoint(x: floor(size.width / 2.0), y: 0.0), size: CGSize(width: size.width - floor(size.width / 2.0), height: size.height))
self.doneHighlightBackgroundNode.frame = CGRect(origin: CGPoint(x: floor(size.width / 2.0), y: 0.0), size: CGSize(width: size.width - floor(size.width / 2.0), height: size.height))
self.separatorNode.frame = CGRect(origin: CGPoint(x: floor(size.width / 2.0), y: 0.0), size: CGSize(width: UIScreenPixel, height: size.height + layout.intrinsicInsets.bottom))
self.topSeparatorNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: UIScreenPixel))
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.backgroundNode.update(size: CGSize(width: size.width, height: size.height + layout.intrinsicInsets.bottom), transition: .immediate)
let inset: CGFloat = 16.0
let buttonHeight: CGFloat = 50.0
let doneFrame = CGRect(origin: CGPoint(x: inset, y: 2.0), size: CGSize(width: size.width - inset * 2.0, height: buttonHeight))
self.doneButton.frame = doneFrame
self.doneButtonBackgroundNode.frame = doneFrame
self.doneButtonBackgroundNode.update(size: doneFrame.size, cornerRadius: 14.0, transition: transition)
self.doneButtonBackgroundView.frame = doneFrame
self.doneButtonSolidBackgroundNode.frame = doneFrame
let doneTitleSize = self.doneButtonTitleNode.updateLayout(doneFrame.size)
self.doneButtonTitleNode.frame = CGRect(origin: CGPoint(x: doneFrame.minX + floorToScreenPixels((doneFrame.width - doneTitleSize.width) / 2.0), y: doneFrame.minY + floorToScreenPixels((doneFrame.height - doneTitleSize.height) / 2.0)), size: doneTitleSize)
let _ = self.doneButtonSolidTitleNode.updateLayout(doneFrame.size)
self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame
}
@objc func cancelPressed() {

View File

@ -38,7 +38,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
private let backgroundNode: NavigationBackgroundNode
private let checkNode: CheckNode
private let colorNode: ASImageNode
private let textNode: ASTextNode
private let textNode: ImmediateTextNode
private var textSize: CGSize?
@ -73,16 +73,17 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
self._value = value
self.title = title
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.3))
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xffffff, alpha: 0.4))
self.backgroundNode.cornerRadius = 14.0
self.checkNode = CheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5))
self.checkNode = CheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: true, borderWidth: 1.5))
self.checkNode.isUserInteractionEnabled = false
self.colorNode = ASImageNode()
self.textNode = ASTextNode()
self.textNode = ImmediateTextNode()
self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white)
self.textNode.textShadowColor = UIColor(rgb: 0x000000, alpha: 0.1)
super.init()
@ -139,7 +140,11 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) {
didSet {
self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate)
if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) {
self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate)
} else {
self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate)
}
}
}
@ -221,7 +226,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
}
override func measure(_ constrainedSize: CGSize) -> CGSize {
let size = self.textNode.measure(constrainedSize)
let size = self.textNode.updateLayout(constrainedSize)
self.textSize = size
return CGSize(width: ceil(size.width) + 48.0, height: 30.0)
}

View File

@ -221,7 +221,7 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
}
}
private var validLayout: CGSize?
private var validLayout: (CGSize, CGFloat)?
var patternChanged: ((TelegramWallpaper?, Int32?, Bool) -> Void)?
@ -401,8 +401,8 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: self.labelNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
self.labelNode.attributedText = NSAttributedString(string: self.labelNode.attributedText?.string ?? "", font: Font.regular(14.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
if let (size, bottomInset) = self.validLayout {
self.updateLayout(size: size, bottomInset: bottomInset, transition: .immediate)
}
}
@ -478,11 +478,12 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
}
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, bottomInset)
transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition)
let backgroundFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height + bottomInset)
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
self.backgroundNode.update(size: backgroundFrame.size, transition: transition)
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: UIScreenPixel))
let titleSize = self.titleNode.updateLayout(self.bounds.size)

View File

@ -55,7 +55,7 @@ private enum UsernameSetupEntryId: Hashable {
private enum UsernameSetupEntry: ItemListNodeEntry {
case publicLinkHeader(PresentationTheme, String)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String, Bool)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String, String)
case publicLinkInfo(PresentationTheme, String)
@ -99,8 +99,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
} else {
return false
}
case let .editablePublicLink(lhsTheme, lhsStrings, lhsPrefix, lhsCurrentText, lhsText):
if case let .editablePublicLink(rhsTheme, rhsStrings, rhsPrefix, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPrefix == rhsPrefix, lhsCurrentText == rhsCurrentText, lhsText == rhsText {
case let .editablePublicLink(lhsTheme, lhsStrings, lhsPrefix, lhsCurrentText, lhsText, lhsEnabled):
if case let .editablePublicLink(rhsTheme, rhsStrings, rhsPrefix, rhsCurrentText, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPrefix == rhsPrefix, lhsCurrentText == rhsCurrentText, lhsText == rhsText, lhsEnabled == rhsEnabled {
return true
} else {
return false
@ -197,8 +197,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
switch self {
case let .publicLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .editablePublicLink(theme, _, prefix, currentText, text):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, spacing: 10.0, clearType: .always, tag: UsernameEntryTag.username, sectionId: self.section, textUpdated: { updatedText in
case let .editablePublicLink(theme, _, prefix, currentText, text, enabled):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: enabled ? prefix : "", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, spacing: 10.0, clearType: enabled ? .always : .none, enabled: enabled, tag: UsernameEntryTag.username, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePublicLinkText(currentText, updatedText)
}, action: {
})
@ -292,7 +292,7 @@ private struct UsernameSetupControllerState: Equatable {
}
}
private func usernameSetupControllerEntries(presentationData: PresentationData, view: PeerView, state: UsernameSetupControllerState, temporaryOrder: [String]?) -> [UsernameSetupEntry] {
private func usernameSetupControllerEntries(presentationData: PresentationData, view: PeerView, state: UsernameSetupControllerState, temporaryOrder: [String]?, mode: UsernameSetupMode) -> [UsernameSetupEntry] {
var entries: [UsernameSetupEntry] = []
if let peer = view.peers[view.peerId] as? TelegramUser {
@ -308,7 +308,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
}
entries.append(.publicLinkHeader(presentationData.theme, presentationData.strings.Username_Username))
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Username_Title, peer.editableUsername, currentUsername))
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Username_Title, peer.editableUsername, currentUsername, mode == .account))
if let status = state.addressNameValidationStatus {
let statusText: String
switch status {
@ -348,16 +348,18 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
entries.append(.publicLinkStatus(presentationData.theme, currentUsername, status, statusText, currentUsername))
}
var infoText = presentationData.strings.Username_Help
let otherUsernames = peer.usernames.filter { !$0.flags.contains(.isEditable) }
if otherUsernames.isEmpty {
infoText += "\n\n"
let hintText = presentationData.strings.Username_LinkHint(currentUsername.replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "")).string.replacingOccurrences(of: "]", with: "]()")
infoText += hintText
if case .bot = mode {
entries.append(.publicLinkInfo(presentationData.theme, "This username cannot be edited."))
} else {
var infoText = presentationData.strings.Username_Help
if otherUsernames.isEmpty {
infoText += "\n\n"
let hintText = presentationData.strings.Username_LinkHint(currentUsername.replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "")).string.replacingOccurrences(of: "]", with: "]()")
infoText += hintText
}
entries.append(.publicLinkInfo(presentationData.theme, infoText))
}
entries.append(.publicLinkInfo(presentationData.theme, infoText))
if !otherUsernames.isEmpty {
entries.append(.additionalLinkHeader(presentationData.theme, presentationData.strings.Username_LinksOrder))
@ -382,14 +384,26 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
i += 1
}
entries.append(.additionalLinkInfo(presentationData.theme, presentationData.strings.Username_LinksOrderInfo))
let text: String
switch mode {
case .account:
text = presentationData.strings.Username_LinksOrderInfo
case .bot:
text = presentationData.strings.Username_BotLinksOrderInfo
}
entries.append(.additionalLinkInfo(presentationData.theme, text))
}
}
return entries
}
public func usernameSetupController(context: AccountContext) -> ViewController {
public enum UsernameSetupMode: Equatable {
case account
case bot(PeerId)
}
public func usernameSetupController(context: AccountContext, mode: UsernameSetupMode = .account) -> ViewController {
let statePromise = ValuePromise(UsernameSetupControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: UsernameSetupControllerState())
let updateState: ((UsernameSetupControllerState) -> UsernameSetupControllerState) -> Void = { f in
@ -408,6 +422,17 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
let updateAddressNameDisposable = MetaDisposable()
actionsDisposable.add(updateAddressNameDisposable)
let peerId: PeerId
let domain: AddressNameDomain
switch mode {
case .account:
domain = .account
peerId = context.account.peerId
case let .bot(botPeerId):
domain = .bot(botPeerId)
peerId = botPeerId
}
let arguments = UsernameSetupControllerArguments(account: context.account, updatePublicLinkText: { currentText, text in
if text.isEmpty {
checkAddressNameDisposable.set(nil)
@ -424,7 +449,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
return state.withUpdatedEditingPublicLinkText(text)
}
checkAddressNameDisposable.set((context.engine.peers.validateAddressNameInteractive(domain: .account, name: text)
checkAddressNameDisposable.set((context.engine.peers.validateAddressNameInteractive(domain: domain, name: text)
|> deliverOnMainQueue).start(next: { result in
updateState { state in
return state.withUpdatedAddressNameValidationStatus(result)
@ -432,7 +457,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
}))
}
}, shareLink: {
let _ = (context.account.postbox.loadedPeerWithId(context.account.peerId)
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> deliverOnMainQueue).start(next: { peer in
var currentAddressName: String = peer.addressName ?? ""
@ -456,7 +481,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
dismissInputImpl?()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_ActivateAlertTitle, text: presentationData.strings.Username_ActivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_ActivateAlertShow, action: {
let _ = (context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: true)
let _ = (context.engine.peers.toggleAddressNameActive(domain: domain, name: name, active: true)
|> deliverOnMainQueue).start(error: { error in
let errorText: String
switch error {
@ -472,7 +497,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
dismissInputImpl?()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_DeactivateAlertTitle, text: presentationData.strings.Username_DeactivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_DeactivateAlertHide, action: {
let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: false).start()
let _ = context.engine.peers.toggleAddressNameActive(domain: domain, name: name, active: false).start()
})]), nil)
}, openAuction: { username in
dismissInputImpl?()
@ -481,8 +506,8 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
})
let temporaryOrder = Promise<[String]?>(nil)
let peerView = context.account.viewTracker.peerView(context.account.peerId)
let peerView = context.account.viewTracker.peerView(peerId)
|> deliverOnMainQueue
let signal = combineLatest(
@ -522,7 +547,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
}
if let updatedAddressNameValue = updatedAddressNameValue {
updateAddressNameDisposable.set((context.engine.peers.updateAddressName(domain: .account, name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue)
updateAddressNameDisposable.set((context.engine.peers.updateAddressName(domain: domain, name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue)
|> deliverOnMainQueue).start(error: { _ in
updateState { state in
return state.withUpdatedUpdatingAddressName(false)
@ -545,7 +570,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
})
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Username_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: usernameSetupControllerEntries(presentationData: presentationData, view: view, state: state, temporaryOrder: temporaryOrder), style: .blocks, focusItemTag: UsernameEntryTag.username, animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: usernameSetupControllerEntries(presentationData: presentationData, view: view, state: state, temporaryOrder: temporaryOrder, mode: mode), style: .blocks, focusItemTag: mode == .account ? UsernameEntryTag.username : nil, animateChanges: true)
return (controllerState, (listState, arguments))
} |> afterDisposed {
@ -667,7 +692,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
break
}
}
let _ = (context.engine.peers.reorderAddressNames(domain: .account, names: currentUsernames)
let _ = (context.engine.peers.reorderAddressNames(domain: domain, names: currentUsernames)
|> deliverOnMainQueue).start(completed: {
temporaryOrder.set(.single(nil))
})

View File

@ -2,6 +2,7 @@
public enum Api {
public enum account {}
public enum auth {}
public enum bots {}
public enum channels {}
public enum communities {}
public enum community {}
@ -991,11 +992,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) }
dict[-1521934870] = { return Api.auth.SentCodeType.parse_sentCodeTypeSetUpEmailRequired($0) }
dict[-1073693790] = { return Api.auth.SentCodeType.parse_sentCodeTypeSms($0) }
dict[-391678544] = { return Api.bots.BotInfo.parse_botInfo($0) }
dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) }
dict[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) }
dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) }
dict[-266911767] = { return Api.channels.ChannelParticipants.parse_channelParticipantsNotModified($0) }
dict[-191450938] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) }
dict[-414818125] = { return Api.communities.CommunityUpdates.parse_communityUpdates($0) }
dict[1805101290] = { return Api.communities.ExportedCommunityInvite.parse_exportedCommunityInvite($0) }
dict[-2662489] = { return Api.communities.ExportedInvites.parse_exportedInvites($0) }
dict[988463765] = { return Api.community.CommunityInvite.parse_communityInvite($0) }
@ -1789,6 +1792,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.auth.SentCodeType:
_1.serialize(buffer, boxed)
case let _1 as Api.bots.BotInfo:
_1.serialize(buffer, boxed)
case let _1 as Api.channels.AdminLogResults:
_1.serialize(buffer, boxed)
case let _1 as Api.channels.ChannelParticipant:
@ -1797,6 +1802,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.channels.SendAsPeers:
_1.serialize(buffer, boxed)
case let _1 as Api.communities.CommunityUpdates:
_1.serialize(buffer, boxed)
case let _1 as Api.communities.ExportedCommunityInvite:
_1.serialize(buffer, boxed)
case let _1 as Api.communities.ExportedInvites:

View File

@ -590,6 +590,50 @@ public extension Api.auth {
}
}
public extension Api.bots {
enum BotInfo: TypeConstructorDescription {
case botInfo(name: String, about: String, description: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .botInfo(let name, let about, let description):
if boxed {
buffer.appendInt32(-391678544)
}
serializeString(name, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .botInfo(let name, let about, let description):
return ("botInfo", [("name", name as Any), ("about", about as Any), ("description", description as Any)])
}
}
public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? {
var _1: String?
_1 = parseString(reader)
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.bots.BotInfo.botInfo(name: _1!, about: _2!, description: _3!)
}
else {
return nil
}
}
}
}
public extension Api.channels {
enum AdminLogResults: TypeConstructorDescription {
case adminLogResults(events: [Api.ChannelAdminLogEvent], chats: [Api.Chat], users: [Api.User])
@ -850,6 +894,68 @@ public extension Api.channels {
}
}
public extension Api.communities {
enum CommunityUpdates: TypeConstructorDescription {
case communityUpdates(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .communityUpdates(let missingPeers, let chats, let users):
if boxed {
buffer.appendInt32(-414818125)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(missingPeers.count))
for item in missingPeers {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .communityUpdates(let missingPeers, let chats, let users):
return ("communityUpdates", [("missingPeers", missingPeers as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_communityUpdates(_ reader: BufferReader) -> CommunityUpdates? {
var _1: [Api.Peer]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.communities.CommunityUpdates.communityUpdates(missingPeers: _1!, chats: _2!, users: _3!)
}
else {
return nil
}
}
}
}
public extension Api.communities {
enum ExportedCommunityInvite: TypeConstructorDescription {
case exportedCommunityInvite(filter: Api.DialogFilter, invite: Api.ExportedCommunityInvite)
@ -1072,115 +1178,3 @@ public extension Api.community {
}
}
public extension Api.contacts {
enum Blocked: TypeConstructorDescription {
case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User])
case blockedSlice(count: Int32, blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .blocked(let blocked, let chats, let users):
if boxed {
buffer.appendInt32(182326673)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(blocked.count))
for item in blocked {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .blockedSlice(let count, let blocked, let chats, let users):
if boxed {
buffer.appendInt32(-513392236)
}
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(blocked.count))
for item in blocked {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .blocked(let blocked, let chats, let users):
return ("blocked", [("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)])
case .blockedSlice(let count, let blocked, let chats, let users):
return ("blockedSlice", [("count", count as Any), ("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_blocked(_ reader: BufferReader) -> Blocked? {
var _1: [Api.PeerBlocked]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self)
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.contacts.Blocked.blocked(blocked: _1!, chats: _2!, users: _3!)
}
else {
return nil
}
}
public static func parse_blockedSlice(_ reader: BufferReader) -> Blocked? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.PeerBlocked]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self)
}
var _3: [Api.Chat]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.contacts.Blocked.blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,115 @@
public extension Api.contacts {
enum Blocked: TypeConstructorDescription {
case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User])
case blockedSlice(count: Int32, blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .blocked(let blocked, let chats, let users):
if boxed {
buffer.appendInt32(182326673)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(blocked.count))
for item in blocked {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .blockedSlice(let count, let blocked, let chats, let users):
if boxed {
buffer.appendInt32(-513392236)
}
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(blocked.count))
for item in blocked {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .blocked(let blocked, let chats, let users):
return ("blocked", [("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)])
case .blockedSlice(let count, let blocked, let chats, let users):
return ("blockedSlice", [("count", count as Any), ("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_blocked(_ reader: BufferReader) -> Blocked? {
var _1: [Api.PeerBlocked]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self)
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.contacts.Blocked.blocked(blocked: _1!, chats: _2!, users: _3!)
}
else {
return nil
}
}
public static func parse_blockedSlice(_ reader: BufferReader) -> Blocked? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.PeerBlocked]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self)
}
var _3: [Api.Chat]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.contacts.Blocked.blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!)
}
else {
return nil
}
}
}
}
public extension Api.contacts {
enum Contacts: TypeConstructorDescription {
case contacts(contacts: [Api.Contact], savedCount: Int32, users: [Api.User])
@ -1264,69 +1376,3 @@ public extension Api.help {
}
}
public extension Api.help {
enum UserInfo: TypeConstructorDescription {
case userInfo(message: String, entities: [Api.MessageEntity], author: String, date: Int32)
case userInfoEmpty
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .userInfo(let message, let entities, let author, let date):
if boxed {
buffer.appendInt32(32192344)
}
serializeString(message, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(entities.count))
for item in entities {
item.serialize(buffer, true)
}
serializeString(author, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
case .userInfoEmpty:
if boxed {
buffer.appendInt32(-206688531)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .userInfo(let message, let entities, let author, let date):
return ("userInfo", [("message", message as Any), ("entities", entities as Any), ("author", author as Any), ("date", date as Any)])
case .userInfoEmpty:
return ("userInfoEmpty", [])
}
}
public static func parse_userInfo(_ reader: BufferReader) -> UserInfo? {
var _1: String?
_1 = parseString(reader)
var _2: [Api.MessageEntity]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
}
var _3: String?
_3 = parseString(reader)
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.help.UserInfo.userInfo(message: _1!, entities: _2!, author: _3!, date: _4!)
}
else {
return nil
}
}
public static func parse_userInfoEmpty(_ reader: BufferReader) -> UserInfo? {
return Api.help.UserInfo.userInfoEmpty
}
}
}

View File

@ -1,3 +1,69 @@
public extension Api.help {
enum UserInfo: TypeConstructorDescription {
case userInfo(message: String, entities: [Api.MessageEntity], author: String, date: Int32)
case userInfoEmpty
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .userInfo(let message, let entities, let author, let date):
if boxed {
buffer.appendInt32(32192344)
}
serializeString(message, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(entities.count))
for item in entities {
item.serialize(buffer, true)
}
serializeString(author, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
case .userInfoEmpty:
if boxed {
buffer.appendInt32(-206688531)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .userInfo(let message, let entities, let author, let date):
return ("userInfo", [("message", message as Any), ("entities", entities as Any), ("author", author as Any), ("date", date as Any)])
case .userInfoEmpty:
return ("userInfoEmpty", [])
}
}
public static func parse_userInfo(_ reader: BufferReader) -> UserInfo? {
var _1: String?
_1 = parseString(reader)
var _2: [Api.MessageEntity]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
}
var _3: String?
_3 = parseString(reader)
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.help.UserInfo.userInfo(message: _1!, entities: _2!, author: _3!, date: _4!)
}
else {
return nil
}
}
public static func parse_userInfoEmpty(_ reader: BufferReader) -> UserInfo? {
return Api.help.UserInfo.userInfoEmpty
}
}
}
public extension Api.messages {
enum AffectedFoundMessages: TypeConstructorDescription {
case affectedFoundMessages(pts: Int32, ptsCount: Int32, offset: Int32, messages: [Int32])
@ -1312,87 +1378,3 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum FeaturedStickers: TypeConstructorDescription {
case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64])
case featuredStickersNotModified(count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
if boxed {
buffer.appendInt32(-1103615738)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sets.count))
for item in sets {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(unread.count))
for item in unread {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
case .featuredStickersNotModified(let count):
if boxed {
buffer.appendInt32(-958657434)
}
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)])
case .featuredStickersNotModified(let count):
return ("featuredStickersNotModified", [("count", count as Any)])
}
}
public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: [Api.StickerSetCovered]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
}
var _5: [Int64]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!)
}
else {
return nil
}
}
public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,87 @@
public extension Api.messages {
enum FeaturedStickers: TypeConstructorDescription {
case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64])
case featuredStickersNotModified(count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
if boxed {
buffer.appendInt32(-1103615738)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sets.count))
for item in sets {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(unread.count))
for item in unread {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
case .featuredStickersNotModified(let count):
if boxed {
buffer.appendInt32(-958657434)
}
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)])
case .featuredStickersNotModified(let count):
return ("featuredStickersNotModified", [("count", count as Any)])
}
}
public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: [Api.StickerSetCovered]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
}
var _5: [Int64]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!)
}
else {
return nil
}
}
public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum ForumTopics: TypeConstructorDescription {
case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32)
@ -1456,57 +1540,3 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum StickerSetInstallResult: TypeConstructorDescription {
case stickerSetInstallResultArchive(sets: [Api.StickerSetCovered])
case stickerSetInstallResultSuccess
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stickerSetInstallResultArchive(let sets):
if boxed {
buffer.appendInt32(904138920)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sets.count))
for item in sets {
item.serialize(buffer, true)
}
break
case .stickerSetInstallResultSuccess:
if boxed {
buffer.appendInt32(946083368)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stickerSetInstallResultArchive(let sets):
return ("stickerSetInstallResultArchive", [("sets", sets as Any)])
case .stickerSetInstallResultSuccess:
return ("stickerSetInstallResultSuccess", [])
}
}
public static func parse_stickerSetInstallResultArchive(_ reader: BufferReader) -> StickerSetInstallResult? {
var _1: [Api.StickerSetCovered]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
}
let _c1 = _1 != nil
if _c1 {
return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(sets: _1!)
}
else {
return nil
}
}
public static func parse_stickerSetInstallResultSuccess(_ reader: BufferReader) -> StickerSetInstallResult? {
return Api.messages.StickerSetInstallResult.stickerSetInstallResultSuccess
}
}
}

View File

@ -1,3 +1,57 @@
public extension Api.messages {
enum StickerSetInstallResult: TypeConstructorDescription {
case stickerSetInstallResultArchive(sets: [Api.StickerSetCovered])
case stickerSetInstallResultSuccess
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stickerSetInstallResultArchive(let sets):
if boxed {
buffer.appendInt32(904138920)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sets.count))
for item in sets {
item.serialize(buffer, true)
}
break
case .stickerSetInstallResultSuccess:
if boxed {
buffer.appendInt32(946083368)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stickerSetInstallResultArchive(let sets):
return ("stickerSetInstallResultArchive", [("sets", sets as Any)])
case .stickerSetInstallResultSuccess:
return ("stickerSetInstallResultSuccess", [])
}
}
public static func parse_stickerSetInstallResultArchive(_ reader: BufferReader) -> StickerSetInstallResult? {
var _1: [Api.StickerSetCovered]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
}
let _c1 = _1 != nil
if _c1 {
return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(sets: _1!)
}
else {
return nil
}
}
public static func parse_stickerSetInstallResultSuccess(_ reader: BufferReader) -> StickerSetInstallResult? {
return Api.messages.StickerSetInstallResult.stickerSetInstallResultSuccess
}
}
}
public extension Api.messages {
enum Stickers: TypeConstructorDescription {
case stickers(hash: Int64, stickers: [Api.Document])

View File

@ -1819,15 +1819,17 @@ public extension Api.functions.bots {
}
}
public extension Api.functions.bots {
static func getBotInfo(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[String]>) {
static func getBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.bots.BotInfo>) {
let buffer = Buffer()
buffer.appendInt32(1978405606)
buffer.appendInt32(-589753091)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {bot!.serialize(buffer, true)}
serializeString(langCode, buffer: buffer, boxed: false)
return (FunctionDescription(name: "bots.getBotInfo", parameters: [("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [String]? in
return (FunctionDescription(name: "bots.getBotInfo", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.BotInfo? in
let reader = BufferReader(buffer)
var result: [String]?
if let _ = reader.readInt32() {
result = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
var result: Api.bots.BotInfo?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.bots.BotInfo
}
return result
})
@ -1848,6 +1850,26 @@ public extension Api.functions.bots {
})
}
}
public extension Api.functions.bots {
static func reorderUsernames(bot: Api.InputUser, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1760972350)
bot.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(order.count))
for item in order {
serializeString(item, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "bots.reorderUsernames", parameters: [("bot", String(describing: bot)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.bots {
static func resetBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
@ -1932,14 +1954,16 @@ public extension Api.functions.bots {
}
}
public extension Api.functions.bots {
static func setBotInfo(flags: Int32, langCode: String, about: String?, description: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
static func setBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String, name: String?, about: String?, description: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1553604742)
buffer.appendInt32(282013987)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {bot!.serialize(buffer, true)}
serializeString(langCode, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {serializeString(name!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {serializeString(about!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "bots.setBotInfo", parameters: [("flags", String(describing: flags)), ("langCode", String(describing: langCode)), ("about", String(describing: about)), ("description", String(describing: description))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
return (FunctionDescription(name: "bots.setBotInfo", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("langCode", String(describing: langCode)), ("name", String(describing: name)), ("about", String(describing: about)), ("description", String(describing: description))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
@ -1965,6 +1989,23 @@ public extension Api.functions.bots {
})
}
}
public extension Api.functions.bots {
static func toggleUsername(bot: Api.InputUser, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(87861619)
bot.serialize(buffer, true)
serializeString(username, buffer: buffer, boxed: false)
active.serialize(buffer, true)
return (FunctionDescription(name: "bots.toggleUsername", parameters: [("bot", String(describing: bot)), ("username", String(describing: username)), ("active", String(describing: active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.channels {
static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
@ -2986,6 +3027,21 @@ public extension Api.functions.communities {
})
}
}
public extension Api.functions.communities {
static func getCommunityUpdates(community: Api.InputCommunity) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.communities.CommunityUpdates>) {
let buffer = Buffer()
buffer.appendInt32(693556789)
community.serialize(buffer, true)
return (FunctionDescription(name: "communities.getCommunityUpdates", parameters: [("community", String(describing: community))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.communities.CommunityUpdates? in
let reader = BufferReader(buffer)
var result: Api.communities.CommunityUpdates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.communities.CommunityUpdates
}
return result
})
}
}
public extension Api.functions.communities {
static func getExportedInvites(community: Api.InputCommunity) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.communities.ExportedInvites>) {
let buffer = Buffer()
@ -3001,6 +3057,21 @@ public extension Api.functions.communities {
})
}
}
public extension Api.functions.communities {
static func hideCommunityUpdates(community: Api.InputCommunity) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(224889775)
community.serialize(buffer, true)
return (FunctionDescription(name: "communities.hideCommunityUpdates", parameters: [("community", String(describing: community))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.communities {
static func joinCommunityInvite(slug: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
@ -3391,21 +3462,6 @@ public extension Api.functions.contacts {
})
}
}
public extension Api.functions.folders {
static func deleteFolder(folderId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(472471681)
serializeInt32(folderId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "folders.deleteFolder", parameters: [("folderId", String(describing: folderId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.folders {
static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
@ -7959,12 +8015,13 @@ public extension Api.functions.photos {
}
}
public extension Api.functions.photos {
static func updateProfilePhoto(flags: Int32, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
static func updateProfilePhoto(flags: Int32, bot: Api.InputUser?, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
let buffer = Buffer()
buffer.appendInt32(473782614)
buffer.appendInt32(166207545)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {bot!.serialize(buffer, true)}
id.serialize(buffer, true)
return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
let reader = BufferReader(buffer)
var result: Api.photos.Photo?
if let signature = reader.readInt32() {
@ -7995,15 +8052,16 @@ public extension Api.functions.photos {
}
}
public extension Api.functions.photos {
static func uploadProfilePhoto(flags: Int32, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
static func uploadProfilePhoto(flags: Int32, bot: Api.InputUser?, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
let buffer = Buffer()
buffer.appendInt32(154966609)
buffer.appendInt32(59286453)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 5) != 0 {bot!.serialize(buffer, true)}
if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {videoEmojiMarkup!.serialize(buffer, true)}
return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", String(describing: flags)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs)), ("videoEmojiMarkup", String(describing: videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs)), ("videoEmojiMarkup", String(describing: videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
let reader = BufferReader(buffer)
var result: Api.photos.Photo?
if let signature = reader.readInt32() {

View File

@ -85,8 +85,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .joinedByRequest)
case let .messageActionWebViewDataSentMe(text, _), let .messageActionWebViewDataSent(text):
return TelegramMediaAction(action: .webViewData(text))
case let .messageActionGiftPremium(_, currency, amount, months, _, _):
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months))
case let .messageActionGiftPremium(_, currency, amount, months, cryptoCurrency, cryptoAmount):
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount))
case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId):
return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId))
case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, hidden):

View File

@ -36,7 +36,7 @@ extension TelegramPeerUsername {
extension TelegramUser {
convenience init(user: Api.User) {
switch user {
case let .user(flags, _, id, accessHash, firstName, lastName, username, phone, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, usernames):
case let .user(flags, flags2, id, accessHash, firstName, lastName, username, phone, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, usernames):
let representations: [TelegramMediaImageRepresentation] = photo.flatMap(parsedTelegramProfilePhoto) ?? []
let isMin = (flags & (1 << 20)) != 0
@ -80,6 +80,9 @@ extension TelegramUser {
if (flags & (1 << 27)) != 0 {
botFlags.insert(.canBeAddedToAttachMenu)
}
if (flags2 & (1 << 1)) != 0 {
botFlags.insert(.canEdit)
}
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
}
@ -93,7 +96,7 @@ extension TelegramUser {
static func merge(_ lhs: TelegramUser?, rhs: Api.User) -> TelegramUser? {
switch rhs {
case let .user(flags, _, _, rhsAccessHash, _, _, _, _, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, usernames):
case let .user(flags, _, _, rhsAccessHash, _, _, _, _, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, _):
let isMin = (flags & (1 << 20)) != 0
if !isMin {
return TelegramUser(user: rhs)
@ -142,6 +145,9 @@ extension TelegramUser {
if (flags & (1 << 27)) != 0 {
botFlags.insert(.canBeAddedToAttachMenu)
}
if let botInfo = lhs.botInfo, botInfo.flags.contains(.canEdit) {
botFlags.insert(.canEdit)
}
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
}

View File

@ -151,6 +151,35 @@ public struct CachedUserFlags: OptionSet {
public static let translationHidden = CachedUserFlags(rawValue: 1 << 0)
}
public final class EditableBotInfo: PostboxCoding, Equatable {
public let name: String
public let about: String
public let description: String
public init(name: String, about: String, description: String) {
self.name = name
self.about = about
self.description = description
}
public init(decoder: PostboxDecoder) {
self.name = decoder.decodeStringForKey("n", orElse: "")
self.about = decoder.decodeStringForKey("a", orElse: "")
self.description = decoder.decodeStringForKey("d", orElse: "")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.name, forKey: "n")
encoder.encodeString(self.about, forKey: "a")
encoder.encodeString(self.description, forKey: "d")
}
public static func ==(lhs: EditableBotInfo, rhs: EditableBotInfo) -> Bool {
return lhs.name == rhs.name && lhs.about == rhs.about && lhs.description == rhs.description
}
}
public final class CachedUserData: CachedPeerData {
public let about: String?
public let botInfo: BotInfo?

View File

@ -95,7 +95,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case setChatTheme(emoji: String)
case joinedByRequest
case webViewData(String)
case giftPremium(currency: String, amount: Int64, months: Int32)
case giftPremium(currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?)
case topicCreated(title: String, iconColor: Int32, iconFileId: Int64?)
case topicEdited(components: [ForumTopicEditComponent])
case suggestedProfilePhoto(image: TelegramMediaImage?)
@ -170,7 +170,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 26:
self = .webViewData(decoder.decodeStringForKey("t", orElse: ""))
case 27:
self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), months: decoder.decodeInt32ForKey("months", orElse: 0))
self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), months: decoder.decodeInt32ForKey("months", orElse: 0), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"))
case 28:
self = .topicCreated(title: decoder.decodeStringForKey("title", orElse: ""), iconColor: decoder.decodeInt32ForKey("iconColor", orElse: 0), iconFileId: decoder.decodeOptionalInt64ForKey("iconFileId"))
case 29:
@ -310,11 +310,15 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case let .webViewData(text):
encoder.encodeInt32(26, forKey: "_rawValue")
encoder.encodeString(text, forKey: "t")
case let .giftPremium(currency, amount, months):
case let .giftPremium(currency, amount, months, cryptoCurrency, cryptoAmount):
encoder.encodeInt32(27, forKey: "_rawValue")
encoder.encodeString(currency, forKey: "currency")
encoder.encodeInt64(amount, forKey: "amount")
encoder.encodeInt32(months, forKey: "months")
if let cryptoCurrency = cryptoCurrency, let cryptoAmount = cryptoAmount {
encoder.encodeString(cryptoCurrency, forKey: "cryptoCurrency")
encoder.encodeInt64(cryptoAmount, forKey: "cryptoAmount")
}
case let .topicCreated(title, iconColor, iconFileId):
encoder.encodeInt32(28, forKey: "_rawValue")
encoder.encodeString(title, forKey: "title")

View File

@ -33,6 +33,7 @@ public struct BotUserInfoFlags: OptionSet {
public static let worksWithGroups = BotUserInfoFlags(rawValue: (1 << 1))
public static let requiresGeolocationForInlineRequests = BotUserInfoFlags(rawValue: (1 << 3))
public static let canBeAddedToAttachMenu = BotUserInfoFlags(rawValue: (1 << 4))
public static let canEdit = BotUserInfoFlags(rawValue: (1 << 5))
}
public struct BotUserInfo: PostboxCoding, Equatable {

View File

@ -23,6 +23,7 @@ public enum AddressNameAvailability: Equatable {
public enum AddressNameDomain {
case account
case peer(PeerId)
case bot(PeerId)
case theme(TelegramTheme)
}
@ -55,8 +56,26 @@ func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -
func _internal_addressNameAvailability(account: Account, domain: AddressNameDomain, name: String) -> Signal<AddressNameAvailability, NoError> {
return account.postbox.transaction { transaction -> Signal<AddressNameAvailability, NoError> in
switch domain {
case .account:
return account.network.request(Api.functions.account.checkUsername(username: name))
case .account:
return account.network.request(Api.functions.account.checkUsername(username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
return .available
case .boolFalse:
return .taken
}
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid)
}
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
@ -72,58 +91,42 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma
return .single(.invalid)
}
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
return .available
case .boolFalse:
return .taken
}
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
return .available
case .boolFalse:
return .taken
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid)
}
}
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
return .available
case .boolFalse:
return .taken
}
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid)
}
}
} else {
return .single(.invalid)
}
case .theme:
return account.network.request(Api.functions.account.createTheme(flags: 0, slug: name, title: "", document: .inputDocumentEmpty, settings: nil))
|> map { _ -> AddressNameAvailability in
return .available
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "THEME_SLUG_OCCUPIED" {
return .single(.taken)
} else if error.errorDescription == "THEME_SLUG_INVALID" {
return .single(.invalid)
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.available)
return .single(.invalid)
}
}
} else {
return .single(.invalid)
}
case .bot:
return .single(.invalid)
case .theme:
return account.network.request(Api.functions.account.createTheme(flags: 0, slug: name, title: "", document: .inputDocumentEmpty, settings: nil))
|> map { _ -> AddressNameAvailability in
return .available
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "THEME_SLUG_OCCUPIED" {
return .single(.taken)
} else if error.errorDescription == "THEME_SLUG_INVALID" {
return .single(.invalid)
} else {
return .single(.available)
}
}
}
} |> switchToLatest
}
@ -172,6 +175,8 @@ func _internal_updateAddressName(account: Account, domain: AddressNameDomain, na
} else {
return .fail(.generic)
}
case .bot:
return .fail(.generic)
case let .theme(theme):
let flags: Int32 = 1 << 0
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: nil, title: nil, document: nil, settings: nil))
@ -233,126 +238,187 @@ public enum ToggleAddressNameActiveError {
func _internal_toggleAddressNameActive(account: Account, domain: AddressNameDomain, name: String, active: Bool) -> Signal<Void, ToggleAddressNameActiveError> {
return account.postbox.transaction { transaction -> Signal<Void, ToggleAddressNameActiveError> in
switch domain {
case .account:
return account.network.request(Api.functions.account.toggleUsername(username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramUser {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedUser = peer.withUpdatedUsernames(updatedNames)
updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.toggleUsername(channel: inputChannel, username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedPeer = peer.withUpdatedAddressNames(updatedNames)
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
case .account:
return account.network.request(Api.functions.account.toggleUsername(username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .fail(.generic)
return .generic
}
case .theme:
return .complete()
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramUser {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedUser = peer.withUpdatedUsernames(updatedNames)
updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.toggleUsername(channel: inputChannel, username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedPeer = peer.withUpdatedAddressNames(updatedNames)
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
} else {
return .fail(.generic)
}
case let .bot(peerId):
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.bots.toggleUsername(bot: inputUser, username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedPeer = peer.withUpdatedAddressNames(updatedNames)
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
} else {
return .fail(.generic)
}
case .theme:
return .fail(.generic)
}
} |> mapError { _ -> ToggleAddressNameActiveError in } |> switchToLatest
}
@ -364,42 +430,61 @@ public enum ReorderAddressNamesError {
func _internal_reorderAddressNames(account: Account, domain: AddressNameDomain, names: [TelegramPeerUsername]) -> Signal<Void, ReorderAddressNamesError> {
return account.postbox.transaction { transaction -> Signal<Void, ReorderAddressNamesError> in
switch domain {
case .account:
return account.network.request(Api.functions.account.reorderUsernames(order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
case .account:
return account.network.request(Api.functions.account.reorderUsernames(order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in
return .generic
}
|> mapToSignal { result -> Signal<Void, ReorderAddressNamesError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramUser {
let updatedUser = peer.withUpdatedUsernames(names)
updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ReorderAddressNamesError in }
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.reorderUsernames(channel: inputChannel, order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in
return .generic
}
|> mapToSignal { result -> Signal<Void, ReorderAddressNamesError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramUser {
let updatedUser = peer.withUpdatedUsernames(names)
updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
let updatedPeer = peer.withUpdatedAddressNames(names)
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ReorderAddressNamesError in }
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.reorderUsernames(channel: inputChannel, order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in
return .generic
}
|> mapToSignal { result -> Signal<Void, ReorderAddressNamesError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
let updatedPeer = peer.withUpdatedAddressNames(names)
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ReorderAddressNamesError in }
}
} else {
return .fail(.generic)
} else {
return .fail(.generic)
}
case let .bot(peerId):
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.bots.reorderUsernames(bot: inputUser, order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in
return .generic
}
case .theme:
return .complete()
|> mapToSignal { result -> Signal<Void, ReorderAddressNamesError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
let updatedPeer = peer.withUpdatedAddressNames(names)
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ReorderAddressNamesError in }
}
} else {
return .fail(.generic)
}
case .theme:
return .fail(.generic)
}
} |> mapError { _ -> ReorderAddressNamesError in } |> switchToLatest
}

View File

@ -198,7 +198,16 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
if let _ = videoEmojiMarkup {
flags |= (1 << 4)
}
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, bot: nil, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else if let user = peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit), let inputUser = apiInputUser(peer) {
if fallback {
flags |= (1 << 3)
}
if let _ = videoEmojiMarkup {
flags |= (1 << 4)
}
flags |= (1 << 5)
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, bot: inputUser, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else if let inputUser = apiInputUser(peer) {
if let customPeerPhotoMode = customPeerPhotoMode {
switch customPeerPhotoMode {
@ -408,14 +417,20 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
}
}
} else {
if let _ = peer as? TelegramUser {
if let user = peer as? TelegramUser {
let request: Signal<Api.photos.Photo, MTRpcError>
if peer.id == accountPeerId {
var flags: Int32 = 0
if fallback {
flags |= (1 << 0)
}
request = network.request(Api.functions.photos.updateProfilePhoto(flags: flags, id: Api.InputPhoto.inputPhotoEmpty))
request = network.request(Api.functions.photos.updateProfilePhoto(flags: flags, bot: nil, id: Api.InputPhoto.inputPhotoEmpty))
} else if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit), let inputUser = apiInputUser(peer) {
var flags: Int32 = (1 << 1)
if fallback {
flags |= (1 << 0)
}
request = network.request(Api.functions.photos.updateProfilePhoto(flags: flags, bot: inputUser, id: Api.InputPhoto.inputPhotoEmpty))
} else if let inputUser = apiInputUser(peer) {
let flags: Int32 = (1 << 4)
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: nil, video: nil, videoStartTs: nil, videoEmojiMarkup: nil))
@ -557,18 +572,18 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
switch reference {
case let .cloud(imageId, accessHash, fileReference):
return network.request(Api.functions.photos.updateProfilePhoto(flags: 0, id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))))
|> `catch` { _ -> Signal<Api.photos.Photo, NoError> in
case let .cloud(imageId, accessHash, fileReference):
return network.request(Api.functions.photos.updateProfilePhoto(flags: 0, bot: nil, id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))))
|> `catch` { _ -> Signal<Api.photos.Photo, NoError> in
return .complete()
}
|> mapToSignal { photo -> Signal<TelegramMediaImage?, NoError> in
if case let .photo(photo, _) = photo {
return .single(telegramMediaImageFromApiPhoto(photo))
} else {
return .complete()
}
|> mapToSignal { photo -> Signal<TelegramMediaImage?, NoError> in
if case let .photo(photo, _) = photo {
return .single(telegramMediaImageFromApiPhoto(photo))
} else {
return .complete()
}
}
}
}
}
@ -593,7 +608,7 @@ func _internal_removeAccountPhoto(account: Account, reference: TelegramMediaImag
if fallback {
flags |= (1 << 0)
}
let api = Api.functions.photos.updateProfilePhoto(flags: flags, id: Api.InputPhoto.inputPhotoEmpty)
let api = Api.functions.photos.updateProfilePhoto(flags: flags, bot: nil, id: Api.InputPhoto.inputPhotoEmpty)
return account.network.request(api)
|> map { _ in }
|> retryRequest

View File

@ -628,6 +628,14 @@ public extension TelegramEngine {
public func updatePeerDescription(peerId: PeerId, description: String?) -> Signal<Void, UpdatePeerDescriptionError> {
return _internal_updatePeerDescription(account: self.account, peerId: peerId, description: description)
}
public func updateBotName(peerId: PeerId, name: String) -> Signal<Void, UpdateBotInfoError> {
return _internal_updateBotName(account: self.account, peerId: peerId, name: name)
}
public func updateBotAbout(peerId: PeerId, about: String) -> Signal<Void, UpdateBotInfoError> {
return _internal_updateBotAbout(account: self.account, peerId: peerId, about: about)
}
public func getNextUnreadChannel(peerId: PeerId, chatListFilterId: Int32?, getFilterPredicate: @escaping (ChatListFilterData) -> ChatListFilterPredicate) -> Signal<(peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)?, NoError> {
return self.account.postbox.transaction { transaction -> (peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)? in

View File

@ -0,0 +1,105 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum UpdateBotInfoError {
case generic
}
func _internal_updateBotName(account: Account, peerId: PeerId, name: String) -> Signal<Void, UpdateBotInfoError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdateBotInfoError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
var flags: Int32 = 1 << 2
flags |= (1 << 3)
return account.network.request(Api.functions.bots.setBotInfo(flags: flags, bot: inputUser, langCode: "", name: name, about: nil, description: nil))
|> mapError { _ -> UpdateBotInfoError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateBotInfoError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result {
updatePeers(transaction: transaction, peers: [peer]) { _, peer in
var updatedPeer = peer
if let user = peer as? TelegramUser {
updatedPeer = user.withUpdatedNames(firstName: name, lastName: nil)
}
return updatedPeer
}
}
}
|> mapError { _ -> UpdateBotInfoError in }
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateBotInfoError in }
|> switchToLatest
}
func _internal_updateBotAbout(account: Account, peerId: PeerId, about: String) -> Signal<Void, UpdateBotInfoError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdateBotInfoError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
var flags: Int32 = 1 << 2
flags |= (1 << 0)
return account.network.request(Api.functions.bots.setBotInfo(flags: flags, bot: inputUser, langCode: "", name: nil, about: about, description: nil))
|> mapError { _ -> UpdateBotInfoError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateBotInfoError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedAbout(about)
} else {
return current
}
})
}
}
|> mapError { _ -> UpdateBotInfoError in }
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateBotInfoError in }
|> switchToLatest
}
func _internal_updateBotDescription(account: Account, peerId: PeerId, description: String) -> Signal<Void, UpdateBotInfoError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdateBotInfoError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
var flags: Int32 = 1 << 2
flags |= (1 << 1)
return account.network.request(Api.functions.bots.setBotInfo(flags: flags, bot: inputUser, langCode: "", name: nil, about: nil, description: description))
|> mapError { _ -> UpdateBotInfoError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateBotInfoError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result {
// transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
// if let current = current as? CachedChannelData {
// return current.withUpdatedAbout(description)
// } else if let current = current as? CachedGroupData {
// return current.withUpdatedAbout(description)
// } else {
// return current
// }
// })
}
}
|> mapError { _ -> UpdateBotInfoError in }
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateBotInfoError in }
|> switchToLatest
}

View File

@ -181,16 +181,41 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
}
if rawPeerId == accountPeerId {
return (.inputUserSelf, transaction.getPeer(rawPeerId), rawPeerId)
return (.inputUserSelf, rawPeer, rawPeerId)
} else {
return (apiInputUser(peer), peer, peer.id)
}
}
|> mapToSignal { inputUser, maybePeer, peerId -> Signal<Bool, NoError> in
if let inputUser = inputUser {
return network.request(Api.functions.users.getFullUser(id: inputUser))
|> retryRequest
|> mapToSignal { result -> Signal<Bool, NoError> in
let editableBotInfo: Signal<EditableBotInfo?, NoError>
if let user = maybePeer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
let flags: Int32 = (1 << 0)
editableBotInfo = network.request(Api.functions.bots.getBotInfo(flags: flags, bot: inputUser, langCode: ""))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.bots.BotInfo?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<EditableBotInfo?, NoError> in
if let result = result {
switch result {
case let .botInfo(name, about, description):
return .single(EditableBotInfo(name: name, about: about, description: description))
}
} else {
return .single(nil)
}
}
} else {
editableBotInfo = .single(nil)
}
return combineLatest(
network.request(Api.functions.users.getFullUser(id: inputUser))
|> retryRequest,
editableBotInfo
)
|> mapToSignal { result, editableBotInfo -> Signal<Bool, NoError> in
return postbox.transaction { transaction -> Bool in
switch result {
case let .userFull(fullUser, chats, users):
@ -241,7 +266,6 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
let canPinMessages = (userFullFlags & (1 << 7)) != 0
let pinnedMessageId = userFullPinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })
let peerStatusSettings = PeerStatusSettings(apiSettings: userFullSettings)
let hasScheduledMessages = (userFullFlags & 1 << 12) != 0
@ -266,7 +290,17 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
premiumGiftOptions = []
}
return previous.withUpdatedAbout(userFullAbout).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFullCommonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages)
return previous.withUpdatedAbout(userFullAbout)
.withUpdatedBotInfo(botInfo)
.withUpdatedCommonGroupCount(userFullCommonChatsCount)
.withUpdatedIsBlocked(isBlocked)
.withUpdatedVoiceCallsAvailable(voiceCallsAvailable)
.withUpdatedVideoCallsAvailable(videoCallsAvailable)
.withUpdatedCallsPrivate(callsPrivate)
.withUpdatedCanPinMessages(canPinMessages)
.withUpdatedPeerStatusSettings(peerStatusSettings)
.withUpdatedPinnedMessageId(pinnedMessageId)
.withUpdatedHasScheduledMessages(hasScheduledMessages)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedThemeEmoticon(userFullThemeEmoticon)
.withUpdatedPhoto(.known(photo))

View File

@ -710,7 +710,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
case let .webViewData(text):
attributedString = NSAttributedString(string: strings.Notification_WebAppSentData(text).string, font: titleFont, textColor: primaryTextColor)
case let .giftPremium(currency, amount, _):
case let .giftPremium(currency, amount, _, _, _):
let price = formatCurrencyAmount(amount, currency: currency)
if message.author?.id == accountPeerId {
attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_SentYou(price)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])

View File

@ -102,7 +102,7 @@ public final class ChatControllerInteraction {
public let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool
public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void
public let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool
public let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool
public let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool
public let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void
public let activateSwitchInline: (PeerId?, String) -> Void
@ -214,7 +214,7 @@ public final class ChatControllerInteraction {
sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool,
sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void,
sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool,
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool,
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool,
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void,
activateSwitchInline: @escaping (PeerId?, String) -> Void,

View File

@ -1620,7 +1620,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
if let (collection, result) = item.contextResult {
let _ = controllerInteraction.sendBotContextResultAsGif(collection, result, view, rect, false)
let _ = controllerInteraction.sendBotContextResultAsGif(collection, result, view, rect, false, false)
} else {
let _ = controllerInteraction.sendGif(item.file, view, rect, false, false)
}
@ -2078,7 +2078,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
if isSaved {
let _ = self?.controllerInteraction?.sendGif(file, sourceView, sourceRect, false, false)
} else if let (collection, result) = contextResult {
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, false)
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, false, false)
}
})))
@ -2099,7 +2099,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
if isSaved {
let _ = self?.controllerInteraction?.sendGif(file, sourceView, sourceRect, true, false)
} else if let (collection, result) = contextResult {
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, true)
let _ = self?.controllerInteraction?.sendBotContextResultAsGif(collection, result, sourceView, sourceRect, true, false)
}
})))
}

View File

@ -226,7 +226,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
multiplexedNode.fileSelected = { [weak self] file, sourceNode, sourceRect in
if let (collection, result) = file.contextResult {
let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode.view, sourceRect, false)
let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode.view, sourceRect, false, false)
} else {
let _ = self?.controllerInteraction.sendGif(file.file, sourceNode.view, sourceRect, false, false)
}

View File

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

View File

@ -0,0 +1,301 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 6.000000 15.669922 cm
0.000000 0.000000 0.000000 scn
7.000000 1.330078 m
7.000000 0.665078 l
7.665000 0.665078 l
7.665000 1.330078 l
7.000000 1.330078 l
h
3.500000 8.995078 m
3.132731 8.995078 2.835000 8.697348 2.835000 8.330078 c
2.835000 7.962809 3.132731 7.665078 3.500000 7.665078 c
3.500000 8.995078 l
h
3.500000 0.665078 m
7.000000 0.665078 l
7.000000 1.995078 l
3.500000 1.995078 l
3.500000 0.665078 l
h
7.665000 1.330078 m
7.665000 4.830078 l
6.335000 4.830078 l
6.335000 1.330078 l
7.665000 1.330078 l
h
3.500000 8.995078 m
1.199734 8.995078 -0.665000 7.130344 -0.665000 4.830078 c
0.665000 4.830078 l
0.665000 6.395805 1.934273 7.665078 3.500000 7.665078 c
3.500000 8.995078 l
h
7.665000 4.830078 m
7.665000 7.130344 5.800266 8.995078 3.500000 8.995078 c
3.500000 7.665078 l
5.065727 7.665078 6.335000 6.395805 6.335000 4.830078 c
7.665000 4.830078 l
h
3.500000 1.995078 m
1.934273 1.995078 0.665000 3.264351 0.665000 4.830078 c
-0.665000 4.830078 l
-0.665000 2.529812 1.199734 0.665078 3.500000 0.665078 c
3.500000 1.995078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 6.000000 4.669922 cm
0.000000 0.000000 0.000000 scn
7.000000 8.330078 m
7.665000 8.330078 l
7.665000 8.995078 l
7.000000 8.995078 l
7.000000 8.330078 l
h
7.665000 4.830078 m
7.665000 8.330078 l
6.335000 8.330078 l
6.335000 4.830078 l
7.665000 4.830078 l
h
7.000000 8.995078 m
3.500000 8.995078 l
3.500000 7.665078 l
7.000000 7.665078 l
7.000000 8.995078 l
h
3.500000 8.995078 m
1.199734 8.995078 -0.665000 7.130344 -0.665000 4.830078 c
0.665000 4.830078 l
0.665000 6.395805 1.934273 7.665078 3.500000 7.665078 c
3.500000 8.995078 l
h
3.500000 0.665078 m
5.800266 0.665078 7.665000 2.529812 7.665000 4.830078 c
6.335000 4.830078 l
6.335000 3.264351 5.065727 1.995078 3.500000 1.995078 c
3.500000 0.665078 l
h
3.500000 1.995078 m
1.934273 1.995078 0.665000 3.264351 0.665000 4.830078 c
-0.665000 4.830078 l
-0.665000 2.529812 1.199734 0.665078 3.500000 0.665078 c
3.500000 1.995078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 17.000000 4.669922 cm
0.000000 0.000000 0.000000 scn
0.000000 8.330078 m
0.000000 8.995078 l
-0.665000 8.995078 l
-0.665000 8.330078 l
0.000000 8.330078 l
h
7.665000 4.830078 m
7.665000 5.197348 7.367270 5.495078 7.000000 5.495078 c
6.632730 5.495078 6.335000 5.197348 6.335000 4.830078 c
7.665000 4.830078 l
h
3.500000 8.995078 m
0.000000 8.995078 l
0.000000 7.665078 l
3.500000 7.665078 l
3.500000 8.995078 l
h
-0.665000 8.330078 m
-0.665000 4.830078 l
0.665000 4.830078 l
0.665000 8.330078 l
-0.665000 8.330078 l
h
7.665000 4.830078 m
7.665000 7.130344 5.800266 8.995078 3.500000 8.995078 c
3.500000 7.665078 l
5.065727 7.665078 6.335000 6.395805 6.335000 4.830078 c
7.665000 4.830078 l
h
3.500000 0.665078 m
5.800266 0.665078 7.665000 2.529812 7.665000 4.830078 c
6.335000 4.830078 l
6.335000 3.264351 5.065727 1.995078 3.500000 1.995078 c
3.500000 0.665078 l
h
3.500000 1.995078 m
1.934273 1.995078 0.665000 3.264351 0.665000 4.830078 c
-0.665000 4.830078 l
-0.665000 2.529812 1.199734 0.665078 3.500000 0.665078 c
3.500000 1.995078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 17.000000 15.669922 cm
0.000000 0.000000 0.000000 scn
0.000000 1.330078 m
-0.665000 1.330078 l
-0.665000 0.665078 l
0.000000 0.665078 l
0.000000 1.330078 l
h
3.500000 8.995078 m
3.132731 8.995078 2.835000 8.697348 2.835000 8.330078 c
2.835000 7.962809 3.132731 7.665078 3.500000 7.665078 c
3.500000 8.995078 l
h
7.665000 4.830078 m
7.665000 5.197348 7.367270 5.495078 7.000000 5.495078 c
6.632730 5.495078 6.335000 5.197348 6.335000 4.830078 c
7.665000 4.830078 l
h
0.000000 0.665078 m
3.500000 0.665078 l
3.500000 1.995078 l
0.000000 1.995078 l
0.000000 0.665078 l
h
-0.665000 4.830078 m
-0.665000 1.330078 l
0.665000 1.330078 l
0.665000 4.830078 l
-0.665000 4.830078 l
h
3.500000 8.995078 m
1.199734 8.995078 -0.665000 7.130344 -0.665000 4.830078 c
0.665000 4.830078 l
0.665000 6.395805 1.934273 7.665078 3.500000 7.665078 c
3.500000 8.995078 l
h
7.665000 4.830078 m
7.665000 7.130344 5.800266 8.995078 3.500000 8.995078 c
3.500000 7.665078 l
5.065727 7.665078 6.335000 6.395805 6.335000 4.830078 c
7.665000 4.830078 l
h
3.500000 0.665078 m
5.800266 0.665078 7.665000 2.529812 7.665000 4.830078 c
6.335000 4.830078 l
6.335000 3.264351 5.065727 1.995078 3.500000 1.995078 c
3.500000 0.665078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 13.000000 11.669922 cm
0.000000 0.000000 0.000000 scn
0.000000 5.330078 m
0.000000 5.995078 l
-0.665000 5.995078 l
-0.665000 5.330078 l
0.000000 5.330078 l
h
4.000000 5.330078 m
4.665000 5.330078 l
4.665000 5.995078 l
4.000000 5.995078 l
4.000000 5.330078 l
h
4.000000 1.330078 m
4.000000 0.665078 l
4.665000 0.665078 l
4.665000 1.330078 l
4.000000 1.330078 l
h
0.000000 1.330078 m
-0.665000 1.330078 l
-0.665000 0.665078 l
0.000000 0.665078 l
0.000000 1.330078 l
h
0.000000 4.665078 m
4.000000 4.665078 l
4.000000 5.995078 l
0.000000 5.995078 l
0.000000 4.665078 l
h
3.335000 5.330078 m
3.335000 1.330078 l
4.665000 1.330078 l
4.665000 5.330078 l
3.335000 5.330078 l
h
4.000000 1.995078 m
0.000000 1.995078 l
0.000000 0.665078 l
4.000000 0.665078 l
4.000000 1.995078 l
h
0.665000 1.330078 m
0.665000 5.330078 l
-0.665000 5.330078 l
-0.665000 1.330078 l
0.665000 1.330078 l
h
f
n
Q
endstream
endobj
3 0 obj
5291
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000005381 00000 n
0000005404 00000 n
0000005577 00000 n
0000005651 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
5710
%%EOF

View File

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

View File

@ -0,0 +1,271 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 6.000000 4.668945 cm
0.000000 0.000000 0.000000 scn
2.184038 18.895081 m
1.882134 19.487600 l
2.184038 18.895081 l
h
0.435974 17.147017 m
-0.156545 17.448921 l
0.435974 17.147017 l
h
17.564026 17.147017 m
18.156546 17.448921 l
17.564026 17.147017 l
h
15.815962 1.767029 m
15.514058 2.359549 l
15.815962 1.767029 l
h
17.564026 3.515093 m
16.971506 3.816997 l
17.564026 3.515093 l
h
2.184038 1.767029 m
1.882134 1.174509 l
2.184038 1.767029 l
h
0.435974 3.515093 m
-0.156545 3.213190 l
0.435974 3.515093 l
h
6.400000 0.666056 m
11.600000 0.666056 l
11.600000 1.996056 l
6.400000 1.996056 l
6.400000 0.666056 l
h
18.664999 7.731055 m
18.664999 12.931055 l
17.334999 12.931055 l
17.334999 7.731055 l
18.664999 7.731055 l
h
11.599999 19.996056 m
6.400000 19.996056 l
6.400000 18.666054 l
11.599999 18.666054 l
11.599999 19.996056 l
h
-0.665000 12.931055 m
-0.665000 7.731054 l
0.665000 7.731054 l
0.665000 12.931055 l
-0.665000 12.931055 l
h
6.400000 19.996056 m
5.290868 19.996056 4.419025 19.996572 3.718656 19.939350 c
3.010523 19.881493 2.419329 19.761314 1.882134 19.487600 c
2.485942 18.302561 l
2.804394 18.464821 3.201076 18.562630 3.826960 18.613766 c
4.460607 18.665537 5.268921 18.666054 6.400000 18.666054 c
6.400000 19.996056 l
h
0.665000 12.931055 m
0.665000 14.062133 0.665517 14.870447 0.717288 15.504095 c
0.768425 16.129978 0.866234 16.526661 1.028493 16.845114 c
-0.156545 17.448921 l
-0.430260 16.911726 -0.550438 16.320532 -0.608295 15.612399 c
-0.665517 14.912029 -0.665000 14.040186 -0.665000 12.931055 c
0.665000 12.931055 l
h
1.882134 19.487600 m
1.004358 19.040350 0.290704 18.326696 -0.156545 17.448921 c
1.028493 16.845114 l
1.348231 17.472633 1.858421 17.982824 2.485942 18.302561 c
1.882134 19.487600 l
h
18.664999 12.931055 m
18.664999 14.040187 18.665518 14.912029 18.608295 15.612399 c
18.550438 16.320532 18.430260 16.911726 18.156546 17.448921 c
16.971506 16.845114 l
17.133766 16.526661 17.231575 16.129978 17.282711 15.504095 c
17.334482 14.870447 17.334999 14.062134 17.334999 12.931055 c
18.664999 12.931055 l
h
11.599999 18.666054 m
12.731078 18.666054 13.539392 18.665537 14.173039 18.613766 c
14.798923 18.562630 15.195606 18.464821 15.514058 18.302561 c
16.117865 19.487600 l
15.580671 19.761314 14.989477 19.881493 14.281344 19.939350 c
13.580975 19.996572 12.709131 19.996056 11.599999 19.996056 c
11.599999 18.666054 l
h
18.156546 17.448921 m
17.709295 18.326696 16.995642 19.040350 16.117865 19.487600 c
15.514058 18.302561 l
16.141579 17.982824 16.651770 17.472633 16.971506 16.845114 c
18.156546 17.448921 l
h
11.600000 0.666056 m
12.709132 0.666056 13.580975 0.665537 14.281344 0.722759 c
14.989477 0.780617 15.580671 0.900795 16.117865 1.174509 c
15.514058 2.359549 l
15.195606 2.197289 14.798923 2.099480 14.173039 2.048344 c
13.539392 1.996572 12.731078 1.996056 11.600000 1.996056 c
11.600000 0.666056 l
h
17.334999 7.731055 m
17.334999 6.599977 17.334482 5.791662 17.282711 5.158015 c
17.231575 4.532131 17.133766 4.135448 16.971506 3.816997 c
18.156546 3.213190 l
18.430260 3.750383 18.550438 4.341578 18.608295 5.049710 c
18.665518 5.750080 18.664999 6.621923 18.664999 7.731055 c
17.334999 7.731055 l
h
16.117865 1.174509 m
16.995642 1.621759 17.709295 2.335413 18.156546 3.213190 c
16.971506 3.816997 l
16.651770 3.189476 16.141579 2.679285 15.514058 2.359549 c
16.117865 1.174509 l
h
6.400000 1.996056 m
5.268922 1.996056 4.460608 1.996572 3.826960 2.048344 c
3.201076 2.099480 2.804394 2.197289 2.485942 2.359549 c
1.882134 1.174509 l
2.419329 0.900795 3.010523 0.780617 3.718656 0.722759 c
4.419025 0.665537 5.290868 0.666056 6.400000 0.666056 c
6.400000 1.996056 l
h
-0.665000 7.731054 m
-0.665000 6.621922 -0.665517 5.750080 -0.608295 5.049710 c
-0.550438 4.341578 -0.430260 3.750383 -0.156545 3.213190 c
1.028493 3.816997 l
0.866234 4.135448 0.768425 4.532131 0.717288 5.158015 c
0.665517 5.791662 0.665000 6.599977 0.665000 7.731054 c
-0.665000 7.731054 l
h
2.485942 2.359549 m
1.858421 2.679285 1.348231 3.189476 1.028493 3.816997 c
-0.156545 3.213190 l
0.290704 2.335413 1.004358 1.621759 1.882134 1.174509 c
2.485942 2.359549 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 10.000000 17.669922 cm
0.000000 0.000000 0.000000 scn
0.000000 1.995078 m
-0.367269 1.995078 -0.665000 1.697348 -0.665000 1.330078 c
-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c
0.000000 1.995078 l
h
10.000000 0.665078 m
10.367270 0.665078 10.665000 0.962809 10.665000 1.330078 c
10.665000 1.697348 10.367270 1.995078 10.000000 1.995078 c
10.000000 0.665078 l
h
0.000000 0.665078 m
10.000000 0.665078 l
10.000000 1.995078 l
0.000000 1.995078 l
0.000000 0.665078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 10.000000 13.669922 cm
0.000000 0.000000 0.000000 scn
0.000000 1.995078 m
-0.367269 1.995078 -0.665000 1.697348 -0.665000 1.330078 c
-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c
0.000000 1.995078 l
h
10.000000 0.665078 m
10.367270 0.665078 10.665000 0.962809 10.665000 1.330078 c
10.665000 1.697348 10.367270 1.995078 10.000000 1.995078 c
10.000000 0.665078 l
h
0.000000 0.665078 m
10.000000 0.665078 l
10.000000 1.995078 l
0.000000 1.995078 l
0.000000 0.665078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 10.000000 9.669922 cm
0.000000 0.000000 0.000000 scn
0.000000 1.995078 m
-0.367269 1.995078 -0.665000 1.697348 -0.665000 1.330078 c
-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c
0.000000 1.995078 l
h
5.000000 0.665078 m
5.367270 0.665078 5.665000 0.962809 5.665000 1.330078 c
5.665000 1.697348 5.367270 1.995078 5.000000 1.995078 c
5.000000 0.665078 l
h
0.000000 0.665078 m
5.000000 0.665078 l
5.000000 1.995078 l
0.000000 1.995078 l
0.000000 0.665078 l
h
f
n
Q
endstream
endobj
3 0 obj
5728
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000005818 00000 n
0000005841 00000 n
0000006014 00000 n
0000006088 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
6147
%%EOF

View File

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

View File

@ -0,0 +1,172 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.335022 4.334961 cm
0.000000 0.000000 0.000000 scn
12.330000 22.165077 m
12.330000 22.532347 12.032269 22.830078 11.664999 22.830078 c
11.297730 22.830078 11.000000 22.532349 11.000000 22.165077 c
10.999999 20.329762 l
9.808650 20.327427 9.056338 20.308790 8.406540 20.163544 c
5.792393 19.579212 3.750866 17.537685 3.166535 14.923538 c
3.037604 14.346734 3.008435 13.689154 3.001877 12.715419 c
3.000633 12.698801 3.000000 12.682014 3.000000 12.665078 c
2.999991 12.604896 l
2.999971 12.514796 2.999953 12.430834 3.000363 12.352024 c
2.999973 12.169721 2.999984 11.977092 2.999996 11.772916 c
3.000000 11.665075 l
3.000000 11.325851 l
1.328710 11.240018 0.000000 9.857716 0.000000 8.165078 c
0.000000 4.665077 l
0.000000 4.297810 0.297731 4.000078 0.665000 4.000078 c
1.032269 4.000078 1.330000 4.297810 1.330000 4.665077 c
1.330000 8.165078 l
1.330000 9.122908 2.063866 9.909335 3.000000 9.992761 c
3.000000 6.665073 l
3.000000 6.635679 l
2.999995 5.610479 2.999991 4.800386 3.053299 4.147926 c
3.107750 3.481483 3.221116 2.921162 3.481206 2.410706 c
3.904487 1.579967 4.579896 0.904560 5.410632 0.481277 c
5.921087 0.221188 6.481411 0.107822 7.147855 0.053371 c
7.800317 0.000061 8.610416 0.000065 9.635623 0.000072 c
9.664999 0.000072 l
13.664999 0.000072 l
13.694375 0.000072 l
14.719583 0.000065 15.529682 0.000061 16.182144 0.053371 c
16.848589 0.107822 17.408913 0.221188 17.919367 0.481277 c
18.750103 0.904560 19.425512 1.579967 19.848793 2.410706 c
20.108883 2.921162 20.222248 3.481483 20.276699 4.147928 c
20.330009 4.800388 20.330006 5.610489 20.329998 6.635696 c
20.329998 6.665073 l
20.329998 9.992761 l
21.266134 9.909336 22.000000 9.122909 22.000000 8.165078 c
22.000000 4.665077 l
22.000000 4.297810 22.297731 4.000078 22.665001 4.000078 c
23.032270 4.000078 23.330002 4.297810 23.330002 4.665077 c
23.330002 8.165078 l
23.330002 9.857717 22.001289 11.240019 20.329998 11.325851 c
20.329998 11.665077 l
20.330002 11.772936 l
20.330002 11.776800 l
20.330002 11.777779 l
20.330002 11.778754 l
20.330002 11.779727 l
20.330002 11.780697 l
20.330013 11.981822 20.330025 12.171724 20.329638 12.351577 c
20.330050 12.430517 20.330030 12.514627 20.330009 12.604898 c
20.330002 12.665078 l
20.328440 12.665078 l
20.322536 13.666592 20.294603 14.336849 20.163464 14.923538 c
19.579132 17.537685 17.537605 19.579212 14.923459 20.163544 c
14.273661 20.308790 13.521349 20.327427 12.329999 20.329762 c
12.330000 22.165077 l
h
18.865494 14.633408 m
18.976515 14.136731 18.995987 13.539755 18.999325 12.348469 c
18.997746 12.092189 18.992702 11.974581 18.977409 11.878021 c
18.853046 11.092838 18.237240 10.477031 17.452057 10.352671 c
17.322449 10.332142 17.154915 10.330078 16.665001 10.330078 c
6.665000 10.330078 l
6.175085 10.330078 6.007552 10.332142 5.877943 10.352670 c
5.092760 10.477031 4.476953 11.092838 4.352592 11.878021 c
4.337297 11.974588 4.332253 12.092207 4.330675 12.348530 c
4.334013 13.539776 4.353486 14.136740 4.464505 14.633408 c
4.936448 16.744762 6.585317 18.393631 8.696671 18.865574 c
9.280350 18.996040 10.002544 19.000078 11.664999 19.000078 c
13.327456 19.000078 14.049649 18.996040 14.633328 18.865574 c
16.744682 18.393631 18.393551 16.744762 18.865494 14.633408 c
h
4.330000 6.665073 m
4.330000 9.586203 l
4.719618 9.308791 5.175268 9.117384 5.669886 9.039044 c
5.916926 8.999917 6.198448 8.999979 6.604819 9.000070 c
6.665000 9.000078 l
16.665001 9.000078 l
16.725180 9.000070 l
17.131554 8.999979 17.413074 8.999917 17.660114 9.039044 c
18.154732 9.117384 18.610382 9.308791 19.000000 9.586202 c
19.000000 6.665073 l
19.000000 5.604002 18.999481 4.848192 18.951117 4.256233 c
18.903385 3.672035 18.812389 3.306225 18.663754 3.014511 c
18.367985 2.434032 17.896040 1.962086 17.315559 1.666317 c
17.023846 1.517681 16.658035 1.426685 16.073839 1.378956 c
15.481880 1.330589 14.726070 1.330074 13.664999 1.330074 c
9.664999 1.330074 l
8.603928 1.330074 7.848119 1.330589 7.256159 1.378956 c
6.671964 1.426685 6.306152 1.517681 6.014440 1.666317 c
5.433959 1.962086 4.962014 2.434032 4.666245 3.014511 c
4.517610 3.306225 4.426613 3.672035 4.378882 4.256233 c
4.330517 4.848190 4.330000 5.604000 4.330000 6.665073 c
h
8.539999 12.540078 m
9.230355 12.540078 9.789999 13.099722 9.789999 13.790078 c
9.789999 14.480434 9.230355 15.040078 8.539999 15.040078 c
7.849643 15.040078 7.289999 14.480434 7.289999 13.790078 c
7.289999 13.099722 7.849643 12.540078 8.539999 12.540078 c
h
16.040001 13.790078 m
16.040001 13.099722 15.480356 12.540078 14.790000 12.540078 c
14.099644 12.540078 13.540000 13.099722 13.540000 13.790078 c
13.540000 14.480434 14.099644 15.040078 14.790000 15.040078 c
15.480356 15.040078 16.040001 14.480434 16.040001 13.790078 c
h
f*
n
Q
endstream
endobj
3 0 obj
4761
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000004851 00000 n
0000004874 00000 n
0000005047 00000 n
0000005121 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
5180
%%EOF

View File

@ -837,7 +837,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .setChatTheme:
strongSelf.presentThemeSelection()
return true
case let .giftPremium(_, _, duration):
case let .giftPremium(_, _, duration, _, _):
let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId
let toPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? message.id.peerId : strongSelf.context.account.peerId
let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration))
@ -2183,7 +2183,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
return true
}, sendBotContextResultAsGif: { [weak self] collection, result, sourceView, sourceRect, silentPosting in
}, sendBotContextResultAsGif: { [weak self] collection, result, sourceView, sourceRect, silentPosting, resetTextInputState in
guard let strongSelf = self else {
return false
}
@ -2195,7 +2195,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return false
}
strongSelf.enqueueChatContextResult(collection, result, hideVia: true, closeMediaInput: true, silentPosting: silentPosting)
strongSelf.enqueueChatContextResult(collection, result, hideVia: true, closeMediaInput: true, silentPosting: silentPosting, resetTextInputState: resetTextInputState)
return true
}, requestMessageActionCallback: { [weak self] messageId, data, isGame, requiresPassword in
@ -2604,7 +2604,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
strongSelf.chatDisplayNode.dismissInput()
openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a, blockInteraction: true)
self?.push(c)
})
})
}
@ -15040,7 +15040,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
}
private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false) {
private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) {
if !canSendMessagesToChat(self.presentationInterfaceState) {
return
}
@ -15066,12 +15066,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
var state = state
state = state.updatedInterfaceState { interfaceState in
var interfaceState = interfaceState
interfaceState = interfaceState.withUpdatedReplyMessageId(nil)
interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: "")))
interfaceState = interfaceState.withUpdatedComposeDisableUrlPreview(nil)
return interfaceState
if resetTextInputState {
state = state.updatedInterfaceState { interfaceState in
var interfaceState = interfaceState
interfaceState = interfaceState.withUpdatedReplyMessageId(nil)
interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: "")))
interfaceState = interfaceState.withUpdatedComposeDisableUrlPreview(nil)
return interfaceState
}
}
state = state.updatedInputMode { current in
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
@ -18430,22 +18432,42 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let selectedEmoticon: String? = themeEmoticon
let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, initiallySelectedEmoticon: selectedEmoticon, peerName: strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "", previewTheme: { [weak self] emoticon, dark in
if let strongSelf = self {
strongSelf.presentCrossfadeSnapshot()
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark)))
}
}, completion: { [weak self] emoticon in
guard let strongSelf = self, let peerId = peerId else {
return
}
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil)))
let _ = context.engine.themes.setChatTheme(peerId: peerId, emoticon: emoticon).start(completed: { [weak self] in
let controller = ChatThemeScreen(
context: context,
updatedPresentationData: strongSelf.updatedPresentationData,
animatedEmojiStickers: animatedEmojiStickers,
initiallySelectedEmoticon: selectedEmoticon,
peerName: strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "",
previewTheme: { [weak self] emoticon, dark in
if let strongSelf = self {
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((nil, nil)))
strongSelf.presentCrossfadeSnapshot()
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark)))
}
})
})
},
changeWallpaper: {
guard let strongSelf = self else {
return
}
if let themeController = strongSelf.themeScreen {
strongSelf.themeScreen = nil
themeController.dimTapped()
}
let controller = ThemeGridController(context: strongSelf.context)
controller.navigationPresentation = .modal
strongSelf.push(controller)
},
completion: { [weak self] emoticon in
guard let strongSelf = self, let peerId = peerId else {
return
}
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil)))
let _ = context.engine.themes.setChatTheme(peerId: peerId, emoticon: emoticon).start(completed: { [weak self] in
if let strongSelf = self {
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((nil, nil)))
}
})
}
)
controller.passthroughHitTestImpl = { [weak self] _ in
if let strongSelf = self {
return strongSelf.chatDisplayNode.historyNode.view

View File

@ -185,7 +185,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
for media in item.message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case let .giftPremium(_, _, months):
case let .giftPremium(_, _, months, _, _):
duration = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(months)).string
switch months {
case 12:

View File

@ -274,7 +274,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
@ -283,7 +283,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openWallpaper: { [weak self] message in
if let strongSelf = self{
openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in
self?.presentController(c, .window(.root), a)
self?.pushController(c)
})
}
}, openTheme: { _ in

View File

@ -555,6 +555,7 @@ final class ChatThemeScreen: ViewController {
private let initiallySelectedEmoticon: String?
private let peerName: String
private let previewTheme: (String?, Bool?) -> Void
fileprivate let changeWallpaper: () -> Void
private let completion: (String?) -> Void
private var presentationData: PresentationData
@ -570,13 +571,23 @@ final class ChatThemeScreen: ViewController {
}
}
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, peerName: String, previewTheme: @escaping (String?, Bool?) -> Void, completion: @escaping (String?) -> Void) {
init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>),
animatedEmojiStickers: [String: [StickerPackItem]],
initiallySelectedEmoticon: String?,
peerName: String,
previewTheme: @escaping (String?, Bool?) -> Void,
changeWallpaper: @escaping () -> Void,
completion: @escaping (String?) -> Void
) {
self.context = context
self.presentationData = updatedPresentationData.initial
self.animatedEmojiStickers = animatedEmojiStickers
self.initiallySelectedEmoticon = initiallySelectedEmoticon
self.peerName = peerName
self.previewTheme = previewTheme
self.changeWallpaper = changeWallpaper
self.completion = completion
super.init(navigationBarPresentationData: nil)
@ -713,6 +724,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
private let animationContainerNode: ASDisplayNode
private var animationNode: AnimationNode
private let doneButton: SolidRoundedButtonNode
private let wallpaperButton: HighlightableButtonNode
private let listNode: ListView
private var entries: [ThemeSettingsThemeEntry]?
@ -807,6 +819,9 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
self.doneButton.title = initiallySelectedEmoticon == nil ? self.presentationData.strings.Conversation_Theme_DontSetTheme : self.presentationData.strings.Conversation_Theme_Apply
self.wallpaperButton = HighlightableButtonNode()
self.wallpaperButton.setTitle("Set Background from Gallery", with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
@ -829,6 +844,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.contentContainerNode.addSubnode(self.titleNode)
self.contentContainerNode.addSubnode(self.textNode)
self.contentContainerNode.addSubnode(self.doneButton)
self.contentContainerNode.addSubnode(self.wallpaperButton)
self.topContentContainerNode.addSubnode(self.animationContainerNode)
self.animationContainerNode.addSubnode(self.animationNode)
@ -844,6 +860,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
strongSelf.completion?(strongSelf.selectedEmoticon)
}
}
self.wallpaperButton.addTarget(self, action: #selector(self.wallpaperButtonPressed), forControlEvents: .touchUpInside)
self.disposable.set(combineLatest(queue: Queue.mainQueue(), self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), self.selectedEmoticonPromise.get(), self.isDarkAppearancePromise.get()).start(next: { [weak self] themes, selectedEmoticon, isDarkAppearance in
guard let strongSelf = self else {
@ -1020,6 +1037,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.cancel?()
}
@objc func wallpaperButtonPressed() {
self.controller?.changeWallpaper()
}
func dimTapped() {
if self.selectedEmoticon == self.initiallySelectedEmoticon {
self.cancelButtonPressed()
@ -1197,7 +1218,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
let titleHeight: CGFloat = 54.0
let contentHeight = titleHeight + bottomInset + 188.0
let contentHeight = titleHeight + bottomInset + 188.0 + 50.0
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
@ -1235,7 +1256,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let buttonInset: CGFloat = 16.0
let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 6.0, width: contentFrame.width, height: doneButtonHeight))
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - 50.0 - insets.bottom - 6.0, width: contentFrame.width, height: doneButtonHeight))
let wallpaperButtonSize = self.wallpaperButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude))
transition.updateFrame(node: self.wallpaperButton, frame: CGRect(origin: CGPoint(x: floor((contentFrame.width - wallpaperButtonSize.width) / 2.0), y: contentHeight - wallpaperButtonSize.height - insets.bottom - 6.0 - 9.0), size: wallpaperButtonSize))
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame)

View File

@ -385,7 +385,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|> deliverOnMainQueue).start(next: { [weak controller] wallpaper in
controller?.dismiss()
let galleryController = WallpaperGalleryController(context: context, source: .wallpaper(wallpaper, options, colors, intensity, rotation, nil))
present(galleryController, nil)
navigationController?.pushViewController(galleryController)
}, error: { [weak controller] error in
controller?.dismiss()
})

View File

@ -93,7 +93,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, sendEmoji: { _, _, _ in
}, sendGif: { _, _, _, _, _ in
return false
}, sendBotContextResultAsGif: { _, _, _, _, _ in
}, sendBotContextResultAsGif: { _, _, _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _ in

View File

@ -1,6 +1,8 @@
import AsyncDisplayKit
import Display
import TelegramPresentationData
import TextFormat
import Markdown
final class PeerInfoScreenCommentItem: PeerInfoScreenItem {
let id: AnyHashable
@ -47,8 +49,16 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
let verticalInset: CGFloat = 7.0
self.textNode.maximumNumberOfLines = 0
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: presentationData.theme.list.freeTextColor)
self.activateArea.accessibilityLabel = item.text
let textFont = Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize)
let textColor = presentationData.theme.list.freeTextColor
let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}))
self.textNode.attributedText = attributedText
self.activateArea.accessibilityLabel = attributedText.string
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))

View File

@ -991,7 +991,9 @@ func canEditPeerInfo(context: AccountContext, peer: Peer?, chatLocation: ChatLoc
if context.account.peerId == peer?.id {
return true
}
if let channel = peer as? TelegramChannel {
if let user = peer as? TelegramUser, let botInfo = user.botInfo {
return botInfo.flags.contains(.canEdit)
} else if let channel = peer as? TelegramChannel {
if let threadData = threadData {
if chatLocation.threadId == 1 {
return false
@ -1270,6 +1272,9 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag
if user.isDeleted {
return false
}
if let botInfo = user.botInfo {
return botInfo.flags.contains(.canEdit)
}
if let isContact = isContact, !isContact {
return false
}

View File

@ -2120,11 +2120,17 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
contentHeight += 32.0
}
var isEditableBot = false
if let user = peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
isEditableBot = true
}
var fieldKeys: [PeerInfoHeaderTextFieldNodeKey] = []
if let user = peer as? TelegramUser {
if !user.isDeleted {
fieldKeys.append(.firstName)
if user.botInfo == nil {
if isEditableBot {
fieldKeys.append(.description)
} else if user.botInfo == nil {
fieldKeys.append(.lastName)
}
}
@ -2160,6 +2166,8 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
updateText = cachedData.about ?? ""
} else if let cachedData = cachedData as? CachedGroupData {
updateText = cachedData.about ?? ""
} else if let cachedData = cachedData as? CachedUserData {
updateText = cachedData.about ?? ""
} else {
updateText = ""
}
@ -2179,7 +2187,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
switch key {
case .firstName:
placeholder = presentationData.strings.UserInfo_FirstNamePlaceholder
isEnabled = isContact || isSettings
isEnabled = isContact || isSettings || isEditableBot
case .lastName:
placeholder = presentationData.strings.UserInfo_LastNamePlaceholder
isEnabled = isContact || isSettings
@ -2192,7 +2200,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
isEnabled = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
case .description:
placeholder = presentationData.strings.Channel_Edit_AboutItem
isEnabled = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
isEnabled = canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) || isEditableBot
}
let itemHeight = itemNode.update(width: width, safeInset: safeInset, isSettings: isSettings, hasPrevious: hasPrevious, hasNext: key != fieldKeys.last, placeholder: placeholder, isEnabled: isEnabled, presentationData: presentationData, updateText: updateText)
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: itemHeight)))

View File

@ -540,6 +540,7 @@ private final class PeerInfoInteraction {
let dismissInput: () -> Void
let toggleForumTopics: (Bool) -> Void
let displayTopicsLimited: (TopicsLimitedReason) -> Void
let openPeerMention: (String, ChatControllerInteractionNavigateToPeer) -> Void
init(
openUsername: @escaping (String) -> Void,
@ -588,7 +589,8 @@ private final class PeerInfoInteraction {
editingOpenReactionsSetup: @escaping () -> Void,
dismissInput: @escaping () -> Void,
toggleForumTopics: @escaping (Bool) -> Void,
displayTopicsLimited: @escaping (TopicsLimitedReason) -> Void
displayTopicsLimited: @escaping (TopicsLimitedReason) -> Void,
openPeerMention: @escaping (String, ChatControllerInteractionNavigateToPeer) -> Void
) {
self.openUsername = openUsername
self.openPhone = openPhone
@ -637,6 +639,7 @@ private final class PeerInfoInteraction {
self.dismissInput = dismissInput
self.toggleForumTopics = toggleForumTopics
self.displayTopicsLimited = displayTopicsLimited
self.openPeerMention = openPeerMention
}
}
@ -1390,8 +1393,29 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
let ItemReset = 2
let ItemInfo = 3
let ItemDelete = 4
let ItemUsername = 5
if !user.flags.contains(.isSupport) {
let ItemIntro = 6
let ItemCommands = 7
let ItemBotSettings = 8
let ItemBotInfo = 9
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Username, icon: nil, action: {
interaction.editingOpenPublicLinkSetup()
}))
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: "Edit Intro", icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: {
interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: user.addressName ?? "", behavior: .interactive)))
}))
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemCommands, text: "Edit Commands", icon: UIImage(bundleImageName: "Peer Info/BotCommands"), action: {
interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-commands", behavior: .interactive)))
}))
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemBotSettings, text: "Change Bot Settings", icon: UIImage(bundleImageName: "Peer Info/BotSettings"), action: {
interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: user.addressName ?? "", behavior: .interactive)))
}))
items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemBotInfo, text: "Use [@BotFather]() to manage this bot."))
} else if !user.flags.contains(.isSupport) {
let compactName = EnginePeer(user).compactDisplayTitle
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggest, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: {
interaction.suggestPhoto()
@ -2244,6 +2268,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
text = self.presentationData.strings.PeerInfo_TopicsLimitedDiscussionGroups
}
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_topics", scale: 0.066, colors: [:], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
},
openPeerMention: { [weak self] mention, navigation in
self?.openPeerMention(mention, navigation: navigation)
}
)
@ -2616,7 +2643,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, sendEmoji: { _, _, _ in
}, sendGif: { _, _, _, _, _ in
return false
}, sendBotContextResultAsGif: { _, _, _, _, _ in
}, sendBotContextResultAsGif: { _, _, _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _ in
@ -3123,8 +3150,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return
}
}
let peerBio = cachedData.about ?? ""
if (peer.firstName ?? "") != firstName || (peer.lastName ?? "") != lastName || (bio ?? "") != (cachedData.about ?? "") {
if (peer.firstName ?? "") != firstName || (peer.lastName ?? "") != lastName || (bio ?? "") != peerBio {
var updateNameSignal: Signal<Void, NoError> = .complete()
var hasProgress = false
if peer.firstName != firstName || peer.lastName != lastName {
@ -3140,6 +3169,62 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
hasProgress = true
}
var dismissStatus: (() -> Void)?
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
dismissStatus?()
}))
dismissStatus = { [weak statusController] in
self?.activeActionDisposable.set(nil)
statusController?.dismiss()
}
if hasProgress {
strongSelf.controller?.present(statusController, in: .window(.root))
}
strongSelf.activeActionDisposable.set((combineLatest(updateNameSignal, updateBioSignal) |> deliverOnMainQueue
|> deliverOnMainQueue).start(completed: {
dismissStatus?()
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil)
}))
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil)
}
} else if let botInfo = peer.botInfo, botInfo.flags.contains(.canEdit), let cachedData = data.cachedData as? CachedUserData {
let firstName = strongSelf.headerNode.editingContentNode.editingTextForKey(.firstName) ?? ""
let bio = strongSelf.headerNode.editingContentNode.editingTextForKey(.description)
if let bio = bio {
if Int32(bio.count) > strongSelf.context.userLimits.maxAboutLength {
for (_, section) in strongSelf.editingSections {
section.animateErrorIfNeeded()
}
strongSelf.hapticFeedback.error()
return
}
}
let peerBio = cachedData.about ?? ""
if (peer.firstName ?? "") != firstName || (bio ?? "") != peerBio {
var updateNameSignal: Signal<Void, NoError> = .complete()
var hasProgress = false
if peer.firstName != firstName {
updateNameSignal = context.engine.peers.updateBotName(peerId: peer.id, name: firstName)
|> `catch` { _ -> Signal<Void, NoError> in
return .complete()
}
hasProgress = true
}
var updateBioSignal: Signal<Void, NoError> = .complete()
if let bio = bio, bio != cachedData.about {
updateBioSignal = context.engine.peers.updateBotAbout(peerId: peer.id, about: bio)
|> `catch` { _ -> Signal<Void, NoError> in
return .complete()
}
hasProgress = true
}
var dismissStatus: (() -> Void)?
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
dismissStatus?()
@ -4009,8 +4094,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return
}
switch navigation {
case let .chat(_, subject, peekData):
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, keepStack: .always, peekData: peekData))
case let .chat(inputState, subject, peekData):
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, updateTextInputState: inputState, activateInput: inputState != nil ? .text : nil, keepStack: .always, peekData: peekData))
case .info:
if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
@ -4640,7 +4725,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if let user = peer as? TelegramUser {
if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeColors, icon: { theme in
items.append(.action(ContextMenuActionItem(text: "Change Background", icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
@ -6483,16 +6568,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
private func editingOpenPublicLinkSetup() {
var upgradedToSupergroupImpl: (() -> Void)?
let controller = channelVisibilityController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, mode: .generic, upgradedToSupergroup: { _, f in
upgradedToSupergroupImpl?()
f()
})
self.controller?.push(controller)
upgradedToSupergroupImpl = { [weak controller] in
if let controller = controller, let navigationController = controller.navigationController as? NavigationController {
rebuildControllerStackAfterSupergroupUpgrade(controller: controller, navigationController: navigationController)
if let peer = self.data?.peer as? TelegramUser, peer.botInfo != nil {
let controller = usernameSetupController(context: self.context, mode: .bot(self.peerId))
self.controller?.push(controller)
} else {
var upgradedToSupergroupImpl: (() -> Void)?
let controller = channelVisibilityController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, mode: .generic, upgradedToSupergroup: { _, f in
upgradedToSupergroupImpl?()
f()
})
self.controller?.push(controller)
upgradedToSupergroupImpl = { [weak controller] in
if let controller = controller, let navigationController = controller.navigationController as? NavigationController {
rebuildControllerStackAfterSupergroupUpgrade(controller: controller, navigationController: navigationController)
}
}
}
}

View File

@ -1441,7 +1441,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
tapMessage?(message)
}, clickThroughMessage: {
clickThroughMessage?()
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in

View File

@ -125,7 +125,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private let scrollingContainer: ASDisplayNode
private let containerNode: ASDisplayNode
private let backgroundContainerNode: ASDisplayNode
private let backgroundNode: ASImageNode
private let backgroundClipNode: ASDisplayNode
private let backgroundMaskNode: ASDisplayNode
private var effectView: UIView?
private var gradientNode: ASDisplayNode?
private var arrowGradientNode: ASDisplayNode?
@ -155,14 +156,14 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.containerNode = ASDisplayNode()
self.backgroundContainerNode = ASDisplayNode()
self.backgroundMaskNode = ASDisplayNode()
self.backgroundClipNode = ASDisplayNode()
self.backgroundClipNode.backgroundColor = .white
let fillColor = UIColor(white: 0.0, alpha: 0.8)
self.scrollingContainer = ASDisplayNode()
self.backgroundNode = ASImageNode()
self.backgroundNode.image = generateAdjustedStretchableFilledCircleImage(diameter: 15.0, color: fillColor)
func svgPath(_ path: StaticString, scale: CGPoint = CGPoint(x: 1.0, y: 1.0), offset: CGPoint = CGPoint()) throws -> UIBezierPath {
var index: UnsafePointer<UInt8> = path.utf8Start
let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount)
@ -210,42 +211,20 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.arrowContainer = ASDisplayNode()
let fontSize: CGFloat
if case .light = style {
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
self.backgroundContainerNode.clipsToBounds = true
self.backgroundContainerNode.cornerRadius = 14.0
if #available(iOS 13.0, *) {
self.backgroundContainerNode.layer.cornerCurve = .continuous
}
fontSize = 17.0
self.arrowEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
self.arrowContainer.view.addSubview(self.arrowEffectView!)
let maskLayer = CAShapeLayer()
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
maskLayer.path = path.cgPath
}
maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize)
self.arrowContainer.layer.mask = maskLayer
} else if case .default = style {
if case .top = location {
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.backgroundContainerNode.clipsToBounds = true
self.backgroundContainerNode.cornerRadius = 14.0
self.backgroundMaskNode.addSubnode(self.backgroundClipNode)
self.backgroundClipNode.clipsToBounds = true
if case let .point(_, arrowPosition) = location, case .right = arrowPosition {
self.backgroundClipNode.cornerRadius = 8.5
} else {
self.backgroundClipNode.cornerRadius = 14.0
}
if #available(iOS 13.0, *) {
self.backgroundContainerNode.layer.cornerCurve = .continuous
self.backgroundClipNode.layer.cornerCurve = .continuous
}
fontSize = 14.0
self.arrowEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.arrowContainer.view.addSubview(self.arrowEffectView!)
let maskLayer = CAShapeLayer()
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
maskLayer.path = path.cgPath
}
maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize)
self.arrowContainer.layer.mask = maskLayer
} else if case let .gradient(leftColor, rightColor) = style {
self.gradientNode = ASDisplayNode()
self.gradientNode?.setLayerBlock({
@ -278,16 +257,39 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
}
maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize)
self.arrowContainer.layer.mask = maskLayer
} else if case .top = location {
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.containerNode.clipsToBounds = true
self.containerNode.cornerRadius = 14.0
if #available(iOS 13.0, *) {
self.containerNode.layer.cornerCurve = .continuous
}
fontSize = 14.0
} else {
let effect: UIBlurEffect
if case .light = style {
effect = UIBlurEffect(style: .light)
} else {
effect = UIBlurEffect(style: .dark)
}
self.effectView = UIVisualEffectView(effect: effect)
self.backgroundMaskNode.addSubnode(self.backgroundClipNode)
self.backgroundClipNode.clipsToBounds = true
if case let .point(_, arrowPosition) = location, case .right = arrowPosition {
self.backgroundClipNode.cornerRadius = 8.5
} else {
self.backgroundClipNode.cornerRadius = 14.0
}
if #available(iOS 13.0, *) {
self.backgroundClipNode.layer.cornerCurve = .continuous
}
self.backgroundMaskNode.addSubnode(self.arrowContainer)
fontSize = 14.0
let maskLayer = CAShapeLayer()
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
maskLayer.path = path.cgPath
}
maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize)
maskLayer.fillColor = UIColor.white.cgColor
self.arrowContainer.layer.addSublayer(maskLayer)
self.backgroundMaskNode.layer.shouldRasterize = true
self.backgroundMaskNode.layer.rasterizationScale = UIScreen.main.scale
}
self.textNode = ImmediateTextNode()
@ -313,21 +315,12 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
super.init()
self.containerNode.addSubnode(self.backgroundContainerNode)
self.arrowContainer.addSubnode(self.arrowNode)
self.backgroundNode.addSubnode(self.arrowContainer)
if let gradientNode = self.gradientNode {
self.backgroundContainerNode.addSubnode(gradientNode)
self.containerNode.addSubnode(self.arrowContainer)
self.arrowNode.removeFromSupernode()
}
else if let effectView = self.effectView {
} else if let effectView = self.effectView {
self.backgroundContainerNode.view.addSubview(effectView)
if let _ = self.arrowEffectView {
self.containerNode.addSubnode(self.arrowContainer)
self.arrowNode.removeFromSupernode()
}
} else {
self.backgroundContainerNode.addSubnode(self.backgroundNode)
self.backgroundContainerNode.layer.mask = self.backgroundMaskNode.layer
}
self.containerNode.addSubnode(self.textNode)
self.containerNode.addSubnode(self.animatedStickerNode)
@ -476,9 +469,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
transition.updateFrame(node: self.containerNode, frame: backgroundFrame)
transition.updateFrame(node: self.backgroundContainerNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
transition.updateFrame(node: self.backgroundMaskNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0))
transition.updateFrame(node: self.backgroundClipNode, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: backgroundFrame.size))
if let effectView = self.effectView {
transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0))
}
if let gradientNode = self.gradientNode {
transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
@ -490,31 +485,35 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let arrowFrame: CGRect
switch arrowPosition {
case .bottom, .top:
if invertArrow {
arrowFrame = CGRect(origin: CGPoint(x: floor(arrowCenterX - arrowSize.width / 2.0), y: -arrowSize.height), size: arrowSize)
} else {
arrowFrame = CGRect(origin: CGPoint(x: floor(arrowCenterX - arrowSize.width / 2.0), y: backgroundFrame.height), size: arrowSize)
}
ContainedViewLayoutTransition.immediate.updateTransformScale(node: self.arrowContainer, scale: CGPoint(x: 1.0, y: invertArrow ? -1.0 : 1.0))
case .bottom, .top:
if invertArrow {
arrowFrame = CGRect(origin: CGPoint(x: floor(arrowCenterX - arrowSize.width / 2.0), y: -arrowSize.height), size: arrowSize)
} else {
arrowFrame = CGRect(origin: CGPoint(x: floor(arrowCenterX - arrowSize.width / 2.0), y: backgroundFrame.height), size: arrowSize)
}
ContainedViewLayoutTransition.immediate.updateTransformScale(node: self.arrowContainer, scale: CGPoint(x: 1.0, y: invertArrow ? -1.0 : 1.0))
if case .gradient = self.tooltipStyle {
transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: -backgroundFrame.minX, dy: 0.0))
let arrowBounds = CGRect(origin: CGPoint(), size: arrowSize)
self.arrowNode.frame = arrowBounds
self.arrowEffectView?.frame = arrowBounds
self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size)
case .right:
arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.width + arrowSize.height, y: rect.midY), size: CGSize(width: arrowSize.height, height: arrowSize.width))
ContainedViewLayoutTransition.immediate.updateTransformRotation(node: self.arrowContainer, angle: -CGFloat.pi / 2.0)
transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: 0.0, dy: -backgroundFrame.minY - floorToScreenPixels((backgroundFrame.height - arrowSize.width) / 2.0)))
let arrowBounds = CGRect(origin: CGPoint(x: 0.0, y: -0.5), size: arrowSize)
self.arrowNode.frame = arrowBounds
self.arrowEffectView?.frame = arrowBounds
self.arrowGradientNode?.frame = arrowBounds
} else {
transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: -backgroundFrame.minX + 10.0, dy: 10.0))
}
let arrowBounds = CGRect(origin: CGPoint(), size: arrowSize)
self.arrowNode.frame = arrowBounds
self.arrowEffectView?.frame = arrowBounds
self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size)
case .right:
arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.width + arrowSize.height, y: rect.midY), size: CGSize(width: arrowSize.height, height: arrowSize.width))
ContainedViewLayoutTransition.immediate.updateTransformRotation(node: self.arrowContainer, angle: -CGFloat.pi / 2.0)
transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: 8.0 - UIScreenPixel, dy: 16.0 + -backgroundFrame.minY - floorToScreenPixels((backgroundFrame.height + 20.0 - arrowSize.width) / 2.0)))
let arrowBounds = CGRect(origin: .zero, size: arrowSize)
self.arrowNode.frame = arrowBounds
self.arrowEffectView?.frame = arrowBounds
self.arrowGradientNode?.frame = arrowBounds
}
} else {
self.arrowNode.isHidden = true
@ -566,7 +565,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.containerNode.layer.animateScale(from: 0.96, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
if let _ = self.validLayout {
self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -13.0 - self.backgroundNode.frame.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -13.0 - self.backgroundContainerNode.frame.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
case let .point(_, arrowPosition):
self.containerNode.layer.animateSpring(from: NSNumber(value: Float(0.01)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.4, damping: 105.0)
@ -607,7 +606,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
})
self.containerNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
if let _ = self.validLayout {
self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -13.0 - self.backgroundNode.frame.height), duration: 0.3, removeOnCompletion: false, additive: true)
self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -13.0 - self.backgroundContainerNode.frame.height), duration: 0.3, removeOnCompletion: false, additive: true)
}
case let .point(_, arrowPosition):
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in

View File

@ -23,7 +23,7 @@ public extension CharacterSet {
}
public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": true, "https": true]) -> Bool {
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), let scheme = url.scheme, let requiresTopLevelDomain = validSchemes[scheme], let host = url.host, (!requiresTopLevelDomain || host.contains(".")) && url.user == nil {
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), let scheme = url.scheme?.lowercased(), let requiresTopLevelDomain = validSchemes[scheme], let host = url.host, (!requiresTopLevelDomain || host.contains(".")) && url.user == nil {
if requiresTopLevelDomain {
let components = host.components(separatedBy: ".")
let domain = (components.first ?? "")