Merge commit 'ca80657e161b28abde1b2eafa3528e04f5487ca6'

This commit is contained in:
Isaac 2024-01-18 14:39:58 +04:00
commit 941b3a0477
13 changed files with 271 additions and 84 deletions

View File

@ -10886,6 +10886,7 @@ Sorry for the inconvenience.";
"Call.StatusWeakSignal" = "Weak network signal"; "Call.StatusWeakSignal" = "Weak network signal";
"Conversation.ContactAddContact" = "ADD"; "Conversation.ContactAddContact" = "ADD";
"Conversation.ContactAddContactLong" = "ADD CONTACT";
"Conversation.ContactMessage" = "MESSAGE"; "Conversation.ContactMessage" = "MESSAGE";
"Chat.PlayOnceVideoMessageTooltip" = "This video message can only be played once."; "Chat.PlayOnceVideoMessageTooltip" = "This video message can only be played once.";

View File

@ -304,31 +304,35 @@ final class CameraOutput: NSObject {
self.currentMode = mode self.currentMode = mode
self.lastSampleTimestamp = nil self.lastSampleTimestamp = nil
let codecType: AVVideoCodecType
if case .roundVideo = mode {
codecType = .h264
} else {
if hasHEVCHardwareEncoder {
codecType = .hevc
} else {
codecType = .h264
}
}
guard var videoSettings = self.videoOutput.recommendedVideoSettings(forVideoCodecType: codecType, assetWriterOutputFileType: .mp4) else {
return .complete()
}
var dimensions: CGSize = CGSize(width: 1080, height: 1920)
if orientation == .landscapeLeft || orientation == .landscapeRight {
dimensions = CGSize(width: 1920, height: 1080)
}
var orientation = orientation var orientation = orientation
let dimensions: CGSize
let videoSettings: [String: Any]
if case .roundVideo = mode { if case .roundVideo = mode {
videoSettings[AVVideoWidthKey] = 400 dimensions = videoMessageDimensions.cgSize
videoSettings[AVVideoHeightKey] = 400
dimensions = CGSize(width: 400, height: 400)
orientation = .landscapeRight orientation = .landscapeRight
let compressionProperties: [String: Any] = [
AVVideoAverageBitRateKey: 1000 * 1000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
]
videoSettings = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoCompressionPropertiesKey: compressionProperties,
AVVideoWidthKey: Int(dimensions.width),
AVVideoHeightKey: Int(dimensions.height)
]
} else {
let codecType: AVVideoCodecType = hasHEVCHardwareEncoder ? .hevc : .h264
if orientation == .landscapeLeft || orientation == .landscapeRight {
dimensions = CGSize(width: 1920, height: 1080)
} else {
dimensions = CGSize(width: 1080, height: 1920)
}
guard let settings = self.videoOutput.recommendedVideoSettings(forVideoCodecType: codecType, assetWriterOutputFileType: .mp4) else {
return .complete()
}
videoSettings = settings
} }
let audioSettings = self.audioOutput.recommendedAudioSettingsForAssetWriter(writingTo: .mp4) ?? [:] let audioSettings = self.audioOutput.recommendedAudioSettingsForAssetWriter(writingTo: .mp4) ?? [:]
@ -514,10 +518,10 @@ final class CameraOutput: NSObject {
let extensions = CMFormatDescriptionGetExtensions(formatDescription) as! [String: Any] let extensions = CMFormatDescriptionGetExtensions(formatDescription) as! [String: Any]
var updatedExtensions = extensions var updatedExtensions = extensions
updatedExtensions["CVBytesPerRow"] = 400 * 4 updatedExtensions["CVBytesPerRow"] = videoMessageDimensions.width * 4
var newFormatDescription: CMFormatDescription? var newFormatDescription: CMFormatDescription?
var status = CMVideoFormatDescriptionCreate(allocator: nil, codecType: mediaSubType, width: 400, height: 400, extensions: updatedExtensions as CFDictionary, formatDescriptionOut: &newFormatDescription) var status = CMVideoFormatDescriptionCreate(allocator: nil, codecType: mediaSubType, width: videoMessageDimensions.width, height: videoMessageDimensions.height, extensions: updatedExtensions as CFDictionary, formatDescriptionOut: &newFormatDescription)
guard status == noErr, let newFormatDescription else { guard status == noErr, let newFormatDescription else {
return nil return nil
} }

View File

@ -5,6 +5,9 @@ import CoreMedia
import CoreVideo import CoreVideo
import Metal import Metal
import Display import Display
import TelegramCore
let videoMessageDimensions = PixelDimensions(width: 400, height: 400)
func allocateOutputBufferPool(with inputFormatDescription: CMFormatDescription, outputRetainedBufferCountHint: Int) -> ( func allocateOutputBufferPool(with inputFormatDescription: CMFormatDescription, outputRetainedBufferCountHint: Int) -> (
outputBufferPool: CVPixelBufferPool?, outputBufferPool: CVPixelBufferPool?,
@ -114,8 +117,7 @@ class CameraRoundVideoFilter {
} }
self.inputFormatDescription = formatDescription self.inputFormatDescription = formatDescription
let diameter: CGFloat = 400.0 let circleImage = generateImage(videoMessageDimensions.cgSize, opaque: false, scale: 1.0, rotatedContext: { size, context in
let circleImage = generateImage(CGSize(width: diameter, height: diameter), opaque: false, scale: 1.0, rotatedContext: { size, context in
let bounds = CGRect(origin: .zero, size: size) let bounds = CGRect(origin: .zero, size: size)
context.clear(bounds) context.clear(bounds)
context.setFillColor(UIColor.white.cgColor) context.setFillColor(UIColor.white.cgColor)
@ -158,7 +160,7 @@ class CameraRoundVideoFilter {
var sourceImage = CIImage(cvImageBuffer: pixelBuffer) var sourceImage = CIImage(cvImageBuffer: pixelBuffer)
sourceImage = sourceImage.oriented(additional ? .leftMirrored : .right) sourceImage = sourceImage.oriented(additional ? .leftMirrored : .right)
let scale = 400.0 / min(sourceImage.extent.width, sourceImage.extent.height) let scale = CGFloat(videoMessageDimensions.width) / min(sourceImage.extent.width, sourceImage.extent.height)
resizeFilter.setValue(sourceImage, forKey: kCIInputImageKey) resizeFilter.setValue(sourceImage, forKey: kCIInputImageKey)
resizeFilter.setValue(scale, forKey: kCIInputScaleKey) resizeFilter.setValue(scale, forKey: kCIInputScaleKey)
@ -203,18 +205,14 @@ class CameraRoundVideoFilter {
guard let finalImage else { guard let finalImage else {
return nil return nil
} }
if finalImage.extent.width != 400 {
print("wtf: \(finalImage)")
}
var pbuf: CVPixelBuffer? var pbuf: CVPixelBuffer?
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, outputPixelBufferPool!, &pbuf) CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, outputPixelBufferPool!, &pbuf)
guard let outputPixelBuffer = pbuf else { guard let outputPixelBuffer = pbuf else {
return nil return nil
} }
self.ciContext.render(finalImage, to: outputPixelBuffer, bounds: CGRect(origin: .zero, size: CGSize(width: 400, height: 400)), colorSpace: outputColorSpace) self.ciContext.render(finalImage, to: outputPixelBuffer, bounds: CGRect(origin: .zero, size: videoMessageDimensions.cgSize), colorSpace: outputColorSpace)
return outputPixelBuffer return outputPixelBuffer
} }

View File

@ -47,6 +47,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
let title: String let title: String
let titleColor: ItemListDisclosureItemTitleColor let titleColor: ItemListDisclosureItemTitleColor
let titleFont: ItemListDisclosureItemTitleFont let titleFont: ItemListDisclosureItemTitleFont
let titleIcon: UIImage?
let enabled: Bool let enabled: Bool
let label: String let label: String
let labelStyle: ItemListDisclosureLabelStyle let labelStyle: ItemListDisclosureLabelStyle
@ -59,7 +60,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public let shimmeringIndex: Int? public let shimmeringIndex: Int?
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.icon = icon self.icon = icon
self.context = context self.context = context
@ -67,6 +68,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
self.title = title self.title = title
self.titleColor = titleColor self.titleColor = titleColor
self.titleFont = titleFont self.titleFont = titleFont
self.titleIcon = titleIcon
self.enabled = enabled self.enabled = enabled
self.labelStyle = labelStyle self.labelStyle = labelStyle
self.label = label self.label = label
@ -138,6 +140,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
var avatarNode: AvatarNode? var avatarNode: AvatarNode?
let iconNode: ASImageNode let iconNode: ASImageNode
let titleNode: TextNode let titleNode: TextNode
let titleIconNode: ASImageNode
public let labelNode: TextNode public let labelNode: TextNode
var additionalDetailLabelNode: TextNode? var additionalDetailLabelNode: TextNode?
let arrowNode: ASImageNode let arrowNode: ASImageNode
@ -184,6 +187,10 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
self.titleIconNode = ASImageNode()
self.titleIconNode.displayWithoutProcessing = true
self.titleIconNode.displaysAsynchronously = false
self.labelNode = TextNode() self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false self.labelNode.isUserInteractionEnabled = false
@ -626,6 +633,19 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.additionalDetailLabelNode = nil strongSelf.additionalDetailLabelNode = nil
additionalDetailLabelNode.removeFromSupernode() additionalDetailLabelNode.removeFromSupernode()
} }
if let titleIcon = item.titleIcon {
if strongSelf.titleIconNode.supernode == nil {
strongSelf.addSubnode(strongSelf.titleIconNode)
}
strongSelf.titleIconNode.image = titleIcon
strongSelf.titleIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 5.0, y: floor((layout.contentSize.height - titleIcon.size.height) / 2.0) - 1.0), size: titleIcon.size)
} else {
if strongSelf.titleIconNode.supernode != nil {
strongSelf.titleIconNode.removeFromSupernode()
}
}
if case .textWithIcon = item.labelStyle { if case .textWithIcon = item.labelStyle {
if let updatedLabelImage = updatedLabelImage { if let updatedLabelImage = updatedLabelImage {

View File

@ -97,7 +97,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case forwardPrivacy(PresentationTheme, String, String) case forwardPrivacy(PresentationTheme, String, String)
case groupPrivacy(PresentationTheme, String, String) case groupPrivacy(PresentationTheme, String, String)
case voiceMessagePrivacy(PresentationTheme, String, String, Bool) case voiceMessagePrivacy(PresentationTheme, String, String, Bool)
case messagePrivacy(Bool) case messagePrivacy(PresentationTheme, Bool, Bool)
case bioPrivacy(PresentationTheme, String, String) case bioPrivacy(PresentationTheme, String, String)
case selectivePrivacyInfo(PresentationTheme, String) case selectivePrivacyInfo(PresentationTheme, String)
case passcode(PresentationTheme, String, Bool, String) case passcode(PresentationTheme, String, Bool, String)
@ -240,14 +240,14 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .voiceMessagePrivacy(lhsTheme, lhsText, lhsValue, lhsLocked): case let .voiceMessagePrivacy(lhsTheme, lhsText, lhsValue, lhsHasPremium):
if case let .voiceMessagePrivacy(rhsTheme, rhsText, rhsValue, rhsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsLocked == rhsLocked { if case let .voiceMessagePrivacy(rhsTheme, rhsText, rhsValue, rhsHasPremium) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsHasPremium == rhsHasPremium {
return true return true
} else { } else {
return false return false
} }
case let .messagePrivacy(value): case let .messagePrivacy(lhsTheme, lhsValue, lhsHasPremium):
if case .messagePrivacy(value) = rhs { if case let .messagePrivacy(rhsTheme, rhsValue, rhsHasPremium) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsHasPremium == rhsHasPremium {
return true return true
} else { } else {
return false return false
@ -390,12 +390,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openGroupsPrivacy() arguments.openGroupsPrivacy()
}) })
case let .voiceMessagePrivacy(_, text, value, locked): case let .voiceMessagePrivacy(theme, text, value, hasPremium):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, labelStyle: locked ? .textWithIcon(UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon")!.precomposed()) : .text, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: value, labelStyle: !hasPremium ? .textWithIcon(UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon")!.precomposed()) : .text, sectionId: self.section, style: .blocks, action: {
arguments.openVoiceMessagePrivacy() arguments.openVoiceMessagePrivacy()
}) })
case let .messagePrivacy(value): case let .messagePrivacy(theme, value, hasPremium):
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.Settings_Privacy_Messages, label: !value ? presentationData.strings.Settings_Privacy_Messages_ValueEveryone : presentationData.strings.Settings_Privacy_Messages_ValueContactsAndPremium, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.Settings_Privacy_Messages, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: !value ? presentationData.strings.Settings_Privacy_Messages_ValueEveryone : presentationData.strings.Settings_Privacy_Messages_ValueContactsAndPremium, sectionId: self.section, style: .blocks, action: {
arguments.openMessagePrivacy() arguments.openMessagePrivacy()
}) })
case let .bioPrivacy(_, text, value): case let .bioPrivacy(_, text, value):
@ -591,8 +591,8 @@ private func privacyAndSecurityControllerEntries(
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls))) entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls)))
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.groupInvitations))) entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.groupInvitations)))
if !isPremiumDisabled { if !isPremiumDisabled {
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), !isPremium)) entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), isPremium))
entries.append(.messagePrivacy(privacySettings.globalSettings.nonContactChatsRequirePremium)) entries.append(.messagePrivacy(presentationData.theme, privacySettings.globalSettings.nonContactChatsRequirePremium, isPremium))
} }
} else { } else {
entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, presentationData.strings.Channel_NotificationLoading)) entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, presentationData.strings.Channel_NotificationLoading))
@ -603,7 +603,7 @@ private func privacyAndSecurityControllerEntries(
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, presentationData.strings.Channel_NotificationLoading)) entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, presentationData.strings.Channel_NotificationLoading))
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, presentationData.strings.Channel_NotificationLoading)) entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, presentationData.strings.Channel_NotificationLoading))
if !isPremiumDisabled { if !isPremiumDisabled {
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, !isPremium)) entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, isPremium))
} }
//entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp)) //entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))

