Stories improvements

This commit is contained in:
Ilya Laktyushin 2023-06-27 20:53:24 +02:00
parent b10ae1b12a
commit 8ac9121c5e
28 changed files with 402 additions and 148 deletions

View File

@ -1369,7 +1369,7 @@ public final class CalendarMessageScreen: ViewController {
if self.selectionState?.dayRange == nil {
if let selectionToolbarNode = self.selectionToolbarNode {
let toolbarFrame = selectionToolbarNode.view.convert(selectionToolbarNode.bounds, to: self.view)
self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in
self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip), style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in
return .dismiss(consume: false)
}), in: .current)
}

View File

@ -165,11 +165,13 @@ private final class VideoRecorderImpl {
if !self.savedTransitionImage, let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
self.savedTransitionImage = true
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
if let cgImage = self.imageContext.createCGImage(ciImage, from: ciImage.extent) {
self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right)
} else {
self.savedTransitionImage = false
Queue.concurrentDefaultQueue().async {
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
if let cgImage = self.imageContext.createCGImage(ciImage, from: ciImage.extent) {
self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right)
} else {
self.savedTransitionImage = false
}
}
}

View File

@ -2356,7 +2356,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize())
parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in
parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: .animation(name: "ChatListFoldersTooltip", delay: 0.6), location: .point(location, .bottom), shouldDismissOnTouch: { point in
guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else {
return .dismiss(consume: false)
}
@ -2697,11 +2697,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY - 8.0), size: CGSize())
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize())
let tooltipController = TooltipScreen(
context: self.context,
account: self.context.account,
sharedContext: self.context.sharedContext,
text: "Stories from \(peer.compactDisplayTitle) will now be shown in Contacts, not Chats.",
text: .markdown(text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Contacts, not Chats."),
icon: .peer(peer: peer, isStory: true),
action: TooltipScreen.Action(
title: "Undo",
action: { [weak self] in
if let self {
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false)
}
}
),
location: .point(location, .bottom),
shouldDismissOnTouch: { _ in return .dismiss(consume: false) }
)

View File

@ -96,15 +96,25 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
return
}
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY - 8.0), size: CGSize())
let tooltipController = TooltipScreen(
account: context.account,
sharedContext: context.sharedContext,
text: "Stories from \(peer?.compactDisplayTitle ?? "") will now be shown in Chats, not Contacts.",
location: .point(location, .bottom),
shouldDismissOnTouch: { _ in return .dismiss(consume: false) }
)
contactsController?.present(tooltipController, in: .window(.root))
if let peer {
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize())
let tooltipController = TooltipScreen(
context: context,
account: context.account,
sharedContext: context.sharedContext,
text: .markdown(text: "Stories from \(peer.compactDisplayTitle) will now be shown in Chats, not Contacts."),
icon: .peer(peer: peer, isStory: true),
action: TooltipScreen.Action(
title: "Undo",
action: {
context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true)
}
),
location: .point(location, .bottom),
shouldDismissOnTouch: { _ in return .dismiss(consume: false) }
)
contactsController?.present(tooltipController, in: .window(.root))
}
})))
return items

View File

@ -828,7 +828,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
text = strongSelf.presentationData.strings.Location_ProximityTip(EnginePeer(peer).compactDisplayTitle).string
}
strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}))
})

View File

@ -179,7 +179,7 @@ public final class QrCodeScanScreen: ViewController {
return
}
if let navigationController = navigationController as? NavigationController {
self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: self.presentationData.strings.AuthSessions_AddedDeviceTitle, text: session?.appName ?? "Telegram for macOS", cancel: self.presentationData.strings.AuthSessions_AddedDeviceTerminate), elevatedLayout: false, animateInAsReplacement: false, action: { value in
self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: self.presentationData.strings.AuthSessions_AddedDeviceTitle, text: session?.appName ?? "Telegram for macOS", cancel: self.presentationData.strings.AuthSessions_AddedDeviceTerminate, destructive: true), elevatedLayout: false, animateInAsReplacement: false, action: { value in
if value == .undo, let session = session {
let _ = activeSessionsContext.remove(hash: session.hash).start()
return true

View File

@ -456,7 +456,7 @@ public final class ThemePreviewController: ViewController {
guard let strongSelf = self, !displayed else {
return
}
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .actionSucceeded(title: strongSelf.presentationData.strings.Theme_ThemeChanged, text: strongSelf.presentationData.strings.Theme_ThemeChangedText, cancel: strongSelf.presentationData.strings.Undo_Undo), elevatedLayout: true, animateInAsReplacement: false, action: { value in
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .actionSucceeded(title: strongSelf.presentationData.strings.Theme_ThemeChanged, text: strongSelf.presentationData.strings.Theme_ThemeChangedText, cancel: strongSelf.presentationData.strings.Undo_Undo, destructive: false), elevatedLayout: true, animateInAsReplacement: false, action: { value in
if value == .undo {
Queue.mainQueue().after(0.2) {
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> PresentationThemeSettings in

View File

@ -1729,7 +1729,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
if let strongSelf = self, (count < 2 && currentTimestamp > timestamp + 24 * 60 * 60) {
strongSelf.displayedPreviewTooltip = true
let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode, style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.35)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode), style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.35)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
})
strongSelf.galleryController()?.present(controller, in: .current)

View File

@ -529,7 +529,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, let controller = strongSelf.getController?(), value == 2 {
let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: strongSelf.strings.Conversation_AudioRateOptionsTooltip), style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
})
controller.present(tooltipController, in: .window(.root))

View File

@ -761,7 +761,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
return
}
self.present?(TooltipScreen(account: self.account, sharedContext: self.sharedContext, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in
self.present?(TooltipScreen(account: self.account, sharedContext: self.sharedContext, text: .plain(text: self.presentationData.strings.Call_CameraOrScreenTooltip), style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}))
}

