diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index dc3ddd766b..31dcde8fef 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15098,11 +15098,12 @@ Error: %8$@"; "PeerInfo.NotesInfo" = "only visible to you"; "PeerInfo.AddNotesPlaceholder" = "Add Notes"; "PeerInfo.AddNotesInfo" = "Notes are only visible to you."; - "PeerInfo.NoteActionCopy" = "Copy Note"; "PeerInfo.NoteActionEdit" = "Edit Note"; "PeerInfo.ToastNoteCopied" = "Note copied to clipboard."; +"PeerInfo.ViewDiscussion" = "View Discussion"; + "MESSAGE_SUGGEST_BIRTHDAY" = "%@ suggested you your birthday"; "Gift.UnavailableAction.Title" = "Action Locked"; @@ -15111,3 +15112,22 @@ Error: %8$@"; "Gift.UnavailableAction.OpenFragment_URL" = "https://fragment.com"; "Gift.Unique.Telegram" = "Telegram"; + +"VoiceChat.ContextEnableMessages" = "Enable Messages"; +"VoiceChat.ContextDisableMessages" = "Disable Messages"; + +"VoiceChat.ToastMessagesEnabled" = "Messages enabled"; +"VoiceChat.ToastMessagesDisabled" = "Messages disabled"; + +"VoiceChat.Rotate" = "rotate"; +"VoiceChat.Message" = "message"; +"VoiceChat.ShareButton" = "share"; +"VoiceChat.MessagePlaceholder" = "Message"; +"VoiceChat.SubtitleParticipants_1" = " participant"; +"VoiceChat.SubtitleParticipants_any" = " participants"; +"VoiceChat.SubtitleInvited_1" = " invited"; +"VoiceChat.SubtitleInvited_any" = " invited"; + +"ProfileLevelInfo.NegativeRating" = "Negative rating"; + +"Notification.StarsGift.Assigned" = "You started displaying %@ on your Telegram profile page."; diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 45c8775d18..5af4fe01a9 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -505,6 +505,7 @@ public protocol PresentationGroupCall: AnyObject { func disableScreencast() func switchVideoCamera() func updateDefaultParticipantsAreMuted(isMuted: Bool) + func updateMessagesEnabled(isEnabled: Bool) func setVolume(peerId: EnginePeer.Id, volume: Int32, sync: Bool) func setRequestedVideoList(items: [PresentationGroupCallRequestedVideo]) func setSuspendVideoChannelRequests(_ value: Bool) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index bec620d3b2..e2ec36e6ab 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -237,7 +237,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode { var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0) glassBackgroundFrame.size.height -= 8.0 transition.updateFrame(view: glassBackgroundView, frame: glassBackgroundFrame, beginWithCurrentState: true) - glassBackgroundView.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: .init(kind: .panel, color: UIColor(rgb: 0x4d4f5c, alpha: 0.6)), transition: ComponentTransition(transition)) + glassBackgroundView.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x25272e, alpha: 0.72)), transition: ComponentTransition(transition)) transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height)).insetBy(dx: -10.0, dy: -10.0)) } else { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index fc44b58dfe..96d968d7cf 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -26,7 +26,6 @@ import GZip import BalancedTextComponent import Markdown import PremiumStarComponent -import GlassBackgroundComponent public final class ReactionItem { public struct Reaction: Equatable { diff --git a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift index b2a54f4493..81a88e294c 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift @@ -13,10 +13,20 @@ import AccountContext import TextFormat import TelegramPresentationData import ReactionSelectionNode +import BundleIconComponent +import Markdown + +private let glassColor = UIColor(rgb: 0x25272e, alpha: 0.72) final class MessageItemComponent: Component { + public enum Icon: Equatable { + case peer(EnginePeer) + case icon(String) + } + private let context: AccountContext - private let peer: EnginePeer + private let icon: Icon + private let isNotification: Bool private let text: String private let entities: [MessageTextEntity] private let availableReactions: [ReactionItem]? @@ -24,14 +34,16 @@ final class MessageItemComponent: Component { init( context: AccountContext, - peer: EnginePeer, + icon: Icon, + isNotification: Bool, text: String, entities: [MessageTextEntity], availableReactions: [ReactionItem]?, avatarTapped: @escaping () -> Void = {} ) { self.context = context - self.peer = peer + self.icon = icon + self.isNotification = isNotification self.text = text self.entities = entities self.availableReactions = availableReactions @@ -42,7 +54,7 @@ final class MessageItemComponent: Component { if lhs.context !== rhs.context { return false } - if lhs.peer != rhs.peer { + if lhs.icon != rhs.icon { return false } if lhs.text != rhs.text { @@ -61,6 +73,7 @@ final class MessageItemComponent: Component { private let container: UIView private let background: GlassBackgroundView private let avatarNode: AvatarNode + private let icon: ComponentView private let text: ComponentView weak var standaloneReactionAnimation: StandaloneReactionAnimation? @@ -74,6 +87,7 @@ final class MessageItemComponent: Component { self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 12.0)) + self.icon = ComponentView() self.text = ComponentView() super.init(frame: frame) @@ -107,8 +121,8 @@ final class MessageItemComponent: Component { transition.setPosition(view: textSnapshotView, position: CGPoint(x: textSnapshotView.center.x + 71.0, y: textSnapshotView.center.y)) let initialSize = self.background.frame.size - self.background.update(size: globalFrame.size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .panel, color: UIColor(rgb: 0x4d4f5c, alpha: 0.6)), transition: .immediate) - self.background.update(size: initialSize, cornerRadius: 18.0, isDark: true, tintColor: .init(kind: .panel, color: UIColor(rgb: 0x4d4f5c, alpha: 0.6)), transition: transition) + self.background.update(size: globalFrame.size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: .immediate) + self.background.update(size: initialSize, cornerRadius: 18.0, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: transition) let deltaX = (globalFrame.width - self.container.frame.width) / 2.0 let deltaY = (globalFrame.height - self.container.frame.height) / 2.0 @@ -147,45 +161,51 @@ final class MessageItemComponent: Component { let textColor: UIColor = .white let linkColor: UIColor = UIColor(rgb: 0x59b6fa) - let minimalHeight: CGFloat = 36.0 + let minimalHeight: CGFloat = component.isNotification ? 50.0 : 36.0 let cornerRadius = minimalHeight * 0.5 - let avatarInset: CGFloat = 4.0 - let avatarSize = CGSize(width: minimalHeight - avatarInset * 2.0, height: minimalHeight - avatarInset * 2.0) + let avatarInset: CGFloat = component.isNotification ? 10.0 : 4.0 + let avatarSize = CGSize(width: component.isNotification ? 30.0 : 28.0, height: component.isNotification ? 30.0 : 28.0) let avatarSpacing: CGFloat = 10.0 + let iconSpacing: CGFloat = 10.0 let rightInset: CGFloat = 13.0 let avatarFrame = CGRect(origin: CGPoint(x: avatarInset, y: avatarInset), size: avatarSize) - if component.peer.smallProfileImage != nil { - self.avatarNode.setPeerV2( - context: component.context, - theme: theme, - peer: component.peer, - authorOfMessage: nil, - overrideImage: nil, - emptyColor: nil, - clipStyle: .round, - synchronousLoad: true, - displayDimensions: avatarSize - ) - } else { - self.avatarNode.setPeer( - context: component.context, - theme: theme, - peer: component.peer, - clipStyle: .round, - synchronousLoad: true, - displayDimensions: avatarSize - ) + if case let .peer(peer) = component.icon { + if peer.smallProfileImage != nil { + self.avatarNode.setPeerV2( + context: component.context, + theme: theme, + peer: peer, + authorOfMessage: nil, + overrideImage: nil, + emptyColor: nil, + clipStyle: .round, + synchronousLoad: true, + displayDimensions: avatarSize + ) + } else { + self.avatarNode.setPeer( + context: component.context, + theme: theme, + peer: peer, + clipStyle: .round, + synchronousLoad: true, + displayDimensions: avatarSize + ) + } } if self.avatarNode.bounds.isEmpty { self.avatarNode.frame = avatarFrame } else { transition.setFrame(view: self.avatarNode.view, frame: avatarFrame) } - - var peerName = component.peer.compactDisplayTitle - if peerName.count > 40 { - peerName = "\(peerName.prefix(40))…" + + var peerName = "" + if !component.isNotification, case let .peer(peer) = component.icon { + peerName = peer.compactDisplayTitle + if peerName.count > 40 { + peerName = "\(peerName.prefix(40))…" + } } let text = component.text @@ -208,8 +228,32 @@ final class MessageItemComponent: Component { } } - let attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: textFont, boldItalicFont: boldTextFont, fixedFont: textFont, blockQuoteFont: textFont, message: nil, entityFiles: self.entityFiles).mutableCopy() as! NSMutableAttributedString - attributedText.insert(NSAttributedString(string: peerName + " ", font: boldTextFont, textColor: textColor), at: 0) + let attributedText: NSAttributedString + if component.isNotification { + attributedText = parseMarkdownIntoAttributedString( + text, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: linkColor), + linkAttribute: { _ in return nil } + ) + ) + } else { + let textWithAppliedEntities = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: textFont, boldItalicFont: boldTextFont, fixedFont: textFont, blockQuoteFont: textFont, message: nil, entityFiles: self.entityFiles).mutableCopy() as! NSMutableAttributedString + if !peerName.isEmpty { + textWithAppliedEntities.insert(NSAttributedString(string: peerName + " ", font: boldTextFont, textColor: textColor), at: 0) + } + attributedText = textWithAppliedEntities + } + + let spacing: CGFloat + switch component.icon { + case .peer: + spacing = avatarSpacing + case .icon: + spacing = iconSpacing + } let textSize = self.text.update( transition: transition, @@ -223,12 +267,28 @@ final class MessageItemComponent: Component { lineSpacing: 0.0 )), environment: {}, - containerSize: CGSize(width: availableSize.width - avatarInset - avatarSize.width - avatarSpacing - rightInset, height: .greatestFiniteMagnitude) + containerSize: CGSize(width: availableSize.width - avatarInset - avatarSize.width - spacing - rightInset, height: .greatestFiniteMagnitude) ) - let size = CGSize(width: avatarInset + avatarSize.width + avatarSpacing + textSize.width + rightInset, height: max(minimalHeight, textSize.height + 15.0)) + let size = CGSize(width: avatarInset + avatarSize.width + spacing + textSize.width + rightInset, height: max(minimalHeight, textSize.height + 15.0)) - let textFrame = CGRect(origin: CGPoint(x: avatarInset + avatarSize.width + avatarSpacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + if case let .icon(iconName) = component.icon { + let iconSize = self.icon.update( + transition: transition, + component: AnyComponent(BundleIconComponent(name: iconName, tintColor: .white)), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + let iconFrame = CGRect(origin: CGPoint(x: avatarInset, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.container.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + } + + let textFrame = CGRect(origin: CGPoint(x: avatarInset + avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) if let textView = self.text.view { if textView.superview == nil { self.container.addSubview(textView) @@ -238,7 +298,7 @@ final class MessageItemComponent: Component { transition.setFrame(view: self.container, frame: CGRect(origin: CGPoint(), size: size)) - self.background.update(size: size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .panel, color: UIColor(rgb: 0x4d4f5c, alpha: 0.6)), transition: transition) + self.background.update(size: size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: transition) transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) if isFirstTime, let availableReactions = component.availableReactions, let textView = self.text.view { diff --git a/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift index 3dcd378652..b51de73eef 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift @@ -10,17 +10,20 @@ import ReactionSelectionNode final class MessageListComponent: Component { struct Item: Equatable { let id: AnyHashable - let peer: EnginePeer + let icon: MessageItemComponent.Icon + let isNotification: Bool let text: String let entities: [MessageTextEntity] } class SendActionTransition { + public let randomId: Int64 public let textSnapshotView: UIView public let globalFrame: CGRect public let cornerRadius: CGFloat - init(textSnapshotView: UIView, globalFrame: CGRect, cornerRadius: CGFloat) { + init(randomId: Int64, textSnapshotView: UIView, globalFrame: CGRect, cornerRadius: CGFloat) { + self.randomId = randomId self.textSnapshotView = textSnapshotView self.globalFrame = globalFrame self.cornerRadius = cornerRadius @@ -141,7 +144,7 @@ final class MessageListComponent: Component { let previousContentHeight = self.scrollView.contentSize.height let wasAtBottom = self.isAtBottom(tolerance: 1.0) - let maxWidth: CGFloat = 300.0 + let maxWidth: CGFloat = 330.0 var measured: [(id: AnyHashable, size: CGSize, item: MessageListComponent.Item, itemTransition: ComponentTransition)] = [] measured.reserveCapacity(component.items.count) @@ -160,7 +163,8 @@ final class MessageListComponent: Component { transition: transition, component: AnyComponent(MessageItemComponent( context: component.context, - peer: item.peer, + icon: item.icon, + isNotification: item.isNotification, text: item.text, entities: item.entities, availableReactions: component.availableReactions @@ -182,7 +186,7 @@ final class MessageListComponent: Component { validKeys.insert(entry.id) if let itemView = self.itemViews[entry.id]?.view { var customAnimation = false - if entry.item.peer.id == component.context.account.peerId, let _ = self.nextSendActionTransition { + if let nextSendActionTransition = self.nextSendActionTransition, entry.id == AnyHashable(nextSendActionTransition.randomId) { customAnimation = true } let itemFrame = CGRect( diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 9f0a665baf..cb2bb907e9 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -1104,7 +1104,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } } - case let .call(isTerminated, _, _, _, _, _, _): + case let .call(isTerminated, _, _, _, _, _, _, _): if isTerminated { self.markAsCanBeRemoved() } @@ -2615,6 +2615,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { if (state.isCreator || self.stateValue.adminIds.contains(self.accountContext.account.peerId)) && state.defaultParticipantsAreMuted.canChange { self.stateValue.defaultParticipantMuteState = state.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted } + self.stateValue.messagesAreEnabled = state.messagesAreEnabled.isEnabled self.stateValue.recordingStartTimestamp = state.recordingStartTimestamp self.stateValue.title = state.title self.stateValue.scheduleTimestamp = state.scheduleTimestamp @@ -3907,6 +3908,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.participantsContext?.updateDefaultParticipantsAreMuted(isMuted: isMuted) } + public func updateMessagesEnabled(isEnabled: Bool) { + self.participantsContext?.updateMessagesEnabled(isEnabled: isEnabled) + } + func video(endpointId: String) -> Signal? { return Signal { [weak self] subscriber in guard let self else { @@ -3997,9 +4002,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { }) } - public func sendMessage(text: String, entities: [MessageTextEntity]) { + public func sendMessage(randomId: Int64? = nil, text: String, entities: [MessageTextEntity]) { if let messagesContext = self.messagesContext { - messagesContext.send(fromId: self.joinAsPeerId, text: text, entities: entities) + messagesContext.send(fromId: self.joinAsPeerId, randomId: randomId, text: text, entities: entities) } } } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift index d809a49d04..f9865647a8 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift @@ -36,6 +36,7 @@ final class VideoChatActionButtonComponent: Component { case video case rotateCamera case message + case share case leave } @@ -43,6 +44,7 @@ final class VideoChatActionButtonComponent: Component { case video(isActive: Bool) case rotateCamera case message + case share case leave fileprivate var iconType: IconType { @@ -64,6 +66,8 @@ final class VideoChatActionButtonComponent: Component { return .rotateCamera case .message: return .message + case .share: + return .share case .leave: return .leave } @@ -142,12 +146,11 @@ final class VideoChatActionButtonComponent: Component { let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2) - let genericBackgroundColor = UIColor(rgb: 0x2d2f38, alpha: 0.6) + let genericBackgroundColor = UIColor(rgb: 0x25272e, alpha: 0.72) let blueColor = UIColor(rgb: 0x21477d) let titleText: String let backgroundColor: UIColor - var tintColorKind: GlassBackgroundView.TintColor.Kind = .custom let iconDiameter: CGFloat var isEnabled: Bool = true switch component.content { @@ -183,9 +186,6 @@ final class VideoChatActionButtonComponent: Component { backgroundColor = genericBackgroundColor case .muted: backgroundColor = !isActive ? genericBackgroundColor : blueColor - if isActive { - tintColorKind = .custom - } case .unmuted: backgroundColor = !isActive ? genericBackgroundColor : UIColor(rgb: 0x34C659) case .raiseHand, .scheduled: @@ -193,8 +193,7 @@ final class VideoChatActionButtonComponent: Component { } iconDiameter = 58.0 case .rotateCamera: - //TODO:localize - titleText = "rotate" + titleText = component.strings.VoiceChat_Rotate switch component.microphoneState { case .connecting: backgroundColor = genericBackgroundColor @@ -207,14 +206,16 @@ final class VideoChatActionButtonComponent: Component { } iconDiameter = 44.0 case .message: - //TODO:localize - titleText = "message" + titleText = component.strings.VoiceChat_Message + backgroundColor = genericBackgroundColor + iconDiameter = 56.0 + case .share: + titleText = component.strings.VoiceChat_ShareButton backgroundColor = genericBackgroundColor iconDiameter = 56.0 case .leave: titleText = component.strings.VoiceChat_Leave - backgroundColor = UIColor(rgb: 0x4f1613) - tintColorKind = .custom + backgroundColor = UIColor(rgb: 0x330d0b) iconDiameter = 22.0 } @@ -246,6 +247,8 @@ final class VideoChatActionButtonComponent: Component { self.contentImage = UIImage(bundleImageName: "Call/CallSwitchCameraButton")?.precomposed().withRenderingMode(.alwaysTemplate) case .message: self.contentImage = UIImage(bundleImageName: "Call/CallMessageButton")?.precomposed().withRenderingMode(.alwaysTemplate) + case .share: + self.contentImage = UIImage(bundleImageName: "Call/CallShareButton")?.precomposed().withRenderingMode(.alwaysTemplate) case .leave: self.contentImage = generateImage(CGSize(width: 28.0, height: 28.0), opaque: false, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) @@ -291,7 +294,7 @@ final class VideoChatActionButtonComponent: Component { } } - self.background.update(size: size, cornerRadius: size.width * 0.5, isDark: true, tintColor: .init(kind: tintColorKind, color: backgroundColor), transition: tintTransition) + self.background.update(size: size, cornerRadius: size.width * 0.5, isDark: true, tintColor: .init(kind: .custom, color: backgroundColor), transition: tintTransition) transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) * 0.5), y: size.height + 5.0), size: titleSize) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatNotifications.swift b/submodules/TelegramCallsUI/Sources/VideoChatNotifications.swift new file mode 100644 index 0000000000..8fcf7a61de --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/VideoChatNotifications.swift @@ -0,0 +1,23 @@ +import Foundation +import SwiftSignalKit +import TelegramCore + +enum VideoChatNotificationIcon { + case peer(EnginePeer) + case icon(String) +} + +extension VideoChatScreenComponent.View { + func displayNotification(icon: VideoChatNotificationIcon, text: String, duration: Int32) { + let id = Int64.random(in: 0 ..< .max) + + let expiresOn = Int32(CFAbsoluteTimeGetCurrent()) + duration + self.messageNotifications.append((id: id, icon: icon, text: text, expiresOn: expiresOn)) + self.state?.updated(transition: .spring(duration: 0.4)) + + Queue.mainQueue().after(Double(duration)) { + self.messageNotifications.removeAll(where: { $0.id == id }) + self.state?.updated(transition: .spring(duration: 0.4)) + } + } +} diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 81a53afd43..ce0bed3673 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -158,6 +158,15 @@ extension VideoChatCall { conferenceSource.setCurrentAudioOutput(output) } } + + func setMessagesEnabled(isEnabled: Bool) { + switch self { + case let .group(group): + group.updateMessagesEnabled(isEnabled: isEnabled) + case .conferenceSource: + break + } + } } final class VideoChatScreenComponent: Component { @@ -239,7 +248,7 @@ final class VideoChatScreenComponent: Component { let videoControlButton = ComponentView() let leaveButton = ComponentView() let microphoneButton = ComponentView() - let messageButton = ComponentView() + var messageButton: ComponentView? let speakerButton = ComponentView() @@ -265,6 +274,7 @@ final class VideoChatScreenComponent: Component { weak var disappearingReactionContextNode: ReactionContextNode? weak var willDismissReactionContextNode: ReactionContextNode? + var messageNotifications: [(id: Int64, icon: VideoChatNotificationIcon, text: String, expiresOn: Int32)] = [] let messagesList = ComponentView() var messagesState: GroupCallMessagesContext.State? var messagesStateDisposable: Disposable? @@ -495,6 +505,9 @@ final class VideoChatScreenComponent: Component { if let messageListView = self.messagesList.view, view.isDescendant(of: messageListView) { return false } + if let inputPanelView = self.inputPanel.view, view.isDescendant(of: inputPanelView) { + return false + } } return true } @@ -1020,9 +1033,13 @@ final class VideoChatScreenComponent: Component { } private func onMessagePressed() { - self.inputPanelIsActive = true - self.state?.updated() - self.activateInput() + if let callState = self.callState, callState.messagesAreEnabled { + self.inputPanelIsActive = true + self.state?.updated() + self.activateInput() + } else if let inviteLinks = self.inviteLinks { + self.presentShare(inviteLinks) + } } private func onLeavePressed() { @@ -1371,7 +1388,7 @@ final class VideoChatScreenComponent: Component { }) } - private func sendInput() { + private func sendInput(randomId: Int64? = nil) { guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else { return } @@ -1385,11 +1402,11 @@ final class VideoChatScreenComponent: Component { return } let entities = generateTextEntities(text.string, enabledTypes: [.mention, .hashtag, .allUrl], currentEntities: generateChatInputTextEntities(text)) - call.sendMessage(text: text.string, entities: entities) + call.sendMessage(randomId: randomId, text: text.string, entities: entities) } inputPanelView.clearSendMessageInput(updateState: true) - + // self.currentInputMode = .text // if hasFirstResponder(self) { // self.endEditing(true) @@ -1824,7 +1841,8 @@ final class VideoChatScreenComponent: Component { } else { text = environment.strings.VoiceChat_DisplayAsSuccess(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string } - self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + self.displayNotification(icon: .peer(peer), text: text, duration: 3) + //self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) }) self.memberEventsDisposable?.dispose() @@ -1849,8 +1867,10 @@ final class VideoChatScreenComponent: Component { } if displayEvent { - let text = environment.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string - self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + let text = environment.strings.VoiceChat_PeerJoinedText("**\(event.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder))**").string + + self.displayNotification(icon: .peer(event.peer), text: text, duration: 3) + //self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) } } } else { @@ -2294,10 +2314,7 @@ final class VideoChatScreenComponent: Component { alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) } - var navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5) + navigationButtonInset, y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize) - if buttonsOnTheSide { - navigationRightButtonFrame.origin.x += 42.0 - } + let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5) + navigationButtonInset, y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize) if let navigationRightButtonView = self.navigationRightButton.view { if navigationRightButtonView.superview == nil { self.containerView.addSubview(navigationRightButtonView) @@ -2368,16 +2385,15 @@ final class VideoChatScreenComponent: Component { var idleTitleStatusText: [AnimatedTextComponent.Item] = [] if let callState = self.callState { if callState.networkState == .connected, let members = self.members { - //TODO:localize let totalCount = max(1, members.totalCount) idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .number(totalCount, minDigits: 0))) - idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(1), isUnbreakable: false, content: .text(totalCount == 1 ? " participant" : " participants"))) + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(1), isUnbreakable: false, content: .text(environment.strings.VoiceChat_SubtitleParticipants(Int32(totalCount))))) if let lastTitleEvent = self.lastTitleEvent { idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(6), isUnbreakable: false, content: .text(", \(lastTitleEvent)"))) } else if !self.invitedPeers.isEmpty { idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(3), isUnbreakable: true, content: .text(", "))) idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(4), isUnbreakable: false, content: .number(self.invitedPeers.count, minDigits: 0))) - idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(5), isUnbreakable: false, content: .text(" invited"))) + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(5), isUnbreakable: false, content: .text(environment.strings.VoiceChat_SubtitleInvited(Int32(self.invitedPeers.count))))) } } else if callState.scheduleTimestamp != nil { idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .text(environment.strings.VoiceChat_Scheduled))) @@ -2557,6 +2573,7 @@ final class VideoChatScreenComponent: Component { let videoButtonContent: VideoChatActionButtonComponent.Content? let videoControlButtonContent: VideoChatActionButtonComponent.Content + let messageButtonContent: VideoChatActionButtonComponent.Content? var buttonAudio: VideoChatActionButtonComponent.Content.Audio = .speaker var buttonIsEnabled = false @@ -2599,12 +2616,27 @@ final class VideoChatScreenComponent: Component { } } - + if let callState = self.callState, !callState.messagesAreEnabled { + if let _ = self.inviteLinks { + messageButtonContent = .share + } else { + messageButtonContent = nil + } + } else { + messageButtonContent = .message + } + let actionButtonDiameter: CGFloat = 56.0 let expandedMicrophoneButtonDiameter: CGFloat = actionButtonDiameter let collapsedMicrophoneButtonDiameter: CGFloat = actionButtonDiameter // 116.0 - let buttonsCount = videoButtonContent == nil ? 4 : 5 + var buttonsCount = 3 + if let _ = videoButtonContent { + buttonsCount += 1 + } + if let _ = messageButtonContent { + buttonsCount += 1 + } let buttonsWidth: CGFloat = actionButtonDiameter * CGFloat(buttonsCount) let remainingButtonsSpace: CGFloat @@ -2688,21 +2720,42 @@ final class VideoChatScreenComponent: Component { var thirdActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: actionButtonSize) if buttonsCount == 4 { - if buttonsOnTheSide { - firstActionButtonFrame.origin.x = microphoneButtonFrame.minX - secondActionButtonFrame.origin.x = microphoneButtonFrame.minX - thirdActionButtonFrame.origin.x = microphoneButtonFrame.minX - fourthActionButtonFrame.origin.x = microphoneButtonFrame.minX - - microphoneButtonFrame.origin.y = availableSize.height * 0.5 - landscapeControlsSpacing * 0.5 - actionButtonDiameter - firstActionButtonFrame.origin.y = microphoneButtonFrame.minY - landscapeControlsSpacing - actionButtonDiameter - thirdActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing - fourthActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing + actionButtonDiameter + landscapeControlsSpacing - } else { - microphoneButtonFrame.origin.x = availableSize.width * 0.5 - actionMicrophoneButtonSpacing * 0.5 - actionButtonDiameter - firstActionButtonFrame.origin.x = microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter - thirdActionButtonFrame.origin.x = microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing - fourthActionButtonFrame.origin.x = microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing + actionButtonDiameter + actionMicrophoneButtonSpacing + if let _ = messageButtonContent { + if buttonsOnTheSide { + firstActionButtonFrame.origin.x = microphoneButtonFrame.minX + secondActionButtonFrame.origin.x = microphoneButtonFrame.minX + thirdActionButtonFrame.origin.x = microphoneButtonFrame.minX + fourthActionButtonFrame.origin.x = microphoneButtonFrame.minX + + microphoneButtonFrame.origin.y = availableSize.height * 0.5 - landscapeControlsSpacing * 0.5 - actionButtonDiameter + firstActionButtonFrame.origin.y = microphoneButtonFrame.minY - landscapeControlsSpacing - actionButtonDiameter + thirdActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing + fourthActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing + actionButtonDiameter + landscapeControlsSpacing + } else { + microphoneButtonFrame.origin.x = availableSize.width * 0.5 - actionMicrophoneButtonSpacing * 0.5 - actionButtonDiameter + firstActionButtonFrame.origin.x = microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter + thirdActionButtonFrame.origin.x = microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing + fourthActionButtonFrame.origin.x = microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing + actionButtonDiameter + actionMicrophoneButtonSpacing + } + } + if let _ = videoButtonContent { + if buttonsOnTheSide { + firstActionButtonFrame.origin.x = microphoneButtonFrame.minX + secondActionButtonFrame.origin.x = microphoneButtonFrame.minX + thirdActionButtonFrame.origin.x = microphoneButtonFrame.minX + fourthActionButtonFrame.origin.x = microphoneButtonFrame.minX + + microphoneButtonFrame.origin.y = availableSize.height * 0.5 - landscapeControlsSpacing * 0.5 - actionButtonDiameter + firstActionButtonFrame.origin.y = microphoneButtonFrame.minY - landscapeControlsSpacing - actionButtonDiameter + thirdActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing + fourthActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing + actionButtonDiameter + landscapeControlsSpacing + } else { + microphoneButtonFrame.origin.x = availableSize.width * 0.5 + actionMicrophoneButtonSpacing * 0.5 + firstActionButtonFrame.origin.x = microphoneButtonFrame.minX - actionMicrophoneButtonSpacing * 2.0 - actionButtonDiameter * 2.0 + secondActionButtonFrame.origin.x = microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter + //thirdActionButtonFrame.origin.x = microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing + fourthActionButtonFrame.origin.x = microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing + } } } else if buttonsOnTheSide { firstActionButtonFrame.origin.x = microphoneButtonFrame.minX @@ -3127,7 +3180,6 @@ final class VideoChatScreenComponent: Component { transition.setPosition(view: microphoneButtonView, position: microphoneButtonFrame.center) transition.setBounds(view: microphoneButtonView, bounds: CGRect(origin: CGPoint(), size: microphoneButtonFrame.size)) } - let _ = self.speakerButton.update( transition: transition, @@ -3202,8 +3254,12 @@ final class VideoChatScreenComponent: Component { ) if let videoButtonView = videoButton.view { if videoButtonView.superview == nil { - if let speakerButtonView = self.speakerButton.view { - self.containerView.insertSubview(videoButtonView, belowSubview: speakerButtonView) + if let microphoneButtonView = self.microphoneButton.view { + self.containerView.insertSubview(videoButtonView, aboveSubview: microphoneButtonView) + } + if !transition.animation.isImmediate { + transition.animateScale(view: videoButtonView, from: 0.01, to: 1.0) + transition.animateAlpha(view: videoButtonView, from: 0.0, to: 1.0) } } videoButtonTransition.setPosition(view: videoButtonView, position: secondActionButtonFrame.center) @@ -3213,40 +3269,64 @@ final class VideoChatScreenComponent: Component { self.videoButton = nil if let videoButtonView = videoButton.view { let transition = ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut)) - transition.animateScale(view: videoButtonView, from: 1.0, to: 0.01) - transition.animateAlpha(view: videoButtonView, from: 1.0, to: 0.0, completion: { _ in + transition.setScale(view: videoButtonView, scale: 0.01) + transition.setAlpha(view: videoButtonView, alpha: 0.0, completion: { _ in videoButtonView.removeFromSuperview() }) } } - let _ = self.messageButton.update( - transition: transition, - component: AnyComponent(PlainButtonComponent( - content: AnyComponent(VideoChatActionButtonComponent( - strings: environment.strings, - content: .message, - microphoneState: actionButtonMicrophoneState, - isCollapsed: areButtonsActuallyCollapsed || buttonsOnTheSide - )), - effectAlignment: .center, - action: { [weak self] in - guard let self else { - return - } - self.onMessagePressed() - }, - animateAlpha: false - )), - environment: {}, - containerSize: CGSize(width: actionButtonDiameter, height: actionButtonDiameter) - ) - if let messageButtonView = self.messageButton.view { - if messageButtonView.superview == nil { - self.containerView.addSubview(messageButtonView) + if let messageButtonContent { + var messageButtonTransition = transition + let messageButton: ComponentView + if let current = self.messageButton { + messageButton = current + } else { + messageButtonTransition = .immediate + messageButton = ComponentView() + self.messageButton = messageButton + } + + let _ = messageButton.update( + transition: messageButtonTransition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(VideoChatActionButtonComponent( + strings: environment.strings, + content: messageButtonContent, + microphoneState: actionButtonMicrophoneState, + isCollapsed: areButtonsActuallyCollapsed || buttonsOnTheSide + )), + effectAlignment: .center, + action: { [weak self] in + self?.onMessagePressed() + }, + animateAlpha: false + )), + environment: {}, + containerSize: CGSize(width: actionButtonDiameter, height: actionButtonDiameter) + ) + if let messageButtonView = messageButton.view { + if messageButtonView.superview == nil { + if let microphoneButtonView = self.microphoneButton.view { + self.containerView.insertSubview(messageButtonView, aboveSubview: microphoneButtonView) + } + if !transition.animation.isImmediate { + transition.animateScale(view: messageButtonView, from: 0.01, to: 1.0) + transition.animateAlpha(view: messageButtonView, from: 0.0, to: 1.0) + } + } + messageButtonTransition.setPosition(view: messageButtonView, position: thirdActionButtonFrame.center) + messageButtonTransition.setBounds(view: messageButtonView, bounds: CGRect(origin: CGPoint(), size: thirdActionButtonFrame.size)) + } + } else if let messageButton = self.messageButton, messageButton.view?.superview != nil { + self.messageButton = nil + if let messageButtonView = messageButton.view { + let transition = ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut)) + transition.setScale(view: messageButtonView, scale: 0.01) + transition.setAlpha(view: messageButtonView, alpha: 0.0, completion: { _ in + messageButtonView.removeFromSuperview() + }) } - transition.setPosition(view: messageButtonView, position: thirdActionButtonFrame.center) - transition.setBounds(view: messageButtonView, bounds: CGRect(origin: CGPoint(), size: thirdActionButtonFrame.size)) } let _ = self.leaveButton.update( @@ -3399,7 +3479,6 @@ final class VideoChatScreenComponent: Component { characterLimit = Int(value) } - //TODO:localize self.inputPanel.parentState = state inputPanelSize = self.inputPanel.update( transition: transition, @@ -3409,7 +3488,7 @@ final class VideoChatScreenComponent: Component { theme: environment.theme, strings: environment.strings, style: .glass, - placeholder: .plain("Message"), + placeholder: .plain(environment.strings.VoiceChat_MessagePlaceholder), sendPaidMessageStars: nil, maxLength: characterLimit, queryTypes: [.mention, .hashtag], @@ -3419,10 +3498,8 @@ final class VideoChatScreenComponent: Component { nextInputMode: { _ in return nextInputMode }, areVoiceMessagesAvailable: false, presentController: { c in - //controller.present(c, in: .window(.root)) }, presentInGlobalOverlay: { c in - //controller.presentInGlobalOverlay(c) }, sendMessageAction: { [weak self] transition in guard let self else { @@ -3431,7 +3508,7 @@ final class VideoChatScreenComponent: Component { if self.inputPanelExternalState.hasText { self.nextSendMessageTransition = transition - self.sendInput() + self.sendInput(randomId: transition?.randomId) } else { self.inputPanelIsActive = false self.deactivateInput() @@ -3783,7 +3860,10 @@ final class VideoChatScreenComponent: Component { if let next = self.nextSendMessageTransition { self.nextSendMessageTransition = nil sendActionTransition = MessageListComponent.SendActionTransition( - textSnapshotView: next.textSnapshotView, globalFrame: next.globalFrame, cornerRadius: next.cornerRadius + randomId: next.randomId, + textSnapshotView: next.textSnapshotView, + globalFrame: next.globalFrame, + cornerRadius: next.cornerRadius ) } @@ -3796,13 +3876,34 @@ final class VideoChatScreenComponent: Component { messageItems.append( MessageListComponent.Item( id: message.id, - peer: author, + icon: .peer(author), + isNotification: false, text: message.text, entities: message.entities ) ) } } + + for notification in self.messageNotifications { + let icon: MessageItemComponent.Icon + switch notification.icon { + case let .peer(peer): + icon = .peer(peer) + case let .icon(name): + icon = .icon(name) + } + messageItems.insert( + MessageListComponent.Item( + id: notification.id, + icon: icon, + isNotification: true, + text: notification.text, + entities: [] + ), + at: 0 + ) + } let normalMessagesBottomInset: CGFloat = buttonsOnTheSide ? 16.0 : availableSize.height - microphoneButtonFrame.minY + 16.0 let messagesBottomInset: CGFloat = max(inputPanelBottomInset + inputPanelSize.height + 31.0, normalMessagesBottomInset) + reactionsInset diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift index 92ff915aab..0d95cca58d 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift @@ -227,6 +227,29 @@ extension VideoChatScreenComponent.View { } self.openTitleEditing() }))) + + items.append(.action(ContextMenuActionItem(text: callState.messagesAreEnabled ? environment.strings.VoiceChat_ContextDisableMessages : environment.strings.VoiceChat_ContextEnableMessages, icon: { theme -> UIImage? in + return generateTintedImage(image: UIImage(bundleImageName: callState.messagesAreEnabled ? "Call/MessagesDisable" : "Call/MessagesEnable"), color: theme.actionSheet.primaryTextColor) + }, action: { [weak self] _, f in + f(.default) + + guard let self, let currentCall = self.currentCall else { + return + } + let isEnabled = !callState.messagesAreEnabled + currentCall.setMessagesEnabled(isEnabled: isEnabled) + + let iconName: String + let text: String + if isEnabled { + iconName = "Call/ToastMessagesEnabled" + text = environment.strings.VoiceChat_ToastMessagesEnabled + } else { + iconName = "Call/ToastMessagesDisabled" + text = environment.strings.VoiceChat_ToastMessagesDisabled + } + self.displayNotification(icon: .icon(iconName), text: text, duration: 3) + }))) var hasPermissions = true if let peer = self.peer, case let .channel(chatPeer) = peer { diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 8183995e6d..3364d2462d 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -4909,9 +4909,12 @@ func replayFinalState( let canChange = (flags & (1 << 2)) != 0 let isVideoEnabled = (flags & (1 << 9)) != 0 let defaultParticipantsAreMuted = GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: isMuted, canChange: canChange) + let messagesEnabled = (flags & (1 << 17)) != 0 + let canChangeMessagesEnabled = (flags & (1 << 18)) != 0 + let messagesAreEnabled = GroupCallParticipantsContext.State.MessagesAreEnabled(isEnabled: messagesEnabled, canChange: canChangeMessagesEnabled) updatedGroupCallParticipants.append(( info.id, - .call(isTerminated: false, defaultParticipantsAreMuted: defaultParticipantsAreMuted, title: title, recordingStartTimestamp: recordStartDate, scheduleTimestamp: scheduleDate, isVideoEnabled: isVideoEnabled, participantCount: Int(participantsCount)) + .call(isTerminated: false, defaultParticipantsAreMuted: defaultParticipantsAreMuted, messagesAreEnabled: messagesAreEnabled, title: title, recordingStartTimestamp: recordStartDate, scheduleTimestamp: scheduleDate, isVideoEnabled: isVideoEnabled, participantCount: Int(participantsCount)) )) default: break @@ -4920,7 +4923,7 @@ func replayFinalState( case let .groupCallDiscarded(callId, _, _): updatedGroupCallParticipants.append(( callId, - .call(isTerminated: true, defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false), title: nil, recordingStartTimestamp: nil, scheduleTimestamp: nil, isVideoEnabled: false, participantCount: nil) + .call(isTerminated: true, defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false), messagesAreEnabled: GroupCallParticipantsContext.State.MessagesAreEnabled(isEnabled: false, canChange: false), title: nil, recordingStartTimestamp: nil, scheduleTimestamp: nil, isVideoEnabled: false, participantCount: nil) )) if let peerId { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index 9fd25dd6c2..37fb9dfe0f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -766,7 +766,10 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, let isMuted = (flags & (1 << 1)) != 0 let canChange = (flags & (1 << 2)) != 0 let isVideoEnabled = (flags & (1 << 9)) != 0 + let messagesEnabled = (flags & (1 << 17)) != 0 + let canChangeMessagesEnabled = (flags & (1 << 18)) != 0 state.defaultParticipantsAreMuted = GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: isMuted, canChange: canChange) + state.messagesAreEnabled = GroupCallParticipantsContext.State.MessagesAreEnabled(isEnabled: messagesEnabled, canChange: canChangeMessagesEnabled) state.title = title state.recordingStartTimestamp = recordStartDate state.scheduleTimestamp = scheduleDate @@ -1583,7 +1586,7 @@ public final class GroupCallParticipantsContext { } case state(update: StateUpdate) - case call(isTerminated: Bool, defaultParticipantsAreMuted: State.DefaultParticipantsAreMuted, title: String?, recordingStartTimestamp: Int32?, scheduleTimestamp: Int32?, isVideoEnabled: Bool, participantCount: Int?) + case call(isTerminated: Bool, defaultParticipantsAreMuted: State.DefaultParticipantsAreMuted, messagesAreEnabled: State.MessagesAreEnabled, title: String?, recordingStartTimestamp: Int32?, scheduleTimestamp: Int32?, isVideoEnabled: Bool, participantCount: Int?) case conferenceChainBlocks(subChainId: Int, blocks: [Data], nextOffset: Int) } @@ -1961,9 +1964,10 @@ public final class GroupCallParticipantsContext { for update in updates { if case let .state(update) = update { stateUpdates.append(update) - } else if case let .call(_, defaultParticipantsAreMuted, title, recordingStartTimestamp, scheduleTimestamp, isVideoEnabled, participantsCount) = update { + } else if case let .call(_, defaultParticipantsAreMuted, messagesAreEnabled, title, recordingStartTimestamp, scheduleTimestamp, isVideoEnabled, participantsCount) = update { var state = self.stateValue.state state.defaultParticipantsAreMuted = defaultParticipantsAreMuted + state.messagesAreEnabled = messagesAreEnabled state.recordingStartTimestamp = recordingStartTimestamp state.title = title state.scheduleTimestamp = scheduleTimestamp @@ -2336,6 +2340,7 @@ public final class GroupCallParticipantsContext { state.adminIds = strongSelf.stateValue.state.adminIds state.isCreator = strongSelf.stateValue.state.isCreator state.defaultParticipantsAreMuted = strongSelf.stateValue.state.defaultParticipantsAreMuted + state.messagesAreEnabled = strongSelf.stateValue.state.messagesAreEnabled state.title = strongSelf.stateValue.state.title state.recordingStartTimestamp = strongSelf.stateValue.state.recordingStartTimestamp state.scheduleTimestamp = strongSelf.stateValue.state.scheduleTimestamp @@ -2583,12 +2588,12 @@ public final class GroupCallParticipantsContext { } public func updateMessagesEnabled(isEnabled: Bool) { - if isEnabled == self.stateValue.state.messagesAreEnabled.isEnabled { - return - } +// if isEnabled == self.stateValue.state.messagesAreEnabled.isEnabled { +// return +// } self.stateValue.state.messagesAreEnabled.isEnabled = isEnabled - self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 2, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: isEnabled ? .boolTrue : .boolFalse)) + self.updateMessagesEnabledDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 2, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: isEnabled ? .boolTrue : .boolFalse)) |> deliverOnMainQueue).start(next: { [weak self] updates in guard let strongSelf = self else { return @@ -3636,7 +3641,7 @@ public final class GroupCallMessagesContext { } } - func send(fromId: EnginePeer.Id, text: String, entities: [MessageTextEntity]) { + func send(fromId: EnginePeer.Id, randomId: Int64?, text: String, entities: [MessageTextEntity]) { let _ = (self.account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(fromId) } @@ -3712,9 +3717,9 @@ public final class GroupCallMessagesContext { }) } - public func send(fromId: EnginePeer.Id, text: String, entities: [MessageTextEntity]) { + public func send(fromId: EnginePeer.Id, randomId: Int64?, text: String, entities: [MessageTextEntity]) { self.impl.with { impl in - impl.send(fromId: fromId, text: text, entities: entities) + impl.send(fromId: fromId, randomId: randomId, text: text, entities: entities) } } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index f21bc61c56..75b72606ed 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1260,9 +1260,13 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes) } } - case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, _): + case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned): if case let .unique(gift) = gift { - if !forAdditionalServiceMessage && !"".isEmpty { + if assigned { + let attributes: [Int: MarkdownAttributeSet] = [0: boldAttributes] + let giftTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))" + attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Assigned(giftTitle)._tuple, body: bodyAttributes, argumentAttributes: attributes) + } else if !forAdditionalServiceMessage && !"".isEmpty { attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor) } else if let messagePeer = message.peers[message.id.peerId] { var peerName = EnginePeer(messagePeer).compactDisplayTitle diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index 5b003f28b9..f8588251df 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -462,12 +462,11 @@ public final class ButtonComponent: Component { if let self, let component = self.component, component.isEnabled { switch component.background.style { case .glass: - let transition = ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)) + let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring)) if highlighted { let highlightedColor = component.background.color.withMultiplied(hue: 1.0, saturation: 0.77, brightness: 1.01) transition.setBackgroundColor(view: self.containerView, color: highlightedColor) transition.setScale(view: self.containerView, scale: 1.05) - } else { transition.setBackgroundColor(view: self.containerView, color: component.background.color) transition.setScale(view: self.containerView, scale: 1.0) diff --git a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift index f360dbcdea..868119c776 100644 --- a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift @@ -529,14 +529,13 @@ public final class MessageInputActionButtonComponent: Component { self.backgroundView = backgroundView } - var tintColor = UIColor(rgb: 0x4d4f5c, alpha: 0.6) - var tintKind: GlassBackgroundView.TintColor.Kind = .panel + var tintColor = UIColor(rgb: 0x25272e, alpha: 0.72) + let tintKind: GlassBackgroundView.TintColor.Kind = .custom if case .send = component.mode { tintColor = UIColor(rgb: 0x029dff) - tintKind = .custom } let buttonSize = CGSize(width: 40.0, height: 40.0) - backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height / 2.0, isDark: false, tintColor: .init(kind: tintKind, color: tintColor), transition: transition) + backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height / 2.0, isDark: true, tintColor: .init(kind: tintKind, color: tintColor), transition: transition) backgroundView.frame = CGRect(origin: .zero, size: buttonSize) } diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index f7928f2ea8..3476b86958 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -157,11 +157,13 @@ public final class MessageInputPanelComponent: Component { } public final class SendActionTransition { + public let randomId: Int64 public let textSnapshotView: UIView public let globalFrame: CGRect public let cornerRadius: CGFloat - init(textSnapshotView: UIView, globalFrame: CGRect, cornerRadius: CGFloat) { + init(randomId: Int64, textSnapshotView: UIView, globalFrame: CGRect, cornerRadius: CGFloat) { + self.randomId = randomId self.textSnapshotView = textSnapshotView self.globalFrame = globalFrame self.cornerRadius = cornerRadius @@ -785,6 +787,7 @@ public final class MessageInputPanelComponent: Component { var sendActionTransition: MessageInputPanelComponent.SendActionTransition? if let snapshotView = self.textClippingView.snapshotView(afterScreenUpdates: false), let backgroundView = self.fieldGlassBackgroundView { sendActionTransition = MessageInputPanelComponent.SendActionTransition( + randomId: Int64.random(in: .min ..< .max), textSnapshotView: snapshotView, globalFrame: backgroundView.convert(backgroundView.bounds, to: nil), cornerRadius: baseFieldHeight * 0.5 @@ -1101,7 +1104,7 @@ public final class MessageInputPanelComponent: Component { self.fieldBackgroundTint.isHidden = true } if let fieldGlassBackgroundView = self.fieldGlassBackgroundView { - fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: .init(kind: .panel, color: UIColor(rgb: 0x4d4f5c, alpha: 0.6)), transition: transition) + fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x25272e, alpha: 0.72)), transition: transition) transition.setFrame(view: fieldGlassBackgroundView, frame: fieldBackgroundFrame) } default: @@ -1348,7 +1351,10 @@ public final class MessageInputPanelComponent: Component { environment: {}, containerSize: availableTextFieldSize ) - let counterFrame = CGRect(origin: CGPoint(x: availableSize.width - insets.right + floorToScreenPixels((insets.right - counterSize.width) * 0.5), y: size.height - insets.bottom - baseFieldHeight - counterSize.height - 5.0), size: counterSize) + var counterFrame = CGRect(origin: CGPoint(x: availableSize.width - insets.right + floorToScreenPixels((insets.right - counterSize.width) * 0.5), y: size.height - insets.bottom - baseFieldHeight - counterSize.height - 5.0), size: counterSize) + if case .glass = component.style { + counterFrame.origin.x -= 7.0 + } if let counterView = self.counter.view { if counterView.superview == nil { self.addSubview(counterView) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 56e8b5201d..1c51a71f3a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -6680,7 +6680,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if !headerButtons.contains(.discussion) && hasDiscussion { //TODO:localize - items.append(.action(ContextMenuActionItem(text: "View Discussion", icon: { theme in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ViewDiscussion, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.dismissWithoutContent) diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift index 047dfe85de..c24f054aeb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift @@ -480,14 +480,13 @@ private final class ProfileLevelInfoScreenComponent: Component { levelFraction = max(0.0, levelFraction) - //TODO:localize let levelInfoSize = self.levelInfo.update( transition: isChangingPreview ? ComponentTransition.immediate.withUserData(ProfileLevelRatingBarComponent.TransitionHint(animate: true)) : .immediate, component: AnyComponent(ProfileLevelRatingBarComponent( theme: environment.theme, value: levelFraction, leftLabel: currentLevel < 0 ? "" : environment.strings.ProfileLevelInfo_LevelIndex(Int32(currentLevel)), - rightLabel: currentLevel < 0 ? "Negative rating" : nextLevel.flatMap { environment.strings.ProfileLevelInfo_LevelIndex(Int32($0)) } ?? "", + rightLabel: currentLevel < 0 ? environment.strings.ProfileLevelInfo_NegativeRating : nextLevel.flatMap { environment.strings.ProfileLevelInfo_LevelIndex(Int32($0)) } ?? "", badgeValue: badgeText, badgeTotal: badgeTextSuffix, level: Int(currentLevel), diff --git a/submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/Contents.json new file mode 100644 index 0000000000..c603e6f2eb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "disablemessages_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/disablemessages_24.pdf b/submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/disablemessages_24.pdf new file mode 100644 index 0000000000..a8574eb66e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/disablemessages_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/Contents.json new file mode 100644 index 0000000000..f55ee0499b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "enablemessages_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/enablemessages_24.pdf b/submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/enablemessages_24.pdf new file mode 100644 index 0000000000..c8c915b4fd Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/enablemessages_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/Contents.json new file mode 100644 index 0000000000..ea4ae18d46 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "toastmessagesdisabled_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/toastmessagesdisabled_30.pdf b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/toastmessagesdisabled_30.pdf new file mode 100644 index 0000000000..0ca6b3052b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/toastmessagesdisabled_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/Contents.json new file mode 100644 index 0000000000..f793a4d80d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "toastmessagesenabled_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/toastmessagesenabled_30.pdf b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/toastmessagesenabled_30.pdf new file mode 100644 index 0000000000..cce6990a97 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/toastmessagesenabled_30.pdf differ