View File

@ -72,6 +72,7 @@ public enum PresentationResourceKey: Int32 {
case itemListCloudIcon case itemListCloudIcon
case itemListTopicArrowIcon case itemListTopicArrowIcon
case itemListAddBoostsIcon case itemListAddBoostsIcon
case itemListPremiumIcon
case statsReactionsIcon case statsReactionsIcon
case statsForwardsIcon case statsForwardsIcon

View File

@ -282,6 +282,30 @@ public struct PresentationResourcesItemList {
}) })
} }
public static func premiumIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListPremiumIcon.rawValue, { theme in
return generateImage(CGSize(width: 16.0, height: 16.0), contextGenerator: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.clear(bounds)
let image = UIImage(bundleImageName: "Item List/PremiumIcon")!
context.clip(to: bounds, mask: image.cgImage!)
let colorsArray: [CGColor] = [
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x8d77ff).cgColor,
UIColor(rgb: 0xb56eec).cgColor,
UIColor(rgb: 0xb56eec).cgColor
]
var locations: [CGFloat] = [0.0, 0.3, 0.5, 0.7, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
})
})
}
public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? { public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? {
if !top && !bottom { if !top && !bottom {
return nil return nil

View File

@ -307,7 +307,18 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
} }
let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false)
let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, nil, false, !canMessage && !canAdd ? item.presentationData.strings.Conversation_ViewContactDetails.uppercased() : item.presentationData.strings.Conversation_ContactAddContact.uppercased(), mainColor, false, false)
let addTitle: String
if !canMessage && !canAdd {
addTitle = item.presentationData.strings.Conversation_ViewContactDetails
} else {
if canMessage {
addTitle = item.presentationData.strings.Conversation_ContactAddContact
} else {
addTitle = item.presentationData.strings.Conversation_ContactAddContactLong
}
}
let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, nil, false, addTitle.uppercased(), mainColor, false, false)
let maxButtonWidth = max(messageButtonWidth, addButtonWidth) let maxButtonWidth = max(messageButtonWidth, addButtonWidth)
var maxContentWidth: CGFloat = avatarSize.width + 7.0 var maxContentWidth: CGFloat = avatarSize.width + 7.0
@ -327,7 +338,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
let lineWidth: CGFloat = 3.0 let lineWidth: CGFloat = 3.0
var buttonCount = 1 var buttonCount = 1
if canMessage { if canMessage && canAdd {
buttonCount += 1 buttonCount += 1
} }
var buttonWidth = floor((boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0 - lineWidth)) var buttonWidth = floor((boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0 - lineWidth))
@ -387,7 +398,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.messageButtonNode.isHidden = !canMessage strongSelf.messageButtonNode.isHidden = !canMessage
let backgroundInsets = layoutConstants.text.bubbleInsets let backgroundInsets = layoutConstants.text.bubbleInsets
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + 5.0), size: CGSize(width: contentWidth - layoutConstants.text.bubbleInsets.right * 2.0, height: layoutSize.height - 34.0)) let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + 5.0), size: CGSize(width: boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0, height: layoutSize.height - 34.0))
if let statusSizeAndApply = statusSizeAndApply { if let statusSizeAndApply = statusSizeAndApply {
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: backgroundFrame.maxY + 3.0), size: statusSizeAndApply.0) strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: backgroundFrame.maxY + 3.0), size: statusSizeAndApply.0)

