mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
b8bf673c5f
commit
4657ac752d
27 changed files with 462 additions and 160 deletions
|
|
@ -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.";
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import GZip
|
|||
import BalancedTextComponent
|
||||
import Markdown
|
||||
import PremiumStarComponent
|
||||
import GlassBackgroundComponent
|
||||
|
||||
public final class ReactionItem {
|
||||
public struct Reaction: Equatable {
|
||||
|
|
|
|||
|
|
@ -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<Empty>
|
||||
private let text: ComponentView<Empty>
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<OngoingGroupCallContext.VideoFrameData, NoError>? {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Empty>()
|
||||
let leaveButton = ComponentView<Empty>()
|
||||
let microphoneButton = ComponentView<Empty>()
|
||||
let messageButton = ComponentView<Empty>()
|
||||
var messageButton: ComponentView<Empty>?
|
||||
|
||||
let speakerButton = ComponentView<Empty>()
|
||||
|
||||
|
|
@ -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<Empty>()
|
||||
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<Empty>
|
||||
if let current = self.messageButton {
|
||||
messageButton = current
|
||||
} else {
|
||||
messageButtonTransition = .immediate
|
||||
messageButton = ComponentView<Empty>()
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "disablemessages_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/disablemessages_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/MessagesDisable.imageset/disablemessages_24.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "enablemessages_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/enablemessages_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/MessagesEnable.imageset/enablemessages_24.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/ToastMessagesDisabled.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "toastmessagesdisabled_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "toastmessagesenabled_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/toastmessagesenabled_30.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/ToastMessagesEnabled.imageset/toastmessagesenabled_30.pdf
vendored
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue