Wallpaper experiment

This commit is contained in:
Ali 2021-11-05 20:35:30 +04:00
parent 0679e4dd7e
commit cdc04c733c
23 changed files with 835 additions and 99 deletions

View File

@ -80,7 +80,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case experimentalCompatibility(Bool)
case enableDebugDataDisplay(Bool)
case acceleratedStickers(Bool)
case mockICE(Bool)
case experimentalBackground(Bool)
case playerEmbedding(Bool)
case playlistPlayback(Bool)
case voiceConference
@ -102,7 +102,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .mockICE:
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground:
return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue
@ -171,7 +171,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 27
case .acceleratedStickers:
return 29
case .mockICE:
case .experimentalBackground:
return 30
case .playerEmbedding:
return 31
@ -804,12 +804,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .mockICE(value):
return ItemListSwitchItem(presentationData: presentationData, title: "mockICE", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .experimentalBackground(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Background Experiment", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.mockICE = value
settings.experimentalBackground = value
return PreferencesEntry(settings)
})
}).start()
@ -926,7 +926,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
entries.append(.mockICE(experimentalSettings.mockICE))
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
}

View File

@ -518,7 +518,22 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte
}
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
completionHandler()
let node = self.node
let _ = (self.node.status
|> take(1)
|> deliverOnMainQueue).start(next: { [weak node] status in
if let node = node, let timestamp = status?.timestamp, let duration = status?.duration {
let nextTimestamp = timestamp + skipInterval.seconds
if nextTimestamp > duration {
node.seek(0.0)
node.pause()
} else {
node.seek(min(duration, nextTimestamp))
}
}
completionHandler()
})
}
public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
@ -539,7 +554,9 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte
private var hiddenMediaManagerIndex: Int?
init(overlayController: OverlayMediaController, mediaManager: MediaManager, accountId: AccountRecordId, hiddenMedia: (MessageId, Media)?, videoNode: UniversalVideoNode, willBegin: @escaping (PictureInPictureContentImpl) -> Void, didEnd: @escaping (PictureInPictureContentImpl) -> Void, expand: @escaping (@escaping () -> Void) -> Void) {
private var messageRemovedDisposable: Disposable?
init(context: AccountContext, overlayController: OverlayMediaController, mediaManager: MediaManager, accountId: AccountRecordId, hiddenMedia: (MessageId, Media)?, videoNode: UniversalVideoNode, canSkip: Bool, willBegin: @escaping (PictureInPictureContentImpl) -> Void, didEnd: @escaping (PictureInPictureContentImpl) -> Void, expand: @escaping (@escaping () -> Void) -> Void) {
self.overlayController = overlayController
self.mediaManager = mediaManager
self.node = videoNode
@ -559,7 +576,7 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte
contentDelegate.pictureInPictureController = pictureInPictureController
pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = false
pictureInPictureController.requiresLinearPlayback = true
pictureInPictureController.requiresLinearPlayback = !canSkip
pictureInPictureController.delegate = self
self.pictureInPictureController = pictureInPictureController
let timer = SwiftSignalKit.Timer(timeout: 0.005, repeat: true, completion: { [weak self] in
@ -586,9 +603,31 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte
}
})
}
if let (messageId, _) = hiddenMedia {
self.messageRemovedDisposable = (context.account.postbox.combinedView(keys: [PostboxViewKey.messages([messageId])])
|> map { views -> Bool in
if let view = views.views[PostboxViewKey.messages([messageId])] as? MessagesView {
if view.messages[messageId] == nil {
return true
}
}
return false
}
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let strongSelf = self else {
return
}
overlayController.removePictureInPictureContent(content: strongSelf)
strongSelf.node.canAttachContent = false
})
}
}
deinit {
self.messageRemovedDisposable?.dispose()
self.pictureInPictureTimer?.invalidate()
self.node.setCanPlaybackWithoutHierarchy(false)
@ -698,6 +737,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var dismissOnOrientationChange = false
private var keepSoundOnDismiss = false
private var hasPictureInPicture = false
private var pictureInPictureButton: UIBarButtonItem?
private var requiresDownload = false
@ -1328,6 +1369,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
if forceEnablePiP || (!isAnimated && !disablePlayerControls && !disablePictureInPicture) {
let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed))
self.pictureInPictureButton = rightBarButtonItem
barButtonItems.append(rightBarButtonItem)
self.hasPictureInPicture = true
} else {
@ -1427,6 +1469,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
pictureInPictureNode.removeFromSupernode()
self.videoNode?.backgroundColor = .black
}
self.pictureInPictureButton?.isEnabled = self.pictureInPictureNode == nil
}
private func shouldAutoplayOnCentrality() -> Bool {
@ -2069,7 +2113,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
case let .message(message):
for media in message.media {
if let media = media as? TelegramMediaFile, media.isVideo {
isNativePictureInPictureSupported = true
if message.id.namespace == Namespaces.Message.Cloud {
isNativePictureInPictureSupported = true
}
}
}
default:
@ -2106,7 +2152,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
break
}
let content = PictureInPictureContentImpl(overlayController: overlayController, mediaManager: self.context.sharedContext.mediaManager, accountId: self.context.account.id, hiddenMedia: hiddenMedia, videoNode: overlayVideoNode, willBegin: { [weak self] content in
let content = PictureInPictureContentImpl(context: self.context, overlayController: overlayController, mediaManager: self.context.sharedContext.mediaManager, accountId: self.context.account.id, hiddenMedia: hiddenMedia, videoNode: overlayVideoNode, canSkip: true, willBegin: { [weak self] content in
guard let strongSelf = self else {
return
}

View File

@ -275,7 +275,7 @@ public final class GradientBackgroundNode: ASDisplayNode {
deinit {
}
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, extendAnimation: Bool = false, backwards: Bool = false) {
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, extendAnimation: Bool, backwards: Bool, completion: @escaping () -> Void) {
let sizeUpdated = self.validLayout != size
self.validLayout = size
@ -372,6 +372,10 @@ public final class GradientBackgroundNode: ASDisplayNode {
animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25
}
animation.completion = { _ in
completion()
}
self.contentView.layer.removeAnimation(forKey: "contents")
self.contentView.layer.add(animation, forKey: "contents")
@ -405,7 +409,11 @@ public final class GradientBackgroundNode: ASDisplayNode {
for cloneNode in self.cloneNodes {
cloneNode.value?.image = dimmedImage
}
completion()
}
} else {
completion()
}
} else if sizeUpdated {
let image = generateGradient(size: imageSize, colors: self.colors, positions: positions)
@ -419,6 +427,10 @@ public final class GradientBackgroundNode: ASDisplayNode {
}
self.validPhase = self.phase
completion()
} else {
completion()
}
transition.updateFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
@ -440,13 +452,14 @@ public final class GradientBackgroundNode: ASDisplayNode {
self.colors = colors
self.invalidated = true
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
self.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {})
}
}
}
public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool = false, backwards: Bool = false) {
public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool, backwards: Bool, completion: @escaping () -> Void) {
guard case let .animated(duration, _) = transition, duration > 0.001 else {
completion()
return
}
@ -463,7 +476,9 @@ public final class GradientBackgroundNode: ASDisplayNode {
GradientBackgroundNode.sharedPhase = self.phase
}
if let size = self.validLayout {
self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation, backwards: backwards)
self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation, backwards: backwards, completion: completion)
} else {
completion()
}
}
}