View File

@ -2308,7 +2308,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else {
text = presentationData.strings.VoiceChat_RecordingInProgress
}
strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: true)
}), in: .window(.root))
}
@ -3505,7 +3505,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
if !callState.subscribedToScheduled {
let location = self.actionButton.view.convert(self.actionButton.bounds, to: self.view).center
let point = CGRect(origin: CGPoint(x: location.x - 5.0, y: location.y - 5.0 - 68.0), size: CGSize(width: 10.0, height: 10.0))
self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.VoiceChat_ReminderNotify), style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}), in: .window(.root))
}
@ -6409,7 +6409,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
point.origin.y += 32.0
}
}
self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in
self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.VoiceChat_UnmuteSuggestion), style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}), in: .window(.root))
}

View File

@ -1589,7 +1589,7 @@ public class CameraScreen: ViewController {
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 - 4.0), size: CGSize())
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "Draft Saved", location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Draft Saved"), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in
return .ignore
})
self.controller?.present(controller, in: .current)
@ -1604,7 +1604,7 @@ public class CameraScreen: ViewController {
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 tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "Enable Dual Camera Mode", location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Enable Dual Camera Mode"), location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in
return .ignore
})
self.controller?.present(tooltipController, in: .current)
@ -1617,9 +1617,9 @@ public class CameraScreen: ViewController {
let parentFrame = self.view.convert(self.bounds, to: nil)
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 - 3.0), size: CGSize())
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "Take photos or videos to share with all your contacts or close friends at once.", location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(3.0), inset: 16.0, shouldDismissOnTouch: { _ in
return .ignore
})
self.controller?.present(tooltipController, in: .current)
@ -2106,6 +2106,11 @@ public class CameraScreen: ViewController {
if let layout = self.validLayout, case .regular = layout.metrics.widthClass {
return
}
if self.node.hasAppeared {
self.dismissAllTooltips()
}
let transitionFraction = max(0.0, min(1.0, transitionFraction))
let offsetX = floorToScreenPixels((1.0 - transitionFraction) * self.node.frame.width * -1.0)
transition.updateTransform(layer: self.node.backgroundView.layer, transform: CGAffineTransform(translationX: offsetX, y: 0.0))

View File

@ -265,6 +265,7 @@ public final class MediaEditor {
videoTrimRange: nil,
videoIsMuted: false,
videoIsFullHd: false,
videoIsMirrored: false,
additionalVideoPath: nil,
additionalVideoPosition: nil,
additionalVideoScale: nil,
@ -582,6 +583,12 @@ public final class MediaEditor {
}
}
public func setVideoIsMirrored(_ videoIsMirrored: Bool) {
self.updateValues(mode: .skipRendering) { values in
return values.withUpdatedVideoIsMirrored(videoIsMirrored)
}
}
public func setVideoIsFullHd(_ videoIsFullHd: Bool) {
self.updateValues(mode: .skipRendering) { values in
return values.withUpdatedVideoIsFullHd(videoIsFullHd)

View File

@ -84,6 +84,9 @@ public final class MediaEditorValues: Codable, Equatable {
if lhs.videoIsFullHd != rhs.videoIsFullHd {
return false
}
if lhs.videoIsMirrored != rhs.videoIsMirrored {
return false
}
if lhs.additionalVideoPath != rhs.additionalVideoPath {
return false
}
@ -143,6 +146,7 @@ public final class MediaEditorValues: Codable, Equatable {
case videoTrimRange
case videoIsMuted
case videoIsFullHd
case videoIsMirrored
case additionalVideoPath
case additionalVideoPosition
@ -167,6 +171,7 @@ public final class MediaEditorValues: Codable, Equatable {
public let videoTrimRange: Range<Double>?
public let videoIsMuted: Bool
public let videoIsFullHd: Bool
public let videoIsMirrored: Bool
public let additionalVideoPath: String?
public let additionalVideoPosition: CGPoint?
@ -189,6 +194,7 @@ public final class MediaEditorValues: Codable, Equatable {
videoTrimRange: Range<Double>?,
videoIsMuted: Bool,
videoIsFullHd: Bool,
videoIsMirrored: Bool,
additionalVideoPath: String?,
additionalVideoPosition: CGPoint?,
additionalVideoScale: CGFloat?,
@ -208,6 +214,7 @@ public final class MediaEditorValues: Codable, Equatable {
self.videoTrimRange = videoTrimRange
self.videoIsMuted = videoIsMuted
self.videoIsFullHd = videoIsFullHd
self.videoIsMirrored = videoIsMirrored
self.additionalVideoPath = additionalVideoPath
self.additionalVideoPosition = additionalVideoPosition
self.additionalVideoScale = additionalVideoScale
@ -240,6 +247,7 @@ public final class MediaEditorValues: Codable, Equatable {
self.videoTrimRange = try container.decodeIfPresent(Range<Double>.self, forKey: .videoTrimRange)
self.videoIsMuted = try container.decode(Bool.self, forKey: .videoIsMuted)
self.videoIsFullHd = try container.decodeIfPresent(Bool.self, forKey: .videoIsFullHd) ?? false
self.videoIsMirrored = try container.decodeIfPresent(Bool.self, forKey: .videoIsMirrored) ?? false
self.additionalVideoPath = try container.decodeIfPresent(String.self, forKey: .additionalVideoPath)
self.additionalVideoPosition = try container.decodeIfPresent(CGPoint.self, forKey: .additionalVideoPosition)
@ -283,6 +291,7 @@ public final class MediaEditorValues: Codable, Equatable {
try container.encodeIfPresent(self.videoTrimRange, forKey: .videoTrimRange)
try container.encode(self.videoIsMuted, forKey: .videoIsMuted)
try container.encode(self.videoIsFullHd, forKey: .videoIsFullHd)
try container.encode(self.videoIsMirrored, forKey: .videoIsMirrored)
try container.encodeIfPresent(self.additionalVideoPath, forKey: .additionalVideoPath)
try container.encodeIfPresent(self.additionalVideoPosition, forKey: .additionalVideoPosition)
@ -306,43 +315,48 @@ public final class MediaEditorValues: Codable, Equatable {
}
public func makeCopy() -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedAdditionalVideo(path: String, positionChanges: [VideoPositionChange]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: path, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: path, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: drawing, entities: entities, toolValues: self.toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: drawing, entities: entities, toolValues: self.toolValues)
}
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: toolValues)
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: toolValues)
}
public var resultDimensions: PixelDimensions {

View File

@ -345,11 +345,17 @@ public final class MediaEditorVideoExport {
private func setupWithAsset(_ asset: AVAsset, additionalAsset: AVAsset?) {
self.reader = try? AVAssetReader(asset: asset)
self.textureRotation = textureRotatonForAVAsset(asset)
var mirror = false
if additionalAsset == nil, self.configuration.values.videoIsMirrored {
mirror = true
}
self.textureRotation = textureRotatonForAVAsset(asset, mirror: mirror)
if let additionalAsset {
self.additionalReader = try? AVAssetReader(asset: additionalAsset)
self.additionalTextureRotation = textureRotatonForAVAsset(additionalAsset)
self.additionalTextureRotation = textureRotatonForAVAsset(additionalAsset, mirror: true)
}
guard let reader = self.reader else {
return

View File

@ -696,7 +696,9 @@ final class MediaEditorScreenComponent: Component {
guard let controller = environment.controller() as? MediaEditorScreen else {
return
}
controller.requestCompletion(animated: true)
controller.openPrivacySettings(completion: { [weak controller] in
controller?.requestCompletion(animated: true)
})
}
)),
environment: {},
@ -1142,7 +1144,7 @@ final class MediaEditorScreenComponent: Component {
}
if let privacyButtonView = self.privacyButton.view {
if privacyButtonView.superview == nil {
self.addSubview(privacyButtonView)
//self.addSubview(privacyButtonView)
}
transition.setPosition(view: privacyButtonView, position: privacyButtonFrame.center)
transition.setBounds(view: privacyButtonView, bounds: CGRect(origin: .zero, size: privacyButtonFrame.size))
@ -1841,19 +1843,22 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
imageEntity.scale = 1.49
imageEntity.position = position.getPosition(storyDimensions)
self.entitiesView.add(imageEntity, announce: false)
} else if case let .video(_, _, _, additionalVideoPath, _, _, _, changes, position) = subject, let additionalVideoPath {
let videoEntity = DrawingStickerEntity(content: .dualVideoReference)
videoEntity.referenceDrawingSize = storyDimensions
videoEntity.scale = 1.49
videoEntity.position = position.getPosition(storyDimensions)
self.entitiesView.add(videoEntity, announce: false)
mediaEditor.setAdditionalVideo(additionalVideoPath, positionChanges: changes.map { VideoPositionChange(additional: $0.0, timestamp: $0.1) })
mediaEditor.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation)
if let entityView = self.entitiesView.getView(for: videoEntity.uuid) as? DrawingStickerEntityView {
entityView.updated = { [weak videoEntity, weak self] in
if let self, let videoEntity {
self.mediaEditor?.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation)
} else if case let .video(_, _, mirror, additionalVideoPath, _, _, _, changes, position) = subject {
mediaEditor.setVideoIsMirrored(mirror)
if let additionalVideoPath {
let videoEntity = DrawingStickerEntity(content: .dualVideoReference)
videoEntity.referenceDrawingSize = storyDimensions
videoEntity.scale = 1.49
videoEntity.position = position.getPosition(storyDimensions)
self.entitiesView.add(videoEntity, announce: false)
mediaEditor.setAdditionalVideo(additionalVideoPath, positionChanges: changes.map { VideoPositionChange(additional: $0.0, timestamp: $0.1) })
mediaEditor.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation)
if let entityView = self.entitiesView.getView(for: videoEntity.uuid) as? DrawingStickerEntityView {
entityView.updated = { [weak videoEntity, weak self] in
if let self, let videoEntity {
self.mediaEditor?.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation)
}
}
}
}
@ -2504,7 +2509,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
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 tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "You can set who can view this story.", location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "You can set who can view this story."), location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in
return .ignore
})
self.controller?.present(tooltipController, in: .current)
@ -2527,7 +2532,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
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 tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: isMuted ? "The story will have no sound." : "The story will have sound." , location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? "The story will have no sound." : "The story will have sound."), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in
return .ignore
})
self.muteTooltip = tooltipController
@ -2642,7 +2647,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
text = "Story will disappear in 24 hours."
}
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: text, location: .point(location, .bottom), displayDuration: .default, inset: 7.0, cornerRadius: 9.0, shouldDismissOnTouch: { _ in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), location: .point(location, .bottom), displayDuration: .default, inset: 7.0, cornerRadius: 9.0, shouldDismissOnTouch: { _ in
return .ignore
})
self.storyArchiveTooltip = tooltipController
@ -3133,7 +3138,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.displayNode.view.addInteraction(dropInteraction)
}
func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil) {
func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil, completion: @escaping () -> Void = {}) {
self.hapticFeedback.impact(.light)
let privacy = privacy ?? self.state.privacy
@ -3157,6 +3162,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return
}
self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive)
completion()
},
editCategory: { [weak self] privacy in
guard let self else {
@ -3166,7 +3172,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
guard let self else {
return
}
self.openPrivacySettings(MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive))
self.openPrivacySettings(MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive), completion: completion)
})
}
)

