This commit is contained in:
Isaac 2025-09-29 00:52:52 +08:00
commit 3fe6791076
19 changed files with 199 additions and 103 deletions

View file

@ -15132,3 +15132,19 @@ Error: %8$@";
"Notification.StarsGift.Assigned" = "You started displaying %@ on your Telegram profile page.";
"Gift.View.SenderInfo" = "**%@** sent you this gift on **%@**.";
"VideoChat.EncryptionKeyLabelShort" = "E2EE";
"VoiceChat.MuteShort" = "mute";
"Stars.Intro.Transaction.GiftDropOriginalDetails" = "Gift Description Removal";
"Stars.Intro.Transaction.PrepaidGiftUpgrade" = "Gift Upgrade";
"Gift.Upgrade.SeePriceDecrease" = "See how price will decrease >";
"ProfileColorSetup.ProfileColorPreviewInfo" = "You can also change color of your name and customize replies to you. [Change >]()";
"ProfileColorSetup.ApplyStyle" = "Apply Style";
"ProfileColorSetup.MyGifts" = "My Gifts";
"ProfileColorSetup.NoProfileGiftsPlaceholder" = "You don't have any gifts you can use as styles for your profile.";
"ProfileColorSetup.NoNameGiftsPlaceholder" = "You don't have any gifts you can use as styles for your name.";
"ProfileColorSetup.BrowseGiftsForPurchase" = "Browse gifts available for purchase >";

View file

@ -291,7 +291,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
case .iPhone16Pro, .iPhone16ProMax:
return 54.0
case .iPhoneAir:
return 54.0
return 57.0
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax:
return 44.0
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:

View file

@ -144,7 +144,7 @@ final class MessageListComponent: Component {
let previousContentHeight = self.scrollView.contentSize.height
let wasAtBottom = self.isAtBottom(tolerance: 1.0)
let maxWidth: CGFloat = 330.0
let maxWidth: CGFloat = min(availableSize.width - 16.0, 330.0)
var measured: [(id: AnyHashable, size: CGSize, item: MessageListComponent.Item, itemTransition: ComponentTransition)] = []
measured.reserveCapacity(component.items.count)

View file

@ -1105,7 +1105,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
}
case let .call(isTerminated, _, _, _, _, _, _, _):
case let .call(isTerminated, _, _, _, _, _, _, _, _):
if isTerminated {
self.markAsCanBeRemoved()
}

View file

@ -465,6 +465,7 @@ final class VideoChatEncryptionKeyComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let emoji: [String]
let isShort: Bool
let isExpanded: Bool
let tapAction: () -> Void
@ -472,12 +473,14 @@ final class VideoChatEncryptionKeyComponent: Component {
theme: PresentationTheme,
strings: PresentationStrings,
emoji: [String],
isShort: Bool,
isExpanded: Bool,
tapAction: @escaping () -> Void
) {
self.theme = theme
self.strings = strings
self.emoji = emoji
self.isShort = isShort
self.isExpanded = isExpanded
self.tapAction = tapAction
}
@ -492,6 +495,9 @@ final class VideoChatEncryptionKeyComponent: Component {
if lhs.emoji != rhs.emoji {
return false
}
if lhs.isShort != rhs.isShort {
return false
}
if lhs.isExpanded != rhs.isExpanded {
return false
}
@ -501,8 +507,7 @@ final class VideoChatEncryptionKeyComponent: Component {
final class View: UIView {
private let containerView: UIView
private var emojiItems: [ComponentView<Empty>] = []
private let background = ComponentView<Empty>()
private let backgroundShadowLayer = SimpleLayer()
private let backgroundView = GlassBackgroundView()
private let collapsedText = ComponentView<Empty>()
private let expandedText = ComponentView<Empty>()
private let expandedSeparatorLayer = SimpleLayer()
@ -521,11 +526,12 @@ final class VideoChatEncryptionKeyComponent: Component {
override init(frame: CGRect) {
self.containerView = UIView()
self.containerView.clipsToBounds = true
//self.containerView.clipsToBounds = true
super.init(frame: frame)
self.addSubview(self.containerView)
self.containerView.addSubview(self.backgroundView)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.addGestureRecognizer(tapRecognizer)
@ -631,7 +637,7 @@ final class VideoChatEncryptionKeyComponent: Component {
let collapsedTextSize = self.collapsedText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.strings.VideoChat_EncryptionKeyLabel, font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor))
text: .plain(NSAttributedString(string: component.isShort ? component.strings.VideoChat_EncryptionKeyLabelShort : component.strings.VideoChat_EncryptionKeyLabel, font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 1000.0, height: 1000.0)
@ -688,36 +694,12 @@ final class VideoChatEncryptionKeyComponent: Component {
let backgroundSize = component.isExpanded ? expandedSize : collapsedSize
let backgroundCornerRadius: CGFloat = component.isExpanded ? 26.0 : collapsedSize.height * 0.5
let _ = self.background.update(
transition: transition,
component: AnyComponent(GlassBackgroundComponent(size: backgroundSize, cornerRadius: backgroundCornerRadius, isDark: true, tintColor: .init(kind: .panel, color: UIColor(rgb: 0x1f1f27)))),
environment: {},
containerSize: backgroundSize
)
let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize)
if self.backgroundShadowLayer.superlayer == nil {
self.backgroundShadowLayer.backgroundColor = UIColor.clear.cgColor
self.containerView.layer.addSublayer(self.backgroundShadowLayer)
}
self.backgroundShadowLayer.shadowOpacity = 0.3
self.backgroundShadowLayer.shadowColor = UIColor.black.cgColor
self.backgroundShadowLayer.shadowRadius = 5.0
self.backgroundShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
alphaTransition.setAlpha(layer: self.backgroundShadowLayer, alpha: component.isExpanded ? 1.0 : 0.0)
transition.setFrame(layer: self.backgroundShadowLayer, frame: backgroundFrame)
transition.setCornerRadius(layer: self.backgroundShadowLayer, cornerRadius: backgroundCornerRadius)
transition.setShadowPath(layer: self.backgroundShadowLayer, path: UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: backgroundFrame.size), cornerRadius: backgroundCornerRadius).cgPath)
if let backgroundView = self.background.view {
if backgroundView.superview == nil {
self.containerView.addSubview(backgroundView)
}
transition.setFrame(view: backgroundView, frame: backgroundFrame)
}
self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundCornerRadius, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x161616, alpha: 0.6)), transition: transition)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
var collapsedEmojiLeftOffset = collapsedSideInset
var collapsedEmojiRightOffset = collapsedSize.width - collapsedSideInset