View File

@ -94,7 +94,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
if let strongSelf = self {
strongSelf.inputFieldNode.append(character)
if let gradientNode = strongSelf.backgroundCustomNode as? GradientBackgroundNode {
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring))
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), extendAnimation: false, backwards: false, completion: {})
}
}
}
@ -102,7 +102,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
if let strongSelf = self {
let _ = strongSelf.inputFieldNode.delete()
if let gradientNode = strongSelf.backgroundCustomNode as? GradientBackgroundNode {
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), backwards: true)
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), extendAnimation: false, backwards: true, completion: {})
}
}
}
@ -167,7 +167,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
self.hapticFeedback.tap()
let result = self.inputFieldNode.delete()
if result, let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), backwards: true)
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), extendAnimation: false, backwards: true, completion: {})
}
}
@ -337,7 +337,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.backgroundDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring), extendAnimation: true)
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring), extendAnimation: true, backwards: false, completion: {})
}
}
self.titleNode.setAttributedText(NSAttributedString(string: self.strings.EnterPasscode_EnterPasscode, font: titleFont, textColor: .white), animation: .none)
@ -355,7 +355,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
gradientNode.animateEvent(transition: .animated(duration: 0.35, curve: .spring))
gradientNode.animateEvent(transition: .animated(duration: 0.35, curve: .spring), extendAnimation: false, backwards: false, completion: {})
self.backgroundDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
if !iconFrame.isEmpty {
@ -385,7 +385,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring))
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring), extendAnimation: false, backwards: false, completion: {})
}
self.inputFieldNode.animateIn()
self.keyboardNode.animateIn()
@ -425,7 +425,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
self.hapticFeedback.error()
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
gradientNode.animateEvent(transition: .animated(duration: 1.5, curve: .spring), extendAnimation: true, backwards: true)
gradientNode.animateEvent(transition: .animated(duration: 1.5, curve: .spring), extendAnimation: true, backwards: true, completion: {})
}
}
@ -440,7 +440,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
if let backgroundCustomNode = self.backgroundCustomNode {
transition.updateFrame(node: backgroundCustomNode, frame: bounds)
if let gradientBackgroundNode = backgroundCustomNode as? GradientBackgroundNode {
gradientBackgroundNode.updateLayout(size: bounds.size, transition: transition)
gradientBackgroundNode.updateLayout(size: bounds.size, transition: transition, extendAnimation: false, backwards: false, completion: {})
}
}
transition.updateFrame(view: self.effectView, frame: bounds)

View File

@ -62,7 +62,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel
self.scrollNode = ASScrollNode()
self.chatBackgroundNode = createWallpaperBackgroundNode(context: context)
self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.chatBackgroundNode.displaysAsynchronously = false
self.messagesContainerNode = ASDisplayNode()

View File

@ -132,7 +132,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
return { item, params, neighbors in
if currentBackgroundNode == nil {
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context)
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
}
currentBackgroundNode?.update(wallpaper: item.wallpaper)
currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)

View File