View File

@ -458,8 +458,7 @@ final class ShareWithPeersScreenComponent: Component {
let sectionTitle: String
if section.id == 0 {
let hours = component.timeout / 3600
sectionTitle = "WHO CAN VIEW FOR \(hours) HOURS"
sectionTitle = "WHO CAN VIEW"
} else {
if case .chats = component.stateContext.subject {
sectionTitle = "CHATS"
@ -1063,10 +1062,12 @@ final class ShareWithPeersScreenComponent: Component {
}
navigationButtonsWidth += navigationLeftButtonSize.width + navigationSideInset
var actionButtonTitle = "Save Settings"
let title: String
switch component.stateContext.subject {
case .stories:
title = "Share Story"
actionButtonTitle = "Post Story"
case .chats:
title = "Send as a Message"
case let .contacts(category):
@ -1125,7 +1126,6 @@ final class ShareWithPeersScreenComponent: Component {
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset, y: navigationHeight), size: CGSize(width: containerWidth, height: UIScreenPixel)))
let actionButtonTitle: String = "Save Settings"
let actionButtonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(

View File

@ -712,6 +712,9 @@ public final class StoryItemSetContainerComponent: Component {
if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.displayViewList {
return true
}
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
return true
}
if self.privacyController != nil {
return true
}
@ -2235,7 +2238,7 @@ public final class StoryItemSetContainerComponent: Component {
let tooltipScreen = TooltipScreen(
account: component.context.account,
sharedContext: component.context.sharedContext,
text: "You are seeing this story because you have\nbeen added to \(component.slice.peer.compactDisplayTitle)'s list of close friends.", style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .manual(true), shouldDismissOnTouch: { _ in
text: .markdown(text: "You are seeing this story because you have\nbeen added to **\(component.slice.peer.compactDisplayTitle)'s** list of close friends."), style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .manual(true), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}
)

View File

@ -338,7 +338,7 @@ final class StoryItemSetContainerSendMessage {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat"),
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
@ -380,7 +380,7 @@ final class StoryItemSetContainerSendMessage {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat"),
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
@ -442,7 +442,7 @@ final class StoryItemSetContainerSendMessage {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat"),
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
@ -491,7 +491,7 @@ final class StoryItemSetContainerSendMessage {
if isVideo {
if self.videoRecorderValue == nil {
if let currentInputPanelFrame = view.inputPanel.view?.frame {
self.videoRecorder.set(.single(legacyInstantVideoController(theme: component.theme, panelFrame: view.convert(currentInputPanelFrame, to: nil), context: component.context, peerId: peer.id, slowmodeState: nil, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, send: { [weak self, weak view] videoController, message in
self.videoRecorder.set(.single(legacyInstantVideoController(theme: component.theme, panelFrame: view.convert(currentInputPanelFrame, to: nil), context: component.context, peerId: peer.id, slowmodeState: nil, hasSchedule: true, send: { [weak self, weak view] videoController, message in
guard let self, let view, let component = view.component else {
return
}
@ -2057,28 +2057,36 @@ final class StoryItemSetContainerSendMessage {
}
private func sendMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) {
guard let component = view.component else {
guard let component = view.component, let controller = component.controller() else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: self.transformEnqueueMessages(view: view, messages: messages, silentPosting: false))
|> deliverOnMainQueue).start()
|> deliverOnMainQueue).start(next: { [weak controller] messageIds in
if let controller {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
if case .undo = action, let messageId = messageIds.first {
view?.navigateToPeer(peer: peer, messageId: messageId)
}
return false
}
), in: .current)
}
}
})
donateSendMessageIntent(account: component.context.account, sharedContext: component.context.sharedContext, intentContext: .chat, peerIds: [peer.id])
if let attachmentController = self.attachmentController {
attachmentController.dismiss(animated: true)
}
if let controller = component.controller() {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .succeed(text: "Message Sent"),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
}
}
private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
@ -2139,9 +2147,7 @@ final class StoryItemSetContainerSendMessage {
strongSelf.sendMessages(view: view, peer: peer, messages: messages.map { $0.withUpdatedReplyToMessageId(replyToMessageId).withUpdatedReplyToStoryId(replyToStoryId) }, media: true)
if let _ = scheduleTime {
completion()
}
completion()
}
}))
}

View File

@ -130,7 +130,7 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode {
tooltipController.location = .point(location, .bottom)
}
} else {
let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: self.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: self.strings.Bot_TapToUse), icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
return .ignore
})
controller.alwaysVisible = true

View File

@ -14490,7 +14490,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var found = false
self.forEachController({ controller in
if let controller = controller as? TooltipScreen {
if controller.text == solution.text && controller.textEntities == solution.entities {
if controller.text == .entities(text: solution.text, entities: solution.entities) {
found = true
controller.dismiss()
return false
@ -14502,7 +14502,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: solution.text, entities: solution.entities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, shouldDismissOnTouch: { point in
return .ignore
}, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else {
@ -14583,7 +14583,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var found = false
self.forEachController({ controller in
if let controller = controller as? TooltipScreen {
if controller.text == psaText {
if controller.text == .plain(text: psaText) {
found = true
controller.dismiss()
return false
@ -14595,7 +14595,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
return .ignore
}, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else {
@ -14683,7 +14683,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var found = false
self.forEachController({ controller in
if let controller = controller as? TooltipScreen {
if controller.text == psaText {
if controller.text == .plain(text: psaText) {
found = true
controller.resetDismissTimeout()
@ -14709,7 +14709,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
return .ignore
}, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else {

View File

@ -1484,7 +1484,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
tooltipController.location = .point(location, .bottom)
}
} else {
let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: interfaceState.strings.Bot_TapToUse), icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
return .ignore
})
controller.alwaysVisible = true

View File

@ -1262,7 +1262,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 {
strongSelf.displayedPreviewTooltip = true
strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLightShort : strongSelf.presentationData.strings.Conversation_Theme_PreviewDarkShort, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLightShort : strongSelf.presentationData.strings.Conversation_Theme_PreviewDarkShort), style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}))

View File

@ -8691,7 +8691,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return
}
let buttonFrame = buttonNode.view.convert(buttonNode.bounds, to: self.view)
controller.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in
controller.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.SharedMedia_CalendarTooltip), style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in
return .dismiss(consume: false)
}), in: .current)
}

View File

@ -20,6 +20,10 @@ swift_library(
"//submodules/TextFormat:TextFormat",
"//submodules/UrlEscaping:UrlEscaping",
"//submodules/AccountContext:AccountContext",
"//submodules/AvatarNode:AvatarNode",
"//submodules/ComponentFlow",
"//submodules/Markdown",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
],
visibility = [
"//visibility:public",

View File

@ -11,11 +11,11 @@ import TelegramCore
import TextFormat
import UrlEscaping
import AccountContext
public protocol TooltipCustomContentNode: ASDisplayNode {
func animateIn()
func updateLayout(size: CGSize) -> CGSize
}
import AvatarNode
import ComponentFlow
import AvatarStoryIndicatorComponent
import AccountContext
import Markdown
public enum TooltipActiveTextItem {
case url(String, Bool)
@ -109,7 +109,7 @@ private class DownArrowsIconNode: ASDisplayNode {
private final class TooltipScreenNode: ViewControllerTracingNode {
private let tooltipStyle: TooltipScreen.Style
private let icon: TooltipScreen.Icon?
private let customContentNode: TooltipCustomContentNode?
private let action: TooltipScreen.Action?
var location: TooltipScreen.Location {
didSet {
if let layout = self.validLayout {
@ -134,8 +134,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private let arrowContainer: ASDisplayNode
private let animatedStickerNode: AnimatedStickerNode
private var downArrowsNode: DownArrowsIconNode?
private var avatarNode: AvatarNode?
private var avatarStoryIndicator: ComponentView<Empty>?
private let textNode: ImmediateTextNode
private let closeButtonNode: HighlightableButtonNode
private var closeButtonNode: HighlightableButtonNode?
private var actionButtonNode: HighlightableButtonNode?
private var isArrowInverted: Bool = false
@ -143,10 +146,24 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private var validLayout: ContainerViewLayout?
init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon? = nil, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, cornerRadius: CGFloat? = nil, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) {
init(
context: AccountContext?,
account: Account,
sharedContext: SharedAccountContext,
text: TooltipScreen.Text,
textAlignment: TooltipScreen.Alignment,
style: TooltipScreen.Style,
icon: TooltipScreen.Icon? = nil,
action: TooltipScreen.Action? = nil,
location: TooltipScreen.Location,
displayDuration: TooltipScreen.DisplayDuration,
inset: CGFloat = 13.0,
cornerRadius: CGFloat? = nil,
shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?)
{
self.tooltipStyle = style
self.icon = icon
self.customContentNode = customContentNode
self.action = action
self.location = location
self.displayDuration = displayDuration
self.inset = inset
@ -164,6 +181,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.scrollingContainer = ASDisplayNode()
let theme = sharedContext.currentPresentationData.with { $0 }.theme
func svgPath(_ path: StaticString, scale: CGPoint = CGPoint(x: 1.0, y: 1.0), offset: CGPoint = CGPoint()) throws -> UIBezierPath {
var index: UnsafePointer<UInt8> = path.utf8Start
let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount)
@ -210,7 +229,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.arrowContainer = ASDisplayNode()
let theme = sharedContext.currentPresentationData.with { $0 }.theme
let fontSize: CGFloat
if case .top = location {
let backgroundColor: UIColor
@ -310,25 +328,56 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 0
let baseFont = Font.regular(fontSize)
let boldFont = Font.semibold(14.0)
let italicFont = Font.italic(fontSize)
let boldItalicFont = Font.semiboldItalic(fontSize)
let fixedFont = Font.monospace(fontSize)
self.textNode.attributedText = stringWithAppliedEntities(text, entities: textEntities, baseColor: .white, linkColor: .white, baseFont: Font.regular(fontSize), linkFont: Font.regular(fontSize), boldFont: Font.semibold(14.0), italicFont: Font.italic(fontSize), boldItalicFont: Font.semiboldItalic(fontSize), fixedFont: Font.monospace(fontSize), blockQuoteFont: Font.regular(fontSize), underlineLinks: true, external: false, message: nil)
let textColor: UIColor = .white
let attributedText: NSAttributedString
switch text {
case let .plain(text):
attributedText = NSAttributedString(string: text, font: baseFont, textColor: textColor)
case let .entities(text, entities):
attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: baseFont, linkFont: baseFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, blockQuoteFont: baseFont, underlineLinks: true, external: false, message: nil)
case let .markdown(text):
let markdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: baseFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldFont, textColor: textColor),
link: MarkdownAttributeSet(font: baseFont, textColor: textColor),
linkAttribute: { _ in
return nil
}
)
attributedText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes)
}
self.textNode.attributedText = attributedText
self.textNode.textAlignment = textAlignment == .center ? .center : .natural
self.animatedStickerNode = DefaultAnimatedStickerNodeImpl()
switch icon {
case .none:
break
case .chatListPress:
self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListFoldersTooltip"), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animatedStickerNode.automaticallyLoadFirstFrame = true
case .info:
self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "anim_infotip"), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
case let .animation(animationName, _):
self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animatedStickerNode.automaticallyLoadFirstFrame = true
case .downArrows:
self.downArrowsNode = DownArrowsIconNode()
case let .peer(peer, _):
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
if let context {
self.avatarNode?.setPeer(context: context, theme: defaultDarkPresentationTheme, peer: peer)
}
}
self.closeButtonNode = HighlightableButtonNode()
self.closeButtonNode.setImage(UIImage(bundleImageName: "Components/Close"), for: .normal)
if case .manual = displayDuration {
self.closeButtonNode = HighlightableButtonNode()
self.closeButtonNode?.setImage(UIImage(bundleImageName: "Components/Close"), for: .normal)
}
super.init()
@ -343,16 +392,28 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.containerNode.addSubnode(self.textNode)
self.containerNode.addSubnode(self.animatedStickerNode)
if case .manual = displayDuration {
self.containerNode.addSubnode(self.closeButtonNode)
if let closeButtonNode = self.closeButtonNode {
self.containerNode.addSubnode(closeButtonNode)
}
if let downArrowsNode = self.downArrowsNode {
self.containerNode.addSubnode(downArrowsNode)
}
if let avatarNode = self.avatarNode {
self.containerNode.addSubnode(avatarNode)
}
self.scrollingContainer.addSubnode(self.containerNode)
self.addSubnode(self.scrollingContainer)
if let action {
let actionColor = theme.list.itemAccentColor.withMultiplied(hue: 1.0, saturation: 0.64, brightness: 1.08)
let actionButtonNode = HighlightableButtonNode()
actionButtonNode.hitTestSlop = UIEdgeInsets(top: -16.0, left: -16.0, bottom: -16.0, right: -16.0)
actionButtonNode.setAttributedTitle(NSAttributedString(string: action.title, font: Font.regular(17.0), textColor: actionColor), for: .normal)
self.containerNode.addSubnode(actionButtonNode)
self.actionButtonNode = actionButtonNode
}
self.textNode.linkHighlightColor = UIColor.white.withAlphaComponent(0.5)
self.textNode.highlightAttributeAction = { attributes in
let highlightedAttributes = [
@ -412,7 +473,15 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
}
}
self.closeButtonNode.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
self.actionButtonNode?.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside)
self.closeButtonNode?.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
}
@objc private func actionPressed() {
if let action = self.action {
action.action()
self.requestDismiss()
}
}
@objc private func closePressed() {
@ -441,11 +510,15 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
animationSize = CGSize(width: 24.0, height: 32.0)
animationInset = (40.0 - animationSize.width) / 2.0
animationSpacing = 8.0
case .chatListPress:
case let .animation(animationName, _):
animationSize = CGSize(width: 32.0, height: 32.0)
animationInset = (70.0 - animationSize.width) / 2.0
if animationName == "ChatListFoldersTooltip" {
animationInset = (70.0 - animationSize.width) / 2.0
} else {
animationInset = 0.0
}
animationSpacing = 8.0
case .info:
case .peer:
animationSize = CGSize(width: 32.0, height: 32.0)
animationInset = 0.0
animationSpacing = 8.0
@ -453,24 +526,41 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let containerWidth = max(100.0, min(layout.size.width, 614.0) - (sideInset + layout.safeInsets.left) * 2.0)
let textSize = self.textNode.updateLayout(CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing, height: .greatestFiniteMagnitude))
var actionSize: CGSize = .zero
var buttonInset: CGFloat = 0.0
if let actionButtonNode = self.actionButtonNode {
actionSize = actionButtonNode.measure(CGSize(width: containerWidth, height: .greatestFiniteMagnitude))
buttonInset += actionSize.width + 32.0
}
if self.closeButtonNode != nil {
buttonInset += 24.0
}
let textSize = self.textNode.updateLayout(CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: .greatestFiniteMagnitude))
var backgroundFrame: CGRect
let backgroundHeight: CGFloat
var backgroundHeight: CGFloat
switch self.tooltipStyle {
case .default, .gradient, .customBlur:
backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0
case .light:
backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0)
backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0
if self.actionButtonNode != nil {
backgroundHeight += 2.0
}
case .light:
backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0)
}
var invertArrow = false
switch self.location {
case let .point(rect, arrowPosition):
var backgroundWidth = textSize.width + contentInset * 2.0 + animationSize.width + animationSpacing
if self.closeButtonNode.supernode != nil {
backgroundWidth += 24.0
if self.closeButtonNode != nil || self.actionButtonNode != nil {
backgroundWidth += buttonInset
}
if self.actionButtonNode != nil, case .compact = layout.metrics.widthClass {
backgroundWidth = containerWidth
}
switch arrowPosition {
case .bottom, .top:
@ -554,8 +644,14 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let textFrame = CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)
transition.updateFrame(node: self.textNode, frame: textFrame)
let closeSize = CGSize(width: 44.0, height: 44.0)
transition.updateFrame(node: self.closeButtonNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX - 6.0, y: floor((backgroundHeight - closeSize.height) / 2.0)), size: closeSize))
if let closeButtonNode = self.closeButtonNode {
let closeSize = CGSize(width: 44.0, height: 44.0)
transition.updateFrame(node: closeButtonNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX - 6.0, y: floor((backgroundHeight - closeSize.height) / 2.0)), size: closeSize))
}
if let actionButtonNode = self.actionButtonNode {
transition.updateFrame(node: actionButtonNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.width - actionSize.width - 16.0, y: floor((backgroundHeight - actionSize.height) / 2.0)), size: actionSize))
}
let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame)
@ -566,6 +662,59 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
transition.updateFrame(node: downArrowsNode, frame: CGRect(origin: CGPoint(x: animationFrame.midX - arrowsSize.width / 2.0, y: animationFrame.midY - arrowsSize.height / 2.0), size: arrowsSize))
downArrowsNode.setupAnimations()
}
if let avatarNode = self.avatarNode {
var avatarFrame = animationFrame
if let icon, case let .peer(_, isStory) = icon, isStory {
let indicatorTransition: Transition = .immediate
let avatarStoryIndicator: ComponentView<Empty>
if let current = self.avatarStoryIndicator {
avatarStoryIndicator = current
} else {
avatarStoryIndicator = ComponentView()
self.avatarStoryIndicator = avatarStoryIndicator
}
let storyIndicatorScale: CGFloat = 1.0
var indicatorFrame = CGRect(origin: CGPoint(x: avatarFrame.minX + 4.0, y: avatarFrame.minY + 4.0), size: CGSize(width: avatarFrame.width - 4.0 - 4.0, height: avatarFrame.height - 4.0 - 4.0))
indicatorFrame.origin.x -= (avatarFrame.width - avatarFrame.width * storyIndicatorScale) * 0.5
let _ = avatarStoryIndicator.update(
transition: indicatorTransition,
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: true,
hasUnseenCloseFriendsItems: false,
theme: defaultDarkPresentationTheme,
activeLineWidth: 1.0 + UIScreenPixel,
inactiveLineWidth: 1.0 + UIScreenPixel,
counters: nil
)),
environment: {},
containerSize: indicatorFrame.size
)
if let avatarStoryIndicatorView = avatarStoryIndicator.view {
if avatarStoryIndicatorView.superview == nil {
avatarStoryIndicatorView.isUserInteractionEnabled = false
self.containerNode.view.addSubview(avatarStoryIndicatorView)
}
indicatorTransition.setPosition(view: avatarStoryIndicatorView, position: indicatorFrame.center)
indicatorTransition.setBounds(view: avatarStoryIndicatorView, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size))
indicatorTransition.setScale(view: avatarStoryIndicatorView, scale: storyIndicatorScale)
}
avatarFrame = avatarFrame.insetBy(dx: 4.0, dy: 4.0)
} else {
if let avatarStoryIndicator = self.avatarStoryIndicator {
self.avatarStoryIndicator = nil
avatarStoryIndicator.view?.removeFromSuperview()
}
}
transition.updateFrame(node: avatarNode, frame: avatarFrame)
avatarNode.updateSize(size: avatarFrame.size)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -587,6 +736,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
return nil
}
}
if let actionButtonNode = self.actionButtonNode, let result = actionButtonNode.hitTest(self.convert(point, to: actionButtonNode), with: event) {
return result
}
switch self.shouldDismissOnTouch(point) {
case .ignore:
break
@ -628,12 +780,12 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let animationDelay: Double
switch self.icon {
case .chatListPress:
animationDelay = 0.6
case .info:
animationDelay = 0.2
case let .animation(_, delay):
animationDelay = delay
case .none, .downArrows:
animationDelay = 0.0
case .peer:
animationDelay = 0.0
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + animationDelay, execute: { [weak self] in
@ -684,9 +836,28 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
}
public final class TooltipScreen: ViewController {
public enum Text: Equatable {
case plain(text: String)
case entities(text: String, entities: [MessageTextEntity])
case markdown(text: String)
}
public class Action {
public let title: String
public let action: () -> Void
public init(
title: String,
action: @escaping () -> Void
) {
self.title = title
self.action = action
}
}
public enum Icon {
case info
case chatListPress
case animation(name: String, delay: Double)
case peer(peer: EnginePeer, isStory: Bool)
case downArrows
}
@ -720,13 +891,19 @@ public final class TooltipScreen: ViewController {
case gradient(UIColor, UIColor)
}
public enum Alignment {
case natural
case center
}
private let context: AccountContext?
private let account: Account
private let sharedContext: SharedAccountContext
public let text: String
public let textEntities: [MessageTextEntity]
public let text: TooltipScreen.Text
public let textAlignment: TooltipScreen.Alignment
private let style: TooltipScreen.Style
private let icon: TooltipScreen.Icon?
private let customContentNode: TooltipCustomContentNode?
private let action: TooltipScreen.Action?
public var location: TooltipScreen.Location {
didSet {
if self.isNodeLoaded {
@ -755,13 +932,14 @@ public final class TooltipScreen: ViewController {
public var alwaysVisible = false
public init(
context: AccountContext? = nil,
account: Account,
sharedContext: SharedAccountContext,
text: String,
textEntities: [MessageTextEntity] = [],
text: TooltipScreen.Text,
textAlignment: TooltipScreen.Alignment = .natural,
style: TooltipScreen.Style = .default,
icon: TooltipScreen.Icon? = nil,
customContentNode: TooltipCustomContentNode? = nil,
action: TooltipScreen.Action? = nil,
location: TooltipScreen.Location,
displayDuration: DisplayDuration = .default,
inset: CGFloat = 13.0,
@ -769,13 +947,14 @@ public final class TooltipScreen: ViewController {
shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch,
openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil
) {
self.context = context
self.account = account
self.sharedContext = sharedContext
self.text = text
self.textEntities = textEntities
self.textAlignment = textAlignment
self.style = style
self.icon = icon
self.customContentNode = customContentNode
self.action = action
self.location = location
self.displayDuration = displayDuration
self.inset = inset
@ -839,7 +1018,7 @@ public final class TooltipScreen: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = TooltipScreenNode(account: self.account, sharedContext: self.sharedContext, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
self.displayNode = TooltipScreenNode(context: self.context, account: self.account, sharedContext: self.sharedContext, text: self.text, textAlignment: self.textAlignment, style: self.style, icon: self.icon, action: self.action, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
guard let strongSelf = self else {
return
}

View File

@ -15,7 +15,7 @@ public enum UndoOverlayContent {
case info(title: String?, text: String, timeout: Double?)
case emoji(name: String, text: String)
case swipeToReply(title: String, text: String)
case actionSucceeded(title: String, text: String, cancel: String)
case actionSucceeded(title: String, text: String, cancel: String, destructive: Bool)
case stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext)
case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?)
case chatAddedToFolder(chatTitle: String, folderTitle: String)

View File

@ -221,14 +221,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
isUserInteractionEnabled = true
}
case let .actionSucceeded(title, text, cancel):
case let .actionSucceeded(title, text, cancel, destructive):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
undoTextColor = UIColor(rgb: 0xff7b74)
if destructive {
undoTextColor = UIColor(rgb: 0xff7b74)
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)