diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index dc3ab8adb9..fd13c0a581 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -104,7 +104,8 @@ "PUSH_ALBUM" = "%1$@|sent you an album"; "PUSH_MESSAGE_FILES_TEXT_1" = "sent you a file"; "PUSH_MESSAGE_FILES_TEXT_any" = "sent you %d files"; -"PUSH_MESSAGE_THEME" = "%1$@|changed theme to %2$@"; +"PUSH_MESSAGE_THEME" = "%1$@|changed chat theme to %2$@"; +"PUSH_MESSAGE_NOTHEME" = "%1$@|disabled chat theme"; "PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@"; "PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message"; @@ -178,6 +179,7 @@ "PUSH_CHAT_MESSAGE_DOCS_TEXT_1" = "{author} sent a file"; "PUSH_CHAT_MESSAGE_DOCS_TEXT_any" = "{author} sent %d files"; "PUSH_CHAT_MESSAGE_THEME" = "%1$@|set theme to %3$@ in the group %2$@"; +"PUSH_CHAT_MESSAGE_NOTHEME" = "%1$@|disabled theme in the group %2$@"; "PUSH_PINNED_TEXT" = "%1$@|pinned \"%2$@\" "; "PUSH_PINNED_NOTEXT" = "%1$@|pinned a message"; diff --git a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift index 80d2d84b5b..8fa62b2673 100644 --- a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift +++ b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift @@ -288,8 +288,11 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets())) iconApply() + let placeholderBackgroundColor: UIColor + switch item.style { case .plain: + placeholderBackgroundColor = item.presentationData.theme.list.plainBackgroundColor if strongSelf.backgroundNode.supernode != nil { strongSelf.backgroundNode.removeFromSupernode() } @@ -297,7 +300,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { strongSelf.topStripeNode.removeFromSupernode() } if strongSelf.bottomStripeNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) + strongSelf.addSubnode(strongSelf.bottomStripeNode) } if strongSelf.maskNode.supernode != nil { strongSelf.maskNode.removeFromSupernode() @@ -312,6 +315,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: stripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - stripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.isHidden = last case .blocks: + placeholderBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor if strongSelf.backgroundNode.supernode == nil { strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) } @@ -395,8 +399,8 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { let subtitleFrame = strongSelf.addressNode.frame shapes.append(.roundedRectLine(startPoint: CGPoint(x: subtitleFrame.minX, y: subtitleFrame.minY + floor((subtitleFrame.height - lineDiameter) / 2.0)), width: subtitleLineWidth, diameter: lineDiameter)) - - shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize) + + shimmerNode.update(backgroundColor: placeholderBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize) } else if let shimmerNode = strongSelf.placeholderNode { strongSelf.placeholderNode = nil shimmerNode.removeFromSupernode() diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 760306bf00..86bffdc117 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -35,7 +35,7 @@ public let defaultServiceBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.2) public let defaultPresentationTheme = makeDefaultDayPresentationTheme(serviceBackgroundColor: defaultServiceBackgroundColor, day: false, preview: false) public let defaultDayAccentColor = UIColor(rgb: 0x007ee5) -public func customizeDefaultDayTheme(theme: PresentationTheme, specialMode: Bool = false, editing: Bool, title: String?, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, serviceBackgroundColor: UIColor?) -> PresentationTheme { +public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, serviceBackgroundColor: UIColor?) -> PresentationTheme { if (theme.referenceTheme != .day && theme.referenceTheme != .dayClassic) { return theme } @@ -51,85 +51,35 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, specialMode: Bool var outgoingAccent: UIColor? var suggestedWallpaper: TelegramWallpaper? - var bubbleColors = bubbleColors - if specialMode, outgoingAccentColor == nil, bubbleColors.count < 3, let color = bubbleColors.first.flatMap({ UIColor(rgb: $0) }) { - let colorHSB = color.hsb - if colorHSB.b > 0.9 { - let bubbleColor = color.withMultiplied(hue: 0.9, saturation: 1.3, brightness: 1.0) - bubbleColors = [bubbleColor.rgb] - - let colorPairs: [(UInt32, UInt32)] = [ - (0xe5f9d7, 0x6cd516), - (0xe7f5ff, 0x43b6f9), - (0xe3f7f5, 0x4ccbb8), - (0xfff6cf, 0xe8b816), - (0xfffac9, 0xe2c714), - (0xc5a61e, 0xd6b534) - ] - - func generateAccentColor(color: UIColor) -> UIColor { - var nearest: (color: (UInt32, UInt32), distance: Int32)? - for (sample, accentSample) in colorPairs { - let distance = color.distance(to: UIColor(rgb: sample)) - if let currentNearest = nearest { - if distance < currentNearest.distance { - nearest = ((sample, accentSample), distance) - } + var bubbleColors = bubbleColors + if bubbleColors.isEmpty, editing { + if day { + let accentColor = accentColor ?? defaultDayAccentColor + bubbleColors = [accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98).rgb, accentColor.rgb] + outgoingAccent = outgoingAccentColor + } else { + if let accentColor = accentColor, !accentColor.alpha.isZero { + let hsb = accentColor.hsb + bubbleColors = [UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0).rgb] + if let outgoingAccentColor = outgoingAccentColor { + outgoingAccent = outgoingAccentColor + } else { + if accentColor.lightness > 0.705 { + outgoingAccent = UIColor(hue: hsb.0, saturation: min(1.0, hsb.1 * 1.1), brightness: min(hsb.2, 0.6), alpha: 1.0) } else { - nearest = ((sample, accentSample), distance) + outgoingAccent = accentColor } } - - if let colors = nearest?.color { - let colorHsb = color.hsb - let similarColorHsb = UIColor(rgb: colors.0).hsb - let accentColorHsb = UIColor(rgb: colors.1).hsb - - let correction = (similarColorHsb.0 > 0.0 ? colorHsb.0 / similarColorHsb.0 : 1.0, similarColorHsb.1 > 0.0 ? colorHsb.1 / similarColorHsb.1 : 1.0, similarColorHsb.2 > 0.0 ? colorHsb.2 / similarColorHsb.2 : 1.0) - let correctedComplementingColor = UIColor(hue: min(1.0, accentColorHsb.0 * correction.0), saturation: min(1.0, accentColorHsb.1 * correction.1), brightness: min(1.0, accentColorHsb.2 * correction.2), alpha: 1.0) - return correctedComplementingColor - } else { - return color - } + + suggestedWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: defaultBuiltinWallpaperGradientColors.map(\.rgb), settings: WallpaperSettings())) + } else { + bubbleColors = [UIColor(rgb: 0xe1ffc7).rgb] + suggestedWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: defaultBuiltinWallpaperGradientColors.map(\.rgb), settings: WallpaperSettings())) + outgoingAccent = outgoingAccentColor } - - outgoingAccent = generateAccentColor(color: color) - } else { - let bubbleColor = color.withMultiplied(hue: 1.014, saturation: 0.12, brightness: 1.29) - bubbleColors = [bubbleColor.rgb] - - outgoingAccent = color } } else { - if bubbleColors.isEmpty, editing { - if day { - let accentColor = accentColor ?? defaultDayAccentColor - bubbleColors = [accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98).rgb, accentColor.rgb] - outgoingAccent = outgoingAccentColor - } else { - if let accentColor = accentColor, !accentColor.alpha.isZero { - let hsb = accentColor.hsb - bubbleColors = [UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0).rgb] - if let outgoingAccentColor = outgoingAccentColor { - outgoingAccent = outgoingAccentColor - } else { - if accentColor.lightness > 0.705 { - outgoingAccent = UIColor(hue: hsb.0, saturation: min(1.0, hsb.1 * 1.1), brightness: min(hsb.2, 0.6), alpha: 1.0) - } else { - outgoingAccent = accentColor - } - } - - suggestedWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: defaultBuiltinWallpaperGradientColors.map(\.rgb), settings: WallpaperSettings())) - } else { - bubbleColors = [UIColor(rgb: 0xe1ffc7).rgb] - suggestedWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: defaultBuiltinWallpaperGradientColors.map(\.rgb), settings: WallpaperSettings())) - outgoingAccent = outgoingAccentColor - } - } - } else { - outgoingAccent = outgoingAccentColor - } + outgoingAccent = outgoingAccentColor } var accentColor = accentColor diff --git a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift index 64b17cf8a5..6c2104cde2 100644 --- a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift @@ -19,13 +19,13 @@ public func makeDefaultPresentationTheme(reference: PresentationBuiltinThemeRefe return theme } -public func customizePresentationTheme(_ theme: PresentationTheme, specialMode: Bool = false, editing: Bool, title: String? = nil, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { +public func customizePresentationTheme(_ theme: PresentationTheme, editing: Bool, title: String? = nil, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { if accentColor == nil && bubbleColors.isEmpty && backgroundColors.isEmpty && wallpaper == nil { return theme } switch theme.referenceTheme { case .day, .dayClassic: - return customizeDefaultDayTheme(theme: theme, specialMode: specialMode, editing: editing, title: title, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, serviceBackgroundColor: nil) + return customizeDefaultDayTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, serviceBackgroundColor: nil) case .night: return customizeDefaultDarkPresentationTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, baseColor: baseColor) case .nightAccent: @@ -33,17 +33,25 @@ public func customizePresentationTheme(_ theme: PresentationTheme, specialMode: } } -public func makePresentationTheme(settings: TelegramThemeSettings, specialMode: Bool = false, title: String? = nil, serviceBackgroundColor: UIColor? = nil) -> PresentationTheme? { +public func makePresentationTheme(settings: TelegramThemeSettings, title: String? = nil, serviceBackgroundColor: UIColor? = nil) -> PresentationTheme? { let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: serviceBackgroundColor, preview: false) - return customizePresentationTheme(defaultTheme, specialMode: specialMode, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper) + return customizePresentationTheme(defaultTheme, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper) } -public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, specialMode: Bool = false, preview: Bool = false) -> PresentationTheme? { +public func makePresentationTheme(cloudTheme: TelegramTheme) -> PresentationTheme? { + guard let settings = cloudTheme.settings else { + return nil + } + let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: nil, preview: false) + return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper) +} + +public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? { let theme: PresentationTheme switch themeReference { case let .builtin(reference): let defaultTheme = makeDefaultPresentationTheme(reference: reference, extendingThemeReference: extendingThemeReference, serviceBackgroundColor: serviceBackgroundColor, preview: preview) - theme = customizePresentationTheme(defaultTheme, specialMode: specialMode, editing: true, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper, baseColor: baseColor) + theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper, baseColor: baseColor) case let .local(info): if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) { theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 7f23605b5c..a40c1502f5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3926,6 +3926,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let accountManager = context.sharedContext.accountManager + let currentThemeEmoticon = Atomic<(String?, Bool)?>(value: nil) self.presentationDataDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, themeSettings, context.engine.themes.getChatThemes(accountManager: accountManager, onlyCached: false), themeEmoticon, self.themeEmoticonAndDarkAppearancePreviewPromise.get()).start(next: { [weak self] presentationData, themeSettings, chatThemes, themeEmoticon, themeEmoticonAndDarkAppearance in if let strongSelf = self { let (themeEmoticonPreview, darkAppearancePreview) = themeEmoticonAndDarkAppearance @@ -3944,17 +3945,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } var presentationData = presentationData + var useDarkAppearance = presentationData.theme.overallDarkAppearance + if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) { - var useDarkAppearance = presentationData.theme.overallDarkAppearance if let darkAppearancePreview = darkAppearancePreview { useDarkAppearance = darkAppearancePreview } let customTheme = useDarkAppearance ? theme.darkTheme : theme.theme if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) { - presentationData = presentationData.withUpdated(theme: theme) - presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper) + presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper) } } else if let darkAppearancePreview = darkAppearancePreview { + useDarkAppearance = darkAppearancePreview let lightTheme: PresentationTheme let lightWallpaper: TelegramWallpaper @@ -4009,24 +4011,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if darkAppearancePreview { - presentationData = presentationData.withUpdated(theme: darkTheme) - presentationData = presentationData.withUpdated(chatWallpaper: darkWallpaper) + presentationData = presentationData.withUpdated(theme: darkTheme).withUpdated(chatWallpaper: darkWallpaper) } else { - presentationData = presentationData.withUpdated(theme: lightTheme) - presentationData = presentationData.withUpdated(chatWallpaper: lightWallpaper) + presentationData = presentationData.withUpdated(theme: lightTheme).withUpdated(chatWallpaper: lightWallpaper) } } let isFirstTime = !strongSelf.didSetPresentationData strongSelf.presentationData = presentationData strongSelf.didSetPresentationData = true - if isFirstTime || previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { + let previousThemeEmoticon = currentThemeEmoticon.swap((themeEmoticon, useDarkAppearance)) + + if isFirstTime || previousTheme != presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { strongSelf.themeAndStringsUpdated() controllerInteraction.updatedPresentationData = strongSelf.updatedPresentationData strongSelf.presentationDataPromise.set(.single(strongSelf.presentationData)) - if !isFirstTime && previousTheme !== presentationData.theme { + if !isFirstTime && (previousThemeEmoticon?.0 != themeEmoticon || previousThemeEmoticon?.1 != useDarkAppearance) { strongSelf.presentCrossfadeSnapshot(delay: 0.2) } } @@ -5932,7 +5934,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> deliverOnMainQueue).start(next: { [weak self] searchResult in if let strongSelf = self, let (searchResult, searchState, searchLocation) = searchResult { - let controller = ChatSearchResultsController(context: strongSelf.context, location: searchLocation, searchQuery: searchData.query, searchResult: searchResult, searchState: searchState, navigateToMessageIndex: { index in + let controller = ChatSearchResultsController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, location: searchLocation, searchQuery: searchData.query, searchResult: searchResult, searchState: searchState, navigateToMessageIndex: { index in guard let strongSelf = self else { return } @@ -13365,9 +13367,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return updated }) - let _ = (self.cachedDataPromise.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] cachedData in + let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false) + |> map { animatedEmoji -> [String: [StickerPackItem]] in + var animatedEmojiStickers: [String: [StickerPackItem]] = [:] + switch animatedEmoji { + case let .result(_, items, _): + for case let item as StickerPackItem in items { + if let emoji = item.getStringRepresentationsOfIndexKeys().first { + animatedEmojiStickers[emoji.basicEmoji.0] = [item] + let strippedEmoji = emoji.basicEmoji.0.strippedEmoji + if animatedEmojiStickers[strippedEmoji] == nil { + animatedEmojiStickers[strippedEmoji] = [item] + } + } + } + default: + break + } + return animatedEmojiStickers + } + + let _ = (combineLatest(queue: Queue.mainQueue(), self.cachedDataPromise.get(), animatedEmojiStickers) + |> take(1)).start(next: { [weak self] cachedData, animatedEmojiStickers in guard let strongSelf = self else { return } @@ -13383,14 +13404,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G selectedEmoticon = nil } - let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon, dark in + let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon, dark in if let strongSelf = self { strongSelf.presentCrossfadeSnapshot(delay: 0.2) strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark))) } }, completion: { [weak self] emoticon in - strongSelf.presentCrossfadeSnapshot(delay: 0.2) - strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, nil))) + strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil))) let _ = context.engine.themes.setChatTheme(peerId: peerId, emoticon: emoticon).start(completed: { [weak self] in if let strongSelf = self { strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((nil, nil))) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift index 6173d30516..b5e607ccb6 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift @@ -25,9 +25,9 @@ final class ChatSearchResultsController: ViewController { private var presentationDataDisposable: Disposable? - init(context: AccountContext, location: SearchMessagesLocation, searchQuery: String, searchResult: SearchMessagesResult, searchState: SearchMessagesState, navigateToMessageIndex: @escaping (Int) -> Void, resultsUpdated: @escaping (SearchMessagesResult, SearchMessagesState) -> Void) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, location: SearchMessagesLocation, searchQuery: String, searchResult: SearchMessagesResult, searchState: SearchMessagesState, navigateToMessageIndex: @escaping (Int) -> Void, resultsUpdated: @escaping (SearchMessagesResult, SearchMessagesState) -> Void) { self.context = context - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.location = location self.searchQuery = searchQuery self.navigateToMessageIndex = navigateToMessageIndex @@ -39,7 +39,7 @@ final class ChatSearchResultsController: ViewController { self.navigationPresentation = .modal - self.presentationDataDisposable = (context.sharedContext.presentationData + self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { strongSelf.presentationData = presentationData diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 0a176ad40c..e25e983680 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -14,8 +14,12 @@ import PresentationDataUtils import AnimationUI import MergeLists import MediaResources +import StickerResources import WallpaperResources import TooltipUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import ShimmerEffect private func closeButtonImage(theme: PresentationTheme) -> UIImage? { return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in @@ -41,6 +45,7 @@ private func closeButtonImage(theme: PresentationTheme) -> UIImage? { private struct ThemeSettingsThemeEntry: Comparable, Identifiable { let index: Int let emoticon: String? + let emojiFile: TelegramMediaFile? let themeReference: PresentationThemeReference? var selected: Bool let theme: PresentationTheme @@ -58,6 +63,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable { if lhs.emoticon != rhs.emoticon { return false } + if lhs.themeReference?.index != rhs.themeReference?.index { return false } @@ -81,7 +87,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable { } func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem { - return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, themeReference: self.themeReference, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action) + return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action) } } @@ -89,6 +95,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable { private class ThemeSettingsThemeIconItem: ListViewItem { let context: AccountContext let emoticon: String? + let emojiFile: TelegramMediaFile? let themeReference: PresentationThemeReference? let selected: Bool let theme: PresentationTheme @@ -96,9 +103,10 @@ private class ThemeSettingsThemeIconItem: ListViewItem { let wallpaper: TelegramWallpaper? let action: (String?) -> Void - public init(context: AccountContext, emoticon: String?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) { + public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) { self.context = context self.emoticon = emoticon + self.emojiFile = emojiFile self.themeReference = themeReference self.selected = selected self.theme = theme @@ -231,16 +239,37 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { private let containerNode: ASDisplayNode + private let emojiContainerNode: ASDisplayNode private let imageNode: TransformImageNode private let overlayNode: ASImageNode private let textNode: TextNode private let emojiNode: TextNode + private let emojiImageNode: TransformImageNode + private var animatedStickerNode: AnimatedStickerNode? + private var placeholderNode: StickerShimmerEffectNode var snapshotView: UIView? var item: ThemeSettingsThemeIconItem? + + override var visibility: ListViewItemNodeVisibility { + didSet { + self.visibilityStatus = self.visibility != .none + } + } + + private var visibilityStatus: Bool = false { + didSet { + if self.visibilityStatus != oldValue { + self.animatedStickerNode?.visibility = self.visibilityStatus + } + } + } + + private let stickerFetchedDisposable = MetaDisposable() init() { self.containerNode = ASDisplayNode() + self.emojiContainerNode = ASDisplayNode() self.imageNode = TransformImageNode() self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 82.0, height: 108.0)) @@ -257,6 +286,10 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { self.emojiNode = TextNode() self.emojiNode.isUserInteractionEnabled = false + + self.emojiImageNode = TransformImageNode() + + self.placeholderNode = StickerShimmerEffectNode() super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) @@ -264,9 +297,47 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { self.containerNode.addSubnode(self.imageNode) self.containerNode.addSubnode(self.overlayNode) self.containerNode.addSubnode(self.textNode) - self.containerNode.addSubnode(self.emojiNode) + + self.addSubnode(self.emojiContainerNode) + self.emojiContainerNode.addSubnode(self.emojiNode) + self.emojiContainerNode.addSubnode(self.emojiImageNode) + self.emojiContainerNode.addSubnode(self.placeholderNode) + + var firstTime = true + self.emojiImageNode.imageUpdated = { [weak self] image in + guard let strongSelf = self else { + return + } + if image != nil { + strongSelf.removePlaceholder(animated: !firstTime) + if firstTime { + strongSelf.emojiImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + firstTime = false + } } + deinit { + self.stickerFetchedDisposable.dispose() + } + + private func removePlaceholder(animated: Bool) { + if !animated { + self.placeholderNode.removeFromSupernode() + } else { + self.placeholderNode.alpha = 0.0 + self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in + self?.placeholderNode.removeFromSupernode() + }) + } + } + + override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0)) + self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + emojiFrame.minX, y: rect.minY + emojiFrame.minY), size: emojiFrame.size), within: containerSize) + } + func asyncLayout() -> (ThemeSettingsThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode) @@ -275,11 +346,15 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { let currentItem = self.item return { [weak self] item, params in + var updatedEmoticon = false var updatedThemeReference = false var updatedTheme = false var updatedWallpaper = false var updatedSelected = false + if currentItem?.emoticon != item.emoticon { + updatedEmoticon = true + } if currentItem?.themeReference != item.themeReference { updatedThemeReference = true } @@ -296,7 +371,13 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let title = NSAttributedString(string: item.emoticon ?? "❌", font: Font.regular(22.0), textColor: .black) + var emoticon = item.emoticon + if emoticon == "🦁" { + emoticon = "🌳" + } else if emoticon == "🔮" { + emoticon = "🎆" + } + let title = NSAttributedString(string: emoticon != nil ? "" : "❌", font: Font.regular(22.0), textColor: .black) let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 120.0, height: 90.0), insets: UIEdgeInsets()) @@ -324,6 +405,9 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { strongSelf.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) strongSelf.containerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0)) + strongSelf.emojiContainerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + strongSelf.emojiContainerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0)) + let _ = textApply() let _ = emojiApply() @@ -334,14 +418,50 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { strongSelf.overlayNode.frame = strongSelf.imageNode.frame.insetBy(dx: -1.0, dy: -1.0) strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 79.0), size: CGSize(width: 90.0, height: 30.0)) + + let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0)) + if let file = item.emojiFile, updatedEmoticon { + let imageApply = strongSelf.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets())) + imageApply() + strongSelf.emojiImageNode.setSignal(chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: file.resource, animated: true, nilIfEmpty: true)) + strongSelf.emojiImageNode.frame = emojiFrame + + let animatedStickerNode: AnimatedStickerNode + if let current = strongSelf.animatedStickerNode { + animatedStickerNode = current + } else { + animatedStickerNode = AnimatedStickerNode() + animatedStickerNode.started = { [weak self] in + self?.emojiImageNode.isHidden = true + } + strongSelf.animatedStickerNode = animatedStickerNode + strongSelf.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode) + let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 96, height: 96, mode: .direct(cachePathPrefix: pathPrefix)) + } + animatedStickerNode.visibility = strongSelf.visibilityStatus + + strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start()) + + let thumbnailDimensions = PixelDimensions(width: 512, height: 512) + strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, imageSize: thumbnailDimensions.cgSize) + strongSelf.placeholderNode.frame = emojiFrame + } + + if let animatedStickerNode = strongSelf.animatedStickerNode { + animatedStickerNode.frame = emojiFrame + animatedStickerNode.updateLayout(size: emojiFrame.size) + } } }) } } func crossfade() { - if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) { - self.view.addSubview(snapshotView) + if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.transform = self.containerNode.view.transform + snapshotView.frame = self.containerNode.view.frame + self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view) snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() @@ -376,6 +496,7 @@ final class ChatThemeScreen: ViewController { private var animatedIn = false private let context: AccountContext + private let animatedEmojiStickers: [String: [StickerPackItem]] private let initiallySelectedEmoticon: String? private let dismissByTapOutside: Bool private let previewTheme: (String?, Bool?) -> Void @@ -392,9 +513,10 @@ final class ChatThemeScreen: ViewController { } } - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), initiallySelectedEmoticon: String?, dismissByTapOutside: Bool = true, previewTheme: @escaping (String?, Bool?) -> Void, completion: @escaping (String?) -> Void) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, dismissByTapOutside: Bool = true, previewTheme: @escaping (String?, Bool?) -> Void, completion: @escaping (String?) -> Void) { self.context = context self.presentationData = updatedPresentationData.initial + self.animatedEmojiStickers = animatedEmojiStickers self.initiallySelectedEmoticon = initiallySelectedEmoticon self.dismissByTapOutside = dismissByTapOutside self.previewTheme = previewTheme @@ -426,7 +548,7 @@ final class ChatThemeScreen: ViewController { } override public func loadDisplayNode() { - self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, initiallySelectedEmoticon: self.initiallySelectedEmoticon, dismissByTapOutside: self.dismissByTapOutside) + self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, animatedEmojiStickers: self.animatedEmojiStickers, initiallySelectedEmoticon: self.initiallySelectedEmoticon, dismissByTapOutside: self.dismissByTapOutside) self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl self.controllerNode.previewTheme = { [weak self] emoticon, dark in guard let strongSelf = self else { @@ -558,7 +680,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega var dismiss: (() -> Void)? var cancel: (() -> Void)? - init(context: AccountContext, presentationData: PresentationData, initiallySelectedEmoticon: String?, dismissByTapOutside: Bool) { + init(context: AccountContext, presentationData: PresentationData, animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, dismissByTapOutside: Bool) { self.context = context self.initiallySelectedEmoticon = initiallySelectedEmoticon self.selectedEmoticon = initiallySelectedEmoticon @@ -662,18 +784,24 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega var entries: [ThemeSettingsThemeEntry] = [] if strongSelf.initiallySelectedEmoticon != nil { - entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) + entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) } for theme in themes { - entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) + var emoticon = theme.emoji + if emoticon == "🦁" { + emoticon = "🌳" + } else if emoticon == "🔮" { + emoticon = "🎆" + } + entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) } let action: (String?) -> Void = { [weak self] emoticon in if let strongSelf = self, strongSelf.selectedEmoticon != emoticon { strongSelf.animateCrossfade(animateBackground: strongSelf.presentationData.theme.overallDarkAppearance, updateSunIcon: true) - strongSelf.selectedEmoticon = emoticon strongSelf.previewTheme?(emoticon, strongSelf.isDarkAppearance) + strongSelf.selectedEmoticon = emoticon let _ = ensureThemeVisible(listNode: strongSelf.listNode, emoticon: emoticon, animated: true) strongSelf.doneButton.title = emoticon == nil ? strongSelf.presentationData.strings.Conversation_Theme_Reset : strongSelf.presentationData.strings.Conversation_Theme_Apply diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index b22b7baca8..b32b8e0228 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3489,20 +3489,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD let filteredButtons = allHeaderButtons.subtracting(headerButtons) - var canChangeColors = true -// if let peer = peer as? TelegramUser, self.data?.encryptionKeyFingerprint == nil { -// canChangeColors = true -// } - if let peer = peer as? TelegramChannel { - if case .broadcast = peer.info { - canChangeColors = false - } else { - canChangeColors = peer.hasPermission(.changeInfo) - } - } else if let peer = peer as? TelegramGroup, case .member = peer.role { - canChangeColors = !peer.hasBannedPermission(.banChangeInfo) - } else if self.data?.encryptionKeyFingerprint != nil { - canChangeColors = false + var canChangeColors = false + if peer is TelegramUser, self.data?.encryptionKeyFingerprint == nil { + canChangeColors = true } if canChangeColors { @@ -6799,7 +6788,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { } } - self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) + self.presentationDataDisposable = (presentationDataSignal |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 7801fced47..b3153beed7 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -78,6 +78,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { var displayUndo = true var undoText = presentationData.strings.Undo_Undo var undoTextColor = UIColor(rgb: 0x5ac8fa) + undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 1.0, saturation: 0.64, brightness: 1.08) if presentationData.theme.overallDarkAppearance { self.animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor