diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index eb5d35028a..63fa70f5c4 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5837,8 +5837,11 @@ Sorry for the inconvenience."; "VoiceChat.StartRecordingText" = "Do you want to start recording this chat and save the result into an audio file?\n\nOther members will see that the chat is being recorded."; "LiveStream.StartRecordingText" = "Do you want to start recording this live stream and save the result into an audio file?\n\nOther members will see that the chat is being recorded."; "VoiceChat.StartRecordingStart" = "Start"; +"VoiceChat.StartRecordingTextVideo" = "Do you want to start recording this chat and save the result into a video file?\n\nOther members will see that the chat is being recorded."; +"LiveStream.StartRecordingTextVideo" = "Do you want to start recording this live stream and save the result into a video file?\n\nOther members will see that the chat is being recorded."; "VoiceChat.RecordingTitlePlaceholder" = "Audio Title (Optional)"; +"VoiceChat.RecordingTitlePlaceholderVideo" = "Video Title (Optional)"; "VoiceChat.RecordingStarted" = "Voice chat recording started"; "LiveStream.RecordingStarted" = "Live stream recording started"; "VoiceChat.RecordingInProgress" = "Voice chat is being recorded"; @@ -6715,12 +6718,15 @@ Sorry for the inconvenience."; "UserInfo.ChangeColors" = "Change Colors"; "Conversation.Theme.Title" = "Select Theme"; +"Conversation.Theme.Subtitle" = "Theme will be also applied for %@"; "Conversation.Theme.Apply" = "Apply Theme"; "Conversation.Theme.NoTheme" = "No\nTheme"; "Conversation.Theme.Reset" = "Reset Theme for This Chat"; "Conversation.Theme.DontSetTheme" = "Do Not Set Theme"; "Conversation.Theme.SwitchToDark" = "Switch to dark appearance"; "Conversation.Theme.SwitchToLight" = "Switch to light appearance"; +"Conversation.Theme.PreviewDark" = "Tap to see how chat will appear to\n%@ when using night mode."; +"Conversation.Theme.PreviewLight" = "Tap to see how chat will appear to\n%@ when using day mode."; "Conversation.Theme.DismissAlert" = "Do you want to apply the selected theme to the chat?"; "Conversation.Theme.DismissAlertApply" = "Apply"; diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index a1a8c48bc7..7a40c284ff 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -2657,38 +2657,48 @@ public final class VoiceChatController: ViewController { let controller = VoiceChatRecordingSetupController(context: strongSelf.context, completion: { [weak self] videoOrientation in if let strongSelf = self { - strongSelf.call.setShouldBeRecording(true, title: "", videoOrientation: videoOrientation) + let title: String + let text: String + let placeholder: String + if let _ = videoOrientation { + placeholder = strongSelf.presentationData.strings.VoiceChat_RecordingTitlePlaceholderVideo + } else { + placeholder = strongSelf.presentationData.strings.VoiceChat_RecordingTitlePlaceholder + } + if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info { + title = strongSelf.presentationData.strings.LiveStream_StartRecordingTitle + if let _ = videoOrientation { + text = strongSelf.presentationData.strings.LiveStream_StartRecordingTextVideo + } else { + text = strongSelf.presentationData.strings.LiveStream_StartRecordingText + } + } else { + title = strongSelf.presentationData.strings.VoiceChat_StartRecordingTitle + if let _ = videoOrientation { + text = strongSelf.presentationData.strings.VoiceChat_StartRecordingTextVideo + } else { + text = strongSelf.presentationData.strings.VoiceChat_StartRecordingText + } + } - strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: text), action: { _ in return false }) - strongSelf.call.playTone(.recordingStarted) + let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: title, text: text, placeholder: placeholder, value: nil, maxLength: 40, apply: { title in + if let strongSelf = self, let title = title { + strongSelf.call.setShouldBeRecording(true, title: title, videoOrientation: videoOrientation) + + let text: String + if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info { + text = strongSelf.presentationData.strings.LiveStream_RecordingStarted + } else { + text = strongSelf.presentationData.strings.VoiceChat_RecordingStarted + } + + strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: text), action: { _ in return false }) + strongSelf.call.playTone(.recordingStarted) + } + }) + strongSelf.controller?.present(controller, in: .window(.root)) } }) - -// let title: String -// let text: String -// if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info { -// title = strongSelf.presentationData.strings.LiveStream_StartRecordingTitle -// text = strongSelf.presentationData.strings.LiveStream_StartRecordingText -// } else { -// title = strongSelf.presentationData.strings.VoiceChat_StartRecordingTitle -// text = strongSelf.presentationData.strings.VoiceChat_StartRecordingText -// } -// -// let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: title, text: text, placeholder: strongSelf.presentationData.strings.VoiceChat_RecordingTitlePlaceholder, value: nil, maxLength: 40, apply: { title in -// if let strongSelf = self, let title = title { -// strongSelf.call.setShouldBeRecording(true, title: title, videoOrientation: nil) -// -// let text: String -// if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info { -// text = strongSelf.presentationData.strings.LiveStream_RecordingStarted -// } else { -// text = strongSelf.presentationData.strings.VoiceChat_RecordingStarted -// } -// -// strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: text), action: { _ in return false }) -// strongSelf.call.playTone(.recordingStarted) -// } -// }) self?.controller?.present(controller, in: .window(.root)) }))) } diff --git a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift index f90ffa483d..2f761b47a7 100644 --- a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift +++ b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift @@ -26,6 +26,9 @@ public struct EmojiInteraction: Equatable { guard let item = decodedData as? [String: Any] else { return nil } + guard let version = item["v"] as? Int, version == 1 else { + return nil + } guard let animationsArray = item["a"] as? [Any] else { return nil } diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 476abc6c55..d45d82d04b 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -104,6 +104,37 @@ public final class ApplicationSpecificTimestampNotice: NoticeEntry { } } +public final class ApplicationSpecificTimestampAndCounterNotice: NoticeEntry { + public let counter: Int32 + public let timestamp: Int32 + + public init(counter: Int32, timestamp: Int32) { + self.counter = counter + self.timestamp = timestamp + } + + public init(decoder: PostboxDecoder) { + self.counter = decoder.decodeInt32ForKey("v", orElse: 0) + self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.counter, forKey: "v") + encoder.encodeInt32(self.timestamp, forKey: "t") + } + + public func isEqual(to: NoticeEntry) -> Bool { + if let to = to as? ApplicationSpecificTimestampAndCounterNotice { + if self.counter != to.counter || self.timestamp != to.timestamp { + return false + } + return true + } else { + return false + } + } +} + public final class ApplicationSpecificInt64ArrayNotice: NoticeEntry { public let values: [Int64] @@ -167,9 +198,10 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case locationProximityAlertTip = 20 case nextChatSuggestionTip = 21 case dismissedTrendingStickerPacks = 22 - case chatSpecificThemesDarkPreviewTip = 23 case chatForwardOptionsTip = 24 case messageViewsPrivacyTips = 25 + case chatSpecificThemeLightPreviewTip = 26 + case chatSpecificThemeDarkPreviewTip = 27 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -314,8 +346,12 @@ private struct ApplicationSpecificNoticeKeys { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingStickerPacks.key) } - static func chatSpecificThemesDarkPreviewTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemesDarkPreviewTip.key) + static func chatSpecificThemeLightPreviewTip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeLightPreviewTip.key) + } + + static func chatSpecificThemeDarkPreviewTip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeDarkPreviewTip.key) } static func chatForwardOptionsTip() -> NoticeEntryKey { @@ -850,26 +886,51 @@ public struct ApplicationSpecificNotice { } } - public static func getChatSpecificThemesDarkPreviewTip(accountManager: AccountManager) -> Signal { - return accountManager.transaction { transaction -> Int32 in - if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatSpecificThemesDarkPreviewTip()) as? ApplicationSpecificCounterNotice { - return value.value + public static func getChatSpecificThemeLightPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> { + return accountManager.transaction { transaction -> (Int32, Int32) in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeLightPreviewTip()) as? ApplicationSpecificTimestampAndCounterNotice { + return (value.counter, value.timestamp) } else { - return 0 + return (0, 0) } } } - public static func incrementChatSpecificThemesDarkPreviewTip(accountManager: AccountManager, count: Int = 1) -> Signal { + public static func incrementChatSpecificThemeLightPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal { return accountManager.transaction { transaction -> Int in var currentValue: Int32 = 0 - if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatSpecificThemesDarkPreviewTip()) as? ApplicationSpecificCounterNotice { - currentValue = value.value + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeLightPreviewTip()) as? ApplicationSpecificTimestampAndCounterNotice { + currentValue = value.counter } let previousValue = currentValue currentValue += Int32(count) - transaction.setNotice(ApplicationSpecificNoticeKeys.chatSpecificThemesDarkPreviewTip(), ApplicationSpecificCounterNotice(value: currentValue)) + transaction.setNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeLightPreviewTip(), ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) + + return Int(previousValue) + } + } + + public static func getChatSpecificThemeDarkPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> { + return accountManager.transaction { transaction -> (Int32, Int32) in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeDarkPreviewTip()) as? ApplicationSpecificTimestampAndCounterNotice { + return (value.counter, value.timestamp) + } else { + return (0, 0) + } + } + } + + public static func incrementChatSpecificThemeDarkPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeDarkPreviewTip()) as? ApplicationSpecificTimestampAndCounterNotice { + currentValue = value.counter + } + let previousValue = currentValue + currentValue += Int32(count) + + transaction.setNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeDarkPreviewTip(), ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) return Int(previousValue) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ea88469d74..8f15940846 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -7435,15 +7435,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode({ itemNode in if !found, let itemNode = itemNode as? ChatMessageAnimatedStickerItemNode, let item = itemNode.item { if item.message.id == messageId { - for animation in interaction.animations { - if animation.timeOffset > 0.0 { - Queue.mainQueue().after(Double(animation.timeOffset)) { - itemNode.playAdditionalAnimation(index: animation.index) - } - } else { - itemNode.playAdditionalAnimation(index: animation.index) - } - } + itemNode.playEmojiInteraction(interaction) found = true } } @@ -13436,7 +13428,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - + let selectedEmoticon: String? if let cachedData = cachedData as? CachedUserData { selectedEmoticon = cachedData.themeEmoticon @@ -13448,7 +13440,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G selectedEmoticon = nil } - let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon, dark in + let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, initiallySelectedEmoticon: selectedEmoticon, peerName: strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer?.compactDisplayTitle ?? "", previewTheme: { [weak self] emoticon, dark in if let strongSelf = self { strongSelf.presentCrossfadeSnapshot(delay: 0.2) strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark))) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 87da476463..877629edfb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -204,6 +204,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var forceStopAnimations = false + private var hapticFeedback: HapticFeedback? private var haptic: EmojiHaptic? private var mediaPlayer: MediaPlayer? private let mediaStatusDisposable = MetaDisposable() @@ -1352,6 +1353,52 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { item.context.account.updateLocalInputActivity(peerId: PeerActivitySpace(peerId: item.message.id.peerId, category: .global), activity: .interactingWithEmoji(emoticon: textEmoji, messageId: item.message.id, interaction: EmojiInteraction(animations: animations)), isPresent: true) } + func playEmojiInteraction(_ interaction: EmojiInteraction) { + guard interaction.animations.count <= 7 else { + return + } + + var hapticFeedback: HapticFeedback + if let current = self.hapticFeedback { + hapticFeedback = current + } else { + hapticFeedback = HapticFeedback() + self.hapticFeedback = hapticFeedback + } + + var playHaptic = true + if let existingHaptic = self.haptic, existingHaptic.active { + playHaptic = false + } + hapticFeedback.prepareImpact(.light) + hapticFeedback.prepareImpact(.medium) + + var index = 0 + for animation in interaction.animations { + if animation.timeOffset > 0.0 { + Queue.mainQueue().after(Double(animation.timeOffset)) { + self.playAdditionalAnimation(index: animation.index) + if playHaptic { + let style: ImpactHapticFeedbackStyle + if index == 1 { + style = .medium + } else { + style = [.light, .medium].randomElement() ?? .medium + } + hapticFeedback.impact(style) + } + index += 1 + } + } else { + self.playAdditionalAnimation(index: animation.index) + if playHaptic { + hapticFeedback.impact(interaction.animations.count > 1 ? .light : .medium) + } + index += 1 + } + } + } + func playAdditionalAnimation(index: Int) { guard let item = self.item else { return @@ -1523,8 +1570,32 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { firstScalar = UnicodeScalar(heart)! } return .optionalAction({ + var haptic: EmojiHaptic? + if let current = self.haptic { + haptic = current + } else { + if firstScalar.value == heart { + haptic = HeartbeatHaptic() + } else if firstScalar.value == coffin { + haptic = CoffinHaptic() + } else if firstScalar.value == peach { + haptic = PeachHaptic() + } + haptic?.enabled = true + self.haptic = haptic + } + if let animationItems = item.associatedData.additionalAnimatedEmojiStickers[originalTextEmoji] { let syncAnimations = item.message.id.peerId.namespace == Namespaces.Peer.CloudUser + let playHaptic = haptic == nil + + var hapticFeedback: HapticFeedback + if let current = self.hapticFeedback { + hapticFeedback = current + } else { + hapticFeedback = HapticFeedback() + self.hapticFeedback = hapticFeedback + } if syncAnimations { self.startAdditionalAnimationsCommitTimer() @@ -1539,11 +1610,25 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { availableAnimations.removeValue(forKey: previousIndex) } if let (_, previousTimestamp) = previousAnimation { - delay = min(0.2, max(0.0, previousTimestamp + 0.2 - timestamp)) + delay = min(0.15, max(0.0, previousTimestamp + 0.15 - timestamp)) } if let index = availableAnimations.randomElement()?.0 { if delay > 0.0 { Queue.mainQueue().after(delay) { + if playHaptic { + if previousAnimation == nil { + hapticFeedback.impact(.light) + } else { + let style: ImpactHapticFeedbackStyle + if self.enqueuedAdditionalAnimations.count == 1 { + style = .medium + } else { + style = [.light, .medium].randomElement() ?? .medium + } + hapticFeedback.impact(style) + } + } + if syncAnimations { self.enqueuedAdditionalAnimations.append((index, timestamp + delay)) } @@ -1554,6 +1639,20 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } } else { + if playHaptic { + if previousAnimation == nil { + hapticFeedback.impact(.light) + } else { + let style: ImpactHapticFeedbackStyle + if self.enqueuedAdditionalAnimations.count == 1 { + style = .medium + } else { + style = [.light, .medium].randomElement() ?? .medium + } + hapticFeedback.impact(style) + } + } + if syncAnimations { self.enqueuedAdditionalAnimations.append((index, timestamp)) } @@ -1582,22 +1681,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.mediaStatusDisposable.set((mediaPlayer.status |> deliverOnMainQueue).start(next: { [weak self, weak animationNode] status in if let strongSelf = self { - - var haptic: EmojiHaptic? - if let current = strongSelf.haptic { - haptic = current - } else { - if firstScalar.value == heart { - haptic = HeartbeatHaptic() - } else if firstScalar.value == coffin { - haptic = CoffinHaptic() - } else if firstScalar.value == peach { - haptic = PeachHaptic() - } - haptic?.enabled = true - strongSelf.haptic = haptic - } - if let haptic = haptic, !haptic.active { haptic.start(time: 0.0) } diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index 1595531c00..5cadb4c823 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -893,8 +893,4 @@ public class ChatMessageItemView: ListViewItemNode { } } } - - override public var preferredAnimationCurve: (CGFloat) -> CGFloat { - return listViewAnimationCurveSystem - } } diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index b4b9e64b9d..0377049044 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -172,7 +172,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode { case videoMessage(VideoMessage) case mediaInput(MediaInput) } - + final class DecorationItemNode: ASDisplayNode { let itemNode: ChatMessageItemView private let contentNode: ASDisplayNode @@ -640,13 +640,15 @@ public final class ChatMessageTransitionNode: ASDisplayNode { func add(decorationNode: ASDisplayNode, itemNode: ChatMessageItemView) -> DecorationItemNode { let decorationItemNode = DecorationItemNode(itemNode: itemNode, contentNode: decorationNode, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace) decorationItemNode.updateLayout(size: self.bounds.size) + self.decorationItemNodes.append(decorationItemNode) + self.addSubnode(decorationItemNode) - let overlayController = OverlayTransitionContainerController() - overlayController.displayNode.isUserInteractionEnabled = false - overlayController.displayNode.addSubnode(decorationItemNode) - decorationItemNode.overlayController = overlayController - itemNode.item?.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController) +// let overlayController = OverlayTransitionContainerController() +// overlayController.displayNode.isUserInteractionEnabled = false +// overlayController.displayNode.addSubnode(decorationItemNode) +// decorationItemNode.overlayController = overlayController +// itemNode.item?.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController) return decorationItemNode } diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 491ace17bf..4af2ea14f0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -289,8 +289,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { actionTitle = item.presentationData.strings.Conversation_ViewGroup case "telegram_message": actionTitle = item.presentationData.strings.Conversation_ViewMessage - case "telegram_voicechat", "telegram_livestream", "telegram_videochat": - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + case "telegram_voicechat", "telegram_videochat", "telegram_livestream": + if type == "telegram_livestream" { title = item.presentationData.strings.Conversation_LiveStream } else { title = item.presentationData.strings.Conversation_VoiceChat diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 982249e123..16688c4715 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -531,7 +531,7 @@ final class ChatThemeScreen: ViewController { private let context: AccountContext private let animatedEmojiStickers: [String: [StickerPackItem]] private let initiallySelectedEmoticon: String? - private let dismissByTapOutside: Bool + private let peerName: String private let previewTheme: (String?, Bool?) -> Void private let completion: (String?) -> Void @@ -548,12 +548,12 @@ final class ChatThemeScreen: ViewController { } } - 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) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, peerName: String, 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.peerName = peerName self.previewTheme = previewTheme self.completion = completion @@ -583,7 +583,7 @@ final class ChatThemeScreen: ViewController { } override public func loadDisplayNode() { - self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, controller: self, animatedEmojiStickers: self.animatedEmojiStickers, initiallySelectedEmoticon: self.initiallySelectedEmoticon, dismissByTapOutside: self.dismissByTapOutside) + self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, controller: self, animatedEmojiStickers: self.animatedEmojiStickers, initiallySelectedEmoticon: self.initiallySelectedEmoticon, peerName: self.peerName) self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl self.controllerNode.previewTheme = { [weak self] emoticon, dark in guard let strongSelf = self else { @@ -676,7 +676,6 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega private let context: AccountContext private var presentationData: PresentationData private weak var controller: ChatThemeScreen? - private let dismissByTapOutside: Bool private let dimNode: ASDisplayNode private let wrappingScrollNode: ASScrollNode @@ -698,6 +697,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega private var enqueuedTransitions: [ThemeSettingsThemeItemNodeTransition] = [] private var initialized = false + private let peerName: String + private let initiallySelectedEmoticon: String? private var selectedEmoticon: String? { didSet { @@ -723,14 +724,14 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega var dismiss: (() -> Void)? var cancel: (() -> Void)? - init(context: AccountContext, presentationData: PresentationData, controller: ChatThemeScreen, animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, dismissByTapOutside: Bool) { + init(context: AccountContext, presentationData: PresentationData, controller: ChatThemeScreen, animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, peerName: String) { self.context = context self.controller = controller self.initiallySelectedEmoticon = initiallySelectedEmoticon + self.peerName = peerName self.selectedEmoticon = initiallySelectedEmoticon self.selectedEmoticonPromise = ValuePromise(initiallySelectedEmoticon) self.presentationData = presentationData - self.dismissByTapOutside = dismissByTapOutside self.wrappingScrollNode = ASScrollNode() self.wrappingScrollNode.view.alwaysBounceVertical = true @@ -755,6 +756,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor let textColor = self.presentationData.theme.actionSheet.primaryTextColor + let secondaryTextColor = self.presentationData.theme.actionSheet.secondaryTextColor let blurStyle: UIBlurEffect.Style = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark self.effectNode = ASDisplayNode(viewBlock: { @@ -764,11 +766,11 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.contentBackgroundNode = ASDisplayNode() self.contentBackgroundNode.backgroundColor = backgroundColor - let title = self.presentationData.strings.Conversation_Theme_Title self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor) + self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Title, font: Font.semibold(16.0), textColor: textColor) self.textNode = ImmediateTextNode() + self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Subtitle(peerName).string, font: Font.regular(12.0), textColor: secondaryTextColor) self.cancelButton = HighlightableButtonNode() self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) @@ -851,6 +853,11 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega doneButtonTitle = strongSelf.presentationData.strings.Conversation_Theme_Apply } strongSelf.doneButton.title = doneButtonTitle + + strongSelf.themeSelectionsCount += 1 + if strongSelf.themeSelectionsCount == 2 { + strongSelf.maybePresentPreviewTooltip() + } } } let previousEntries = strongSelf.entries ?? [] @@ -946,7 +953,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let previousTheme = self.presentationData.theme self.presentationData = presentationData - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(16.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) + self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) @@ -1010,7 +1018,11 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.previewTheme?(self.selectedEmoticon, isDarkAppearance) self.isDarkAppearance = isDarkAppearance - let _ = ApplicationSpecificNotice.incrementChatSpecificThemesDarkPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3).start() + if isDarkAppearance { + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).start() + } else { + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).start() + } } private func animateCrossfade(animateIcon: Bool = true) { @@ -1069,20 +1081,40 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.bounds = targetBounds self.dimNode.position = dimPosition }) + } + + private var themeSelectionsCount = 0 + private var displayedPreviewTooltip = false + private func maybePresentPreviewTooltip() { + guard !self.displayedPreviewTooltip, !self.animatedOut else { + return + } let frame = self.switchThemeButton.view.convert(self.switchThemeButton.bounds, to: self.view) + let currentTimestamp = Int32(Date().timeIntervalSince1970) - let _ = (ApplicationSpecificNotice.getChatSpecificThemesDarkPreviewTip(accountManager: self.context.sharedContext.accountManager) - |> deliverOnMainQueue).start(next: { [weak self] count in - if let strongSelf = self, count < 3 { - Queue.mainQueue().after(1.0) { - if !strongSelf.animatedOut { - strongSelf.present?(TooltipScreen(account: strongSelf.context.account, text: strongSelf.presentationData.theme.overallDarkAppearance ? strongSelf.presentationData.strings.Conversation_Theme_SwitchToLight : strongSelf.presentationData.strings.Conversation_Theme_SwitchToDark, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in - return .dismiss(consume: false) - })) - - let _ = ApplicationSpecificNotice.incrementChatSpecificThemesDarkPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager).start() - } + let isDark = self.presentationData.theme.overallDarkAppearance + + let signal: Signal<(Int32, Int32), NoError> + if isDark { + signal = ApplicationSpecificNotice.getChatSpecificThemeLightPreviewTip(accountManager: self.context.sharedContext.accountManager) + } else { + signal = ApplicationSpecificNotice.getChatSpecificThemeDarkPreviewTip(accountManager: self.context.sharedContext.accountManager) + } + + let _ = (signal + |> deliverOnMainQueue).start(next: { [weak self] count, timestamp in + if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 { + strongSelf.displayedPreviewTooltip = true + + strongSelf.present?(TooltipScreen(account: strongSelf.context.account, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + return .dismiss(consume: false) + })) + + if isDark { + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() + } else { + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() } } }) @@ -1158,10 +1190,14 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0), size: titleSize) + let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight)) + let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 11.0 + UIScreenPixel), size: titleSize) transition.updateFrame(node: self.titleNode, frame: titleFrame) + let textSize = self.textNode.updateLayout(CGSize(width: width - 90.0, height: titleHeight)) + let textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: 31.0), size: textSize) + transition.updateFrame(node: self.textNode, frame: textFrame) + let switchThemeSize = CGSize(width: 44.0, height: 44.0) let switchThemeFrame = CGRect(origin: CGPoint(x: 3.0, y: 6.0), size: switchThemeSize) transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame) diff --git a/submodules/TelegramUI/Sources/DeclareEncodables.swift b/submodules/TelegramUI/Sources/DeclareEncodables.swift index 05d4689baa..ab9d486b56 100644 --- a/submodules/TelegramUI/Sources/DeclareEncodables.swift +++ b/submodules/TelegramUI/Sources/DeclareEncodables.swift @@ -28,6 +28,7 @@ private var telegramUIDeclaredEncodables: Void = { declareEncodable(ApplicationSpecificVariantNotice.self, f: { ApplicationSpecificVariantNotice(decoder: $0) }) declareEncodable(ApplicationSpecificCounterNotice.self, f: { ApplicationSpecificCounterNotice(decoder: $0) }) declareEncodable(ApplicationSpecificTimestampNotice.self, f: { ApplicationSpecificTimestampNotice(decoder: $0) }) + declareEncodable(ApplicationSpecificTimestampAndCounterNotice.self, f: { ApplicationSpecificTimestampAndCounterNotice(decoder: $0) }) declareEncodable(ApplicationSpecificInt64ArrayNotice.self, f: { ApplicationSpecificInt64ArrayNotice(decoder: $0) }) //declareEncodable(CallListSettings.self, f: { CallListSettings(decoder: $0) }) //declareEncodable(VoiceCallSettings.self, f: { VoiceCallSettings(decoder: $0) }) diff --git a/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift index 8dcbf34f3c..39ceb8f8ce 100644 --- a/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift @@ -156,7 +156,7 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { var text = "" var sourcePeer: (Bool, String)? for message in messages { - if let author = message.effectiveAuthor, !uniquePeerIds.contains(author.id) { + if let author = message.forwardInfo?.author ?? message.effectiveAuthor, !uniquePeerIds.contains(author.id) { uniquePeerIds.insert(author.id) if !authors.isEmpty { authors.append(", ") @@ -268,15 +268,20 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { let filteredMessages = self.messages + var authors = self.authors ?? "" + if forwardOptionsState?.hideNames == true { + authors = self.strings.DialogList_You + } + var title = "" var text = "" if filteredMessages.count == 1, let message = filteredMessages.first { title = self.strings.Conversation_ForwardOptions_ForwardTitleSingle let (string, _) = textStringForForwardedMessage(message, strings: strings) - text = "\(self.authors ?? ""): \(string)" + text = "\(authors): \(string)" } else { title = self.strings.Conversation_ForwardOptions_ForwardTitle(Int32(filteredMessages.count)) - text = self.strings.Conversation_ForwardFrom(self.authors ?? "").string + text = self.strings.Conversation_ForwardFrom(authors).string } self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor) diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index a3aeba2cfd..69e24ea1b0 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -624,10 +624,10 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur game = value } else if queryItem.name == "post" { post = value - } else if queryItem.name == "voicechat" || queryItem.name == "videochat" || queryItem.name == "livestream" { + } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { voiceChat = value } - } else if queryItem.name == "voicechat" || queryItem.name == "videochat" || queryItem.name == "livestream" { + } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { voiceChat = "" } } diff --git a/submodules/UrlEscaping/Sources/UrlEscaping.swift b/submodules/UrlEscaping/Sources/UrlEscaping.swift index 342d13cb92..c7df47f7a0 100644 --- a/submodules/UrlEscaping/Sources/UrlEscaping.swift +++ b/submodules/UrlEscaping/Sources/UrlEscaping.swift @@ -49,7 +49,7 @@ private let validUrlSet: CharacterSet = { var set = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!) set.insert(charactersIn: "A".unicodeScalars.first! ... "Z".unicodeScalars.first!) set.insert(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!) - set.insert(charactersIn: ".?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ ") + set.insert(charactersIn: ".?!@#$^&%*-+=,:;'\"`<>()[]{}/\\|~ ") return set }() diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index bb4b21503c..9793c9856a 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -136,10 +136,10 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { return .peerName(peerName, .groupBotStart(value)) } else if queryItem.name == "game" { return nil - } else if queryItem.name == "voicechat" || queryItem.name == "videochat" || queryItem.name == "livestream" { + } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { return .peerName(peerName, .voiceChat(value)) } - } else if queryItem.name == "voicechat" || queryItem.name == "videochat" || queryItem.name == "livestream" { + } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { return .peerName(peerName, .voiceChat(nil)) } } diff --git a/versions.json b/versions.json index 2716a351e3..a3127779d2 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "8.0.1", + "app": "8.1", "bazel": "4.0.0", "xcode": "12.5.1" }