@ -74,7 +74,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
self.pageControlNode = PageControlNode(dotSpacing: 7.0, dotColor: .white, inactiveDotColor: UIColor.white.withAlphaComponent(0.4))
self.chatListBackgroundNode = ASDisplayNode()
self.chatBackgroundNode = createWallpaperBackgroundNode(context: context)
self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.chatBackgroundNode.displaysAsynchronously = false
self.messagesContainerNode = ASDisplayNode()

View File

@ -174,7 +174,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
if let gradientNode = self.gradientNode {
gradientNode.frame = CGRect(origin: CGPoint(), size: size)
gradientNode.updateLayout(size: size, transition: .immediate)
gradientNode.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {})
}
let progressDiameter: CGFloat = 50.0

View File

@ -285,7 +285,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.backgroundContainerNode = ASDisplayNode()
self.backgroundContainerNode.clipsToBounds = true
self.backgroundWrapperNode = ASDisplayNode()
self.backgroundNode = createWallpaperBackgroundNode(context: context)
self.backgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.messagesContainerNode = ASDisplayNode()
self.messagesContainerNode.clipsToBounds = true

View File

@ -107,7 +107,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.messagesContainerNode.clipsToBounds = true
self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
self.instantChatBackgroundNode = createWallpaperBackgroundNode(context: context)
self.instantChatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.instantChatBackgroundNode.displaysAsynchronously = false
self.ready.set(.single(true))
@ -121,7 +121,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.blurredNode = BlurredImageNode()
self.blurredNode.blurView.contentMode = .scaleAspectFill
self.wallpaperNode = createWallpaperBackgroundNode(context: context)
self.wallpaperNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings, doneButtonType: .set)

View File

@ -138,7 +138,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
return { item, params, neighbors in
if currentBackgroundNode == nil {
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context)
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
}
currentBackgroundNode?.update(wallpaper: item.wallpaper)
currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners)

View File

@ -136,7 +136,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.wrapperNode = ASDisplayNode()
self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = .subsequentUpdates
self.nativeNode = createWallpaperBackgroundNode(context: context)
self.nativeNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.cropNode = WallpaperCropNode()
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter)

View File

@ -964,7 +964,7 @@ final class MessageStoryRenderer {
self.containerNode = ASDisplayNode()
self.instantChatBackgroundNode = createWallpaperBackgroundNode(context: context)
self.instantChatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.instantChatBackgroundNode.displaysAsynchronously = false
self.messagesContainerNode = ASDisplayNode()

View File

@ -7,6 +7,6 @@
NSData * _Nullable prepareSvgImage(NSData * _Nonnull data);
UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size);
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor);
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, bool opaque);
#endif /* Lottie_h */

View File

@ -83,7 +83,7 @@ CGSize aspectFillSize(CGSize size, CGSize bounds) {
@end
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, UIColor *foregroundColor) {
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, UIColor *foregroundColor, bool opaque) {
NSDate *startTime = [NSDate date];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
@ -115,8 +115,8 @@ UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *b
printf("parseTime = %f\n", deltaTime);
startTime = [NSDate date];
UIGraphicsBeginImageContextWithOptions(size, true, 1.0);
UIGraphicsBeginImageContextWithOptions(size, opaque, 1.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height));

View File

@ -2001,7 +2001,7 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
} else {
pollPts = 1
}
return (network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: pollPts, limit: limit))
return (network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: max(pollPts, 1), limit: limit))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.updates.ChannelDifference?, MTRpcError> in
switch error.errorDescription {

View File

@ -508,7 +508,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
default:
break
}
self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: useSharedAnimationPhase, useExperimentalImplementation: self.context.sharedContext.immediateExperimentalUISettings.experimentalBackground)
self.wallpaperReady.set(self.chatBackgroundNode.isReady)
var locationBroadcastPanelSource: LocationBroadcastPanelSource

View File

@ -611,7 +611,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
return
}
var adPeerId: PeerId?
adPeerId = nil
adPeerId = messages.first?.author?.id
if strongSelf.preloadAdPeerId != adPeerId {
strongSelf.preloadAdPeerId = adPeerId

View File

@ -99,7 +99,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.automaticMediaDownloadSettings = context.sharedContext.currentAutomaticMediaDownloadSettings.with { $0 }
self.backgroundNode = createWallpaperBackgroundNode(context: context)
self.backgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true)
self.backgroundNode.isUserInteractionEnabled = false
self.panelBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.chat.inputPanel.panelBackgroundColor)

View File

@ -18,7 +18,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var experimentalCompatibility: Bool
public var enableDebugDataDisplay: Bool
public var acceleratedStickers: Bool
public var mockICE: Bool
public var experimentalBackground: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -36,7 +36,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
experimentalCompatibility: false,
enableDebugDataDisplay: false,
acceleratedStickers: false,
mockICE: false
experimentalBackground: false
)
}
@ -55,7 +55,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
experimentalCompatibility: Bool,
enableDebugDataDisplay: Bool,
acceleratedStickers: Bool,
mockICE: Bool
experimentalBackground: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -71,7 +71,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.experimentalCompatibility = experimentalCompatibility
self.enableDebugDataDisplay = enableDebugDataDisplay
self.acceleratedStickers = acceleratedStickers
self.mockICE = mockICE
self.experimentalBackground = experimentalBackground
}
public init(from decoder: Decoder) throws {
@ -91,7 +91,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.experimentalCompatibility = (try container.decodeIfPresent(Int32.self, forKey: "experimentalCompatibility") ?? 0) != 0
self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
self.mockICE = (try container.decodeIfPresent(Int32.self, forKey: "mockICE") ?? 0) != 0
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
}
public func encode(to encoder: Encoder) throws {
@ -111,7 +111,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode((self.experimentalCompatibility ? 1 : 0) as Int32, forKey: "experimentalCompatibility")
try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay")
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
try container.encode((self.mockICE ? 1 : 0) as Int32, forKey: "mockICE")
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
}
}