View File

@ -532,7 +532,7 @@ public class VideoMessageCameraScreen: ViewController {
fileprivate var liveUploadInterface: LegacyLiveUploadInterface? fileprivate var liveUploadInterface: LegacyLiveUploadInterface?
private var currentLiveUploadPath: String? private var currentLiveUploadPath: String?
fileprivate var currentLiveUploadData: LegacyLiveUploadInterfaceResult? fileprivate var currentLiveUploadData: LegacyLiveUploadInterfaceResult?
fileprivate let backgroundView: UIVisualEffectView fileprivate let backgroundView: UIVisualEffectView
fileprivate let containerView: UIView fileprivate let containerView: UIView
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment> fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
@ -689,16 +689,27 @@ public class VideoMessageCameraScreen: ViewController {
} }
func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) { func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) {
guard let controller = self.controller else {
return
}
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
let _ = ((self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing) let _ = (combineLatest(queue: Queue.mainQueue(),
|> filter { $0 } self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing,
|> take(1)).startStandalone(next: { _ in controller.audioSessionReady.get()
)
|> filter { $0 && $1 }
|> take(1)).startStandalone(next: { _, _ in
f() f()
}) })
} else { } else {
Queue.mainQueue().after(0.35) { let _ = (combineLatest(queue: Queue.mainQueue(),
.single(true) |> delay(0.35, queue: Queue.mainQueue()),
controller.audioSessionReady.get()
)
|> filter { $0 && $1 }
|> take(1)).startStandalone(next: { _, _ in
f() f()
} })
} }
} }
@ -1241,6 +1252,7 @@ public class VideoMessageCameraScreen: ViewController {
fileprivate let completion: (EnqueueMessage?, Bool?, Int32?) -> Void fileprivate let completion: (EnqueueMessage?, Bool?, Int32?) -> Void
private var audioSessionDisposable: Disposable? private var audioSessionDisposable: Disposable?
fileprivate let audioSessionReady = ValuePromise<Bool>(false)
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
@ -1484,11 +1496,13 @@ public class VideoMessageCameraScreen: ViewController {
finalDuration = duration finalDuration = duration
} }
let dimensions = PixelDimensions(width: 400, height: 400)
var thumbnailImage = video.thumbnail var thumbnailImage = video.thumbnail
if startTime > 0.0 { if startTime > 0.0 {
let composition = composition(with: results) let composition = composition(with: results)
let imageGenerator = AVAssetImageGenerator(asset: composition) let imageGenerator = AVAssetImageGenerator(asset: composition)
imageGenerator.maximumSize = CGSize(width: 400, height: 400) imageGenerator.maximumSize = dimensions.cgSize
imageGenerator.appliesPreferredTrackTransform = true imageGenerator.appliesPreferredTrackTransform = true
if let cgImage = try? imageGenerator.copyCGImage(at: CMTime(seconds: startTime, preferredTimescale: composition.duration.timescale), actualTime: nil) { if let cgImage = try? imageGenerator.copyCGImage(at: CMTime(seconds: startTime, preferredTimescale: composition.duration.timescale), actualTime: nil) {
@ -1496,7 +1510,7 @@ public class VideoMessageCameraScreen: ViewController {
} }
} }
let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: PixelDimensions(width: 400, height: 400), cropOffset: .zero, cropRect: CGRect(origin: .zero, size: CGSize(width: 400.0, height: 400.0)), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, nightTheme: false, drawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, qualityPreset: .videoMessage) let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, nightTheme: false, drawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, qualityPreset: .videoMessage)
var resourceAdjustments: VideoMediaResourceAdjustments? = nil var resourceAdjustments: VideoMediaResourceAdjustments? = nil
if let valuesData = try? JSONEncoder().encode(values) { if let valuesData = try? JSONEncoder().encode(values) {
@ -1614,10 +1628,13 @@ public class VideoMessageCameraScreen: ViewController {
} }
private func requestAudioSession() { private func requestAudioSession() {
self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .recordWithOthers, activate: { _ in self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .recordWithOthers, activate: { [weak self] _ in
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true) try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true)
} }
if let self {
self.audioSessionReady.set(true)
}
}, deactivate: { _ in }, deactivate: { _ in
return .single(Void()) return .single(Void())
}) })