View file

@ -365,7 +365,7 @@ final class VideoChatMicButtonComponent: Component {
titleText = component.strings.VoiceChat_Unmute
}
case let .unmuted(isPushToTalk):
titleText = isPushToTalk ? component.strings.VoiceChat_Live : component.strings.VoiceChat_Mute
titleText = isPushToTalk ? component.strings.VoiceChat_Live : component.strings.VoiceChat_MuteShort
case let .raiseHand(isRaised):
if isRaised {
titleText = component.strings.VoiceChat_AskedToSpeak

View file

@ -35,6 +35,7 @@ import TextFormat
import ReactionSelectionNode
import EntityKeyboard
import GlassBackgroundComponent
import EdgeEffect
extension VideoChatCall {
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
@ -255,6 +256,7 @@ final class VideoChatScreenComponent: Component {
let participants = ComponentView<Empty>()
var scheduleInfo: ComponentView<Empty>?
var inputDimView = UIButton()
var inputPanelIsActive = false
let inputPanel = ComponentView<Empty>()
let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
@ -274,7 +276,7 @@ final class VideoChatScreenComponent: Component {
weak var disappearingReactionContextNode: ReactionContextNode?
weak var willDismissReactionContextNode: ReactionContextNode?
var messageNotifications: [(id: Int64, icon: VideoChatNotificationIcon, text: String, expiresOn: Int32)] = []
var toastMessages: [(id: Int64, icon: VideoChatNotificationIcon, text: String, expiresOn: Int32)] = []
let messagesList = ComponentView<Empty>()
var messagesState: GroupCallMessagesContext.State?
var messagesStateDisposable: Disposable?
@ -1433,6 +1435,7 @@ final class VideoChatScreenComponent: Component {
guard let _ = self.inputPanel.view as? MessageInputPanelComponent.View else {
return
}
self.inputPanelIsActive = false
self.currentInputMode = .text
if hasFirstResponder(self) {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
@ -1842,7 +1845,7 @@ 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.displayNotification(icon: .peer(peer), text: text, duration: 3)
self.displayToast(icon: .peer(peer), text: text, duration: 3)
})
self.memberEventsDisposable?.dispose()
@ -1869,7 +1872,7 @@ 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.displayNotification(icon: .peer(event.peer), text: text, duration: 3)
self.displayToast(icon: .peer(event.peer), text: text, duration: 3)
}
}
} else {
@ -2196,6 +2199,8 @@ final class VideoChatScreenComponent: Component {
self.containerView.layer.cornerRadius = containerOffset.isZero ? 0.0 : environment.deviceMetrics.screenCornerRadius
}
transition.setFrame(view: self.inputDimView, frame: CGRect(origin: .zero, size: availableSize))
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: 0.0, y: containerOffset), size: availableSize), completion: { [weak self] completed in
guard let self, completed else {
return
@ -2253,7 +2258,7 @@ final class VideoChatScreenComponent: Component {
let navigationButtonDiameter: CGFloat = 40.0
let navigationButtonInset: CGFloat = 4.0
let panelColor = UIColor(rgb: 0x1f1f27)
let panelColor = UIColor(rgb: 0x161616, alpha: 0.6)
let navigationLeftButtonSize = self.navigationLeftButton.update(
transition: .immediate,
@ -2266,7 +2271,7 @@ final class VideoChatScreenComponent: Component {
size: CGSize(width: 34.0, height: 34.0)
)),
background: AnyComponent(
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), cornerRadius: navigationButtonDiameter * 0.5, isDark: true, tintColor: .init(kind: .panel, color: panelColor))
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), cornerRadius: navigationButtonDiameter * 0.5, isDark: true, tintColor: .init(kind: .custom, color: panelColor))
),
effectAlignment: .center,
minSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter),
@ -2289,7 +2294,7 @@ final class VideoChatScreenComponent: Component {
contentMode: .center
)),
background: AnyComponent(
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), cornerRadius: navigationButtonDiameter * 0.5, isDark: true, tintColor: .init(kind: .panel, color: panelColor))
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), cornerRadius: navigationButtonDiameter * 0.5, isDark: true, tintColor: .init(kind: .custom, color: panelColor))
),
effectAlignment: .center,
minSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter),
@ -2478,7 +2483,7 @@ final class VideoChatScreenComponent: Component {
areButtonsCollapsed = true
areButtonsActuallyCollapsed = false
mainColumnWidth = min(isLandscape ? 340.0 : 320.0, availableSize.width - leftInset - rightInset - 340.0)
mainColumnWidth = min(isLandscape ? 356.0 : 320.0, availableSize.width - leftInset - rightInset - 340.0)
mainColumnSideInset = 0.0
} else {
areButtonsCollapsed = true //self.expandedParticipantsVideoState != nil
@ -2520,6 +2525,7 @@ final class VideoChatScreenComponent: Component {
theme: environment.theme,
strings: environment.strings,
emoji: self.encryptionKeyEmoji ?? [],
isShort: isTwoColumnLayout,
isExpanded: self.isEncryptionKeyExpanded,
tapAction: { [weak self] in
guard let self else {
@ -2961,14 +2967,14 @@ final class VideoChatScreenComponent: Component {
self.containerView.addSubview(encryptionKeyView)
}
ComponentTransition.immediate.setScale(view: encryptionKeyView, scale: 0.001)
encryptionKeyView.alpha = 0.0
//ComponentTransition.immediate.setScale(view: encryptionKeyView, scale: 0.001)
// encryptionKeyView.alpha = 0.0
}
encryptionKeyTransition.setPosition(view: encryptionKeyView, position: encryptionKeyFrame.center)
encryptionKeyTransition.setBounds(view: encryptionKeyView, bounds: CGRect(origin: CGPoint(), size: encryptionKeyFrame.size))
transition.setScale(view: encryptionKeyView, scale: 1.0)
alphaTransition.setAlpha(view: encryptionKeyView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
// transition.setScale(view: encryptionKeyView, scale: 1.0)
// alphaTransition.setAlpha(view: encryptionKeyView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
transition.setZPosition(layer: encryptionKeyView.layer, zPosition: self.isEncryptionKeyExpanded ? 1.0 : 0.0)
@ -3359,7 +3365,22 @@ final class VideoChatScreenComponent: Component {
var inputPanelBottomInset: CGFloat = 0.0
var inputPanelSize: CGSize = .zero
if self.inputPanelIsActive {
let inputPanelAvailableWidth = availableSize.width - environment.safeInsets.left - environment.safeInsets.right
if self.inputDimView.superview == nil {
self.inputDimView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4)
self.inputDimView.addTarget(self, action: #selector(self.deactivateInput), for: .touchUpInside)
self.inputDimView.alpha = 0.0
if let messageListView = self.messagesList.view, messageListView.superview != nil {
self.containerView.insertSubview(self.inputDimView, belowSubview: messageListView)
} else {
self.containerView.addSubview(self.inputDimView)
}
}
ComponentTransition.easeInOut(duration: 0.25).setAlpha(view: self.inputDimView, alpha: 1.0)
let inputPanelInnerInset: CGFloat = 8.0
let inputPanelAvailableWidth = isTwoColumnLayout ? mainColumnWidth + inputPanelInnerInset * 2.0 : min(440.0 + inputPanelInnerInset * 2.0, availableSize.width - environment.safeInsets.left - environment.safeInsets.right)
var inputPanelAvailableHeight = 103.0
let keyboardWasHidden = self.inputPanelExternalState.isKeyboardHidden
@ -3508,7 +3529,6 @@ final class VideoChatScreenComponent: Component {
self.sendInput(randomId: transition?.randomId)
} else {
self.inputPanelIsActive = false
self.deactivateInput()
}
},
@ -3588,7 +3608,17 @@ final class VideoChatScreenComponent: Component {
inputPanelBottomInset = -inputPanelSize.height - environment.safeInsets.bottom
}
}
let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - inputPanelBottomInset - inputPanelSize.height - 3.0), size: inputPanelSize)
let inputPanelOriginX: CGFloat
if isTwoColumnLayout {
if buttonsOnTheSide {
inputPanelOriginX = availableSize.width - landscapeControlsWidth - mainColumnWidth - inputPanelInnerInset
} else {
inputPanelOriginX = availableSize.width - mainColumnWidth - inputPanelInnerInset - rightInset
}
} else {
inputPanelOriginX = floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0)
}
let inputPanelFrame = CGRect(origin: CGPoint(x: inputPanelOriginX, y: availableSize.height - environment.safeInsets.bottom - inputPanelBottomInset - inputPanelSize.height - 3.0), size: inputPanelSize)
if let inputPanelView = self.inputPanel.view {
if inputPanelView.superview == nil {
self.containerView.addSubview(inputPanelView)
@ -3598,6 +3628,8 @@ final class VideoChatScreenComponent: Component {
} else {
self.inputPanelExternalState.isEditing = false
transition.setAlpha(view: self.inputDimView, alpha: 0.0)
if let inputPanelView = self.inputPanel.view {
var inputPanelFrame = inputPanelView.frame
inputPanelFrame.origin.y = availableSize.height
@ -3645,6 +3677,16 @@ final class VideoChatScreenComponent: Component {
effectiveDisplayReactions = true
}
let reactionContextNodeOriginX: CGFloat
if isTwoColumnLayout {
if buttonsOnTheSide {
reactionContextNodeOriginX = availableSize.width - landscapeControlsWidth - mainColumnWidth * 0.5 - availableSize.width * 0.5
} else {
reactionContextNodeOriginX = availableSize.width - mainColumnWidth * 0.5 - availableSize.width * 0.5 - rightInset
}
} else {
reactionContextNodeOriginX = 0.0
}
let reactionsAnchorRect: CGRect = CGRect(origin: CGPoint(x: availableSize.width - 44.0, y: availableSize.height - inputPanelBottomInset - inputPanelSize.height - 18.0), size: CGSize(width: 44.0, height: 44.0))
if let reactionItems = self.reactionItems, effectiveDisplayReactions {
reactionsInset += 48.0
@ -3724,7 +3766,17 @@ final class VideoChatScreenComponent: Component {
}
let targetSize = CGSize(width: 24.0, height: 24.0)
let targetView = UIView(frame: CGRect(origin: CGPoint(x: 230.0, y: self.bounds.height - targetSize.height - 133.0), size: targetSize))
let targetViewOriginX: CGFloat
if isTwoColumnLayout {
if buttonsOnTheSide {
targetViewOriginX = availableSize.width - landscapeControlsWidth - mainColumnWidth * 0.5
} else {
targetViewOriginX = availableSize.width - mainColumnWidth * 0.5
}
} else {
targetViewOriginX = availableSize.width * 0.5
}
let targetView = UIView(frame: CGRect(origin: CGPoint(x: targetViewOriginX + 20.0, y: self.bounds.height - targetSize.height - 133.0), size: targetSize))
targetView.isUserInteractionEnabled = false
self.addSubview(targetView)
@ -3740,6 +3792,7 @@ final class VideoChatScreenComponent: Component {
})
}
self.inputPanelIsActive = false
if hasFirstResponder(self) {
self.currentInputMode = .text
self.endEditing(true)
@ -3825,8 +3878,7 @@ final class VideoChatScreenComponent: Component {
animateReactionsIn = true
self.containerView.addSubview(reactionContextNode.view)
}
reactionContextNodeTransition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
reactionContextNodeTransition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(x: reactionContextNodeOriginX, y: 0.0), size: availableSize))
reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: reactionContextNodeTransition.containedViewLayoutTransition)
if animateReactionsIn {
@ -3848,7 +3900,7 @@ final class VideoChatScreenComponent: Component {
}
if let reactionContextNode = self.disappearingReactionContextNode {
if !reactionContextNode.isAnimatingOutToReaction {
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(x: reactionContextNodeOriginX, y: 0.0), size: availableSize))
reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: reactionContextNode.centerAligned, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
}
}
@ -3883,7 +3935,7 @@ final class VideoChatScreenComponent: Component {
}
}
for notification in self.messageNotifications {
for notification in self.toastMessages {
let icon: MessageItemComponent.Icon
switch notification.icon {
case let .peer(peer):
@ -3914,9 +3966,19 @@ final class VideoChatScreenComponent: Component {
sendActionTransition: sendActionTransition
)),
environment: {},
containerSize: CGSize(width: availableSize.width - environment.safeInsets.left - environment.safeInsets.right, height: availableSize.height - messagesBottomInset)
containerSize: CGSize(width: isTwoColumnLayout ? mainColumnWidth : min(440.0, availableSize.width - environment.safeInsets.left - environment.safeInsets.right), height: availableSize.height - messagesBottomInset)
)
let messagesListFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - messagesListSize.width) / 2.0), y: availableSize.height - messagesListSize.height - messagesBottomInset), size: messagesListSize)
let messageListOriginX: CGFloat
if isTwoColumnLayout {
if buttonsOnTheSide {
messageListOriginX = availableSize.width - landscapeControlsWidth - mainColumnWidth
} else {
messageListOriginX = availableSize.width - mainColumnWidth - rightInset
}
} else {
messageListOriginX = floorToScreenPixels((availableSize.width - messagesListSize.width) / 2.0)
}
let messagesListFrame = CGRect(origin: CGPoint(x: messageListOriginX, y: availableSize.height - messagesListSize.height - messagesBottomInset), size: messagesListSize)
if let messagesListView = self.messagesList.view {
if messagesListView.superview == nil {
messagesListView.isUserInteractionEnabled = false

View file

@ -249,7 +249,7 @@ extension VideoChatScreenComponent.View {
iconName = "Call/ToastMessagesDisabled"
text = environment.strings.VoiceChat_ToastMessagesDisabled
}
self.displayNotification(icon: .icon(iconName), text: text, duration: 3)
self.displayToast(icon: .icon(iconName), text: text, duration: 3)
})))
}