View File

@ -19,6 +19,9 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/WallpaperResources:WallpaperResources",
"//submodules/FastBlur:FastBlur",
"//submodules/Svg:Svg",
"//submodules/GZip:GZip",
"//submodules/AppBundle:AppBundle",
],
visibility = [
"//visibility:public",

View File

@ -9,6 +9,9 @@ import AccountContext
import SwiftSignalKit
import WallpaperResources
import FastBlur
import Svg
import GZip
import AppBundle
private let motionAmount: CGFloat = 32.0
@ -781,12 +784,12 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if let gradientBackgroundNode = self.gradientBackgroundNode {
transition.updateFrame(node: gradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
gradientBackgroundNode.updateLayout(size: size, transition: transition)
gradientBackgroundNode.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {})
}
if let outgoingBubbleGradientBackgroundNode = self.outgoingBubbleGradientBackgroundNode {
transition.updateFrame(node: outgoingBubbleGradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: transition)
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {})
}
self.loadPatternForSizeIfNeeded(size: size, transition: transition)
@ -797,21 +800,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
}
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation)
self.outgoingBubbleGradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation)
}
private func updateBakedBackground() {
guard let size = self.validLayout else {
return
}
let context = DrawingContext(size: size, scale: UIScreenScale, opaque: true)
context.withContext { context in
context.clear(CGRect(origin: CGPoint(), size: size))
}
self.bakedBackgroundView.image = context.generateImage()
self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: {})
self.outgoingBubbleGradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: {})
}
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
@ -824,7 +814,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
let outgoingBubbleGradientBackgroundNode = GradientBackgroundNode(adjustSaturation: false)
if let size = self.validLayout {
outgoingBubbleGradientBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: .immediate)
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {})
}
self.outgoingBubbleGradientBackgroundNode = outgoingBubbleGradientBackgroundNode
}
@ -898,32 +888,473 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
}
}
private protocol WallpaperComponentView: AnyObject {
var view: UIView { get }
func update(size: CGSize, transition: ContainedViewLayoutTransition)
}
final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroundNode {
final class SharedStorage {
}
private class WallpaperComponentView: UIView {
let updated: () -> Void
final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode {
private let bubbleType: WallpaperBubbleType
private let contentNode: ASImageNode
init(updated: @escaping () -> Void) {
self.updated = updated
private var cleanWallpaperNode: ASDisplayNode?
private var gradientWallpaperNode: GradientBackgroundNode.CloneNode?
private weak var backgroundNode: WallpaperBackgroundNodeMergedImpl?
private var index: SparseBag<BubbleBackgroundNodeImpl>.Index?
super.init(frame: CGRect())
private var currentLayout: (rect: CGRect, containerSize: CGSize)?
override var frame: CGRect {
didSet {
if oldValue.size != self.bounds.size {
self.contentNode.frame = self.bounds
if let cleanWallpaperNode = self.cleanWallpaperNode {
cleanWallpaperNode.frame = self.bounds
}
if let gradientWallpaperNode = self.gradientWallpaperNode {
gradientWallpaperNode.frame = self.bounds
}
}
}
}
init(backgroundNode: WallpaperBackgroundNodeMergedImpl, bubbleType: WallpaperBubbleType) {
self.backgroundNode = backgroundNode
self.bubbleType = bubbleType
self.contentNode = ASImageNode()
self.contentNode.displaysAsynchronously = false
self.contentNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.contentNode)
self.index = backgroundNode.bubbleBackgroundNodeReferences.add(BubbleBackgroundNodeReference(node: self))
}
deinit {
if let index = self.index, let backgroundNode = self.backgroundNode {
backgroundNode.bubbleBackgroundNodeReferences.remove(index)
}
}
func updateContents() {
guard let backgroundNode = self.backgroundNode else {
return
}
if let bubbleTheme = backgroundNode.bubbleTheme, let bubbleCorners = backgroundNode.bubbleCorners {
let wallpaper = backgroundNode.wallpaper ?? bubbleTheme.chat.defaultWallpaper
let graphics = PresentationResourcesChat.principalGraphics(theme: bubbleTheme, wallpaper: wallpaper, bubbleCorners: bubbleCorners)
var needsCleanBackground = false
switch self.bubbleType {
case .incoming:
self.contentNode.image = graphics.incomingBubbleGradientImage
if graphics.incomingBubbleGradientImage == nil {
self.contentNode.backgroundColor = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill[0]
} else {
self.contentNode.backgroundColor = nil
}
needsCleanBackground = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 })
case .outgoing:
if backgroundNode.outgoingBubbleGradientBackgroundNode != nil {
self.contentNode.image = nil
self.contentNode.backgroundColor = nil
} else {
self.contentNode.image = graphics.outgoingBubbleGradientImage
if graphics.outgoingBubbleGradientImage == nil {
self.contentNode.backgroundColor = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill[0]
} else {
self.contentNode.backgroundColor = nil
}
needsCleanBackground = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 })
}
case .free:
self.contentNode.image = nil
self.contentNode.backgroundColor = nil
needsCleanBackground = true
}
var isInvertedGradient = false
var hasComplexGradient = false
switch wallpaper {
case let .file(file):
hasComplexGradient = file.settings.colors.count >= 3
if let intensity = file.settings.intensity, intensity < 0 {
isInvertedGradient = true
}
case let .gradient(gradient):
hasComplexGradient = gradient.colors.count >= 3
default:
break
}
var needsGradientBackground = false
var needsWallpaperBackground = false
if isInvertedGradient {
switch self.bubbleType {
case .free:
needsCleanBackground = false
case .incoming, .outgoing:
break
}
}
if needsCleanBackground {
if hasComplexGradient {
needsGradientBackground = backgroundNode.gradient != nil
} else {
needsWallpaperBackground = true
}
}
var gradientBackgroundSource: GradientBackgroundNode? = backgroundNode.gradient?.gradientBackground
if case .outgoing = self.bubbleType {
if let outgoingBubbleGradientBackgroundNode = backgroundNode.outgoingBubbleGradientBackgroundNode {
gradientBackgroundSource = outgoingBubbleGradientBackgroundNode
needsWallpaperBackground = false
needsGradientBackground = true
}
}
if needsWallpaperBackground {
if self.cleanWallpaperNode == nil {
let cleanWallpaperNode = ASImageNode()
cleanWallpaperNode.displaysAsynchronously = false
self.cleanWallpaperNode = cleanWallpaperNode
cleanWallpaperNode.frame = self.bounds
self.insertSubnode(cleanWallpaperNode, at: 0)
}
if let blurredBackgroundContents = backgroundNode.blurredBackgroundContents {
self.cleanWallpaperNode?.contents = blurredBackgroundContents.cgImage
self.cleanWallpaperNode?.backgroundColor = backgroundNode.backgroundColor
} else {
self.cleanWallpaperNode?.contents = nil
self.cleanWallpaperNode?.backgroundColor = backgroundNode.backgroundColor
}
} else {
if let cleanWallpaperNode = self.cleanWallpaperNode {
self.cleanWallpaperNode = nil
cleanWallpaperNode.removeFromSupernode()
}
}
if needsGradientBackground, let gradientBackgroundNode = gradientBackgroundSource {
if self.gradientWallpaperNode == nil {
let gradientWallpaperNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode)
gradientWallpaperNode.frame = self.bounds
self.gradientWallpaperNode = gradientWallpaperNode
self.insertSubnode(gradientWallpaperNode, at: 0)
}
} else {
if let gradientWallpaperNode = self.gradientWallpaperNode {
self.gradientWallpaperNode = nil
gradientWallpaperNode.removeFromSupernode()
}
}
} else {
self.contentNode.image = nil
if let cleanWallpaperNode = self.cleanWallpaperNode {
self.cleanWallpaperNode = nil
cleanWallpaperNode.removeFromSupernode()
}
}
if let (rect, containerSize) = self.currentLayout {
self.update(rect: rect, within: containerSize)
}
}
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
self.currentLayout = (rect, containerSize)
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
transition.updateFrame(layer: self.contentNode.layer, frame: self.bounds)
transition.animateView {
self.contentNode.layer.contentsRect = shiftedContentsRect
}
if let cleanWallpaperNode = self.cleanWallpaperNode {
transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds)
transition.animateView {
cleanWallpaperNode.layer.contentsRect = shiftedContentsRect
}
}
if let gradientWallpaperNode = self.gradientWallpaperNode {
transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds)
transition.animateView {
gradientWallpaperNode.layer.contentsRect = shiftedContentsRect
}
}
}
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) {
self.currentLayout = (rect, containerSize)
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
transition.updateFrame(layer: self.contentNode.layer, frame: self.bounds)
self.contentNode.layer.contentsRect = shiftedContentsRect
if let cleanWallpaperNode = self.cleanWallpaperNode {
transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds)
cleanWallpaperNode.layer.contentsRect = shiftedContentsRect
}
if let gradientWallpaperNode = self.gradientWallpaperNode {
transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds)
gradientWallpaperNode.layer.contentsRect = shiftedContentsRect
}
}
func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
guard let (_, containerSize) = self.currentLayout else {
return
}
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: animationCurve)
let scaledOffset = CGPoint(x: value.x / containerSize.width, y: value.y / containerSize.height)
transition.animateContentsRectPositionAdditive(layer: self.contentNode.layer, offset: scaledOffset)
if let cleanWallpaperNode = self.cleanWallpaperNode {
transition.animateContentsRectPositionAdditive(layer: cleanWallpaperNode.layer, offset: scaledOffset)
}
if let gradientWallpaperNode = self.gradientWallpaperNode {
transition.animateContentsRectPositionAdditive(layer: gradientWallpaperNode.layer, offset: scaledOffset)
}
}
func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
guard let (_, containerSize) = self.currentLayout else {
return
}
let scaledOffset = CGPoint(x: 0.0, y: -value / containerSize.height)
self.contentNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true)
if let cleanWallpaperNode = self.cleanWallpaperNode {
cleanWallpaperNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true)
}
if let gradientWallpaperNode = self.gradientWallpaperNode {
gradientWallpaperNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true)
}
}
}
private final class BubbleBackgroundNodeReference {
weak var node: BubbleBackgroundNodeImpl?
init(node: BubbleBackgroundNodeImpl) {
self.node = node
}
}
private final class WallpaperGradiendComponentView: WallpaperComponentView {
struct Spec: Equatable {
var colors: [UInt32]
}
let spec: Spec
let gradientBackground: GradientBackgroundNode
var view: UIView {
return self.gradientBackground.view
}
init(spec: Spec, updated: @escaping () -> Void) {
self.spec = spec
self.gradientBackground = GradientBackgroundNode(colors: spec.colors.map(UIColor.init(rgb:)), useSharedAnimationPhase: true, adjustSaturation: false)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private final class WallpaperGradiendComponentView: WallpaperComponentView {
struct Spec {
var colors: [UInt32]
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
self.gradientBackground.frame = CGRect(origin: CGPoint(), size: size)
self.gradientBackground.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {})
}
}
private final class WallpaperPatternComponentView: WallpaperComponentView {
struct Spec {
private final class WallpaperColorComponentView: WallpaperComponentView {
struct Spec: Equatable {
var color: UInt32
}
let spec: Spec
let backgroundView: UIView
var view: UIView {
return self.backgroundView
}
init(spec: Spec, updated: @escaping () -> Void) {
self.spec = spec
self.backgroundView = UIView()
self.backgroundView.backgroundColor = UIColor(rgb: spec.color)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
}
}
private final class WallpaperImageComponentView: WallpaperComponentView {
enum Spec: Equatable {
case image(
representation: TelegramMediaImageRepresentation,
isPattern: Bool,
intensity: CGFloat
)
case builtin
}
let spec: Spec
let updated: () -> Void
let imageView: UIImageView
var fetchDisposable: Disposable?
var dataDisposable: Disposable?
var imageData: Data?
private var validSize: CGSize?
var view: UIView {
return self.imageView
}
init(context: AccountContext, spec: Spec, updated: @escaping () -> Void) {
self.spec = spec
self.updated = updated
self.imageView = UIImageView()
self.imageView.contentMode = .scaleAspectFill
switch spec {
case let .image(representation, _, _):
self.fetchDisposable = (fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: representation.resource))
|> deliverOnMainQueue).start()
self.dataDisposable = (context.account.postbox.mediaBox.resourceData(representation.resource)
|> deliverOnMainQueue).start(next: { [weak self] dataValue in
guard let strongSelf = self else {
return
}
if dataValue.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: dataValue.path)) {
strongSelf.imageData = data
if let size = strongSelf.validSize {
strongSelf.updateImage(size: size, data: data)
}
}
})
case .builtin:
if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg"), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) {
self.imageData = data
if let size = self.validSize {
self.updateImage(size: size, data: data)
}
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.fetchDisposable?.dispose()
self.dataDisposable?.dispose()
}
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
let sizeUpdated = self.validSize != size
self.validSize = size
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
if sizeUpdated || self.imageView.image == nil {
if let imageData = self.imageData {
self.updateImage(size: size, data: imageData)
}
}
}
private func updateImage(size: CGSize, data: Data) {
let scale: CGFloat
if UIScreenScale >= 2.9 {
scale = 2.5
} else {
scale = UIScreenScale
}
switch self.spec {
case let .image(_, isPattern, intensity):
if isPattern {
let patternBackgroundColor: UIColor
let patternForegroundColor: UIColor
if intensity < 0.0 {
patternBackgroundColor = .clear
patternForegroundColor = .black
} else {
patternBackgroundColor = .clear
patternForegroundColor = .black
}
if let unpackedData = TGGUnzipData(data, 2 * 1024 * 1024), let patternImage = drawSvgImage(unpackedData, CGSize(width: floor(size.width * scale), height: floor(size.height * scale)), patternBackgroundColor, patternForegroundColor, false) {
if intensity < 0.0 {
self.imageView.image = generateImage(patternImage.size, scale: patternImage.scale, rotatedContext: { size, context in
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
if let cgImage = patternImage.cgImage {
context.setBlendMode(.destinationOut)
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size))
}
})
self.imageView.alpha = 1.0
self.imageView.layer.compositingFilter = nil
self.imageView.backgroundColor = UIColor(white: 0.0, alpha: 1.0 - abs(intensity))
} else {
self.imageView.image = patternImage
self.imageView.alpha = abs(intensity)
self.imageView.layer.compositingFilter = "softLightBlendMode"
self.imageView.backgroundColor = nil
}
}
self.updated()
} else if let image = UIImage(data: data) {
self.imageView.image = image
self.imageView.layer.compositingFilter = nil
self.imageView.alpha = 1.0
self.updated()
}
case .builtin:
if let image = UIImage(data: data) {
self.imageView.image = image
self.imageView.layer.compositingFilter = nil
self.imageView.alpha = 1.0
self.updated()
}
}
}
}
@ -931,8 +1362,17 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
private let storage: SharedStorage
private let staticView: UIImageView
private let dynamicView: UIView
private var color: WallpaperColorComponentView?
private var gradient: WallpaperGradiendComponentView?
private var pattern: WallpaperPatternComponentView?
private var image: WallpaperImageComponentView?
private var blurredBackgroundContents: UIImage?
private var isSettingUpWallpaper: Bool = false
private var wallpaper: TelegramWallpaper?
private var validLayout: CGSize?
private let _isReady = ValuePromise<Bool>(false, ignoreRepeated: true)
var isReady: Signal<Bool, NoError> {
@ -944,11 +1384,19 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
}
}
private var isAnimating: Bool = false
private var bubbleTheme: PresentationTheme?
private var bubbleCorners: PresentationChatBubbleCorners?
private var bubbleBackgroundNodeReferences = SparseBag<BubbleBackgroundNodeReference>()
private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode?
init(context: AccountContext, storage: SharedStorage?) {
self.context = context
self.storage = storage ?? SharedStorage()
self.staticView = UIImageView()
self.dynamicView = UIView()
super.init()
@ -956,70 +1404,294 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
}
func update(wallpaper: TelegramWallpaper) {
self.wallpaper = wallpaper
var colorSpec: WallpaperColorComponentView.Spec?
var gradientSpec: WallpaperGradiendComponentView.Spec?
var imageSpec: WallpaperImageComponentView.Spec?
switch wallpaper {
case let .builtin(wallpaperSettings):
let _ = wallpaperSettings
case .builtin:
imageSpec = WallpaperImageComponentView.Spec.builtin
case let .color(color):
let _ = color
colorSpec = WallpaperColorComponentView.Spec(color: color)
case let .gradient(gradient):
if gradient.colors.count >= 3 {
gradientSpec = WallpaperGradiendComponentView.Spec(colors: gradient.colors)
}
case let .image(representations, settings):
let _ = representations
if let representation = representations.last {
imageSpec = WallpaperImageComponentView.Spec.image(representation: representation, isPattern: false, intensity: 1.0)
}
let _ = settings
case let .file(file):
if file.settings.colors.count >= 3 {
gradientSpec = WallpaperGradiendComponentView.Spec(colors: file.settings.colors)
}
if let dimensions = file.file.dimensions {
let representation = TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: file.file.immediateThumbnailData)
imageSpec = WallpaperImageComponentView.Spec.image(representation: representation, isPattern: file.isPattern, intensity: CGFloat(file.settings.intensity ?? 100) / 100.0)
}
}
if let gradientSpec = gradientSpec {
let gradient: WallpaperGradiendComponentView
if let current = self.gradient {
gradient = current
} else {
gradient = WallpaperGradiendComponentView(updated: { [weak self] in
self?.componentsUpdated()
})
if self.color?.spec != colorSpec {
if let color = self.color {
self.color = nil
color.view.removeFromSuperview()
}
if let colorSpec = colorSpec {
let color = WallpaperColorComponentView(spec: colorSpec, updated: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.componentsUpdated()
})
self.color = color
if let size = self.validLayout {
color.update(size: size, transition: .immediate)
}
self.dynamicView.insertSubview(color.view, at: 0)
self.componentsUpdated()
}
}
if self.gradient?.spec != gradientSpec {
if let gradient = self.gradient {
self.gradient = nil
gradient.view.removeFromSuperview()
}
if let gradientSpec = gradientSpec {
let gradient = WallpaperGradiendComponentView(spec: gradientSpec, updated: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.componentsUpdated()
})
self.gradient = gradient
if let size = self.validLayout {
gradient.update(size: size, transition: .immediate)
}
self.dynamicView.insertSubview(gradient.view, at: 0)
}
}
if self.image?.spec != imageSpec {
if let image = self.image {
self.image = nil
image.view.removeFromSuperview()
}
if let imageSpec = imageSpec {
let image = WallpaperImageComponentView(context: self.context, spec: imageSpec, updated: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.componentsUpdated()
})
self.image = image
if let size = self.validLayout {
image.update(size: size, transition: .immediate)
}
if let gradient = self.gradient {
self.dynamicView.insertSubview(image.view, aboveSubview: gradient.view)
} else {
self.dynamicView.insertSubview(image.view, at: 0)
}
}
let _ = gradient
let _ = gradientSpec
} else if let gradient = self.gradient {
self.gradient = nil
gradient.removeFromSuperview()
}
}
private func componentsUpdated() {
if self.isAnimating {
if self.dynamicView.superview == nil {
self.view.addSubview(self.dynamicView)
self.staticView.isHidden = true
}
self._isReady.set(true)
} else {
self.staticView.isHidden = false
self.dynamicView.removeFromSuperview()
if let size = self.validLayout {
if let color = self.color {
self.staticView.image = nil
self.staticView.backgroundColor = color.backgroundView.backgroundColor
} else {
let gradientImage = self.gradient?.gradientBackground.contentView.image
let gradientFrame = self.gradient?.gradientBackground.frame
let imageImage = self.image?.imageView.image
let imageBackgroundColor = self.image?.imageView.backgroundColor
let imageFrame = self.image?.imageView.frame
let imageAlpha = self.image?.imageView.alpha
let imageFilter = self.image?.imageView.layer.compositingFilter as? String
self.staticView.image = generateImage(size, opaque: true, scale: nil, rotatedContext: { size, context in
UIGraphicsPushContext(context)
if let gradientImage = gradientImage, let gradientFrame = gradientFrame {
gradientImage.draw(in: gradientFrame)
}
if let imageImage = imageImage, let imageFrame = imageFrame, let imageAlpha = imageAlpha {
if imageFilter == "softLightBlendMode" {
context.setBlendMode(.softLight)
}
if let imageBackgroundColor = imageBackgroundColor {
context.setFillColor(imageBackgroundColor.cgColor)
context.fill(imageFrame)
}
context.setAlpha(imageAlpha)
context.translateBy(x: imageFrame.midX, y: imageFrame.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -imageFrame.midX, y: -imageFrame.midY)
if let cgImage = imageImage.cgImage {
let drawingSize = imageImage.size.aspectFilled(imageFrame.size)
context.draw(cgImage, in: CGRect(origin: CGPoint(x: imageFrame.minX + (imageFrame.width - drawingSize.width) / 2.0, y: imageFrame.minX + (imageFrame.height - drawingSize.height) / 2.0), size: drawingSize))
}
context.translateBy(x: imageFrame.midX, y: imageFrame.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -imageFrame.midX, y: -imageFrame.midY)
context.setBlendMode(.normal)
context.setAlpha(1.0)
}
UIGraphicsPopContext()
})
}
self._isReady.set(true)
}
}
}
func _internalUpdateIsSettingUpWallpaper() {
self.isSettingUpWallpaper = true
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
self.staticView.frame = CGRect(origin: CGPoint(), size: size)
if let gradient = self.gradient {
gradient.update(size: size, transition: transition)
}
if let image = self.image {
image.update(size: size, transition: transition)
}
}
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
if let gradient = self.gradient {
self.isAnimating = true
self.componentsUpdated()
gradient.gradientBackground.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.isAnimating = false
strongSelf.componentsUpdated()
})
} else {
self.isAnimating = false
}
}
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {
self.bubbleTheme = bubbleTheme
self.bubbleCorners = bubbleCorners
if bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill.count >= 3 && bubbleTheme.chat.animateMessageColors {
if self.outgoingBubbleGradientBackgroundNode == nil {
let outgoingBubbleGradientBackgroundNode = GradientBackgroundNode(adjustSaturation: false)
if let size = self.validLayout {
outgoingBubbleGradientBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {})
}
self.outgoingBubbleGradientBackgroundNode = outgoingBubbleGradientBackgroundNode
}
self.outgoingBubbleGradientBackgroundNode?.updateColors(colors: bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill)
} else if let _ = self.outgoingBubbleGradientBackgroundNode {
self.outgoingBubbleGradientBackgroundNode = nil
}
self.updateBubbles()
}
}
private func updateBubbles() {
for reference in self.bubbleBackgroundNodeReferences {
reference.node?.updateContents()
}
}
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool {
guard let bubbleTheme = self.bubbleTheme, let bubbleCorners = self.bubbleCorners else {
return false
}
if self.wallpaper == nil && !self.isSettingUpWallpaper {
return false
}
var hasPlainWallpaper = false
let graphicsWallpaper: TelegramWallpaper
if let wallpaper = self.wallpaper {
switch wallpaper {
case .color:
hasPlainWallpaper = true
default:
break
}
graphicsWallpaper = wallpaper
} else {
graphicsWallpaper = bubbleTheme.chat.defaultWallpaper
}
let graphics = PresentationResourcesChat.principalGraphics(theme: bubbleTheme, wallpaper: graphicsWallpaper, bubbleCorners: bubbleCorners)
switch type {
case .incoming:
if graphics.incomingBubbleGradientImage != nil {
return true
}
if bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) {
return !hasPlainWallpaper
}
case .outgoing:
if graphics.outgoingBubbleGradientImage != nil {
return true
}
if bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) {
return !hasPlainWallpaper
}
case .free:
return true
}
return false
}
func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? {
return nil
if !self.hasBubbleBackground(for: type) {
return nil
}
let node = WallpaperBackgroundNodeMergedImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type)
node.updateContents()
return node
}
}
public func createWallpaperBackgroundNode(context: AccountContext, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode {
private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage()
public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false, useExperimentalImplementation: Bool = false) -> WallpaperBackgroundNode {
if forChatDisplay && useExperimentalImplementation {
return WallpaperBackgroundNodeMergedImpl(context: context, storage: useSharedAnimationPhase ? sharedStorage : nil)
}
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
}

View File

@ -1272,7 +1272,7 @@ public func themeImage(account: Account, accountManager: AccountManager<Telegram
case let .pattern(data, colors, intensity):
let wallpaperImage = generateImage(arguments.drawingSize, rotatedContext: { size, context in
drawWallpaperGradientImage(colors.map(UIColor.init(rgb:)), context: context, size: size)
if let unpackedData = TGGUnzipData(data, 2 * 1024 * 1024), let image = drawSvgImage(unpackedData, arguments.drawingSize, .clear, .black) {
if let unpackedData = TGGUnzipData(data, 2 * 1024 * 1024), let image = drawSvgImage(unpackedData, arguments.drawingSize, .clear, .black, true) {
context.setBlendMode(.softLight)
context.setAlpha(abs(CGFloat(intensity)) / 100.0)
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: arguments.drawingSize))