View File

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

View File

@ -0,0 +1,97 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.494995 cm
0.000000 0.000000 0.000000 scn
7.573102 2.542415 m
3.972436 0.336632 l
3.598035 0.107271 3.108589 0.224851 2.879229 0.599252 c
2.767209 0.782110 2.733811 1.002467 2.786615 1.210307 c
3.343997 3.404178 l
3.545203 4.196128 4.087134 4.858135 4.823744 5.211795 c
8.751891 7.097767 l
8.935022 7.185692 9.012203 7.405426 8.924278 7.588558 c
8.853073 7.736866 8.692046 7.819856 8.529942 7.791792 c
4.157411 7.034797 l
3.268577 6.880917 2.357086 7.126360 1.665707 7.705756 c
0.284388 8.863339 l
-0.052136 9.145357 -0.096324 9.646784 0.185694 9.983309 c
0.322857 10.146982 0.520100 10.248593 0.732998 10.265255 c
4.953338 10.595538 l
5.251494 10.618872 5.511330 10.807558 5.625789 11.083856 c
7.253917 15.014055 l
7.421958 15.419697 7.887019 15.612309 8.292661 15.444268 c
8.487435 15.363581 8.642185 15.208831 8.722873 15.014055 c
10.351001 11.083856 l
10.465460 10.807558 10.725295 10.618872 11.023451 10.595538 c
15.266980 10.263440 l
15.704712 10.229183 16.031794 9.846561 15.997537 9.408829 c
15.981057 9.198256 15.881458 9.002894 15.720723 8.865864 c
12.484364 6.106792 l
12.256535 5.912563 12.157140 5.606812 12.227205 5.315742 c
13.222160 1.182478 l
13.324918 0.755602 13.062167 0.326249 12.635291 0.223492 c
12.430174 0.174116 12.213841 0.208297 12.033939 0.318506 c
8.403688 2.542415 l
8.148836 2.698538 7.827954 2.698538 7.573102 2.542415 c
h
f*
n
Q
endstream
endobj
3 0 obj
1440
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.000000 16.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001530 00000 n
0000001553 00000 n
0000001726 00000 n
0000001800 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1859
%%EOF

