Merge commit '23d36baeb29fc9699c88b747ed966291d05f8374'

This commit is contained in:
Ali 2023-02-10 22:19:18 +04:00
commit af88f1840b
46 changed files with 850 additions and 306 deletions

View File

@ -8910,3 +8910,12 @@ Sorry for the inconvenience.";
"Appearance.VoiceOver.Theme" = "%@ Theme";
"ChatList.EmptyChatListWithArchive" = "All of your chats are archived.";
"Conversation.AudioRateTooltip15X" = "Audio will play at 1.5X speed.";
"Conversation.AudioRateOptionsTooltip" = "Long tap for more speed values.";
"ImportStickerPack.EmojiCount_1" = "%@ Emoji";
"ImportStickerPack.EmojiCount_any" = "%@ Emojis";
"ImportStickerPack.ImportingEmojis" = "Importing Emojis";
"ImportStickerPack.CreateNewEmojiPack" = "Create a New Emoji Pack";

View File

@ -371,12 +371,12 @@ private final class LoadingProgressNode: ASDisplayNode {
}
public struct AttachmentMainButtonState {
let text: String?
let backgroundColor: UIColor
let textColor: UIColor
let isVisible: Bool
let isLoading: Bool
let isEnabled: Bool
public let text: String?
public let backgroundColor: UIColor
public let textColor: UIColor
public let isVisible: Bool
public let isLoading: Bool
public let isEnabled: Bool
public init(
text: String?,

View File

@ -2713,7 +2713,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
}
}
mediaAccessoryPanel.setRate = { [weak self] rate in
mediaAccessoryPanel.setRate = { [weak self] rate, fromMenu in
guard let strongSelf = self else {
return
}
@ -2742,21 +2742,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
})
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let slowdown: Bool?
let text: String?
let rate: CGFloat?
if baseRate == .x1 {
slowdown = true
text = presentationData.strings.Conversation_AudioRateTooltipNormal
rate = 1.0
} else if baseRate == .x1_5 {
text = presentationData.strings.Conversation_AudioRateTooltip15X
rate = 1.5
} else if baseRate == .x2 {
slowdown = false
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate = 2.0
} else {
slowdown = nil
text = nil
rate = nil
}
if let slowdown = slowdown {
if let rate, let text, !fromMenu {
controller.present(
UndoOverlayController(
presentationData: presentationData,
content: .audioRate(
slowdown: slowdown,
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate: rate,
text: text
),
elevatedLayout: false,
animateInAsReplacement: hasTooltip,

View File

@ -1357,7 +1357,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
strongSelf.updateVideoVisibility()
} else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
if let photo = peer.largeProfileImage, photo.hasVideo {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
}
}
}))
} else {

View File

@ -45,6 +45,7 @@ swift_library(
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem",
],
visibility = [
"//visibility:public",

View File

@ -22,6 +22,7 @@ import TelegramUIPreferences
import OpenInExternalAppUI
import AVKit
import TextFormat
import SliderContextItem
public enum UniversalVideoGalleryItemContentInfo {
case message(Message)
@ -1267,17 +1268,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.moreBarButtonRateTimestamp = CFAbsoluteTimeGetCurrent()
if abs(effectiveBaseRate - 1.0) > 0.01 {
let rateString: String
if abs(effectiveBaseRate - 0.5) < 0.01 {
rateString = "0.5x"
} else if abs(effectiveBaseRate - 1.5) < 0.01 {
rateString = "1.5x"
} else if abs(effectiveBaseRate - 2.0) < 0.01 {
rateString = "2x"
} else {
rateString = "x"
var stringValue = String(format: "%.1fx", effectiveBaseRate)
if stringValue.hasSuffix(".0x") {
stringValue = stringValue.replacingOccurrences(of: ".0x", with: "x")
}
strongSelf.moreBarButton.setContent(.image(optionsRateImage(rate: rateString, isLarge: true)), animated: animated)
strongSelf.moreBarButton.setContent(.image(optionsRateImage(rate: stringValue, isLarge: true)), animated: animated)
} else {
strongSelf.moreBarButton.setContent(.more(optionsCircleImage(dark: false)), animated: animated)
}
@ -2428,15 +2423,19 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
private func openMoreMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems()
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
return
}
var dismissImpl: (() -> Void)?
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems(dismiss: {
dismissImpl?()
})
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
self.isShowingContextMenuPromise.set(true)
controller.presentInGlobalOverlay(contextController)
dismissImpl = { [weak contextController] in
contextController?.dismiss()
}
contextController.dismissed = { [weak self] in
Queue.mainQueue().after(0.1, {
self?.isShowingContextMenuPromise.set(false)
@ -2455,7 +2454,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return speedList
}
private func contextMenuMainItems() -> Signal<[ContextMenuItem], NoError> {
private func contextMenuMainItems(dismiss: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> {
guard let videoNode = self.videoNode else {
return .single([])
}
@ -2470,6 +2469,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var items: [ContextMenuItem] = []
var speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal
var speedIconText: String = "1x"
for (text, iconText, speed) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
if abs(speed - status.baseRate) < 0.01 {
speedValue = text
speedIconText = iconText
break
}
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in
return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor)
}, action: { c, _ in
guard let strongSelf = self else {
c.dismiss(completion: nil)
return
}
c.setItems(strongSelf.contextMenuSpeedItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
items.append(.separator)
if let (message, _, _) = strongSelf.contentInfo() {
let context = strongSelf.context
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
@ -2493,27 +2515,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
})))
}
var speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal
var speedIconText: String = "1x"
for (text, iconText, speed) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
if abs(speed - status.baseRate) < 0.01 {
speedValue = text
speedIconText = iconText
break
}
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in
return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor)
}, action: { c, _ in
guard let strongSelf = self else {
c.dismiss(completion: nil)
return
}
c.setItems(strongSelf.contextMenuSpeedItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
// if #available(iOS 11.0, *) {
// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
// f(.default)
@ -2593,7 +2594,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
private func contextMenuSpeedItems() -> Signal<[ContextMenuItem], NoError> {
private func contextMenuSpeedItems(dismiss: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> {
guard let videoNode = self.videoNode else {
return .single([])
}
@ -2607,7 +2608,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
}, iconPosition: .left, action: { c, _ in
guard let strongSelf = self else {
c.dismiss(completion: nil)
return
}
c.setItems(strongSelf.contextMenuMainItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
items.append(.custom(SliderContextItem(minValue: 0.05, maxValue: 2.5, value: status.baseRate, valueChanged: { [weak self] newValue, finished in
guard let strongSelf = self, let videoNode = strongSelf.videoNode else {
return
}
videoNode.setBaseRate(newValue)
if finished {
dismiss()
}
}), true))
items.append(.separator)
for (text, _, rate) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
let isSelected = abs(status.baseRate - rate) < 0.01
items.append(.action(ContextMenuActionItem(text: text, icon: { theme in
@ -2631,17 +2654,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
})))
}
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
}, iconPosition: .left, action: { c, _ in
guard let strongSelf = self else {
c.dismiss(completion: nil)
return
}
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
return items
}
}

View File

@ -11,18 +11,39 @@ enum StickerVerificationStatus {
public class ImportStickerPack {
public enum StickerPackType {
case image
case animation
case video
var importType: CreateStickerSetType {
switch self {
public enum ContentType {
case image
case animation
case video
var importType: CreateStickerSetType.ContentType {
switch self {
case .image:
return .image
case .animation:
return .animation
case .video:
return .video
}
}
}
case stickers(content: ContentType)
case emoji(content: ContentType, textColored: Bool)
var contentType: StickerPackType.ContentType {
switch self {
case let .stickers(content), let .emoji(content, _):
return content
}
}
var importType: CreateStickerSetType {
switch self {
case let .stickers(content):
return .stickers(content: content.importType)
case let .emoji(content, textColored):
return .emoji(content: content.importType, textColored: textColored)
}
}
}
@ -51,12 +72,14 @@ public class ImportStickerPack {
let content: Content
let emojis: [String]
let keywords: String
let uuid: UUID
var resource: MediaResource?
init(content: Content, emojis: [String], uuid: UUID = UUID()) {
init(content: Content, emojis: [String], keywords: String, uuid: UUID = UUID()) {
self.content = content
self.emojis = emojis
self.keywords = keywords
self.uuid = uuid
}
@ -88,13 +111,25 @@ public class ImportStickerPack {
self.software = json["software"] as? String ?? ""
let isAnimated = json["isAnimated"] as? Bool ?? false
let isVideo = json["isVideo"] as? Bool ?? false
let isEmoji = json["isEmoji"] as? Bool ?? false
let isTextColored = json["isTextColored"] as? Bool ?? false
let type: StickerPackType
if isAnimated {
type = .animation
} else if isVideo {
type = .video
if isEmoji {
if isAnimated {
type = .emoji(content: .animation, textColored: isTextColored)
} else if isVideo {
type = .emoji(content: .video, textColored: isTextColored)
} else {
type = .emoji(content: .image, textColored: isTextColored)
}
} else {
type = .image
if isAnimated {
type = .stickers(content: .animation)
} else if isVideo {
type = .stickers(content: .video)
} else {
type = .stickers(content: .image)
}
}
self.type = type
@ -102,23 +137,23 @@ public class ImportStickerPack {
if let dataString = sticker["data"] as? String, let mimeType = sticker["mimeType"] as? String, let data = Data(base64Encoded: dataString) {
var content: Sticker.Content?
switch mimeType.lowercased() {
case "image/png":
if case .image = type {
content = .image(data)
}
case "application/x-tgsticker":
if case .animation = type {
content = .animation(data)
}
case "video/webm", "image/webp", "image/gif":
if case .video = type {
content = .video(data, mimeType)
}
default:
break
case "image/png":
if case .image = type.contentType {
content = .image(data)
}
case "application/x-tgsticker":
if case .animation = type.contentType {
content = .animation(data)
}
case "video/webm", "image/webp", "image/gif":
if case .video = type.contentType {
content = .video(data, mimeType)
}
default:
break
}
if let content = content {
return Sticker(content: content, emojis: sticker["emojis"] as? [String] ?? [])
return Sticker(content: content, emojis: sticker["emojis"] as? [String] ?? [], keywords: sticker["keywords"] as? String ?? "")
}
}
return nil

View File

@ -79,7 +79,7 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
Queue.mainQueue().after(0.1) {
self.controllerNode.updateStickerPack(self.stickerPack, verifiedStickers: Set(), declinedStickers: Set(), uploadedStickerResources: [:])
if case .image = self.stickerPack.type {
if case .image = self.stickerPack.type.contentType {
} else {
let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in

View File

@ -394,19 +394,39 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
forceTitleUpdate = true
}
if let _ = self.stickerPack, self.currentItems.isEmpty || self.currentItems.count != self.pendingItems.count || self.pendingItems != self.currentItems || forceTitleUpdate {
let itemsPerRow: Int
if let stickerPack = self.stickerPack, case .emoji = stickerPack.type {
itemsPerRow = 8
} else {
itemsPerRow = 4
}
if let stickerPack = self.stickerPack, self.currentItems.isEmpty || self.currentItems.count != self.pendingItems.count || self.pendingItems != self.currentItems || forceTitleUpdate {
let previousItems = self.currentItems
self.currentItems = self.pendingItems
let titleFont = Font.medium(20.0)
let title: String
if let _ = self.progress {
title = self.presentationData.strings.ImportStickerPack_ImportingStickers
if case .emoji = stickerPack.type {
title = self.presentationData.strings.ImportStickerPack_ImportingEmojis
} else {
title = self.presentationData.strings.ImportStickerPack_ImportingStickers
}
} else {
title = self.presentationData.strings.ImportStickerPack_StickerCount(Int32(self.currentItems.count))
if case .emoji = stickerPack.type {
title = self.presentationData.strings.ImportStickerPack_EmojiCount(Int32(self.currentItems.count))
} else {
title = self.presentationData.strings.ImportStickerPack_StickerCount(Int32(self.currentItems.count))
}
}
self.contentTitleNode.attributedText = stringWithAppliedEntities(title, entities: [], baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleFont, italicFont: titleFont, boldItalicFont: titleFont, fixedFont: titleFont, blockQuoteFont: titleFont, message: nil)
if case .emoji = stickerPack.type {
self.createActionButtonNode.setTitle(self.presentationData.strings.ImportStickerPack_CreateNewEmojiPack, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
} else {
self.createActionButtonNode.setTitle(self.presentationData.strings.ImportStickerPack_CreateNewStickerSet, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
}
if !forceTitleUpdate {
transaction = StickerPackPreviewGridTransaction(previousList: previousItems, list: self.currentItems, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme)
}
@ -422,7 +442,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentContainerFrame.minX, y: self.contentBackgroundNode.frame.minY + titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
let itemsPerRow = 4
let itemWidth = floor(contentFrame.size.width / CGFloat(itemsPerRow))
let rowCount = itemCount / itemsPerRow + (itemCount % itemsPerRow != 0 ? 1 : 0)
@ -605,9 +625,9 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
if let localResource = item.stickerItem.resource {
self.context.account.postbox.mediaBox.copyResourceData(from: localResource.id, to: resource.id)
}
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType))
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords))
} else if let resource = item.stickerItem.resource {
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType))
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords))
}
}
var thumbnailSticker: ImportSticker?
@ -618,7 +638,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
}
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnail.data)
thumbnailSticker = ImportSticker(resource: resource, emojis: [], dimensions: dimensions, mimeType: thumbnail.mimeType)
thumbnailSticker = ImportSticker(resource: resource, emojis: [], dimensions: dimensions, mimeType: thumbnail.mimeType, keywords: thumbnail.keywords)
}
let firstStickerItem = thumbnailSticker ?? stickers.first
@ -636,7 +656,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
if let (_, _, count) = strongSelf.progress {
strongSelf.progress = (1.0, count, count)
var animated = false
if case .image = stickerPack.type {
if case .image = stickerPack.type.contentType {
animated = true
}
strongSelf.radialStatus.transitionToState(.progress(color: strongSelf.presentationData.theme.list.itemAccentColor, lineWidth: 6.0, value: 1.0, cancelEnabled: false, animateRotation: false), animated: animated, synchronous: true, completion: {})
@ -804,7 +824,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
}
self.pendingItems = updatedItems
if case .image = stickerPack.type {
if case .image = stickerPack.type.contentType {
} else {
self.stickerPackReady = stickerPack.stickers.count == (verifiedStickers.count + declinedStickers.count) && updatedItems.count > 0
}

View File

@ -18,7 +18,7 @@ public final class MediaPlaybackStoredState: Codable {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.timestamp = try container.decode(Double.self, forKey: "timestamp")
self.playbackRate = AudioPlaybackRate(rawValue: try container.decode(Int32.self, forKey: "playbackRate")) ?? .x1
self.playbackRate = AudioPlaybackRate(rawValue: try container.decode(Int32.self, forKey: "playbackRate"))
}
public func encode(to encoder: Encoder) throws {

View File

@ -145,6 +145,9 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
if let iconImage = self.iconImage {
context.saveGState()
let iconRect = CGRect(origin: CGPoint(), size: iconImage.size)
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.clip(to: iconRect, mask: iconImage.cgImage!)
context.fill(iconRect)
context.restoreGState()
@ -180,6 +183,9 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
if let iconImage = self.iconImage {
context.saveGState()
let iconRect = CGRect(origin: CGPoint(), size: iconImage.size)
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.clip(to: iconRect, mask: iconImage.cgImage!)
context.fill(iconRect)
context.restoreGState()

View File

@ -379,7 +379,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) }
dict[-930399486] = { return Api.InputStickerSet.parse_inputStickerSetPremiumGifts($0) }
dict[-2044933984] = { return Api.InputStickerSet.parse_inputStickerSetShortName($0) }
dict[-6249322] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) }
dict[853188252] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) }
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) }

View File

@ -392,26 +392,27 @@ public extension Api {
}
public extension Api {
enum InputStickerSetItem: TypeConstructorDescription {
case inputStickerSetItem(flags: Int32, document: Api.InputDocument, emoji: String, maskCoords: Api.MaskCoords?)
case inputStickerSetItem(flags: Int32, document: Api.InputDocument, emoji: String, maskCoords: Api.MaskCoords?, keywords: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords):
case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords, let keywords):
if boxed {
buffer.appendInt32(-6249322)
buffer.appendInt32(853188252)
}
serializeInt32(flags, buffer: buffer, boxed: false)
document.serialize(buffer, true)
serializeString(emoji, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {maskCoords!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(keywords!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords):
return ("inputStickerSetItem", [("flags", flags as Any), ("document", document as Any), ("emoji", emoji as Any), ("maskCoords", maskCoords as Any)])
case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords, let keywords):
return ("inputStickerSetItem", [("flags", flags as Any), ("document", document as Any), ("emoji", emoji as Any), ("maskCoords", maskCoords as Any), ("keywords", keywords as Any)])
}
}
@ -428,12 +429,15 @@ public extension Api {
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.MaskCoords
} }
var _5: String?
if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.InputStickerSetItem.inputStickerSetItem(flags: _1!, document: _2!, emoji: _3!, maskCoords: _4)
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.InputStickerSetItem.inputStickerSetItem(flags: _1!, document: _2!, emoji: _3!, maskCoords: _4, keywords: _5)
}
else {
return nil

View File

@ -7919,6 +7919,25 @@ public extension Api.functions.stickers {
})
}
}
public extension Api.functions.stickers {
static func changeSticker(flags: Int32, sticker: Api.InputDocument, emoji: String?, maskCoords: Api.MaskCoords?, keywords: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.StickerSet>) {
let buffer = Buffer()
buffer.appendInt32(-179077444)
serializeInt32(flags, buffer: buffer, boxed: false)
sticker.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(emoji!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {maskCoords!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(keywords!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "stickers.changeSticker", parameters: [("flags", String(describing: flags)), ("sticker", String(describing: sticker)), ("emoji", String(describing: emoji)), ("maskCoords", String(describing: maskCoords)), ("keywords", String(describing: keywords))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in
let reader = BufferReader(buffer)
var result: Api.messages.StickerSet?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet
}
return result
})
}
}
public extension Api.functions.stickers {
static func changeStickerPosition(sticker: Api.InputDocument, position: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.StickerSet>) {
let buffer = Buffer()
@ -7991,12 +8010,30 @@ public extension Api.functions.stickers {
}
}
public extension Api.functions.stickers {
static func setStickerSetThumb(stickerset: Api.InputStickerSet, thumb: Api.InputDocument) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.StickerSet>) {
static func renameStickerSet(stickerset: Api.InputStickerSet, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.StickerSet>) {
let buffer = Buffer()
buffer.appendInt32(-1707717072)
buffer.appendInt32(306912256)
stickerset.serialize(buffer, true)
thumb.serialize(buffer, true)
return (FunctionDescription(name: "stickers.setStickerSetThumb", parameters: [("stickerset", String(describing: stickerset)), ("thumb", String(describing: thumb))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in
serializeString(title, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stickers.renameStickerSet", parameters: [("stickerset", String(describing: stickerset)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in
let reader = BufferReader(buffer)
var result: Api.messages.StickerSet?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet
}
return result
})
}
}
public extension Api.functions.stickers {
static func setStickerSetThumb(flags: Int32, stickerset: Api.InputStickerSet, thumb: Api.InputDocument?, thumbDocumentId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.StickerSet>) {
let buffer = Buffer()
buffer.appendInt32(-1486204014)
serializeInt32(flags, buffer: buffer, boxed: false)
stickerset.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {thumb!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt64(thumbDocumentId!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "stickers.setStickerSetThumb", parameters: [("flags", String(describing: flags)), ("stickerset", String(describing: stickerset)), ("thumb", String(describing: thumb)), ("thumbDocumentId", String(describing: thumbDocumentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in
let reader = BufferReader(buffer)
var result: Api.messages.StickerSet?
if let signature = reader.readInt32() {

View File

@ -24,6 +24,9 @@ swift_library(
"//submodules/Markdown:Markdown",
"//submodules/TelegramCallsUI:TelegramCallsUI",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
"//submodules/TelegramNotices:TelegramNotices",
"//submodules/TooltipUI:TooltipUI",
"//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem",
],
visibility = [
"//visibility:public",

View File

@ -11,6 +11,9 @@ import AccountContext
import TelegramStringFormatting
import ManagedAnimationNode
import ContextUI
import TelegramNotices
import TooltipUI
import SliderContextItem
private let titleFont = Font.regular(12.0)
private let subtitleFont = Font.regular(10.0)
@ -171,7 +174,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
public var tapAction: (() -> Void)?
public var close: (() -> Void)?
public var setRate: ((AudioPlaybackRate) -> Void)?
public var setRate: ((AudioPlaybackRate, Bool) -> Void)?
public var togglePlayPause: (() -> Void)?
public var playPrevious: (() -> Void)?
public var playNext: (() -> Void)?
@ -184,24 +187,11 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
guard self.playbackBaseRate != oldValue, let playbackBaseRate = self.playbackBaseRate else {
return
}
switch playbackBaseRate {
case .x0_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x1:
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
case .x1_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x2:
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.accentTextColor)))
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
default:
break
}
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
self.rateButton.accessibilityValue = playbackBaseRate.stringValue
self.rateButton.setContent(.image(optionsRateImage(rate: playbackBaseRate.stringValue.uppercased(), color: self.theme.rootController.navigationBar.controlColor)))
}
}
@ -374,18 +364,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
self.scrubbingNode.updateContent(.standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.rootController.navigationBar.accentTextColor, bufferingColor: self.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.5), chapters: []))
if let playbackBaseRate = self.playbackBaseRate {
switch playbackBaseRate {
case .x0_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x1:
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
case .x1_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x2:
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.accentTextColor)))
default:
break
}
self.rateButton.setContent(.image(optionsRateImage(rate: playbackBaseRate.stringValue.uppercased(), color: self.theme.rootController.navigationBar.controlColor)))
}
if let (size, leftInset, rightInset) = self.validLayout {
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
@ -493,6 +472,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight)))
let rateButtonSize = CGSize(width: 30.0, height: minHeight)
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 33.0 - closeButtonSize.width - rateButtonSize.width - rightInset, y: -4.0), size: rateButtonSize))
transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 4.0 + UIScreenPixel), size: CGSize(width: 28.0, height: 28.0)))
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 37.0 - 2.0), size: CGSize(width: size.width, height: 2.0)))
@ -520,7 +500,18 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
} else {
nextRate = .x2
}
self.setRate?(nextRate)
self.setRate?(nextRate, false)
let frame = self.rateButton.view.convert(self.rateButton.bounds, to: nil)
let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, let controller = strongSelf.getController?(), value == 4 {
controller.present(TooltipScreen(account: strongSelf.context.account, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}), in: .window(.root))
}
})
}
private func speedList(strings: PresentationStrings) -> [(String, String, AudioPlaybackRate)] {
@ -533,9 +524,18 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
return speedList
}
private func contextMenuSpeedItems() -> Signal<[ContextMenuItem], NoError> {
private func contextMenuSpeedItems(dismiss: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> {
var items: [ContextMenuItem] = []
items.append(.custom(SliderContextItem(minValue: 0.05, maxValue: 2.5, value: self.playbackBaseRate?.doubleValue ?? 1.0, valueChanged: { [weak self] newValue, finished in
self?.setRate?(AudioPlaybackRate(newValue), true)
if finished {
dismiss()
}
}), true))
items.append(.separator)
for (text, _, rate) in self.speedList(strings: self.strings) {
let isSelected = self.playbackBaseRate == rate
items.append(.action(ContextMenuActionItem(text: text, icon: { theme in
@ -547,7 +547,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
}, action: { [weak self] _, f in
f(.default)
self?.setRate?(rate)
self?.setRate?(rate, true)
})))
}
@ -558,9 +558,14 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
guard let controller = self.getController?() else {
return
}
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems()
var dismissImpl: (() -> Void)?
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems(dismiss: {
dismissImpl?()
})
let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
dismissImpl = { [weak contextController] in
contextController?.dismiss()
}
self.presentInGlobalOverlay?(contextController)
}
@ -626,41 +631,38 @@ private final class PlayPauseIconNode: ManagedAnimationNode {
}
private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 16.0), rotatedContext: { size, context in
let isLarge = "".isEmpty
return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
let lineWidth = 1.0 + UIScreenPixel
context.setLineWidth(lineWidth)
context.setStrokeColor(color.cgColor)
if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: color) {
image.draw(at: CGPoint(x: 0.0, y: 0.0))
}
let string = NSMutableAttributedString(string: rate, font: Font.with(size: 11.0, design: .round, weight: .bold), textColor: color)
let string = NSMutableAttributedString(string: rate, font: Font.with(size: isLarge ? 11.0 : 10.0, design: .round, weight: .semibold), textColor: color)
var offset = CGPoint(x: 1.0, y: 0.0)
var width: CGFloat
if rate.count >= 3 {
if rate == "0.5X" {
if rate == "0.5x" {
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.5
} else {
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.3
}
width = 29.0
} else {
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
width = 19.0
offset.x += -0.3
}
let path = UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - width) / 2.0), y: 0.0, width: width, height: 16.0).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 2.0, height: 2.0))
context.addPath(path.cgPath)
context.strokePath()
if !isLarge {
offset.x *= 0.5
offset.y *= 0.5
}
let boundingRect = string.boundingRect(with: size, options: [], context: nil)
string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + UIScreenPixel + floor((size.height - boundingRect.height) / 2.0)))
string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0)))
UIGraphicsPopContext()
})

