mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-05 04:10:16 +00:00
Merge commit '23d36baeb29fc9699c88b747ed966291d05f8374'
This commit is contained in:
commit
af88f1840b
@ -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";
|
||||
|
@ -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?,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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) }
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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",
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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! {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
22
submodules/TelegramUI/Components/SliderContextItem/BUILD
Normal file
22
submodules/TelegramUI/Components/SliderContextItem/BUILD
Normal 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",
|
||||
],
|
||||
)
|
@ -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
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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?) {
|
||||
|
@ -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: [])
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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?) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user