View File

@ -350,11 +350,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
} }
} }
} }
if isFirstTime, !self.viewOnceButton.isHidden {
self.maybePresentViewOnceTooltip()
}
let panelHeight = defaultHeight(metrics: metrics) let panelHeight = defaultHeight(metrics: metrics)
transition.updateFrame(node: self.deleteButton, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: 1), size: CGSize(width: 40.0, height: 40))) transition.updateFrame(node: self.deleteButton, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: 1), size: CGSize(width: 40.0, height: 40)))
@ -488,6 +484,10 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
} }
} }
if isFirstTime, !self.viewOnceButton.isHidden {
self.maybePresentViewOnceTooltip()
}
return panelHeight return panelHeight
} }

View File

@ -126,6 +126,29 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} }
if !pathComponents.isEmpty && !pathComponents[0].isEmpty { if !pathComponents.isEmpty && !pathComponents[0].isEmpty {
let peerName: String = pathComponents[0] let peerName: String = pathComponents[0]
if pathComponents[0].hasPrefix("+") || pathComponents[0].hasPrefix("%20") {
let component = pathComponents[0].replacingOccurrences(of: "%20", with: "+")
if component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789+").inverted) == nil {
var attach: String?
var startAttach: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "attach" {
attach = value
} else if queryItem.name == "startattach" {
startAttach = value
}
}
}
}
return .phone(component.replacingOccurrences(of: "+", with: ""), attach, startAttach)
} else {
return .join(String(component.dropFirst()))
}
}
if pathComponents.count == 1 { if pathComponents.count == 1 {
if let queryItems = components.queryItems { if let queryItems = components.queryItems {
if peerName == "socks" || peerName == "proxy" { if peerName == "socks" || peerName == "proxy" {
@ -288,27 +311,6 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} }
} else if pathComponents[0].hasPrefix(phonebookUsernamePathPrefix), let idValue = Int64(String(pathComponents[0][pathComponents[0].index(pathComponents[0].startIndex, offsetBy: phonebookUsernamePathPrefix.count)...])) { } else if pathComponents[0].hasPrefix(phonebookUsernamePathPrefix), let idValue = Int64(String(pathComponents[0][pathComponents[0].index(pathComponents[0].startIndex, offsetBy: phonebookUsernamePathPrefix.count)...])) {
return .peerId(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(idValue))) return .peerId(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(idValue)))
} else if pathComponents[0].hasPrefix("+") || pathComponents[0].hasPrefix("%20") {
let component = pathComponents[0].replacingOccurrences(of: "%20", with: "+")
if component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789+").inverted) == nil {
var attach: String?
var startAttach: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "attach" {
attach = value
} else if queryItem.name == "startattach" {
startAttach = value
}
}
}
}
return .phone(component.replacingOccurrences(of: "+", with: ""), attach, startAttach)
} else {
return .join(String(component.dropFirst()))
}
} else if pathComponents[0].hasPrefix("$") || pathComponents[0].hasPrefix("%24") { } else if pathComponents[0].hasPrefix("$") || pathComponents[0].hasPrefix("%24") {
var component = pathComponents[0].replacingOccurrences(of: "%24", with: "$") var component = pathComponents[0].replacingOccurrences(of: "%24", with: "$")
if component.hasPrefix("$") { if component.hasPrefix("$") {