View File

@ -11,7 +11,7 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode {
public let containerNode: MediaNavigationAccessoryContainerNode
public var close: (() -> Void)?
public var setRate: ((AudioPlaybackRate) -> Void)?
public var setRate: ((AudioPlaybackRate, Bool) -> Void)?
public var togglePlayPause: (() -> Void)?
public var tapAction: (() -> Void)?
public var playPrevious: (() -> Void)?
@ -32,8 +32,8 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode {
close()
}
}
self.containerNode.headerNode.setRate = { [weak self] rate in
self?.setRate?(rate)
self.containerNode.headerNode.setRate = { [weak self] rate, fromMenu in
self?.setRate?(rate, fromMenu)
}
self.containerNode.headerNode.togglePlayPause = { [weak self] in
if let strongSelf = self, let togglePlayPause = strongSelf.togglePlayPause {

View File

@ -669,7 +669,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
}
}
mediaAccessoryPanel.setRate = { [weak self] rate in
mediaAccessoryPanel.setRate = { [weak self] rate, fromMenu in
guard let strongSelf = self else {
return
}
@ -687,41 +687,48 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
}
strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
// var hasTooltip = false
// strongSelf.forEachController({ controller in
// if let controller = controller as? UndoOverlayController {
// hasTooltip = true
// controller.dismissWithCommitAction()
// }
// return true
// })
//
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
// let slowdown: Bool?
// if baseRate == .x1 {
// slowdown = true
// } else if baseRate == .x2 {
// slowdown = false
// } else {
// slowdown = nil
// }
// if let slowdown = slowdown {
// strongSelf.present(
// UndoOverlayController(
// presentationData: presentationData,
// content: .audioRate(
// slowdown: slowdown,
// text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
// ),
// elevatedLayout: false,
// animateInAsReplacement: hasTooltip,
// action: { action in
// return true
// }
// ),
// in: .current
// )
// }
var hasTooltip = false
strongSelf.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
hasTooltip = true
controller.dismissWithCommitAction()
}
return true
})
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String?
let rate: CGFloat?
if baseRate == .x1 {
text = presentationData.strings.Conversation_AudioRateTooltipNormal
rate = 1.0
} else if baseRate == .x1_5 {
text = presentationData.strings.Conversation_AudioRateTooltip15X
rate = 1.5
} else if baseRate == .x2 {
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate = 2.0
} else {
text = nil
rate = nil
}
if let rate, let text, !fromMenu {
strongSelf.present(
UndoOverlayController(
presentationData: presentationData,
content: .audioRate(
rate: rate,
text: text
),
elevatedLayout: false,
animateInAsReplacement: hasTooltip,
action: { action in
return true
}
),
in: .current
)
}
})
}
mediaAccessoryPanel.togglePlayPause = { [weak self] in

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 152
return 153
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -81,12 +81,14 @@ public struct ImportSticker {
let emojis: [String]
public let dimensions: PixelDimensions
public let mimeType: String
public let keywords: String
public init(resource: MediaResource, emojis: [String], dimensions: PixelDimensions, mimeType: String) {
public init(resource: MediaResource, emojis: [String], dimensions: PixelDimensions, mimeType: String, keywords: String) {
self.resource = resource
self.emojis = emojis
self.dimensions = dimensions
self.mimeType = mimeType
self.keywords = keywords
}
}
@ -96,9 +98,21 @@ public enum CreateStickerSetStatus {
}
public enum CreateStickerSetType {
case image
case animation
case video
public enum ContentType {
case image
case animation
case video
}
case stickers(content: ContentType)
case emoji(content: ContentType, textColored: Bool)
var contentType: ContentType {
switch self {
case let .stickers(content), let .emoji(content, _):
return content
}
}
}
func _internal_createStickerSet(account: Account, title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, type: CreateStickerSetType, software: String?) -> Signal<CreateStickerSetStatus, CreateStickerSetError> {
@ -133,7 +147,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
}
if resources.count == stickers.count {
var flags: Int32 = 0
switch type {
switch type.contentType {
case .animation:
flags |= (1 << 1)
case .video:
@ -141,12 +155,24 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
default:
break
}
if case let .emoji(_, textColored) = type {
flags |= (1 << 5)
if textColored {
flags |= (1 << 6)
}
}
var inputStickers: [Api.InputStickerSetItem] = []
let stickerDocuments = thumbnail != nil ? resources.dropLast() : resources
for i in 0 ..< stickerDocuments.count {
let sticker = stickers[i]
let resource = resources[i]
inputStickers.append(.inputStickerSetItem(flags: 0, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil))
var flags: Int32 = 0
if sticker.keywords.count > 0 {
flags |= (1 << 1)
}
inputStickers.append(.inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords))
}
var thumbnailDocument: Api.InputDocument?
if thumbnail != nil, let resource = resources.last {

View File

@ -167,6 +167,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case audioTranscriptionSuggestion = 33
case clearStorageDismissedTipSize = 34
case dismissedTrendingEmojiPacks = 35
case audioRateOptionsTip = 36
case translationSuggestion = 37
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -359,6 +361,14 @@ private struct ApplicationSpecificNoticeKeys {
static func dismissedTrendingEmojiPacks() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingEmojiPacks.key)
}
static func translationSuggestionNotice() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.translationSuggestion.key)
}
static func audioRateOptionsTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioRateOptionsTip.key)
}
}
public struct ApplicationSpecificNotice {
@ -1207,22 +1217,84 @@ public struct ApplicationSpecificNotice {
}
}
public static func incrementAudioTranscriptionSuggestion(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
return accountManager.transaction { transaction -> Int in
public static func incrementAudioTranscriptionSuggestion(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int32 = 1) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
let previousValue = currentValue
currentValue += Int32(count)
currentValue += count
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion(), entry)
}
return Int(previousValue)
return previousValue
}
}
public static func translationSuggestion(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<(Int32, Int32), NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.translationSuggestionNotice())
|> map { view -> (Int32, Int32) in
if let value = view.value?.get(ApplicationSpecificTimestampAndCounterNotice.self) {
return (value.counter, value.timestamp)
} else {
return (0, 0)
}
}
}
public static func incrementTranslationSuggestion(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int32 = 1, timestamp: Int32) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
var currentValue: Int32 = 0
var currentTimestamp: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.translationSuggestionNotice())?.get(ApplicationSpecificTimestampAndCounterNotice.self) {
currentValue = value.counter
currentTimestamp = value.timestamp
}
if currentTimestamp > timestamp {
return Int32(currentValue)
} else {
let previousValue = currentValue
currentValue = max(0, Int32(currentValue + count))
if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.translationSuggestionNotice(), entry)
}
return Int32(previousValue)
}
}
}
public static func getAudioRateOptionsTip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.audioRateOptionsTip())?.get(ApplicationSpecificCounterNotice.self) {
return value.value
} else {
return 0
}
}
}
public static func incrementAudioRateOptionsTip(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int32 = 1) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.audioRateOptionsTip())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
let previousValue = currentValue
currentValue += count
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.audioRateOptionsTip(), entry)
}
return previousValue
}
}
public static func reset(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
}

