mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge commit '650ec2296eb46a54ab01fa0241e75a7a5f3a5ddc'
This commit is contained in:
commit
0192d976c8
@ -9863,6 +9863,8 @@ Sorry for the inconvenience.";
|
|||||||
"Story.ViewList.ViewerCount_any" = "%d Viewers";
|
"Story.ViewList.ViewerCount_any" = "%d Viewers";
|
||||||
|
|
||||||
"AuthSessions.MessageApp" = "You allowed this bot to message you when you opened %@.";
|
"AuthSessions.MessageApp" = "You allowed this bot to message you when you opened %@.";
|
||||||
|
"Notification.BotWriteAllowedMenu" = "You allowed this bot to message you when you added it to your attachment menu.";
|
||||||
|
"Notification.BotWriteAllowedRequest" = "You allowed this bot to message you in the app.";
|
||||||
|
|
||||||
"Story.Privacy.PostStoryAs" = "Post Story As";
|
"Story.Privacy.PostStoryAs" = "Post Story As";
|
||||||
"Story.Privacy.PostStoryAsHeader" = "POST STORY AS";
|
"Story.Privacy.PostStoryAsHeader" = "POST STORY AS";
|
||||||
@ -9870,3 +9872,32 @@ Sorry for the inconvenience.";
|
|||||||
"Story.Privacy.KeepOnChannelPageInfo" = "Keep this story on channel profile even after it expires in %@.";
|
"Story.Privacy.KeepOnChannelPageInfo" = "Keep this story on channel profile even after it expires in %@.";
|
||||||
|
|
||||||
"Story.Editor.TooltipPremiumReaction" = "Subscribe to [Telegram Premium]() to use this reaction.";
|
"Story.Editor.TooltipPremiumReaction" = "Subscribe to [Telegram Premium]() to use this reaction.";
|
||||||
|
|
||||||
|
"Story.Privacy.TooltipStoryArchivedChannel" = "Users will see this story on the channel page even after it expires.";
|
||||||
|
|
||||||
|
"Story.Editor.TooltipMutedWithAudio" = "Original audio will be removed";
|
||||||
|
"Story.Editor.TooltipUnmutedWithAudio" = "Original audio will be preserved";
|
||||||
|
|
||||||
|
"SecretImage.ViewOnce.Title" = "Disappearing Photo";
|
||||||
|
"SecretVideo.ViewOnce.Title" = "Disappearing Video";
|
||||||
|
|
||||||
|
"MediaPicker.Timer.Description" = "Choose how long the media will be kept after opening.";
|
||||||
|
"MediaPicker.Timer.ViewOnce" = "View Once";
|
||||||
|
"MediaPicker.Timer.Seconds_1" = "%d Second";
|
||||||
|
"MediaPicker.Timer.Seconds_any" = "%d Seconds";
|
||||||
|
"MediaPicker.Timer.DoNotDelete" = "Do Not Delete";
|
||||||
|
|
||||||
|
"MediaPicker.Timer.Photo.ViewOnceTooltip" = "Photo set to view once.";
|
||||||
|
"MediaPicker.Timer.Photo.TimerTooltip" = "Photo will be deleted in\n%@ seconds after opening.";
|
||||||
|
"MediaPicker.Timer.Photo.KeepTooltip" = "Photo will be kept in chat.";
|
||||||
|
|
||||||
|
"MediaPicker.Timer.Video.ViewOnceTooltip" = "Video set to view once.";
|
||||||
|
"MediaPicker.Timer.Video.TimerTooltip" = "Video will be deleted in\n%@ seconds after opening.";
|
||||||
|
"MediaPicker.Timer.Video.KeepTooltip" = "Video will be kept in chat.";
|
||||||
|
|
||||||
|
"WebApp.AllowWriteTitle" = "Allow Sending Messages?";
|
||||||
|
"WebApp.AllowWriteConfirmation" = "This will allow the bot **%@** to message you on Telegram.";
|
||||||
|
|
||||||
|
"WebApp.SharePhoneTitle" = "Share Phone Number?";
|
||||||
|
"WebApp.SharePhoneConfirmation" = "**%@** will know your phone number. This can be useful for integration with other services.";
|
||||||
|
"WebApp.SharePhoneConfirmationUnblock" = "**%@** will know your phone number. This can be useful for integration with other services.\n\nThis will also unblock the bot.";
|
||||||
|
@ -392,12 +392,16 @@ public final class DrawingStickerEntityView: DrawingEntityView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func onSelection() {
|
override func onSelection() {
|
||||||
|
if self.isReaction {
|
||||||
self.presentReactionSelection()
|
self.presentReactionSelection()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func onDeselection() {
|
func onDeselection() {
|
||||||
|
if self.isReaction {
|
||||||
let _ = self.dismissReactionSelection()
|
let _ = self.dismissReactionSelection()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private weak var reactionContextNode: ReactionContextNode?
|
private weak var reactionContextNode: ReactionContextNode?
|
||||||
fileprivate func presentReactionSelection() {
|
fileprivate func presentReactionSelection() {
|
||||||
|
@ -880,6 +880,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
displayCaption = !self.textNode.isHidden
|
displayCaption = !self.textNode.isHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if metrics.isTablet {
|
||||||
|
self.fullscreenButton.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
var textFrame = CGRect()
|
var textFrame = CGRect()
|
||||||
var visibleTextHeight: CGFloat = 0.0
|
var visibleTextHeight: CGFloat = 0.0
|
||||||
if !self.textNode.isHidden {
|
if !self.textNode.isHidden {
|
||||||
|
@ -184,6 +184,7 @@ public func galleryItemForEntry(
|
|||||||
location: location,
|
location: location,
|
||||||
translateToLanguage: translateToLanguage,
|
translateToLanguage: translateToLanguage,
|
||||||
peerIsCopyProtected: peerIsCopyProtected,
|
peerIsCopyProtected: peerIsCopyProtected,
|
||||||
|
isSecret: isSecret,
|
||||||
displayInfoOnTop: displayInfoOnTop,
|
displayInfoOnTop: displayInfoOnTop,
|
||||||
performAction: performAction,
|
performAction: performAction,
|
||||||
openActionOptions: openActionOptions,
|
openActionOptions: openActionOptions,
|
||||||
@ -265,6 +266,7 @@ public func galleryItemForEntry(
|
|||||||
location: location,
|
location: location,
|
||||||
translateToLanguage: translateToLanguage,
|
translateToLanguage: translateToLanguage,
|
||||||
peerIsCopyProtected: peerIsCopyProtected,
|
peerIsCopyProtected: peerIsCopyProtected,
|
||||||
|
isSecret: isSecret,
|
||||||
displayInfoOnTop: displayInfoOnTop,
|
displayInfoOnTop: displayInfoOnTop,
|
||||||
performAction: performAction,
|
performAction: performAction,
|
||||||
openActionOptions: openActionOptions,
|
openActionOptions: openActionOptions,
|
||||||
|
@ -115,18 +115,20 @@ class ChatImageGalleryItem: GalleryItem {
|
|||||||
let location: MessageHistoryEntryLocation?
|
let location: MessageHistoryEntryLocation?
|
||||||
let translateToLanguage: String?
|
let translateToLanguage: String?
|
||||||
let peerIsCopyProtected: Bool
|
let peerIsCopyProtected: Bool
|
||||||
|
let isSecret: Bool
|
||||||
let displayInfoOnTop: Bool
|
let displayInfoOnTop: Bool
|
||||||
let performAction: (GalleryControllerInteractionTapAction) -> Void
|
let performAction: (GalleryControllerInteractionTapAction) -> Void
|
||||||
let openActionOptions: (GalleryControllerInteractionTapAction, Message) -> Void
|
let openActionOptions: (GalleryControllerInteractionTapAction, Message) -> Void
|
||||||
let present: (ViewController, Any?) -> Void
|
let present: (ViewController, Any?) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, displayInfoOnTop: Bool, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
init(context: AccountContext, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, isSecret: Bool = false, displayInfoOnTop: Bool, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.message = message
|
self.message = message
|
||||||
self.location = location
|
self.location = location
|
||||||
self.translateToLanguage = translateToLanguage
|
self.translateToLanguage = translateToLanguage
|
||||||
self.peerIsCopyProtected = peerIsCopyProtected
|
self.peerIsCopyProtected = peerIsCopyProtected
|
||||||
|
self.isSecret = isSecret
|
||||||
self.displayInfoOnTop = displayInfoOnTop
|
self.displayInfoOnTop = displayInfoOnTop
|
||||||
self.performAction = performAction
|
self.performAction = performAction
|
||||||
self.openActionOptions = openActionOptions
|
self.openActionOptions = openActionOptions
|
||||||
@ -136,7 +138,7 @@ class ChatImageGalleryItem: GalleryItem {
|
|||||||
func node(synchronous: Bool) -> GalleryItemNode {
|
func node(synchronous: Bool) -> GalleryItemNode {
|
||||||
let node = ChatImageGalleryItemNode(context: self.context, presentationData: self.presentationData, performAction: self.performAction, openActionOptions: self.openActionOptions, present: self.present)
|
let node = ChatImageGalleryItemNode(context: self.context, presentationData: self.presentationData, performAction: self.performAction, openActionOptions: self.openActionOptions, present: self.present)
|
||||||
|
|
||||||
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected)
|
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, isSecret: self.isSecret)
|
||||||
for media in self.message.media {
|
for media in self.message.media {
|
||||||
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia, let image = fullMedia as? TelegramMediaImage {
|
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia, let image = fullMedia as? TelegramMediaImage {
|
||||||
node.setImage(userLocation: .peer(self.message.id.peerId), imageReference: .message(message: MessageReference(self.message), media: image))
|
node.setImage(userLocation: .peer(self.message.id.peerId), imageReference: .message(message: MessageReference(self.message), media: image))
|
||||||
@ -175,7 +177,7 @@ class ChatImageGalleryItem: GalleryItem {
|
|||||||
if self.displayInfoOnTop {
|
if self.displayInfoOnTop {
|
||||||
node.titleContentView?.setMessage(self.message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
|
node.titleContentView?.setMessage(self.message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
|
||||||
}
|
}
|
||||||
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected)
|
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, isSecret: self.isSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,6 +206,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
private var message: Message?
|
private var message: Message?
|
||||||
private var translateToLanguage: String?
|
private var translateToLanguage: String?
|
||||||
private var peerIsCopyProtected: Bool = false
|
private var peerIsCopyProtected: Bool = false
|
||||||
|
private var isSecret: Bool = false
|
||||||
private let presentationData: PresentationData
|
private let presentationData: PresentationData
|
||||||
|
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
@ -328,11 +331,12 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize))
|
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String?, peerIsCopyProtected: Bool) {
|
fileprivate func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String?, peerIsCopyProtected: Bool, isSecret: Bool) {
|
||||||
self.message = message
|
self.message = message
|
||||||
self.translateToLanguage = translateToLanguage
|
self.translateToLanguage = translateToLanguage
|
||||||
self.peerIsCopyProtected = peerIsCopyProtected
|
self.peerIsCopyProtected = peerIsCopyProtected
|
||||||
self.imageNode.captureProtected = message.isCopyProtected()
|
self.isSecret = isSecret
|
||||||
|
self.imageNode.captureProtected = message.isCopyProtected() || peerIsCopyProtected || isSecret
|
||||||
self.footerContentNode.setMessage(message, displayInfo: displayInfo, translateToLanguage: translateToLanguage, peerIsCopyProtected: peerIsCopyProtected)
|
self.footerContentNode.setMessage(message, displayInfo: displayInfo, translateToLanguage: translateToLanguage, peerIsCopyProtected: peerIsCopyProtected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode {
|
|||||||
|
|
||||||
var beginTimeAndTimeout: (Double, Double)? {
|
var beginTimeAndTimeout: (Double, Double)? {
|
||||||
didSet {
|
didSet {
|
||||||
if let (beginTime, timeout) = self.beginTimeAndTimeout {
|
if let (beginTime, timeout) = self.beginTimeAndTimeout, Int32(timeout) != viewOnceTimeout {
|
||||||
if self.timeoutNode == nil {
|
if self.timeoutNode == nil {
|
||||||
let timeoutNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
let timeoutNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
||||||
self.timeoutNode = timeoutNode
|
self.timeoutNode = timeoutNode
|
||||||
@ -139,6 +139,7 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
private var messageView: MessageView?
|
private var messageView: MessageView?
|
||||||
private var currentNodeMessageId: MessageId?
|
private var currentNodeMessageId: MessageId?
|
||||||
private var currentNodeMessageIsVideo = false
|
private var currentNodeMessageIsVideo = false
|
||||||
|
private var currentNodeMessageIsViewOnce = false
|
||||||
private var tempFile: TempBoxFile?
|
private var tempFile: TempBoxFile?
|
||||||
|
|
||||||
private let _hiddenMedia = Promise<(MessageId, Media)?>(nil)
|
private let _hiddenMedia = Promise<(MessageId, Media)?>(nil)
|
||||||
@ -263,6 +264,8 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let attribute = message.autoclearAttribute {
|
if let attribute = message.autoclearAttribute {
|
||||||
|
strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout
|
||||||
|
|
||||||
if let countdownBeginTime = attribute.countdownBeginTime {
|
if let countdownBeginTime = attribute.countdownBeginTime {
|
||||||
if let videoDuration = videoDuration {
|
if let videoDuration = videoDuration {
|
||||||
beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, videoDuration)
|
beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, videoDuration)
|
||||||
@ -271,6 +274,8 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let attribute = message.autoremoveAttribute {
|
} else if let attribute = message.autoremoveAttribute {
|
||||||
|
strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout
|
||||||
|
|
||||||
if let countdownBeginTime = attribute.countdownBeginTime {
|
if let countdownBeginTime = attribute.countdownBeginTime {
|
||||||
if let videoDuration = videoDuration {
|
if let videoDuration = videoDuration {
|
||||||
beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, videoDuration)
|
beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, videoDuration)
|
||||||
@ -283,12 +288,20 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
if let file = media as? TelegramMediaFile {
|
if let file = media as? TelegramMediaFile {
|
||||||
if file.isAnimated {
|
if file.isAnimated {
|
||||||
strongSelf.title = strongSelf.presentationData.strings.SecretGif_Title
|
strongSelf.title = strongSelf.presentationData.strings.SecretGif_Title
|
||||||
|
} else {
|
||||||
|
if strongSelf.currentNodeMessageIsViewOnce {
|
||||||
|
strongSelf.title = strongSelf.presentationData.strings.SecretVideo_ViewOnce_Title
|
||||||
} else {
|
} else {
|
||||||
strongSelf.title = strongSelf.presentationData.strings.SecretVideo_Title
|
strongSelf.title = strongSelf.presentationData.strings.SecretVideo_Title
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strongSelf.currentNodeMessageIsViewOnce {
|
||||||
|
strongSelf.title = strongSelf.presentationData.strings.SecretImage_ViewOnce_Title
|
||||||
} else {
|
} else {
|
||||||
strongSelf.title = strongSelf.presentationData.strings.SecretImage_Title
|
strongSelf.title = strongSelf.presentationData.strings.SecretImage_Title
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let beginTimeAndTimeout = beginTimeAndTimeout {
|
if let beginTimeAndTimeout = beginTimeAndTimeout {
|
||||||
strongSelf.controllerNode.beginTimeAndTimeout = beginTimeAndTimeout
|
strongSelf.controllerNode.beginTimeAndTimeout = beginTimeAndTimeout
|
||||||
@ -478,7 +491,7 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
if !self.didSetReady {
|
if !self.didSetReady {
|
||||||
self._ready.set(.single(true))
|
self._ready.set(.single(true))
|
||||||
}
|
}
|
||||||
if !self.currentNodeMessageIsVideo {
|
if !(self.currentNodeMessageIsVideo || self.currentNodeMessageIsViewOnce) {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ public enum LegacyICloudFilePickerMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudFilePickerMode = .default, documentTypes: [String] = ["public.item"], forceDarkTheme: Bool = false, completion: @escaping ([URL]) -> Void) -> ViewController {
|
public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudFilePickerMode = .default, documentTypes: [String] = ["public.item"], forceDarkTheme: Bool = false, dismissed: @escaping () -> Void = {}, completion: @escaping ([URL]) -> Void) -> ViewController {
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in
|
let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
@ -96,6 +96,7 @@ public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudF
|
|||||||
if let legacyController = legacyController {
|
if let legacyController = legacyController {
|
||||||
legacyController.dismiss()
|
legacyController.dismiss()
|
||||||
}
|
}
|
||||||
|
dismissed()
|
||||||
}
|
}
|
||||||
legacyController.bind(controller: UIViewController())
|
legacyController.bind(controller: UIViewController())
|
||||||
return legacyController
|
return legacyController
|
||||||
|
@ -412,6 +412,7 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
|
|||||||
public func storyLocationPickerController(
|
public func storyLocationPickerController(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
location: CLLocationCoordinate2D?,
|
location: CLLocationCoordinate2D?,
|
||||||
|
dismissed: @escaping () -> Void,
|
||||||
completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
|
completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
|
||||||
) -> ViewController {
|
) -> ViewController {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
|
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
@ -427,5 +428,8 @@ public func storyLocationPickerController(
|
|||||||
}
|
}
|
||||||
controller.navigationPresentation = .flatModal
|
controller.navigationPresentation = .flatModal
|
||||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
|
controller.didDismiss = {
|
||||||
|
dismissed()
|
||||||
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,6 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
// self.isLayerBacked = true
|
|
||||||
|
|
||||||
class DisplayLinkProxy: NSObject {
|
class DisplayLinkProxy: NSObject {
|
||||||
weak var target: RadialStatusSecretTimeoutContentNode?
|
weak var target: RadialStatusSecretTimeoutContentNode?
|
||||||
@ -85,8 +84,10 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
|
|||||||
self.displayLink?.isPaused = true
|
self.displayLink?.isPaused = true
|
||||||
self.displayLink?.add(to: RunLoop.main, forMode: .common)
|
self.displayLink?.add(to: RunLoop.main, forMode: .common)
|
||||||
|
|
||||||
|
if icon != nil {
|
||||||
self.addSubnode(self.animationNode)
|
self.addSubnode(self.animationNode)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.displayLink?.invalidate()
|
self.displayLink?.invalidate()
|
||||||
@ -95,7 +96,14 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
|
|||||||
override func layout() {
|
override func layout() {
|
||||||
super.layout()
|
super.layout()
|
||||||
|
|
||||||
self.animationNode.frame = CGRect(x: 6.0, y: 2.0, width: 36.0, height: 36.0)
|
var factor: CGFloat = 0.75
|
||||||
|
var offset: CGFloat = 0.0415
|
||||||
|
if self.bounds.width < 30.0 {
|
||||||
|
factor = 0.66
|
||||||
|
offset = 0.08
|
||||||
|
}
|
||||||
|
let size = floorToScreenPixels(self.bounds.width * factor)
|
||||||
|
self.animationNode.frame = CGRect(x: floorToScreenPixels((self.bounds.width - size) / 2.0), y: ceil(self.bounds.height * offset), width: size, height: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func animateOut(to: RadialStatusNodeState, completion: @escaping () -> Void) {
|
override func animateOut(to: RadialStatusNodeState, completion: @escaping () -> Void) {
|
||||||
@ -194,15 +202,15 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let parameters = parameters as? RadialStatusSecretTimeoutContentNodeParameters {
|
if let parameters = parameters as? RadialStatusSecretTimeoutContentNodeParameters {
|
||||||
if let icon = parameters.icon, let iconImage = icon.cgImage {
|
// if let icon = parameters.icon, let _ = icon.cgImage {
|
||||||
let imageRect = CGRect(origin: CGPoint(x: floor((bounds.size.width - icon.size.width) / 2.0), y: floor((bounds.size.height - icon.size.height) / 2.0)), size: icon.size)
|
// let imageRect = CGRect(origin: CGPoint(x: floor((bounds.size.width - icon.size.width) / 2.0), y: floor((bounds.size.height - icon.size.height) / 2.0)), size: icon.size)
|
||||||
context.saveGState()
|
// context.saveGState()
|
||||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
// context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||||
context.scaleBy(x: 1.0, y: -1.0)
|
// context.scaleBy(x: 1.0, y: -1.0)
|
||||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
// context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||||
context.draw(iconImage, in: imageRect)
|
// context.draw(iconImage, in: imageRect)
|
||||||
context.restoreGState()
|
// context.restoreGState()
|
||||||
}
|
// }
|
||||||
|
|
||||||
let lineWidth: CGFloat
|
let lineWidth: CGFloat
|
||||||
if parameters.sparks {
|
if parameters.sparks {
|
||||||
|
@ -53,16 +53,19 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||||||
case let .messageActionBotAllowed(flags, domain, app):
|
case let .messageActionBotAllowed(flags, domain, app):
|
||||||
if let domain = domain {
|
if let domain = domain {
|
||||||
return TelegramMediaAction(action: .botDomainAccessGranted(domain: domain))
|
return TelegramMediaAction(action: .botDomainAccessGranted(domain: domain))
|
||||||
} else if case let .botApp(_, _, _, _, appName, _, _, _, _) = app {
|
} else {
|
||||||
|
var appName: String?
|
||||||
|
if case let .botApp(_, _, _, _, appNameValue, _, _, _, _) = app {
|
||||||
|
appName = appNameValue
|
||||||
|
}
|
||||||
var type: BotSendMessageAccessGrantedType?
|
var type: BotSendMessageAccessGrantedType?
|
||||||
if (flags & (1 << 3)) != 0 {
|
if (flags & (1 << 1)) != 0 {
|
||||||
type = .request
|
|
||||||
} else if (flags & (1 << 1)) != 0 {
|
|
||||||
type = .attachMenu
|
type = .attachMenu
|
||||||
}
|
}
|
||||||
|
if (flags & (1 << 3)) != 0 {
|
||||||
|
type = .request
|
||||||
|
}
|
||||||
return TelegramMediaAction(action: .botAppAccessGranted(appName: appName, type: type))
|
return TelegramMediaAction(action: .botAppAccessGranted(appName: appName, type: type))
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
case .messageActionSecureValuesSentMe:
|
case .messageActionSecureValuesSentMe:
|
||||||
return nil
|
return nil
|
||||||
|
@ -56,7 +56,11 @@ public class AutoclearTimeoutMessageAttribute: MessageAttribute {
|
|||||||
self.countdownBeginTime = countdownBeginTime
|
self.countdownBeginTime = countdownBeginTime
|
||||||
|
|
||||||
if let countdownBeginTime = countdownBeginTime {
|
if let countdownBeginTime = countdownBeginTime {
|
||||||
|
if self.timeout == viewOnceTimeout {
|
||||||
|
self.automaticTimestampBasedAttribute = (1, countdownBeginTime)
|
||||||
|
} else {
|
||||||
self.automaticTimestampBasedAttribute = (1, countdownBeginTime + timeout)
|
self.automaticTimestampBasedAttribute = (1, countdownBeginTime + timeout)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.automaticTimestampBasedAttribute = nil
|
self.automaticTimestampBasedAttribute = nil
|
||||||
}
|
}
|
||||||
@ -67,7 +71,11 @@ public class AutoclearTimeoutMessageAttribute: MessageAttribute {
|
|||||||
self.countdownBeginTime = decoder.decodeOptionalInt32ForKey("c")
|
self.countdownBeginTime = decoder.decodeOptionalInt32ForKey("c")
|
||||||
|
|
||||||
if let countdownBeginTime = self.countdownBeginTime {
|
if let countdownBeginTime = self.countdownBeginTime {
|
||||||
|
if self.timeout == viewOnceTimeout {
|
||||||
|
self.automaticTimestampBasedAttribute = (1, countdownBeginTime)
|
||||||
|
} else {
|
||||||
self.automaticTimestampBasedAttribute = (1, countdownBeginTime + self.timeout)
|
self.automaticTimestampBasedAttribute = (1, countdownBeginTime + self.timeout)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.automaticTimestampBasedAttribute = nil
|
self.automaticTimestampBasedAttribute = nil
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
case paymentSent(currency: String, totalAmount: Int64, invoiceSlug: String?, isRecurringInit: Bool, isRecurringUsed: Bool)
|
case paymentSent(currency: String, totalAmount: Int64, invoiceSlug: String?, isRecurringInit: Bool, isRecurringUsed: Bool)
|
||||||
case customText(text: String, entities: [MessageTextEntity])
|
case customText(text: String, entities: [MessageTextEntity])
|
||||||
case botDomainAccessGranted(domain: String)
|
case botDomainAccessGranted(domain: String)
|
||||||
case botAppAccessGranted(appName: String, type: BotSendMessageAccessGrantedType?)
|
case botAppAccessGranted(appName: String?, type: BotSendMessageAccessGrantedType?)
|
||||||
case botSentSecureValues(types: [SentSecureValueType])
|
case botSentSecureValues(types: [SentSecureValueType])
|
||||||
case peerJoined
|
case peerJoined
|
||||||
case phoneNumberRequest
|
case phoneNumberRequest
|
||||||
@ -202,7 +202,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
self = .unknown
|
self = .unknown
|
||||||
}
|
}
|
||||||
case 35:
|
case 35:
|
||||||
self = .botAppAccessGranted(appName: decoder.decodeStringForKey("app", orElse: ""), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) })
|
self = .botAppAccessGranted(appName: decoder.decodeOptionalStringForKey("app"), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) })
|
||||||
default:
|
default:
|
||||||
self = .unknown
|
self = .unknown
|
||||||
}
|
}
|
||||||
@ -372,7 +372,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper")
|
encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper")
|
||||||
case let .botAppAccessGranted(appName, type):
|
case let .botAppAccessGranted(appName, type):
|
||||||
encoder.encodeInt32(35, forKey: "_rawValue")
|
encoder.encodeInt32(35, forKey: "_rawValue")
|
||||||
|
if let appName = appName {
|
||||||
encoder.encodeString(appName, forKey: "app")
|
encoder.encodeString(appName, forKey: "app")
|
||||||
|
} else {
|
||||||
|
encoder.encodeNil(forKey: "app")
|
||||||
|
}
|
||||||
if let type = type {
|
if let type = type {
|
||||||
encoder.encodeInt32(type.rawValue, forKey: "atp")
|
encoder.encodeInt32(type.rawValue, forKey: "atp")
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,7 +83,7 @@ func _internal_markMessageContentAsConsumedInteractively(postbox: Postbox, messa
|
|||||||
} else if let attribute = updatedAttributes[i] as? AutoclearTimeoutMessageAttribute {
|
} else if let attribute = updatedAttributes[i] as? AutoclearTimeoutMessageAttribute {
|
||||||
if attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0 {
|
if attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0 {
|
||||||
var timeout = attribute.timeout
|
var timeout = attribute.timeout
|
||||||
if let duration = message.secretMediaDuration {
|
if let duration = message.secretMediaDuration, timeout != viewOnceTimeout {
|
||||||
timeout = max(timeout, Int32(duration))
|
timeout = max(timeout, Int32(duration))
|
||||||
}
|
}
|
||||||
updatedAttributes[i] = AutoclearTimeoutMessageAttribute(timeout: timeout, countdownBeginTime: timestamp)
|
updatedAttributes[i] = AutoclearTimeoutMessageAttribute(timeout: timeout, countdownBeginTime: timestamp)
|
||||||
|
@ -629,8 +629,16 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage())
|
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage())
|
||||||
case let .botDomainAccessGranted(domain):
|
case let .botDomainAccessGranted(domain):
|
||||||
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).string, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).string, font: titleFont, textColor: primaryTextColor)
|
||||||
case let .botAppAccessGranted(appName, _):
|
case let .botAppAccessGranted(appName, type):
|
||||||
attributedString = NSAttributedString(string: strings.AuthSessions_MessageApp(appName).string, font: titleFont, textColor: primaryTextColor)
|
let text: String
|
||||||
|
if type == .attachMenu {
|
||||||
|
text = strings.Notification_BotWriteAllowedMenu
|
||||||
|
} else if type == .request {
|
||||||
|
text = strings.Notification_BotWriteAllowedRequest
|
||||||
|
} else {
|
||||||
|
text = strings.AuthSessions_MessageApp(appName ?? "").string
|
||||||
|
}
|
||||||
|
attributedString = NSAttributedString(string: text, font: titleFont, textColor: primaryTextColor)
|
||||||
case let .botSentSecureValues(types):
|
case let .botSentSecureValues(types):
|
||||||
var typesString = ""
|
var typesString = ""
|
||||||
var hasIdentity = false
|
var hasIdentity = false
|
||||||
|
@ -1466,6 +1466,7 @@ public class CameraScreen: ViewController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate var captureStartTimestamp: Double?
|
||||||
private func setupCamera() {
|
private func setupCamera() {
|
||||||
guard self.camera == nil else {
|
guard self.camera == nil else {
|
||||||
return
|
return
|
||||||
@ -1575,6 +1576,7 @@ public class CameraScreen: ViewController {
|
|||||||
|
|
||||||
camera.focus(at: CGPoint(x: 0.5, y: 0.5), autoFocus: true)
|
camera.focus(at: CGPoint(x: 0.5, y: 0.5), autoFocus: true)
|
||||||
camera.startCapture()
|
camera.startCapture()
|
||||||
|
self.captureStartTimestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
|
|
||||||
@ -2515,9 +2517,20 @@ public class CameraScreen: ViewController {
|
|||||||
guard let self, !self.didStopCameraCapture else {
|
guard let self, !self.didStopCameraCapture else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
|
if let startTimestamp = self.node.captureStartTimestamp {
|
||||||
|
let difference = currentTimestamp - startTimestamp
|
||||||
|
if difference < 2.0 {
|
||||||
|
Queue.mainQueue().after(2.0 - difference) {
|
||||||
self.didStopCameraCapture = true
|
self.didStopCameraCapture = true
|
||||||
self.node.pauseCameraCapture()
|
self.node.pauseCameraCapture()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.didStopCameraCapture = true
|
||||||
|
self.node.pauseCameraCapture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let resumeCameraCapture = { [weak self] in
|
let resumeCameraCapture = { [weak self] in
|
||||||
guard let self, self.didStopCameraCapture else {
|
guard let self, self.didStopCameraCapture else {
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ContextReferenceButtonComponent",
|
||||||
|
module_name = "ContextReferenceButtonComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/ContextUI",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -0,0 +1,123 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
|
||||||
|
public final class ContextReferenceButtonComponent: Component {
|
||||||
|
let content: AnyComponent<Empty>
|
||||||
|
let tag: AnyObject?
|
||||||
|
let minSize: CGSize?
|
||||||
|
let action: (UIView, ContextGesture?) -> Void
|
||||||
|
|
||||||
|
public init(
|
||||||
|
content: AnyComponent<Empty>,
|
||||||
|
tag: AnyObject? = nil,
|
||||||
|
minSize: CGSize?,
|
||||||
|
action: @escaping (UIView, ContextGesture?) -> Void
|
||||||
|
) {
|
||||||
|
self.content = content
|
||||||
|
self.tag = tag
|
||||||
|
self.minSize = minSize
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: ContextReferenceButtonComponent, rhs: ContextReferenceButtonComponent) -> Bool {
|
||||||
|
if lhs.content != rhs.content {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.tag !== rhs.tag {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.minSize != rhs.minSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView, ComponentTaggedView {
|
||||||
|
let buttonView: HighlightableButtonNode
|
||||||
|
let sourceView: ContextControllerSourceNode
|
||||||
|
let contextContentView: ContextReferenceContentNode
|
||||||
|
|
||||||
|
private let componentView: ComponentView<Empty>
|
||||||
|
|
||||||
|
private var component: ContextReferenceButtonComponent?
|
||||||
|
|
||||||
|
public func matches(tag: Any) -> Bool {
|
||||||
|
if let component = self.component, let componentTag = component.tag {
|
||||||
|
let tag = tag as AnyObject
|
||||||
|
if componentTag === tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
self.componentView = ComponentView()
|
||||||
|
self.buttonView = HighlightableButtonNode()
|
||||||
|
self.sourceView = ContextControllerSourceNode()
|
||||||
|
self.contextContentView = ContextReferenceContentNode()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.addSubview(self.buttonView.view)
|
||||||
|
self.buttonView.addSubnode(self.sourceView)
|
||||||
|
self.sourceView.addSubnode(self.contextContentView)
|
||||||
|
|
||||||
|
self.sourceView.activated = { [weak self] gesture, _ in
|
||||||
|
if let self, let component = self.component {
|
||||||
|
component.action(self, gesture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.buttonView.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func pressed() {
|
||||||
|
self.component?.action(self, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(component: ContextReferenceButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
let componentSize = self.componentView.update(
|
||||||
|
transition: transition,
|
||||||
|
component: component.content,
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
|
||||||
|
var size = componentSize
|
||||||
|
if let minSize = component.minSize {
|
||||||
|
size.width = max(size.width, minSize.width)
|
||||||
|
size.height = max(size.height, minSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let componentView = self.componentView.view {
|
||||||
|
componentView.isUserInteractionEnabled = false
|
||||||
|
if componentView.superview == nil {
|
||||||
|
self.contextContentView.view.addSubview(componentView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: componentView, frame: CGRect(origin: CGPoint(x: floor((size.width - componentSize.width) / 2.0), y: floor((size.height - componentSize.height) / 2.0)), size: componentSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: self.buttonView.view, frame: CGRect(origin: .zero, size: size))
|
||||||
|
transition.setFrame(view: self.sourceView.view, frame: CGRect(origin: .zero, size: size))
|
||||||
|
transition.setFrame(view: self.contextContentView.view, frame: CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -69,9 +69,13 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var scheduledMessageInput: MessageInputPanelComponent.SendMessageInput?
|
||||||
public func setCaption(_ caption: NSAttributedString?) {
|
public func setCaption(_ caption: NSAttributedString?) {
|
||||||
|
let sendMessageInput = MessageInputPanelComponent.SendMessageInput.text(caption ?? NSAttributedString())
|
||||||
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
|
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||||
view.setSendMessageInput(value: .text(caption ?? NSAttributedString()), updateState: true)
|
view.setSendMessageInput(value: sendMessageInput, updateState: true)
|
||||||
|
} else {
|
||||||
|
self.scheduledMessageInput = sendMessageInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +85,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func setTimeout(_ timeout: Int32) {
|
public func setTimeout(_ timeout: Int32) {
|
||||||
|
self.dismissTimeoutTooltip()
|
||||||
var timeout: Int32? = timeout
|
var timeout: Int32? = timeout
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = nil
|
timeout = nil
|
||||||
@ -142,6 +147,12 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
maxInputPanelHeight = 60.0
|
maxInputPanelHeight = 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resetInputContents: MessageInputPanelComponent.SendMessageInput?
|
||||||
|
if let scheduledMessageInput = self.scheduledMessageInput {
|
||||||
|
resetInputContents = scheduledMessageInput
|
||||||
|
self.scheduledMessageInput = nil
|
||||||
|
}
|
||||||
|
|
||||||
self.inputPanel.parentState = self.state
|
self.inputPanel.parentState = self.state
|
||||||
let inputPanelSize = self.inputPanel.update(
|
let inputPanelSize = self.inputPanel.update(
|
||||||
transition: Transition(transition),
|
transition: Transition(transition),
|
||||||
@ -156,7 +167,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
maxLength: 1024,
|
maxLength: 1024,
|
||||||
queryTypes: [.mention],
|
queryTypes: [.mention],
|
||||||
alwaysDarkWhenHasText: false,
|
alwaysDarkWhenHasText: false,
|
||||||
resetInputContents: nil,
|
resetInputContents: resetInputContents,
|
||||||
nextInputMode: { _ in
|
nextInputMode: { _ in
|
||||||
return .emoji
|
return .emoji
|
||||||
},
|
},
|
||||||
@ -180,9 +191,9 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
likeAction: nil,
|
likeAction: nil,
|
||||||
likeOptionsAction: nil,
|
likeOptionsAction: nil,
|
||||||
inputModeAction: nil,
|
inputModeAction: nil,
|
||||||
timeoutAction: self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser ? { [weak self] sourceView in
|
timeoutAction: self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser ? { [weak self] sourceView, gesture in
|
||||||
if let self {
|
if let self {
|
||||||
self.presentTimeoutSetup(sourceView: sourceView)
|
self.presentTimeoutSetup(sourceView: sourceView, gesture: gesture)
|
||||||
}
|
}
|
||||||
} : nil,
|
} : nil,
|
||||||
forwardAction: nil,
|
forwardAction: nil,
|
||||||
@ -234,7 +245,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
return inputPanelSize.height - 8.0
|
return inputPanelSize.height - 8.0
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentTimeoutSetup(sourceView: UIView) {
|
private func presentTimeoutSetup(sourceView: UIView, gesture: ContextGesture?) {
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
@ -250,12 +261,12 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
|
|
||||||
let currentValue = self.currentTimeout
|
let currentValue = self.currentTimeout
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
||||||
let title = "Choose how long the media will be kept after opening."
|
let title = presentationData.strings.MediaPicker_Timer_Description
|
||||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "View Once", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_ViewOnce, icon: { theme in
|
||||||
return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
}, action: { _, a in
|
}, action: { _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -263,31 +274,19 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
updateTimeout(viewOnceTimeout)
|
updateTimeout(viewOnceTimeout)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "3 Seconds", icon: { theme in
|
let values: [Int32] = [3, 10, 30]
|
||||||
return currentValue == 3 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
|
for value in values {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_Seconds(value), icon: { theme in
|
||||||
|
return currentValue == value ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
}, action: { _, a in
|
}, action: { _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
updateTimeout(3)
|
updateTimeout(value)
|
||||||
})))
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "10 Seconds", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_DoNotDelete, icon: { theme in
|
||||||
return currentValue == 10 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}, action: { _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
updateTimeout(10)
|
|
||||||
})))
|
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "30 Seconds", icon: { theme in
|
|
||||||
return currentValue == 30 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}, action: { _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
updateTimeout(30)
|
|
||||||
})))
|
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Do Not Delete", icon: { theme in
|
|
||||||
return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
}, action: { _, a in
|
}, action: { _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -295,34 +294,41 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
updateTimeout(nil)
|
updateTimeout(nil)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
self.present(contextController)
|
self.present(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
private weak var tooltipController: TooltipScreen?
|
private weak var tooltipController: TooltipScreen?
|
||||||
private func presentTimeoutTooltip(sourceView: UIView, timeout: Int32?) {
|
|
||||||
guard let superview = self.view.superview?.superview else {
|
private func dismissTimeoutTooltip() {
|
||||||
return
|
|
||||||
}
|
|
||||||
if let tooltipController = self.tooltipController {
|
if let tooltipController = self.tooltipController {
|
||||||
self.tooltipController = nil
|
self.tooltipController = nil
|
||||||
tooltipController.dismiss()
|
tooltipController.dismiss()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentTimeoutTooltip(sourceView: UIView, timeout: Int32?) {
|
||||||
|
guard let superview = self.view.superview?.superview else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.dismissTimeoutTooltip()
|
||||||
|
|
||||||
let parentFrame = superview.convert(superview.bounds, to: nil)
|
let parentFrame = superview.convert(superview.bounds, to: nil)
|
||||||
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 2.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 2.0), size: CGSize())
|
||||||
|
|
||||||
|
let isVideo = !"".isEmpty
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let text: String
|
let text: String
|
||||||
let iconName: String
|
let iconName: String
|
||||||
if timeout == viewOnceTimeout {
|
if timeout == viewOnceTimeout {
|
||||||
text = "Photo set to view once."
|
text = isVideo ? presentationData.strings.MediaPicker_Timer_Video_ViewOnceTooltip : presentationData.strings.MediaPicker_Timer_Photo_ViewOnceTooltip
|
||||||
iconName = "anim_autoremove_on"
|
iconName = "anim_autoremove_on"
|
||||||
} else if let timeout {
|
} else if let timeout {
|
||||||
text = "Photo will be deleted in \(timeout) seconds after opening."
|
text = isVideo ? presentationData.strings.MediaPicker_Timer_Video_TimerTooltip("\(timeout)").string : presentationData.strings.MediaPicker_Timer_Photo_TimerTooltip("\(timeout)").string
|
||||||
iconName = "anim_autoremove_on"
|
iconName = "anim_autoremove_on"
|
||||||
} else {
|
} else {
|
||||||
text = "Photo will be kept in chat."
|
text = isVideo ? presentationData.strings.MediaPicker_Timer_Video_KeepTooltip : presentationData.strings.MediaPicker_Timer_Photo_KeepTooltip
|
||||||
iconName = "anim_autoremove_off"
|
iconName = "anim_autoremove_off"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +336,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
|||||||
account: self.context.account,
|
account: self.context.account,
|
||||||
sharedContext: self.context.sharedContext,
|
sharedContext: self.context.sharedContext,
|
||||||
text: .plain(text: text),
|
text: .plain(text: text),
|
||||||
balancedTextLayout: true,
|
balancedTextLayout: false,
|
||||||
style: .customBlur(UIColor(rgb: 0x18181a), 0.0),
|
style: .customBlur(UIColor(rgb: 0x18181a), 0.0),
|
||||||
arrowStyle: .small,
|
arrowStyle: .small,
|
||||||
icon: .animation(name: iconName, delay: 0.1, tintColor: nil),
|
icon: .animation(name: iconName, delay: 0.1, tintColor: nil),
|
||||||
|
@ -389,7 +389,7 @@ public final class MediaEditorVideoExport {
|
|||||||
|
|
||||||
try? videoTrack.insertTimeRange(timeRange, of: videoAssetTrack, at: .zero)
|
try? videoTrack.insertTimeRange(timeRange, of: videoAssetTrack, at: .zero)
|
||||||
|
|
||||||
if let audioAssetTrack = asset.tracks(withMediaType: .audio).first, let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) {
|
if let audioAssetTrack = asset.tracks(withMediaType: .audio).first, let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid), !self.configuration.values.videoIsMuted {
|
||||||
try? audioTrack.insertTimeRange(timeRange, of: audioAssetTrack, at: .zero)
|
try? audioTrack.insertTimeRange(timeRange, of: audioAssetTrack, at: .zero)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ public final class MediaEditorVideoExport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let audioTracks = inputAsset.tracks(withMediaType: .audio)
|
let audioTracks = inputAsset.tracks(withMediaType: .audio)
|
||||||
if audioTracks.count > 0, !self.configuration.values.videoIsMuted {
|
if audioTracks.count > 0, !self.configuration.values.videoIsMuted || self.configuration.values.audioTrack != nil {
|
||||||
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
|
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
|
||||||
audioOutput.alwaysCopiesSampleData = false
|
audioOutput.alwaysCopiesSampleData = false
|
||||||
if reader.canAdd(audioOutput) {
|
if reader.canAdd(audioOutput) {
|
||||||
|
@ -1117,7 +1117,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
timeoutAction: isEditingStory ? nil : { [weak self] view in
|
timeoutAction: isEditingStory ? nil : { [weak self] view, gesture in
|
||||||
guard let self, let controller = self.environment?.controller() as? MediaEditorScreen else {
|
guard let self, let controller = self.environment?.controller() as? MediaEditorScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1130,7 +1130,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
hasPremium = false
|
hasPremium = false
|
||||||
}
|
}
|
||||||
controller?.presentTimeoutSetup(sourceView: view, hasPremium: hasPremium)
|
controller?.presentTimeoutSetup(sourceView: view, gesture: gesture, hasPremium: hasPremium)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
forwardAction: nil,
|
forwardAction: nil,
|
||||||
@ -2778,7 +2778,22 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
|
||||||
|
|
||||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? self.presentationData.strings.Story_Editor_TooltipMuted : self.presentationData.strings.Story_Editor_TooltipUnmuted), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
let text: String
|
||||||
|
if let _ = self.mediaEditor?.values.audioTrack {
|
||||||
|
if isMuted {
|
||||||
|
text = self.presentationData.strings.Story_Editor_TooltipMutedWithAudio
|
||||||
|
} else {
|
||||||
|
text = self.presentationData.strings.Story_Editor_TooltipUnmutedWithAudio
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if isMuted {
|
||||||
|
text = self.presentationData.strings.Story_Editor_TooltipMuted
|
||||||
|
} else {
|
||||||
|
text = self.presentationData.strings.Story_Editor_TooltipUnmuted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||||
return .ignore
|
return .ignore
|
||||||
})
|
})
|
||||||
self.muteTooltip = tooltipController
|
self.muteTooltip = tooltipController
|
||||||
@ -2957,7 +2972,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
location = draft.location
|
location = draft.location
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let locationController = storyLocationPickerController(context: self.context, location: location, completion: { [weak self] location, queryId, resultId, address, countryCode in
|
let locationController = storyLocationPickerController(
|
||||||
|
context: self.context,
|
||||||
|
location: location,
|
||||||
|
dismissed: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.mediaEditor?.play()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
completion: { [weak self] location, queryId, resultId, address, countryCode in
|
||||||
if let self {
|
if let self {
|
||||||
let emojiFile: Signal<TelegramMediaFile?, NoError>
|
let emojiFile: Signal<TelegramMediaFile?, NoError>
|
||||||
if let countryCode {
|
if let countryCode {
|
||||||
@ -3035,7 +3058,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
func presentAudioPicker() {
|
func presentAudioPicker() {
|
||||||
self.controller?.present(legacyICloudFilePicker(theme: self.presentationData.theme, mode: .import, documentTypes: ["public.mp3"], forceDarkTheme: true, completion: { [weak self] urls in
|
self.controller?.present(legacyICloudFilePicker(theme: self.presentationData.theme, mode: .import, documentTypes: ["public.mp3"], forceDarkTheme: true, dismissed: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
Queue.mainQueue().after(0.1) {
|
||||||
|
self.mediaEditor?.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, completion: { [weak self] urls in
|
||||||
guard let self, let mediaEditor = self.mediaEditor, !urls.isEmpty, let url = urls.first else {
|
guard let self, let mediaEditor = self.mediaEditor, !urls.isEmpty, let url = urls.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -3060,10 +3089,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.requestUpdate(transition: .easeInOut(duration: 0.2))
|
self.requestUpdate(transition: .easeInOut(duration: 0.2))
|
||||||
|
|
||||||
Queue.mainQueue().after(0.1) {
|
|
||||||
self.mediaEditor?.play()
|
|
||||||
}
|
|
||||||
}), in: .window(.root))
|
}), in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3081,7 +3106,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
action: { [weak self] f in
|
action: { [weak self] f in
|
||||||
f.dismissWithResult(.default)
|
f.dismissWithResult(.default)
|
||||||
if let self {
|
if let self {
|
||||||
self.mediaEditor?.setAudioTrack(nil)
|
if let mediaEditor = self.mediaEditor {
|
||||||
|
mediaEditor.setAudioTrack(nil)
|
||||||
|
|
||||||
|
if !mediaEditor.sourceIsVideo && !mediaEditor.isPlaying {
|
||||||
|
mediaEditor.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
self.requestUpdate(transition: .easeInOut(duration: 0.25))
|
self.requestUpdate(transition: .easeInOut(duration: 0.25))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3872,7 +3903,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentTimeoutSetup(sourceView: UIView, hasPremium: Bool) {
|
func presentTimeoutSetup(sourceView: UIView, gesture: ContextGesture?, hasPremium: Bool) {
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
@ -3893,7 +3924,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
||||||
let title = presentationData.strings.Story_Editor_ExpirationText
|
let title = presentationData.strings.Story_Editor_ExpirationText
|
||||||
let currentValue = self.state.privacy.timeout
|
let currentValue = self.state.privacy.timeout
|
||||||
let currentArchived = self.state.privacy.pin
|
|
||||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||||
@ -3929,7 +3959,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(24), icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(24), icon: { theme in
|
||||||
return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
return currentValue == 86400 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
}, action: { _, a in
|
}, action: { _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
@ -3951,7 +3981,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
self.present(contextController, in: .window(.root))
|
self.present(contextController, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,8 @@ swift_library(
|
|||||||
"//submodules/AnimatedCountLabelNode",
|
"//submodules/AnimatedCountLabelNode",
|
||||||
"//submodules/TelegramUI/Components/MessageInputActionButtonComponent",
|
"//submodules/TelegramUI/Components/MessageInputActionButtonComponent",
|
||||||
"//submodules/SearchPeerMembers",
|
"//submodules/SearchPeerMembers",
|
||||||
|
"//submodules/ContextUI",
|
||||||
|
"//submodules/TelegramUI/Components/ContextReferenceButtonComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -18,6 +18,9 @@ import AudioToolbox
|
|||||||
import AnimatedTextComponent
|
import AnimatedTextComponent
|
||||||
import AnimatedCountLabelNode
|
import AnimatedCountLabelNode
|
||||||
import MessageInputActionButtonComponent
|
import MessageInputActionButtonComponent
|
||||||
|
import ContextReferenceButtonComponent
|
||||||
|
|
||||||
|
private let timeoutButtonTag = GenericComponentViewTag()
|
||||||
|
|
||||||
public final class MessageInputPanelComponent: Component {
|
public final class MessageInputPanelComponent: Component {
|
||||||
public struct ContextQueryTypes: OptionSet {
|
public struct ContextQueryTypes: OptionSet {
|
||||||
@ -120,7 +123,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
public let likeAction: (() -> Void)?
|
public let likeAction: (() -> Void)?
|
||||||
public let likeOptionsAction: ((UIView, ContextGesture?) -> Void)?
|
public let likeOptionsAction: ((UIView, ContextGesture?) -> Void)?
|
||||||
public let inputModeAction: (() -> Void)?
|
public let inputModeAction: (() -> Void)?
|
||||||
public let timeoutAction: ((UIView) -> Void)?
|
public let timeoutAction: ((UIView, ContextGesture?) -> Void)?
|
||||||
public let forwardAction: (() -> Void)?
|
public let forwardAction: (() -> Void)?
|
||||||
public let moreAction: ((UIView, ContextGesture?) -> Void)?
|
public let moreAction: ((UIView, ContextGesture?) -> Void)?
|
||||||
public let presentVoiceMessagesUnavailableTooltip: ((UIView) -> Void)?
|
public let presentVoiceMessagesUnavailableTooltip: ((UIView) -> Void)?
|
||||||
@ -172,7 +175,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
likeAction: (() -> Void)?,
|
likeAction: (() -> Void)?,
|
||||||
likeOptionsAction: ((UIView, ContextGesture?) -> Void)?,
|
likeOptionsAction: ((UIView, ContextGesture?) -> Void)?,
|
||||||
inputModeAction: (() -> Void)?,
|
inputModeAction: (() -> Void)?,
|
||||||
timeoutAction: ((UIView) -> Void)?,
|
timeoutAction: ((UIView, ContextGesture?) -> Void)?,
|
||||||
forwardAction: (() -> Void)?,
|
forwardAction: (() -> Void)?,
|
||||||
moreAction: ((UIView, ContextGesture?) -> Void)?,
|
moreAction: ((UIView, ContextGesture?) -> Void)?,
|
||||||
presentVoiceMessagesUnavailableTooltip: ((UIView) -> Void)?,
|
presentVoiceMessagesUnavailableTooltip: ((UIView) -> Void)?,
|
||||||
@ -1456,49 +1459,23 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
|
|
||||||
let accentColor = component.theme.chat.inputPanel.panelControlAccentColor
|
let accentColor = component.theme.chat.inputPanel.panelControlAccentColor
|
||||||
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
|
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
|
||||||
func generateIcon(value: String, selected: Bool) -> UIImage? {
|
|
||||||
let image = UIImage(bundleImageName: "Media Editor/Timeout")!
|
|
||||||
let valueString = NSAttributedString(string: value, font: Font.with(size: value.count == 1 ? 12.0 : 10.0, design: .round, weight: .semibold), textColor: .white, paragraphAlignment: .center)
|
|
||||||
|
|
||||||
return generateImage(image.size, contextGenerator: { size, context in
|
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
|
||||||
context.clear(bounds)
|
|
||||||
|
|
||||||
if selected {
|
|
||||||
context.setFillColor(accentColor.cgColor)
|
|
||||||
context.fillEllipse(in: CGRect(origin: .zero, size: size))
|
|
||||||
} else {
|
|
||||||
if let cgImage = image.cgImage {
|
|
||||||
context.draw(cgImage, in: CGRect(origin: .zero, size: size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset: CGPoint = CGPoint(x: 0.0, y: -3.0 - UIScreenPixel)
|
|
||||||
if value == "∞" {
|
|
||||||
offset.x += UIScreenPixel
|
|
||||||
offset.y += 1.0 - UIScreenPixel
|
|
||||||
}
|
|
||||||
|
|
||||||
let valuePath = CGMutablePath()
|
|
||||||
valuePath.addRect(bounds.offsetBy(dx: offset.x, dy: offset.y))
|
|
||||||
let valueFramesetter = CTFramesetterCreateWithAttributedString(valueString as CFAttributedString)
|
|
||||||
let valyeFrame = CTFramesetterCreateFrame(valueFramesetter, CFRangeMake(0, valueString.length), valuePath, nil)
|
|
||||||
CTFrameDraw(valyeFrame, context)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let icon = generateIcon(value: timeoutValue, selected: component.timeoutSelected)
|
|
||||||
let timeoutButtonSize = self.timeoutButton.update(
|
let timeoutButtonSize = self.timeoutButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(Button(
|
component: AnyComponent(ContextReferenceButtonComponent(
|
||||||
content: AnyComponent(Image(image: icon, size: CGSize(width: 20.0, height: 20.0))),
|
content: AnyComponent(
|
||||||
action: { [weak self] in
|
TimeoutContentComponent(
|
||||||
guard let self, let timeoutButtonView = self.timeoutButton.view else {
|
color: .white,
|
||||||
return
|
accentColor: accentColor,
|
||||||
|
isSelected: component.timeoutSelected,
|
||||||
|
value: timeoutValue
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tag: timeoutButtonTag,
|
||||||
|
minSize: CGSize(width: 32.0, height: 32.0),
|
||||||
|
action: { view, gesture in
|
||||||
|
timeoutAction(view, gesture)
|
||||||
}
|
}
|
||||||
timeoutAction(timeoutButtonView)
|
)),
|
||||||
}
|
|
||||||
).minSize(CGSize(width: 32.0, height: 32.0))),
|
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
|
||||||
|
public final class TimeoutContentComponent: Component {
|
||||||
|
public let color: UIColor
|
||||||
|
public let accentColor: UIColor
|
||||||
|
public let isSelected: Bool
|
||||||
|
public let value: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
color: UIColor,
|
||||||
|
accentColor: UIColor,
|
||||||
|
isSelected: Bool,
|
||||||
|
value: String
|
||||||
|
) {
|
||||||
|
self.color = color
|
||||||
|
self.accentColor = accentColor
|
||||||
|
self.isSelected = isSelected
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: TimeoutContentComponent, rhs: TimeoutContentComponent) -> Bool {
|
||||||
|
if lhs.color != rhs.color {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.accentColor != rhs.accentColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isSelected != rhs.isSelected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.value != rhs.value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private var component: TimeoutContentComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private let background: UIImageView
|
||||||
|
private let foreground: UIImageView
|
||||||
|
private let text = ComponentView<Empty>()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.background = UIImageView(image: UIImage(bundleImageName: "Media Editor/Timeout"))
|
||||||
|
self.foreground = UIImageView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.background)
|
||||||
|
self.addSubview(self.foreground)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: TimeoutContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
let previousComponent = self.component
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
let size = CGSize(width: 20.0, height: 20.0)
|
||||||
|
if previousComponent?.accentColor != component.accentColor {
|
||||||
|
self.foreground.image = generateFilledCircleImage(diameter: size.width, color: component.accentColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated = false
|
||||||
|
if let previousComponent {
|
||||||
|
if previousComponent.isSelected != component.isSelected {
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
if previousComponent.value != component.value {
|
||||||
|
if let textView = self.text.view, let snapshotView = textView.snapshotView(afterScreenUpdates: false) {
|
||||||
|
snapshotView.frame = textView.frame
|
||||||
|
self.addSubview(snapshotView)
|
||||||
|
snapshotView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -3.0), duration: 0.2, removeOnCompletion: false, additive: true)
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
snapshotView.removeFromSuperview()
|
||||||
|
})
|
||||||
|
|
||||||
|
textView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
textView.layer.animatePosition(from: CGPoint(x: 0.0, y: 3.0), to: .zero, duration: 0.2, additive: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fontSize: CGFloat
|
||||||
|
let textOffset: CGFloat
|
||||||
|
if component.value.count == 1 {
|
||||||
|
fontSize = 12.0
|
||||||
|
textOffset = UIScreenPixel
|
||||||
|
} else {
|
||||||
|
fontSize = 10.0
|
||||||
|
textOffset = -UIScreenPixel
|
||||||
|
}
|
||||||
|
|
||||||
|
let font = Font.with(size: fontSize, design: .round, weight: .semibold)
|
||||||
|
let textSize = self.text.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Text(text: component.value, font: font, color: .white)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
|
)
|
||||||
|
if let textView = self.text.view {
|
||||||
|
if textView.superview == nil {
|
||||||
|
self.addSubview(textView)
|
||||||
|
}
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0) + UIScreenPixel, y: floorToScreenPixels((size.height - textSize.height) / 2.0) + textOffset), size: textSize)
|
||||||
|
transition.setPosition(view: textView, position: textFrame.center)
|
||||||
|
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.background.frame = CGRect(origin: .zero, size: size)
|
||||||
|
|
||||||
|
self.foreground.bounds = CGRect(origin: .zero, size: size)
|
||||||
|
self.foreground.center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
|
||||||
|
let foregroundTransition: Transition = updated ? .easeInOut(duration: 0.2) : transition
|
||||||
|
foregroundTransition.setScale(view: self.foreground, scale: component.isSelected ? 1.0 : 0.001)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -345,6 +345,8 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
private var searchStateContext: ShareWithPeersScreen.StateContext?
|
private var searchStateContext: ShareWithPeersScreen.StateContext?
|
||||||
private var searchStateDisposable: Disposable?
|
private var searchStateDisposable: Disposable?
|
||||||
|
|
||||||
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
private var effectiveStateValue: ShareWithPeersScreen.State? {
|
private var effectiveStateValue: ShareWithPeersScreen.State? {
|
||||||
return self.searchStateContext?.stateValue ?? self.defaultStateValue
|
return self.searchStateContext?.stateValue ?? self.defaultStateValue
|
||||||
}
|
}
|
||||||
@ -501,6 +503,8 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
let translation = recognizer.translation(in: self)
|
let translation = recognizer.translation(in: self)
|
||||||
self.dismissPanState = DismissPanState(translation: translation.y)
|
self.dismissPanState = DismissPanState(translation: translation.y)
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
|
self.updateModalOverlayTransition(transition: .immediate)
|
||||||
case .cancelled, .ended:
|
case .cancelled, .ended:
|
||||||
if self.dismissPanState != nil {
|
if self.dismissPanState != nil {
|
||||||
let translation = recognizer.translation(in: self)
|
let translation = recognizer.translation(in: self)
|
||||||
@ -510,8 +514,13 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
|
|
||||||
if translation.y > 100.0 || velocity.y > 10.0 {
|
if translation.y > 100.0 || velocity.y > 10.0 {
|
||||||
controller.requestDismiss()
|
controller.requestDismiss()
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
self.updateModalOverlayTransition(transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -549,7 +558,11 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
case .pin:
|
case .pin:
|
||||||
if self.selectedOptions.contains(.pin) {
|
if self.selectedOptions.contains(.pin) {
|
||||||
animationName = "anim_profileadd"
|
animationName = "anim_profileadd"
|
||||||
|
if let peerId = self.sendAsPeerId, peerId.namespace != Namespaces.Peer.CloudUser {
|
||||||
|
text = presentationData.strings.Story_Privacy_TooltipStoryArchivedChannel
|
||||||
|
} else {
|
||||||
text = presentationData.strings.Story_Privacy_TooltipStoryArchived
|
text = presentationData.strings.Story_Privacy_TooltipStoryArchived
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
animationName = "anim_autoremove_on"
|
animationName = "anim_autoremove_on"
|
||||||
text = presentationData.strings.Story_Privacy_TooltipStoryExpires
|
text = presentationData.strings.Story_Privacy_TooltipStoryExpires
|
||||||
@ -776,7 +789,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = ShareWithPeersScreen(
|
let peersController = ShareWithPeersScreen(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
||||||
stateContext: stateContext,
|
stateContext: stateContext,
|
||||||
@ -791,10 +804,40 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.environment?.controller()?.push(controller)
|
if let controller = self.environment?.controller() as? ShareWithPeersScreen {
|
||||||
|
controller.dismissAllTooltips()
|
||||||
|
controller.push(peersController)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateModalOverlayTransition(transition: Transition) {
|
||||||
|
guard let _ = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||||
|
topOffset = max(0.0, topOffset)
|
||||||
|
if let dismissPanState = self.dismissPanState {
|
||||||
|
topOffset += dismissPanState.translation
|
||||||
|
}
|
||||||
|
|
||||||
|
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
||||||
|
var topOffsetFraction = topOffset / topOffsetDistance
|
||||||
|
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
||||||
|
|
||||||
|
let transitionFactor: CGFloat = 1.0 - topOffsetFraction
|
||||||
|
if let controller = environment.controller() {
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
var transition = transition
|
||||||
|
if controller.modalStyleOverlayTransitionFactor.isZero && transitionFactor > 0.0, transition.animation.isImmediate {
|
||||||
|
transition = .spring(duration: 0.4)
|
||||||
|
}
|
||||||
|
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: Transition) {
|
private func updateScrolling(transition: Transition) {
|
||||||
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
|
guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
@ -813,21 +856,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
var bottomAlpha: CGFloat = bottomDistance / bottomAlphaDistance
|
var bottomAlpha: CGFloat = bottomDistance / bottomAlphaDistance
|
||||||
bottomAlpha = max(0.0, min(1.0, bottomAlpha))
|
bottomAlpha = max(0.0, min(1.0, bottomAlpha))
|
||||||
|
|
||||||
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
self.updateModalOverlayTransition(transition: transition)
|
||||||
self.topOffsetDistance = topOffsetDistance
|
|
||||||
var topOffsetFraction = topOffset / topOffsetDistance
|
|
||||||
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
|
||||||
|
|
||||||
let transitionFactor: CGFloat = 1.0 - topOffsetFraction
|
|
||||||
if let controller = environment.controller() {
|
|
||||||
Queue.mainQueue().justDispatch {
|
|
||||||
var transition = transition
|
|
||||||
if controller.modalStyleOverlayTransitionFactor.isZero && transitionFactor > 0.0, transition.animation.isImmediate {
|
|
||||||
transition = .spring(duration: 0.4)
|
|
||||||
}
|
|
||||||
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var visibleBounds = self.scrollView.bounds
|
var visibleBounds = self.scrollView.bounds
|
||||||
visibleBounds.origin.y -= itemLayout.topInset
|
visibleBounds.origin.y -= itemLayout.topInset
|
||||||
@ -996,9 +1025,9 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
|
|
||||||
let subtitle: String?
|
let subtitle: String?
|
||||||
if case .user = peer {
|
if case .user = peer {
|
||||||
subtitle = "personal account"
|
subtitle = environment.strings.VoiceChat_PersonalAccount
|
||||||
} else {
|
} else {
|
||||||
subtitle = "channel"
|
subtitle = environment.strings.Channel_Status
|
||||||
}
|
}
|
||||||
|
|
||||||
var isStories = false
|
var isStories = false
|
||||||
@ -1037,6 +1066,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
if isStories {
|
if isStories {
|
||||||
let _ = self.presentSendAsPeer()
|
let _ = self.presentSendAsPeer()
|
||||||
} else {
|
} else {
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
self.environment?.controller()?.dismiss()
|
self.environment?.controller()?.dismiss()
|
||||||
self.component?.peerCompletion(peer.id)
|
self.component?.peerCompletion(peer.id)
|
||||||
}
|
}
|
||||||
|
@ -13213,7 +13213,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
let buttons: Signal<([AttachmentButtonType], [AttachmentButtonType], AttachmentButtonType?), NoError>
|
let buttons: Signal<([AttachmentButtonType], [AttachmentButtonType], AttachmentButtonType?), NoError>
|
||||||
if !isScheduledMessages {
|
if !isScheduledMessages && !peer.isDeleted {
|
||||||
buttons = self.context.engine.messages.attachMenuBots()
|
buttons = self.context.engine.messages.attachMenuBots()
|
||||||
|> map { attachMenuBots in
|
|> map { attachMenuBots in
|
||||||
var buttons = availableButtons
|
var buttons = availableButtons
|
||||||
|
@ -189,7 +189,7 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
|
|||||||
var isRevealed = false
|
var isRevealed = false
|
||||||
var tapped: () -> Void = {}
|
var tapped: () -> Void = {}
|
||||||
|
|
||||||
init(enableAnimations: Bool) {
|
init(hasImageOverlay: Bool, enableAnimations: Bool) {
|
||||||
self.blurredImageNode = TransformImageNode()
|
self.blurredImageNode = TransformImageNode()
|
||||||
self.blurredImageNode.contentAnimations = []
|
self.blurredImageNode.contentAnimations = []
|
||||||
|
|
||||||
@ -212,7 +212,9 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
if hasImageOverlay {
|
||||||
self.addSubnode(self.blurredImageNode)
|
self.addSubnode(self.blurredImageNode)
|
||||||
|
}
|
||||||
self.addSubnode(self.dustNode)
|
self.addSubnode(self.dustNode)
|
||||||
self.addSubnode(self.buttonNode)
|
self.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
@ -1986,7 +1988,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
} else {
|
} else {
|
||||||
secretProgressIcon = PresentationResourcesChat.chatBubbleSecretMediaCompactIcon(theme)
|
secretProgressIcon = PresentationResourcesChat.chatBubbleSecretMediaCompactIcon(theme)
|
||||||
}
|
}
|
||||||
if isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, let beginTime = maybeBeginTime {
|
if isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, let beginTime = maybeBeginTime, Int32(timeout) != viewOnceTimeout {
|
||||||
state = .secretTimeout(color: messageTheme.mediaOverlayControlColors.foregroundColor, icon: secretProgressIcon, beginTime: beginTime, timeout: timeout, sparks: true)
|
state = .secretTimeout(color: messageTheme.mediaOverlayControlColors.foregroundColor, icon: secretProgressIcon, beginTime: beginTime, timeout: timeout, sparks: true)
|
||||||
backgroundColor = messageTheme.mediaDateAndStatusFillColor
|
backgroundColor = messageTheme.mediaDateAndStatusFillColor
|
||||||
} else if isSecretMedia, let _ = secretProgressIcon {
|
} else if isSecretMedia, let _ = secretProgressIcon {
|
||||||
@ -2043,7 +2045,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
|
|
||||||
if isSecretMedia {
|
if isSecretMedia {
|
||||||
let remainingTime: Int32?
|
let remainingTime: Int32?
|
||||||
if let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout {
|
if let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, Int32(timeout) != viewOnceTimeout {
|
||||||
if let beginTime = maybeBeginTime {
|
if let beginTime = maybeBeginTime {
|
||||||
let elapsedTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - beginTime
|
let elapsedTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - beginTime
|
||||||
remainingTime = Int32(max(0.0, timeout - elapsedTime))
|
remainingTime = Int32(max(0.0, timeout - elapsedTime))
|
||||||
@ -2133,7 +2135,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
|
|
||||||
if displaySpoiler {
|
if displaySpoiler {
|
||||||
if self.extendedMediaOverlayNode == nil {
|
if self.extendedMediaOverlayNode == nil {
|
||||||
let extendedMediaOverlayNode = ExtendedMediaOverlayNode(enableAnimations: self.context?.sharedContext.energyUsageSettings.fullTranslucency ?? true)
|
let extendedMediaOverlayNode = ExtendedMediaOverlayNode(hasImageOverlay: !isSecretMedia, enableAnimations: self.context?.sharedContext.energyUsageSettings.fullTranslucency ?? true)
|
||||||
extendedMediaOverlayNode.tapped = { [weak self] in
|
extendedMediaOverlayNode.tapped = { [weak self] in
|
||||||
self?.internallyVisible = true
|
self?.internallyVisible = true
|
||||||
self?.updateVisibility()
|
self?.updateVisibility()
|
||||||
|
@ -1104,7 +1104,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
if result {
|
if result {
|
||||||
sendEvent(true)
|
sendEvent(true)
|
||||||
} else {
|
} else {
|
||||||
controller.present(textAlertController(context: self.context, updatedPresentationData: controller.updatedPresentationData, title: "Allow Sending Messages?", text: "Allow \(controller.botName) to send messages?", actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
let alertController = textAlertController(context: self.context, updatedPresentationData: controller.updatedPresentationData, title: self.presentationData.strings.WebApp_AllowWriteTitle, text: self.presentationData.strings.WebApp_AllowWriteConfirmation(controller.botName).string, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
||||||
sendEvent(false)
|
sendEvent(false)
|
||||||
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in
|
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1115,16 +1115,23 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
sendEvent(true)
|
sendEvent(true)
|
||||||
})
|
})
|
||||||
})]), in: .window(.root))
|
})], parseMarkdown: true)
|
||||||
|
alertController.dismissed = { byOutsideTap in
|
||||||
|
if byOutsideTap {
|
||||||
|
sendEvent(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.present(alertController, in: .window(.root))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func shareAccountContact() {
|
fileprivate func shareAccountContact() {
|
||||||
guard let controller = self.controller else {
|
guard let controller = self.controller, let botId = self.controller?.botId, let botName = self.controller?.botName else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let sendEvent: (Bool) -> Void = { success in
|
let sendEvent: (Bool) -> Void = { success in
|
||||||
var paramsString: String
|
var paramsString: String
|
||||||
if success {
|
if success {
|
||||||
@ -1135,28 +1142,76 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.webView?.sendEvent(name: "phone_requested", data: paramsString)
|
self.webView?.sendEvent(name: "phone_requested", data: paramsString)
|
||||||
}
|
}
|
||||||
|
|
||||||
let alertController = textAlertController(context: self.context, updatedPresentationData: controller.updatedPresentationData, title: self.presentationData.strings.Conversation_ShareBotContactConfirmationTitle, text: self.presentationData.strings.Conversation_ShareBotContactConfirmation, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
let context = self.context
|
||||||
sendEvent(false)
|
let _ = (self.context.engine.data.get(
|
||||||
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in
|
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
|
||||||
guard let self else {
|
TelegramEngine.EngineData.Item.Peer.IsBlocked(id: botId)
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak controller] accountPeer, isBlocked in
|
||||||
|
guard let self, let controller, let accountPeer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|
var requiresUnblock = false
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
if case let .known(value) = isBlocked, value {
|
||||||
if let self, let botId = self.controller?.botId, let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty {
|
requiresUnblock = true
|
||||||
let _ = enqueueMessages(account: self.context.account, peerId: botId, messages: [
|
|
||||||
.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil)), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
|
|
||||||
]).start()
|
|
||||||
sendEvent(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let text: String
|
||||||
|
if requiresUnblock {
|
||||||
|
text = self.presentationData.strings.WebApp_SharePhoneConfirmationUnblock(botName).string
|
||||||
|
} else {
|
||||||
|
text = self.presentationData.strings.WebApp_SharePhoneConfirmation(botName).string
|
||||||
|
}
|
||||||
|
|
||||||
|
let alertController = textAlertController(context: self.context, updatedPresentationData: controller.updatedPresentationData, title: self.presentationData.strings.WebApp_SharePhoneTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
||||||
|
sendEvent(false)
|
||||||
|
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in
|
||||||
|
guard let self, case let .user(user) = accountPeer, let phone = user.phone, !phone.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendMessageSignal = enqueueMessages(account: self.context.account, peerId: botId, messages: [
|
||||||
|
.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: phone, peerId: user.id, vCardData: nil)), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
|
||||||
|
])
|
||||||
|
|> mapToSignal { messageIds in
|
||||||
|
if let maybeMessageId = messageIds.first, let messageId = maybeMessageId {
|
||||||
|
return context.account.pendingMessageManager.pendingMessageStatus(messageId)
|
||||||
|
|> mapToSignal { status, _ -> Signal<Bool, NoError> in
|
||||||
|
if status != nil {
|
||||||
|
return .never()
|
||||||
|
} else {
|
||||||
|
return .single(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendMessage = {
|
||||||
|
let _ = (sendMessageSignal
|
||||||
|
|> deliverOnMainQueue).start(completed: {
|
||||||
|
sendEvent(true)
|
||||||
})
|
})
|
||||||
})])
|
}
|
||||||
|
|
||||||
|
if requiresUnblock {
|
||||||
|
let _ = (context.engine.privacy.requestUpdatePeerIsBlocked(peerId: botId, isBlocked: false)
|
||||||
|
|> deliverOnMainQueue).start(completed: {
|
||||||
|
sendMessage()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
sendMessage()
|
||||||
|
}
|
||||||
|
})], parseMarkdown: true)
|
||||||
alertController.dismissed = { byOutsideTap in
|
alertController.dismissed = { byOutsideTap in
|
||||||
if byOutsideTap {
|
if byOutsideTap {
|
||||||
sendEvent(false)
|
sendEvent(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.present(alertController, in: .window(.root))
|
controller.present(alertController, in: .window(.root))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func invokeCustomMethod(requestId: String, method: String, params: String) {
|
fileprivate func invokeCustomMethod(requestId: String, method: String, params: String) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user