View file

@ -8,15 +8,15 @@ enum VideoChatNotificationIcon {
}
extension VideoChatScreenComponent.View {
func displayNotification(icon: VideoChatNotificationIcon, text: String, duration: Int32) {
func displayToast(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.toastMessages.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.toastMessages.removeAll(where: { $0.id == id })
self.state?.updated(transition: .spring(duration: 0.4))
}
}

View file

@ -4905,6 +4905,7 @@ func replayFinalState(
switch call {
case let .groupCall(flags, _, _, participantsCount, title, _, recordStartDate, scheduleDate, _, _, _, _):
let isMin = (flags & (1 << 19)) != 0
let isMuted = (flags & (1 << 1)) != 0
let canChange = (flags & (1 << 2)) != 0
let isVideoEnabled = (flags & (1 << 9)) != 0
@ -4914,7 +4915,7 @@ func replayFinalState(
let messagesAreEnabled = GroupCallParticipantsContext.State.MessagesAreEnabled(isEnabled: messagesEnabled, canChange: canChangeMessagesEnabled)
updatedGroupCallParticipants.append((
info.id,
.call(isTerminated: false, defaultParticipantsAreMuted: defaultParticipantsAreMuted, messagesAreEnabled: messagesAreEnabled, 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), isMin: isMin)
))
default:
break
@ -4923,7 +4924,7 @@ func replayFinalState(
case let .groupCallDiscarded(callId, _, _):
updatedGroupCallParticipants.append((
callId,
.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)
.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, isMin: false)
))
if let peerId {

View file

@ -763,17 +763,18 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
switch call {
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _):
let isMin = (flags & (1 << 19)) != 0
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.defaultParticipantsAreMuted = GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: isMuted, canChange: isMin ? state.defaultParticipantsAreMuted.canChange : canChange)
state.messagesAreEnabled = GroupCallParticipantsContext.State.MessagesAreEnabled(isEnabled: messagesEnabled, canChange: isMin ? state.messagesAreEnabled.canChange : canChangeMessagesEnabled)
state.title = title
state.recordingStartTimestamp = recordStartDate
state.scheduleTimestamp = scheduleDate
state.isVideoEnabled = isVideoEnabled
state.isVideoEnabled = isMin ? state.isVideoEnabled : isVideoEnabled
state.unmutedVideoLimit = Int(unmutedVideoLimit)
default:
break
@ -1586,7 +1587,7 @@ public final class GroupCallParticipantsContext {
}
case state(update: StateUpdate)
case call(isTerminated: Bool, defaultParticipantsAreMuted: State.DefaultParticipantsAreMuted, messagesAreEnabled: State.MessagesAreEnabled, 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?, isMin: Bool)
case conferenceChainBlocks(subChainId: Int, blocks: [Data], nextOffset: Int)
}
@ -1964,14 +1965,14 @@ public final class GroupCallParticipantsContext {
for update in updates {
if case let .state(update) = update {
stateUpdates.append(update)
} else if case let .call(_, defaultParticipantsAreMuted, messagesAreEnabled, title, recordingStartTimestamp, scheduleTimestamp, isVideoEnabled, participantsCount) = update {
} else if case let .call(_, defaultParticipantsAreMuted, messagesAreEnabled, title, recordingStartTimestamp, scheduleTimestamp, isVideoEnabled, participantsCount, isMin) = update {
var state = self.stateValue.state
state.defaultParticipantsAreMuted = defaultParticipantsAreMuted
state.messagesAreEnabled = messagesAreEnabled
state.defaultParticipantsAreMuted = isMin ? State.DefaultParticipantsAreMuted(isMuted: defaultParticipantsAreMuted.isMuted, canChange: state.defaultParticipantsAreMuted.canChange) : defaultParticipantsAreMuted
state.messagesAreEnabled = isMin ? State.MessagesAreEnabled(isEnabled: messagesAreEnabled.isEnabled, canChange: state.messagesAreEnabled.canChange) : messagesAreEnabled
state.recordingStartTimestamp = recordingStartTimestamp
state.title = title
state.scheduleTimestamp = scheduleTimestamp
state.isVideoEnabled = isVideoEnabled
state.isVideoEnabled = isMin ? state.isVideoEnabled : isVideoEnabled
if let participantsCount = participantsCount {
state.totalCount = participantsCount
}

View file

@ -726,6 +726,9 @@ private extension StarsContext.State.Transaction {
if (apiFlags & (1 << 25)) != 0 {
flags.insert(.isStarGiftPrepaidUpgrade)
}
if (apiFlags & (1 << 26)) != 0 {
flags.insert(.isStarGiftDropOriginalDetails)
}
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
let _ = subscriptionPeriod
@ -782,6 +785,7 @@ public final class StarsContext {
public static let isStarGiftResale = Flags(rawValue: 1 << 9)
public static let isPostsSearch = Flags(rawValue: 1 << 10)
public static let isStarGiftPrepaidUpgrade = Flags(rawValue: 1 << 11)
public static let isStarGiftDropOriginalDetails = Flags(rawValue: 1 << 12)
}
public enum Peer: Equatable {

View file

@ -4792,8 +4792,7 @@ private final class GiftViewSheetContent: CombinedComponent {
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: theme.actionSheet.controlAccentColor)!, theme)
}
//TODO:localize
let attributedString = NSMutableAttributedString(string: "See how price will decrease >", font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor)
let attributedString = NSMutableAttributedString(string: strings.Gift_Upgrade_SeePriceDecrease, font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor)
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
}

View file

@ -896,8 +896,9 @@ public final class MessageInputPanelComponent: Component {
}
},
isOneLineWhenUnfocused: component.style == .media,
emptyLineHandling: component.style == .glass ? .notAllowed : .allowed,
formatMenuAvailability: component.isFormattingLocked ? .locked : .available(TextFieldComponent.FormatMenuAvailability.Action.all),
returnKeyType: component.style == .glass ? .done : .default,
returnKeyType: component.style == .glass ? .send : .default,
lockedFormatAction: {
component.presentTextFormattingTooltip?()
},

View file

@ -1503,17 +1503,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
if let note = cachedData.note, !note.text.isEmpty {
var entities = note.entities
if !context.isPremium {
entities = entities.filter { entity in
switch entity.type {
case .Url, .TextUrl:
return false
default:
return true
}
}
if context.isPremium {
entities = generateTextEntities(note.text, enabledTypes: [.mention, .hashtag, .allUrl], currentEntities: entities)
}
items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_Notes, rightLabel: presentationData.strings.PeerInfo_NotesInfo, text: note.text, entities: entities, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: []), action: nil, linkItemAction: bioLinkAction, button: nil, contextAction: noteContextAction, requestLayout: { animated in
items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_Notes, rightLabel: presentationData.strings.PeerInfo_NotesInfo, text: note.text, entities: entities, handleSpoilers: true, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: []), action: nil, linkItemAction: bioLinkAction, button: nil, contextAction: noteContextAction, requestLayout: { animated in
interaction.requestLayout(animated)
}))
}
@ -4589,7 +4582,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let noteUpdateSignal: Signal<Never, ContactUpdateError>
if noteUpdated, let note {
let entities = generateTextEntities(note.string, enabledTypes: [.mention, .hashtag, .allUrl], currentEntities: generateChatInputTextEntities(note))
let entities = generateChatInputTextEntities(note)
noteUpdateSignal = context.engine.contacts.updateContactNote(peerId: peer.id, text: note.string, entities: entities)
|> mapError { _ -> ContactUpdateError in
return .generic
@ -6680,7 +6673,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
}
if !headerButtons.contains(.discussion) && hasDiscussion {
//TODO:localize
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

View file

@ -16,8 +16,15 @@ import BalancedTextComponent
import GiftLoadingShimmerView
final class GiftListItemComponent: Component {
enum Subject {
case profile
case name
}
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let subject: Subject
let gifts: [StarGift.UniqueGift]
let starGifts: [StarGift]
let selectedId: Int64?
@ -28,6 +35,8 @@ final class GiftListItemComponent: Component {
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
subject: Subject,
gifts: [StarGift.UniqueGift],
starGifts: [StarGift],
selectedId: Int64?,
@ -37,6 +46,8 @@ final class GiftListItemComponent: Component {
) {
self.context = context
self.theme = theme
self.strings = strings
self.subject = subject
self.gifts = gifts
self.starGifts = starGifts
self.selectedId = selectedId
@ -49,6 +60,12 @@ final class GiftListItemComponent: Component {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.subject != rhs.subject {
return false
}
if lhs.gifts != rhs.gifts {
return false
}
@ -156,6 +173,7 @@ final class GiftListItemComponent: Component {
self.resaleGiftsState = state
if !self.isUpdating {
let transition: ComponentTransition = isFirstTime ? .easeInOut(duration: 0.25) : .immediate
self.state?.updated(transition: transition)
component.updated(transition)
}
isFirstTime = false
@ -164,6 +182,7 @@ final class GiftListItemComponent: Component {
if !self.isUpdating {
let transition: ComponentTransition = .easeInOut(duration: 0.25)
self.state?.updated(transition: transition)
component.updated(transition)
}
}
@ -186,11 +205,10 @@ final class GiftListItemComponent: Component {
let rowSpacing: CGFloat = 10.0
let itemsInRow = 3
//TODO:localize
var tabSelectorItems: [TabSelectorComponent.Item] = []
tabSelectorItems.append(TabSelectorComponent.Item(
id: AnyHashable(Int64(0)),
title: "My Gifts"
title: component.strings.ProfileColorSetup_MyGifts
))
for gift in component.starGifts {
@ -257,6 +275,9 @@ final class GiftListItemComponent: Component {
var uniqueGifts: [StarGift.UniqueGift] = []
for gift in resaleGiftsState.gifts {
if case let .unique(uniqueGift) = gift {
if case let .peerId(peerId) = uniqueGift.owner, component.context.account.peerId == peerId {
continue
}
uniqueGifts.append(uniqueGift)
}
}
@ -288,12 +309,21 @@ final class GiftListItemComponent: Component {
environment: {},
containerSize: CGSize(width: emptyAnimationHeight, height: emptyAnimationHeight)
)
//TODO:localize
let emptyText: String
switch component.subject {
case .profile:
emptyText = component.strings.ProfileColorSetup_NoProfileGiftsPlaceholder
case .name:
emptyText = component.strings.ProfileColorSetup_NoNameGiftsPlaceholder
}
let emptyResultsTextSize = self.emptyResultsText.update(
transition: .immediate,
component: AnyComponent(
BalancedTextComponent(
text: .plain(NSAttributedString(string: "You don't have any gifts you can use as styles for your profile.", font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor)),
text: .plain(NSAttributedString(string: emptyText, font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)
@ -306,7 +336,7 @@ final class GiftListItemComponent: Component {
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: component.theme.list.itemAccentColor)!, component.theme)
}
let buttonAttributedString = NSMutableAttributedString(string: "Browse gifts available for purchase >", font: Font.regular(15.0), textColor: component.theme.list.itemAccentColor, paragraphAlignment: .center)
let buttonAttributedString = NSMutableAttributedString(string: component.strings.ProfileColorSetup_BrowseGiftsForPurchase, font: Font.regular(15.0), textColor: component.theme.list.itemAccentColor, paragraphAlignment: .center)
if let range = buttonAttributedString.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
buttonAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: buttonAttributedString.string))
}

View file

@ -1221,7 +1221,6 @@ final class UserAppearanceScreenComponent: Component {
maximumNumberOfLines: 0
))))
//TODO:localize
let footerAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor),
@ -1230,7 +1229,7 @@ final class UserAppearanceScreenComponent: Component {
return (TelegramTextAttributes.URL, contents)
}
)
let previewFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("You can also change color of your name and customize replies to you. [Change >]()", attributes: footerAttributes))
let previewFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.ProfileColorSetup_ProfileColorPreviewInfo, attributes: footerAttributes))
if let range = previewFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
previewFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: previewFooterText.string))
}
@ -1392,7 +1391,6 @@ final class UserAppearanceScreenComponent: Component {
if let status = resolvedState.emojiStatus, case let .starGift(id, _, _, _, _, _, _, _, _) = status.content {
selectedGiftId = id
}
//TODO:localize
let giftsSectionSize = self.profileGiftsSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
@ -1400,7 +1398,7 @@ final class UserAppearanceScreenComponent: Component {
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Your Gifts".uppercased(),
string: environment.strings.NameColor_GiftTitle.uppercased(),
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
@ -1412,6 +1410,8 @@ final class UserAppearanceScreenComponent: Component {
GiftListItemComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
subject: .profile,
gifts: contentsData.gifts,
starGifts: contentsData.starGifts,
selectedId: selectedGiftId,
@ -1647,7 +1647,6 @@ final class UserAppearanceScreenComponent: Component {
if case let .collectible(collectibleColor) = resolvedState.nameColor {
selectedGiftId = collectibleColor.collectibleId
}
//TODO:localize
var peerColorStarGifts: [StarGift] = []
for gift in contentsData.starGifts {
@ -1663,7 +1662,7 @@ final class UserAppearanceScreenComponent: Component {
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Your Gifts".uppercased(),
string: environment.strings.NameColor_GiftTitle.uppercased(),
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
@ -1675,6 +1674,8 @@ final class UserAppearanceScreenComponent: Component {
GiftListItemComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
subject: .name,
gifts: self.starGifts,
starGifts: peerColorStarGifts,
selectedId: selectedGiftId,
@ -1721,9 +1722,8 @@ final class UserAppearanceScreenComponent: Component {
contentHeight += bottomContentInset
//TODO:localize
let buttonSideInset: CGFloat = environment.safeInsets.left + 36.0
var buttonTitle = "Apply Style" // environment.strings.Channel_Appearance_ApplyButton
var buttonTitle = environment.strings.ProfileColorSetup_ApplyStyle
var buttonAttributedSubtitleString: NSMutableAttributedString?
if let gift = self.selectedProfileGift, let resellAmounts = gift.resellAmounts, let starsAmount = resellAmounts.first(where: { $0.currency == .stars }) {

View file

@ -315,7 +315,14 @@ final class StarsTransactionsListPanelComponent: Component {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = environment.strings.Stars_Intro_Transaction_PaidMessage(item.paidMessageCount ?? 1)
} else if let starGift = item.starGift {
if item.flags.contains(.isStarGiftUpgrade), case let .unique(gift) = starGift {
if item.flags.contains(.isStarGiftPrepaidUpgrade) {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = environment.strings.Stars_Intro_Transaction_PrepaidGiftUpgrade
} else if item.flags.contains(.isStarGiftDropOriginalDetails), case let .unique(gift) = starGift {
itemTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, environment.dateTimeFormat.groupingSeparator))"
itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftDropOriginalDetails
uniqueGift = gift
} else if item.flags.contains(.isStarGiftUpgrade), case let .unique(gift) = starGift {
itemTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, environment.dateTimeFormat.groupingSeparator))"
itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftUpgrade
uniqueGift = gift

View file

@ -375,15 +375,16 @@ final class ChatAdPanelNode: ASDisplayNode {
}
}
let (adLayout, adApply) = makeAdLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Chat_BotAd_Title, font: Font.semibold(14.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width, height: .greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: .zero))
let titleConstrainedSize = CGSize(width: width - contentLeftInset - contentRightInset - textRightInset - adLayout.size.width - 90.0, height: CGFloat.greatestFiniteMagnitude)
let textConstrainedSize = CGSize(width: width - contentLeftInset - contentRightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude)
let (adLayout, adApply) = makeAdLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Chat_BotAd_Title, font: Font.semibold(14.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: .zero))
var titleText: String = ""
if let author = message.author {
titleText = EnginePeer(author).compactDisplayTitle
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleText, font: Font.semibold(14.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: .zero))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleText, font: Font.semibold(14.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: titleConstrainedSize, alignment: .natural, cutout: nil, insets: .zero))
let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)