View File

@ -0,0 +1,22 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "SliderContextItem",
module_name = "SliderContextItem",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/ContextUI:ContextUI",
"//submodules/TelegramPresentationData:TelegramPresentationData",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,184 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ContextUI
import TelegramPresentationData
public final class SliderContextItem: ContextMenuCustomItem {
private let minValue: CGFloat
private let maxValue: CGFloat
private let value: CGFloat
private let valueChanged: (CGFloat, Bool) -> Void
public init(minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.minValue = minValue
self.maxValue = maxValue
self.value = value
self.valueChanged = valueChanged
}
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return SliderContextItemNode(presentationData: presentationData, getController: getController, minValue: self.minValue, maxValue: self.maxValue, value: self.value, valueChanged: self.valueChanged)
}
}
private let textFont = Font.regular(17.0)
private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private var presentationData: PresentationData
private let backgroundTextNode: ImmediateTextNode
private let foregroundNode: ASDisplayNode
private let foregroundTextNode: ImmediateTextNode
let minValue: CGFloat
let maxValue: CGFloat
var value: CGFloat = 1.0 {
didSet {
self.updateValue()
}
}
private let valueChanged: (CGFloat, Bool) -> Void
private let hapticFeedback = HapticFeedback()
init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.presentationData = presentationData
self.minValue = minValue
self.maxValue = maxValue
self.value = value
self.valueChanged = valueChanged
self.backgroundTextNode = ImmediateTextNode()
self.backgroundTextNode.isAccessibilityElement = false
self.backgroundTextNode.isUserInteractionEnabled = false
self.backgroundTextNode.displaysAsynchronously = false
self.backgroundTextNode.textAlignment = .left
self.foregroundNode = ASDisplayNode()
self.foregroundNode.clipsToBounds = true
self.foregroundNode.isAccessibilityElement = false
self.foregroundNode.backgroundColor = UIColor(rgb: 0xffffff)
self.foregroundNode.isUserInteractionEnabled = false
self.foregroundTextNode = ImmediateTextNode()
self.foregroundTextNode.isAccessibilityElement = false
self.foregroundTextNode.isUserInteractionEnabled = false
self.foregroundTextNode.displaysAsynchronously = false
self.foregroundTextNode.textAlignment = .left
super.init()
self.isUserInteractionEnabled = true
self.addSubnode(self.backgroundTextNode)
self.addSubnode(self.foregroundNode)
self.foregroundNode.addSubnode(self.foregroundTextNode)
}
override func didLoad() {
super.didLoad()
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
self.view.addGestureRecognizer(panGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.view.addGestureRecognizer(tapGestureRecognizer)
}
func updateTheme(presentationData: PresentationData) {
self.presentationData = presentationData
self.updateValue()
}
private func updateValue(transition: ContainedViewLayoutTransition = .immediate) {
let width = self.frame.width
let range = self.maxValue - self.minValue
let value = (self.value - self.minValue) / range
transition.updateFrameAdditive(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: value * width, height: self.frame.height)))
var stringValue = String(format: "%.1fx", self.value)
if stringValue.hasSuffix(".0x") {
stringValue = stringValue.replacingOccurrences(of: ".0x", with: "x")
}
self.backgroundTextNode.attributedText = NSAttributedString(string: stringValue, font: textFont, textColor: UIColor(rgb: 0xffffff))
self.foregroundTextNode.attributedText = NSAttributedString(string: stringValue, font: textFont, textColor: UIColor(rgb: 0x000000))
let _ = self.backgroundTextNode.updateLayout(CGSize(width: 70.0, height: .greatestFiniteMagnitude))
let _ = self.foregroundTextNode.updateLayout(CGSize(width: 70.0, height: .greatestFiniteMagnitude))
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let valueWidth: CGFloat = 70.0
let height: CGFloat = 45.0
var textSize = self.backgroundTextNode.updateLayout(CGSize(width: valueWidth, height: .greatestFiniteMagnitude))
textSize.width = valueWidth
return (CGSize(width: height * 3.0, height: height), { size, transition in
let leftInset: CGFloat = 17.0
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - textSize.height) / 2.0)), size: textSize)
transition.updateFrameAdditive(node: self.backgroundTextNode, frame: textFrame)
transition.updateFrameAdditive(node: self.foregroundTextNode, frame: textFrame)
self.updateValue(transition: transition)
})
}
@objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
let range = self.maxValue - self.minValue
switch gestureRecognizer.state {
case .began:
break
case .changed:
let previousValue = self.value
let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
let delta = translation / self.bounds.width * range
self.value = max(self.minValue, min(self.maxValue, self.value + delta))
gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view)
if self.value == 2.0 && previousValue != 2.0 {
self.hapticFeedback.impact(.soft)
} else if self.value == 1.0 && previousValue != 1.0 {
self.hapticFeedback.impact(.soft)
} else if self.value == 2.5 && previousValue != 2.5 {
self.hapticFeedback.impact(.soft)
} else if self.value == 0.05 && previousValue != 0.05 {
self.hapticFeedback.impact(.soft)
}
if abs(previousValue - self.value) >= 0.001 {
self.valueChanged(self.value, false)
}
case .ended:
let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
let delta = translation / self.bounds.width * range
self.value = max(self.minValue, min(self.maxValue, self.value + delta))
self.valueChanged(self.value, true)
default:
break
}
}
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
let range = self.maxValue - self.minValue
let location = gestureRecognizer.location(in: gestureRecognizer.view)
self.value = max(self.minValue, min(self.maxValue, location.x / range))
self.valueChanged(self.value, true)
}
func canBeHighlighted() -> Bool {
return false
}
func updateIsHighlighted(isHighlighted: Bool) {
}
func performAction() {
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -174,7 +174,9 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
strongSelf.updateVideoVisibility()
} else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
if let photo = peer.largeProfileImage, photo.hasVideo {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
}
}
}))
} else {

View File

@ -428,7 +428,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
}
return .url(url: url, concealed: concealed)
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
@ -455,7 +455,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
break
case let .url(url, concealed):
self.item?.controllerInteraction.openUrl(url, concealed, nil, nil)
case let .peerMention(peerId, _):
case let .peerMention(peerId, _, _):
if let item = self.item {
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
@ -481,7 +481,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
break
case let .url(url, _):
item.controllerInteraction.longTap(.url(url), nil)
case let .peerMention(peerId, mention):
case let .peerMention(peerId, mention, _):
item.controllerInteraction.longTap(.peerMention(peerId, mention), nil)
case let .textMention(name):
item.controllerInteraction.longTap(.mention(name), nil)

View File

@ -3550,7 +3550,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let f = {
let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
|> take(1)
|> deliverOnMainQueue).start(next: { sharedData in
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
guard let strongSelf = self else {
return
}
let translationSettings: TranslationSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
translationSettings = current
@ -3565,6 +3568,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let (_, language) = canTranslateText(context: context, text: text.string, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages)
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
let controller = TranslateScreen(context: context, text: text.string, canCopy: canCopy, fromLanguage: language)
controller.pushController = { [weak self] c in
self?.effectiveNavigationController?._keepModalDismissProgress = true
@ -6717,9 +6722,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.translationStateDisposable = (combineLatest(
queue: .concurrentDefaultQueue(),
isPremium,
isHidden
) |> mapToSignal { isPremium, isHidden -> Signal<ChatPresentationTranslationState?, NoError> in
if isPremium && !isHidden {
isHidden,
ApplicationSpecificNotice.translationSuggestion(accountManager: self.context.sharedContext.accountManager)
) |> mapToSignal { isPremium, isHidden, counterAndTimestamp -> Signal<ChatPresentationTranslationState?, NoError> in
var maybeSuggestPremium = false
if counterAndTimestamp.0 >= 3 {
maybeSuggestPremium = true
}
if (isPremium || maybeSuggestPremium) && !isHidden {
return chatTranslationState(context: context, peerId: peerId)
|> map { translationState -> ChatPresentationTranslationState? in
if let translationState, !translationState.fromLang.isEmpty {

View File

@ -475,7 +475,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
}
return .url(url: url, concealed: concealed)
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: true)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {

View File

@ -1163,7 +1163,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
return .url(url: url, concealed: concealed)
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {

View File

@ -109,7 +109,7 @@ enum ChatMessageBubbleContentTapAction {
case none
case url(url: String, concealed: Bool)
case textMention(String)
case peerMention(PeerId, String)
case peerMention(peerId: PeerId, mention: String, openProfile: Bool)
case botCommand(String)
case hashtag(String?, String)
case instantPage

View File

@ -3650,13 +3650,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return .action({
self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage)
})
case let .peerMention(peerId, _):
case let .peerMention(peerId, _, openProfile):
return .action({ [weak self] in
if let item = self?.item {
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let self = self, let item = self.item, let peer = peer {
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
item.controllerInteraction.openPeer(peer, openProfile ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
}
})
}
@ -3791,7 +3791,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return .action({
item.controllerInteraction.longTap(.url(url), message)
})
case let .peerMention(peerId, mention):
case let .peerMention(peerId, mention, _):
return .action({
item.controllerInteraction.longTap(.peerMention(peerId, mention), message)
})

View File

@ -586,50 +586,10 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
}
strongSelf.updateVideoVisibility()
} else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
if let photo = peer.largeProfileImage, photo.hasVideo {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
}
}
// let cachedPeerData = peerView.cachedData
// if let cachedPeerData = cachedPeerData as? CachedUserData, case let .known(maybePhoto) = cachedPeerData.photo {
// if let photo = maybePhoto, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer) {
// let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
// let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
// let videoContent = NativeVideoContent(id: .profileVideo(videoId, "\(Int32.random(in: 0 ..< Int32.max))"), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false, storeAfterDownload: nil)
// if videoContent.id != strongSelf.videoContent?.id {
// strongSelf.videoNode?.removeFromSupernode()
// strongSelf.videoContent = videoContent
// }
//
// if strongSelf.hierarchyTrackingLayer == nil {
// let hierarchyTrackingLayer = HierarchyTrackingLayer()
// hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
// guard let strongSelf = self else {
// return
// }
// strongSelf.trackingIsInHierarchy = true
// }
//
// hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
// guard let strongSelf = self else {
// return
// }
// strongSelf.trackingIsInHierarchy = false
// }
// strongSelf.hierarchyTrackingLayer = hierarchyTrackingLayer
// strongSelf.layer.addSublayer(hierarchyTrackingLayer)
// }
// } else {
// strongSelf.videoContent = nil
//
// strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer()
// strongSelf.hierarchyTrackingLayer = nil
// }
//
// strongSelf.updateVideoVisibility()
// } else {
}))
} else {
self.cachedDataDisposable.set(nil)

View File

@ -416,7 +416,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
return .url(url: url, concealed: concealed)
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {

View File

@ -1703,7 +1703,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
return .url(url: url, concealed: concealed)
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {

View File

@ -582,7 +582,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
return .url(url: url, concealed: concealed)
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {

View File

@ -16,6 +16,8 @@ import MoreButtonNode
import ContextUI
import TranslateUI
import TelegramUIPreferences
import TelegramNotices
import PremiumUI
final class ChatTranslationPanelNode: ASDisplayNode {
private let context: AccountContext
@ -26,7 +28,8 @@ final class ChatTranslationPanelNode: ASDisplayNode {
private let buttonIconNode: ASImageNode
private let buttonTextNode: ImmediateTextNode
private let moreButton: MoreButtonNode
private let closeButton: HighlightableButtonNode
private var theme: PresentationTheme?
private var chatInterfaceState: ChatPresentationInterfaceState?
@ -47,6 +50,11 @@ final class ChatTranslationPanelNode: ASDisplayNode {
self.moreButton = MoreButtonNode(theme: context.sharedContext.currentPresentationData.with { $0 }.theme)
self.moreButton.iconNode.enqueueState(.more, animated: false)
self.moreButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton = HighlightableButtonNode()
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton.displaysAsynchronously = false
super.init()
@ -65,6 +73,9 @@ final class ChatTranslationPanelNode: ASDisplayNode {
strongSelf.morePressed(node: strongSelf.moreButton.contextSourceNode, gesture: gesture)
}
}
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
self.addSubnode(self.closeButton)
}
func animateOut() {
@ -90,6 +101,7 @@ final class ChatTranslationPanelNode: ASDisplayNode {
self.buttonIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/Translate"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
self.moreButton.theme = interfaceState.theme
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelEncircledCloseIconImage(interfaceState.theme), for: [])
}
if themeUpdated || isEnabledUpdated {
@ -139,6 +151,17 @@ final class ChatTranslationPanelNode: ASDisplayNode {
let moreButtonSize = self.moreButton.measure(CGSize(width: 100.0, height: panelHeight))
self.moreButton.frame = CGRect(origin: CGPoint(x: width - contentRightInset - moreButtonSize.width, y: floorToScreenPixels((panelHeight - moreButtonSize.height) / 2.0)), size: moreButtonSize)
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
self.closeButton.frame = CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize)
if interfaceState.isPremium {
self.moreButton.isHidden = false
self.closeButton.isHidden = true
} else {
self.moreButton.isHidden = true
self.closeButton.isHidden = false
}
let buttonPadding: CGFloat = 10.0
let buttonSpacing: CGFloat = 10.0
let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: width - contentRightInset - moreButtonSize.width, height: panelHeight))
@ -154,12 +177,30 @@ final class ChatTranslationPanelNode: ASDisplayNode {
return panelHeight
}
@objc private func closePressed() {
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: self.context.sharedContext.accountManager, count: -100, timestamp: Int32(Date().timeIntervalSince1970) + 60 * 60 * 24 * 7).start()
}
@objc private func buttonPressed() {
guard let translationState = self.chatInterfaceState?.translationState else {
return
}
self.interfaceInteraction?.toggleTranslation(translationState.isEnabled ? .original : .translated)
let isPremium = self.chatInterfaceState?.isPremium ?? false
if isPremium {
self.interfaceInteraction?.toggleTranslation(translationState.isEnabled ? .original : .translated)
} else if !translationState.isEnabled {
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .translation, action: {
let controller = PremiumIntroScreen(context: context, source: .translation)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
self.interfaceInteraction?.chatController()?.push(controller)
}
}
@objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {

View File

@ -36,7 +36,7 @@ private func generateCollapseIcon(theme: PresentationTheme) -> UIImage? {
}
private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 16.0), rotatedContext: { size, context in
return generateImage(CGSize(width: 36.0, height: 16.0), rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
@ -50,7 +50,11 @@ private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage?
var offset = CGPoint(x: 1.0, y: 0.0)
var width: CGFloat
if rate.count >= 3 {
if rate.count >= 5 {
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.5
width = 33.0
} else if rate.count >= 3 {
if rate == "0.5X" {
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.5
@ -420,6 +424,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
baseRate = .x2
} else if value.status.baseRate.isEqual(to: 1.5) {
baseRate = .x1_5
} else if value.status.baseRate.isEqual(to: 0.5) {
baseRate = .x0_5
} else {
baseRate = .x1
}
@ -786,6 +792,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.rateButton.setImage(optionsRateImage(rate: "2X", color: self.presentationData.theme.list.itemAccentColor), for: [])
case .x1_5:
self.rateButton.setImage(optionsRateImage(rate: "1.5X", color: self.presentationData.theme.list.itemAccentColor), for: [])
case .x0_5:
self.rateButton.setImage(optionsRateImage(rate: "0.5X", color: self.presentationData.theme.list.itemAccentColor), for: [])
default:
self.rateButton.setImage(optionsRateImage(rate: "1X", color: self.presentationData.theme.list.itemSecondaryTextColor), for: [])
}

View File

@ -262,7 +262,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
}
}
mediaAccessoryPanel.setRate = { [weak self] rate in
mediaAccessoryPanel.setRate = { [weak self] rate, fromMenu in
guard let strongSelf = self else {
return
}
@ -291,21 +291,28 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
})
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let slowdown: Bool?
let text: String?
let rate: CGFloat?
if baseRate == .x1 {
slowdown = true
text = presentationData.strings.Conversation_AudioRateTooltipNormal
rate = 1.0
} else if baseRate == .x1_5 {
text = presentationData.strings.Conversation_AudioRateTooltip15X
rate = 1.5
} else if baseRate == .x2 {
slowdown = false
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate = 2.0
} else {
slowdown = nil
text = nil
rate = nil
}
if let slowdown = slowdown {
if let rate, let text, !fromMenu {
controller.present(
UndoOverlayController(
presentationData: presentationData,
content: .audioRate(
slowdown: slowdown,
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate: rate,
text: text
),
elevatedLayout: false,
animateInAsReplacement: hasTooltip,

View File

@ -2821,7 +2821,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
}
titleStringText = title
titleAttributes = MultiScaleTextState.Attributes(font: Font.regular(30.0), color: presentationData.theme.list.itemPrimaryTextColor)
titleAttributes = MultiScaleTextState.Attributes(font: Font.medium(30.0), color: presentationData.theme.list.itemPrimaryTextColor)
smallTitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(30.0), color: .white)
if self.isSettings, let user = peer as? TelegramUser {

View File

@ -15,26 +15,73 @@ public enum MusicPlaybackSettingsLooping: Int32 {
case all = 2
}
public enum AudioPlaybackRate: Int32 {
case x0_5 = 500
case x1 = 1000
case x1_5 = 1500
case x2 = 2000
case x4 = 4000
case x8 = 8000
case x16 = 16000
public enum AudioPlaybackRate: Equatable {
case x0_5
case x1
case x1_5
case x2
case x4
case x8
case x16
case custom(Int32)
public var doubleValue: Double {
return Double(self.rawValue) / 1000.0
}
public var rawValue: Int32 {
switch self {
case .x0_5:
return 500
case .x1:
return 1000
case .x1_5:
return 1500
case .x2:
return 2000
case .x4:
return 4000
case .x8:
return 8000
case .x16:
return 16000
case let .custom(value):
return value
}
}
public init(_ value: Double) {
if let resolved = AudioPlaybackRate(rawValue: Int32(value * 1000.0)) {
self = resolved
} else {
self.init(rawValue: Int32(value * 1000.0))
}
public init(rawValue: Int32) {
switch rawValue {
case 500:
self = .x0_5
case 1000:
self = .x1
case 1500:
self = .x1_5
case 2000:
self = .x2
case 4000:
self = .x4
case 8000:
self = .x8
case 16000:
self = .x16
default:
self = .custom(rawValue)
}
}
public var stringValue: String {
var stringValue = String(format: "%.1fx", self.doubleValue)
if stringValue.hasSuffix(".0x") {
stringValue = stringValue.replacingOccurrences(of: ".0x", with: "x")
}
return stringValue
}
}
public struct MusicPlaybackSettings: Codable, Equatable {
@ -57,7 +104,7 @@ public struct MusicPlaybackSettings: Codable, Equatable {
self.order = MusicPlaybackSettingsOrder(rawValue: try container.decode(Int32.self, forKey: "order")) ?? .regular
self.looping = MusicPlaybackSettingsLooping(rawValue: try container.decode(Int32.self, forKey: "looping")) ?? .none
self.voicePlaybackRate = AudioPlaybackRate(rawValue: try container.decodeIfPresent(Int32.self, forKey: "voicePlaybackRate") ?? AudioPlaybackRate.x1.rawValue) ?? .x1
self.voicePlaybackRate = AudioPlaybackRate(rawValue: try container.decodeIfPresent(Int32.self, forKey: "voicePlaybackRate") ?? AudioPlaybackRate.x1.rawValue)
}
public func encode(to encoder: Encoder) throws {

View File

@ -26,7 +26,7 @@ public enum UndoOverlayContent {
case linkCopied(text: String)
case banned(text: String)
case importedMessage(text: String)
case audioRate(slowdown: Bool, text: String)
case audioRate(rate: CGFloat, text: String)
case forward(savedMessages: Bool, text: String)
case autoDelete(isOn: Bool, title: String?, text: String)
case gigagroupConversion(text: String)

View File

@ -540,11 +540,21 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false
}
self.originalRemainingSeconds = duration
case let .audioRate(slowdown, text):
case let .audioRate(rate, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: slowdown ? "anim_voicespeedstop" : "anim_voicespeed", colors: [:], scale: 0.066)
let animationName: String
if rate == 1.5 {
animationName = "anim_voice1_5x"
} else if rate == 2.0 {
animationName = "anim_voice2x"
} else {
animationName = "anim_voice1x"
}
self.animationNode = AnimationNode(animation: animationName, colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)

View File

@ -199,6 +199,12 @@ public final class WebAppController: ViewController, AttachmentContainable {
private var placeholderNode: ShimmerEffectNode?
fileprivate let loadingProgressPromise = Promise<CGFloat?>(nil)
fileprivate var mainButtonState: AttachmentMainButtonState? {
didSet {
self.mainButtonStatePromise.set(.single(self.mainButtonState))
}
}
fileprivate let mainButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
private let context: AccountContext
@ -397,6 +403,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
@objc fileprivate func mainButtonPressed() {
if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled {
return
}
self.webView?.lastTouchTimestamp = CACurrentMediaTime()
self.webView?.sendEvent(name: "main_button_pressed", data: nil)
}
@ -635,7 +644,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
let isLoading = json["is_progress_visible"] as? Bool
let isEnabled = json["is_active"] as? Bool
let state = AttachmentMainButtonState(text: text, backgroundColor: backgroundColor, textColor: textColor, isVisible: isVisible, isLoading: isLoading ?? false, isEnabled: isEnabled ?? true)
self.mainButtonStatePromise.set(.single(state))
self.mainButtonState = state
}
}
case "web_app_request_viewport":
@ -971,7 +980,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let id = id {
paramsString = "{button_id: \"\(id)\"}"
}
self.webView?.sendEvent(name: "popup_closed", data: paramsString)
self.webView?.sendEvent(name: "popup_closed", data: paramsString ?? "{}")
}
fileprivate func sendPhoneRequestedEvent(phone: String?) {