diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 709b9b880a..a8a29c486a 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -22,6 +22,7 @@ public let repostStoryIcon = generateTintedImage(image: UIImage(bundleImageName: private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed() private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white) private let anonymousSavedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: .white) +private let anonymousSavedMessagesDarkIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: UIColor(white: 1.0, alpha: 0.4)) private let myNotesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/MyNotesIcon"), color: .white) public func avatarPlaceholderFont(size: CGFloat) -> UIFont { @@ -98,7 +99,11 @@ private func calculateColors(context: AccountContext?, explicitColorIndex: Int?, if isColored { colors = AvatarNode.savedMessagesColors } else { - colors = AvatarNode.grayscaleColors + if let theme, theme.overallDarkAppearance { + colors = AvatarNode.grayscaleDarkColors + } else { + colors = AvatarNode.grayscaleColors + } } } else if case .myNotesIcon = icon { colors = AvatarNode.savedMessagesColors @@ -269,6 +274,10 @@ public final class AvatarNode: ASDisplayNode { UIColor(rgb: 0xb1b1b1), UIColor(rgb: 0xcdcdcd) ] + static let grayscaleDarkColors: [UIColor] = [ + UIColor(white: 1.0, alpha: 0.22), UIColor(white: 1.0, alpha: 0.18) + ] + static let savedMessagesColors: [UIColor] = [ UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x72d5fd) ] @@ -928,8 +937,14 @@ public final class AvatarNode: ASDisplayNode { context.scaleBy(x: factor, y: -factor) context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0) - if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon { - context.draw(anonymousSavedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesIcon.size.height) / 2.0)), size: anonymousSavedMessagesIcon.size)) + if let theme = parameters.theme, theme.overallDarkAppearance { + if let anonymousSavedMessagesDarkIcon = anonymousSavedMessagesDarkIcon { + context.draw(anonymousSavedMessagesDarkIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesDarkIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesDarkIcon.size.height) / 2.0)), size: anonymousSavedMessagesDarkIcon.size)) + } + } else { + if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon { + context.draw(anonymousSavedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesIcon.size.height) / 2.0)), size: anonymousSavedMessagesIcon.size)) + } } } else if case .myNotesIcon = parameters.icon { let factor = bounds.size.width / 60.0 diff --git a/submodules/Display/Source/GlobalOverlayPresentationContext.swift b/submodules/Display/Source/GlobalOverlayPresentationContext.swift index 8ebc83f1a8..f7346c5c50 100644 --- a/submodules/Display/Source/GlobalOverlayPresentationContext.swift +++ b/submodules/Display/Source/GlobalOverlayPresentationContext.swift @@ -24,10 +24,10 @@ public func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) -> } public final class HierarchyTrackingNode: ASDisplayNode { - private let f: (Bool) -> Void + public var updated: (Bool) -> Void - public init(_ f: @escaping (Bool) -> Void) { - self.f = f + public init(_ f: @escaping (Bool) -> Void = { _ in }) { + self.updated = f super.init() @@ -37,13 +37,13 @@ public final class HierarchyTrackingNode: ASDisplayNode { override public func didEnterHierarchy() { super.didEnterHierarchy() - self.f(true) + self.updated(true) } override public func didExitHierarchy() { super.didExitHierarchy() - self.f(false) + self.updated(false) } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 79bc8bfbee..b58c216f43 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -57,11 +57,13 @@ private let lockedBackgroundImage: UIImage = generateFilledCircleImage(diameter: private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) private final class StarsButtonEffectLayer: SimpleLayer { + let gradientLayer = SimpleGradientLayer() let emitterLayer = CAEmitterLayer() override init() { super.init() + self.addSublayer(self.gradientLayer) self.addSublayer(self.emitterLayer) } @@ -73,8 +75,8 @@ private final class StarsButtonEffectLayer: SimpleLayer { fatalError("init(coder:) has not been implemented") } - private func setup() { - let color = UIColor(rgb: 0xffbe27) + private func setup(theme: PresentationTheme) { + let color = UIColor(rgb: 0xffbe27, alpha: theme.overallDarkAppearance ? 0.2 : 1.0) let emitter = CAEmitterCell() emitter.name = "emitter" @@ -101,17 +103,31 @@ private final class StarsButtonEffectLayer: SimpleLayer { emitter.setValue([staticColorBehavior], forKey: "emitterBehaviors") self.emitterLayer.emitterCells = [emitter] + + let gradientColor = UIColor(rgb: 0xffbe27, alpha: theme.overallDarkAppearance ? 0.2 : 1.0) + + self.gradientLayer.type = .radial + self.gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5) + self.gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) + self.gradientLayer.colors = [ + gradientColor.withMultipliedAlpha(0.4).cgColor, + gradientColor.withMultipliedAlpha(0.4).cgColor, + gradientColor.withMultipliedAlpha(0.25).cgColor, + gradientColor.withMultipliedAlpha(0.0).cgColor + ] as [CGColor] } - func update(size: CGSize) { + func update(theme: PresentationTheme, size: CGSize, transition: ContainedViewLayoutTransition) { if self.emitterLayer.emitterCells == nil { - self.setup() + self.setup(theme: theme) } self.emitterLayer.emitterShape = .circle self.emitterLayer.emitterSize = CGSize(width: size.width * 0.7, height: size.height * 0.7) self.emitterLayer.emitterMode = .surface self.emitterLayer.frame = CGRect(origin: .zero, size: size) self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0) + + transition.updateFrame(layer: self.gradientLayer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -6.0, dy: -6.0).offsetBy(dx: 0.0, dy: 2.0)) } } @@ -307,7 +323,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { if let starsEffectLayer = self.starsEffectLayer { transition.updateFrame(layer: starsEffectLayer, frame: CGRect(origin: CGPoint(), size: size)) - starsEffectLayer.update(size: size) + starsEffectLayer.update(theme: self.theme, size: size, transition: transition) } let animationSize = self.item.stillAnimation.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 33ef3ccc3a..c5f24be14f 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -842,7 +842,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present } if case .phoneNumber = kind, state.setting == .nobody { - if state.phoneDiscoveryEnabled == false { + if state.phoneDiscoveryEnabled == false || phoneNumber.hasPrefix("888") { entries.append(.phoneDiscoveryHeader(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_DiscoveryHeader)) entries.append(.phoneDiscoveryEverybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.phoneDiscoveryEnabled != false)) entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false)) diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index ea326708b0..14556bc594 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -716,7 +716,11 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, var authorId: PeerId? if let sendAsPeer = sendAsPeer { - authorId = sendAsPeer.id + if let peer = peer as? TelegramChannel, case let .broadcast(info) = peer.info, info.flags.contains(.messagesShouldHaveProfiles) { + authorId = sendAsPeer.id + } else { + authorId = peer.id + } } else if let peer = peer as? TelegramChannel { if case .broadcast = peer.info { authorId = peer.id @@ -748,7 +752,11 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } if info.flags.contains(.messagesShouldHaveSignatures) { - if let sendAsPeer, sendAsPeer.id == peerId { + if let sendAsPeer { + if sendAsPeer.id == peerId { + } else { + attributes.append(AuthorSignatureMessageAttribute(signature: sendAsPeer.debugDisplayTitle)) + } } else { attributes.append(AuthorSignatureMessageAttribute(signature: accountPeer.debugDisplayTitle)) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift index d50eb68079..f531c260a0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift @@ -109,13 +109,7 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network, return .single([]) } - if let channel = peer as? TelegramChannel { - if case .group = channel.info { - } else if case let .broadcast(info) = channel.info { - if !info.flags.contains(.messagesShouldHaveProfiles) { - return .single([]) - } - } + if let _ = peer as? TelegramChannel { } else { return .single([]) } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index d80705ed74..51ccb17da0 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -531,7 +531,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati reactionInactiveForeground: UIColor(rgb: 0xffffff), reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0), reactionActiveForeground: .clear, - reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0), + reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2), reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A), reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0), reactionStarsActiveForeground: .white, @@ -547,7 +547,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati reactionInactiveForeground: UIColor(rgb: 0xffffff), reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0), reactionActiveForeground: .clear, - reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0), + reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2), reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A), reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0), reactionStarsActiveForeground: .white, @@ -569,7 +569,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati reactionInactiveForeground: UIColor(rgb: 0xffffff), reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0), reactionActiveForeground: .clear, - reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0), + reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2), reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A), reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0), reactionStarsActiveForeground: .white, @@ -585,7 +585,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati reactionInactiveForeground: UIColor(rgb: 0xffffff), reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0), reactionActiveForeground: .clear, - reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0), + reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2), reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A), reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0), reactionStarsActiveForeground: .white, @@ -604,7 +604,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati reactionInactiveForeground: UIColor(rgb: 0xffffff), reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0), reactionActiveForeground: .clear, - reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0), + reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2), reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A), reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0), reactionStarsActiveForeground: .white, @@ -620,7 +620,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati reactionInactiveForeground: UIColor(rgb: 0xffffff), reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0), reactionActiveForeground: .clear, - reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0), + reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2), reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A), reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0), reactionStarsActiveForeground: .white, diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 33fed16370..ea82e94fcc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -128,22 +128,15 @@ private final class BalanceComponent: CombinedComponent { } private final class BadgeComponent: Component { - enum Direction { - case left - case right - } let theme: PresentationTheme let title: String - let inertiaDirection: Direction? init( theme: PresentationTheme, - title: String, - inertiaDirection: Direction? + title: String ) { self.theme = theme self.title = title - self.inertiaDirection = inertiaDirection } static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool { @@ -153,9 +146,6 @@ private final class BadgeComponent: Component { if lhs.title != rhs.title { return false } - if lhs.inertiaDirection != rhs.inertiaDirection { - return false - } return true } @@ -175,7 +165,6 @@ private final class BadgeComponent: Component { private var component: BadgeComponent? private var previousAvailableSize: CGSize? - private var previousInertiaDirection: BadgeComponent.Direction? override init(frame: CGRect) { self.badgeView = UIView() @@ -189,6 +178,7 @@ private final class BadgeComponent: Component { self.badgeView.mask = self.badgeMaskView self.badgeForeground = SimpleLayer() + self.badgeForeground.anchorPoint = CGPoint() self.badgeIcon = UIImageView() self.badgeIcon.contentMode = .center @@ -257,31 +247,7 @@ private final class BadgeComponent: Component { self.badgeView.bounds = CGRect(origin: .zero, size: badgeFullSize) - transition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.5, y: 1.0)) - - if component.inertiaDirection != self.previousInertiaDirection { - self.previousInertiaDirection = component.inertiaDirection - - var angle: CGFloat = 0.0 - let transition: ContainedViewLayoutTransition - if let inertiaDirection = component.inertiaDirection { - switch inertiaDirection { - case .left: - angle = 0.22 - case .right: - angle = -0.22 - } - transition = .animated(duration: 0.45, curve: .spring) - } else { - transition = .animated(duration: 0.45, curve: .customSpring(damping: 65.0, initialVelocity: 0.0)) - } - transition.updateTransformRotation(view: self.badgeView, angle: angle) - } - - self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeFullSize.width * 3.0, height: badgeFullSize.height)) - if self.badgeForeground.animation(forKey: "movement") == nil { - self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0 - self.badgeForeground.frame.width * 0.35, y: badgeFullSize.height / 2.0) - } + self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 600.0, height: badgeFullSize.height)) self.badgeIcon.frame = CGRect(x: 10.0, y: 9.0, width: 30.0, height: 30.0) self.badgeLabelMaskView.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 36.0) @@ -320,7 +286,17 @@ private final class BadgeComponent: Component { tailPosition += overflowWidth tailPosition = max(0.0, min(size.width, tailPosition)) - self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: size, tailPosition: tailPosition / size.width).cgPath + let tailPositionFraction = tailPosition / size.width + self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: size, tailPosition: tailPositionFraction).cgPath + + let transition: ContainedViewLayoutTransition = .immediate + transition.updateAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: tailPositionFraction, y: 1.0)) + transition.updatePosition(layer: self.badgeView.layer, position: CGPoint(x: (tailPositionFraction - 0.5) * size.width, y: 0.0)) + } + + func updateBadgeAngle(angle: CGFloat) { + let transition: ContainedViewLayoutTransition = .immediate + transition.updateTransformRotation(view: self.badgeView, angle: angle) } private func setupGradientAnimations() { @@ -331,13 +307,14 @@ private final class BadgeComponent: Component { } else { CATransaction.begin() - let badgeOffset = (self.badgeForeground.frame.width - self.badgeView.bounds.width) / 2.0 let badgePreviousValue = self.badgeForeground.position.x - var badgeNewValue: CGFloat = badgeOffset - if badgeOffset - badgePreviousValue < self.badgeForeground.frame.width * 0.25 { - badgeNewValue -= self.badgeForeground.frame.width * 0.35 + let badgeNewValue: CGFloat + if self.badgeForeground.position.x == -300.0 { + badgeNewValue = 0.0 + } else { + badgeNewValue = -300.0 } - self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height / 2.0) + self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height) let badgeAnimation = CABasicAnimation(keyPath: "position.x") badgeAnimation.duration = 4.5 @@ -1007,6 +984,7 @@ private final class ChatSendStarsScreenComponent: Component { private let scrollView: ScrollView private let scrollContentClippingView: SparseContainerView private let scrollContentView: UIView + private let hierarchyTrackingNode: HierarchyTrackingNode private let leftButton = ComponentView() private let closeButton = ComponentView() @@ -1056,6 +1034,8 @@ private final class ChatSendStarsScreenComponent: Component { private var balanceDisposable: Disposable? + private var badgePhysicsLink: SharedDisplayLinkDriver.Link? + override init(frame: CGRect) { self.bottomOverscrollLimit = 200.0 @@ -1074,6 +1054,8 @@ private final class ChatSendStarsScreenComponent: Component { self.scrollContentView = UIView() + self.hierarchyTrackingNode = HierarchyTrackingNode() + super.init(frame: frame) self.addSubview(self.dimView) @@ -1104,6 +1086,30 @@ private final class ChatSendStarsScreenComponent: Component { self.addSubview(self.navigationBarContainer) self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + + self.addSubnode(self.hierarchyTrackingNode) + + self.hierarchyTrackingNode.updated = { [weak self] value in + guard let self else { + return + } + if value { + if self.badgePhysicsLink == nil { + let badgePhysicsLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] _ in + guard let self else { + return + } + self.updateBadgePhysics() + }) + self.badgePhysicsLink = badgePhysicsLink + } + } else { + if let badgePhysicsLink = self.badgePhysicsLink { + self.badgePhysicsLink = nil + badgePhysicsLink.invalidate() + } + } + } } required init?(coder: NSCoder) { @@ -1175,7 +1181,7 @@ private final class ChatSendStarsScreenComponent: Component { transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) - let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25)) + let topOffsetDistance: CGFloat = min(60.0, floor(itemLayout.containerSize.height * 0.25)) self.topOffsetDistance = topOffsetDistance var topOffsetFraction = topOffset / topOffsetDistance topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) @@ -1217,7 +1223,78 @@ private final class ChatSendStarsScreenComponent: Component { private var previousSliderValue: Float = 0.0 private var previousTimestamp: Double? - private var inertiaDirection: BadgeComponent.Direction? + + private var badgeAngularSpeed: CGFloat = 0.0 + private var badgeAngle: CGFloat = 0.0 + private var previousBadgeX: CGFloat? + private var previousPhysicsTimestamp: Double? + + private func updateBadgePhysics() { + let timestamp = CACurrentMediaTime() + + let deltaTime: CGFloat + if let previousPhysicsTimestamp = self.previousPhysicsTimestamp { + deltaTime = CGFloat(min(1.0 / 60.0, timestamp - previousPhysicsTimestamp)) + } else { + deltaTime = CGFloat(1.0 / 60.0) + } + self.previousPhysicsTimestamp = timestamp + + guard let badgeView = self.badge.view as? BadgeComponent.View else { + return + } + let badgeX = badgeView.center.x + + let horizontalVelocity: CGFloat + if let previousBadgeX = self.previousBadgeX { + horizontalVelocity = (badgeX - previousBadgeX) / deltaTime + } else { + horizontalVelocity = 0.0 + } + self.previousBadgeX = badgeX + + let testSpringFriction: CGFloat = 9.0 + let testSpringConstant: CGFloat = 243.0 + + let frictionConstant: CGFloat = testSpringFriction + let springConstant: CGFloat = testSpringConstant + let time: CGFloat = deltaTime + + var badgeAngle = self.badgeAngle + + badgeAngle -= horizontalVelocity * 0.0001 + if abs(badgeAngle) > 0.22 { + badgeAngle = badgeAngle < 0.0 ? -0.22 : 0.22 + } + + // friction force = velocity * friction constant + let frictionForce = self.badgeAngularSpeed * frictionConstant + // spring force = (target point - current position) * spring constant + let springForce = -badgeAngle * springConstant + // force = spring force - friction force + let force = springForce - frictionForce + + // velocity = current velocity + force * time / mass + self.badgeAngularSpeed = self.badgeAngularSpeed + force * time + // position = current position + velocity * time + badgeAngle = badgeAngle + self.badgeAngularSpeed * time + badgeAngle = badgeAngle.isNaN ? 0.0 : badgeAngle + + let epsilon: CGFloat = 0.01 + if abs(badgeAngle) < epsilon && abs(self.badgeAngularSpeed) < epsilon { + badgeAngle = 0.0 + self.badgeAngularSpeed = 0.0 + } + + if abs(badgeAngle) > 0.22 { + badgeAngle = badgeAngle < 0.0 ? -0.22 : 0.22 + } + + if self.badgeAngle != badgeAngle { + self.badgeAngle = badgeAngle + badgeView.updateBadgeAngle(angle: self.badgeAngle) + } + } func update(component: ChatSendStarsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let environment = environment[ViewControllerComponentContainer.Environment.self].value @@ -1225,7 +1302,13 @@ private final class ChatSendStarsScreenComponent: Component { let resetScrolling = self.scrollView.bounds.width != availableSize.width - let sideInset: CGFloat = 16.0 + let fillingSize: CGFloat + if case .regular = environment.metrics.widthClass { + fillingSize = min(availableSize.width, 414.0) - environment.safeInsets.left * 2.0 + } else { + fillingSize = min(availableSize.width, 428.0) - environment.safeInsets.left * 2.0 + } + let sideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + 16.0 if self.component == nil { self.balance = component.balance @@ -1307,21 +1390,7 @@ private final class ChatSendStarsScreenComponent: Component { let speed = deltaValue / Float(deltaTime) let newSpeed = max(0, min(65.0, speed * 70.0)) - var inertiaDirection: BadgeComponent.Direction? - if newSpeed >= 1.0 { - if delta > 0.0 { - inertiaDirection = .right - } else { - inertiaDirection = .left - } - } - if inertiaDirection != self.inertiaDirection { - self.inertiaDirection = inertiaDirection - self.state?.updated(transition: .immediate) - } - if newSpeed < 0.01 && deltaValue < 0.001 { - } else { self.badgeStars.update(speed: newSpeed, delta: delta) } @@ -1338,10 +1407,6 @@ private final class ChatSendStarsScreenComponent: Component { self.previousTimestamp = nil self.badgeStars.update(speed: 0.0) } - if self.inertiaDirection != nil { - self.inertiaDirection = nil - self.state?.updated(transition: .immediate) - } } )), environment: {}, @@ -1401,17 +1466,11 @@ private final class ChatSendStarsScreenComponent: Component { transition.setFrame(view: sliderBackgroundView, frame: sliderBackgroundFrame) - var effectiveInertiaDirection = self.inertiaDirection - if progressFraction <= 0.03 || progressFraction >= 0.97 { - effectiveInertiaDirection = nil - } - let badgeSize = self.badge.update( transition: transition, component: AnyComponent(BadgeComponent( theme: environment.theme, - title: "\(self.amount.realValue)", - inertiaDirection: effectiveInertiaDirection + title: "\(self.amount.realValue)" )), environment: {}, containerSize: CGSize(width: 200.0, height: 200.0) @@ -1463,7 +1522,7 @@ private final class ChatSendStarsScreenComponent: Component { environment: {}, containerSize: CGSize(width: 120.0, height: 100.0) ) - let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: floor((56.0 - leftButtonSize.height) * 0.5)), size: leftButtonSize) + let leftButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((56.0 - leftButtonSize.height) * 0.5)), size: leftButtonSize) if let leftButtonView = self.leftButton.view { if leftButtonView.superview == nil { self.navigationBarContainer.addSubview(leftButtonView) @@ -2005,7 +2064,7 @@ private final class ChatSendStarsScreenComponent: Component { transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) - transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize)) + transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: fillingSize, height: availableSize.height))) let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset), size: CGSize(width: availableSize.width, height: clippingY - containerInset)) transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift index b347a46bfd..539f4497e5 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift @@ -154,13 +154,17 @@ final class PeerAllowedReactionsScreenComponent: Component { if Set(availableReactions.reactions.filter({ $0.isEnabled }).map(\.value)) == Set(enabledReactions.map(\.reaction)) { allowedReactions = .all } else { - allowedReactions = .limited(enabledReactions.map(\.reaction)) + if enabledReactions.isEmpty { + allowedReactions = .empty + } else { + allowedReactions = .limited(enabledReactions.map(\.reaction)) + } } } else { allowedReactions = .empty } - let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount >= 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.areStarsReactionsEnabled) + let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount >= 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.isEnabled && self.areStarsReactionsEnabled) if self.appliedReactionSettings != reactionSettings { if case .empty = allowedReactions { @@ -255,7 +259,7 @@ final class PeerAllowedReactionsScreenComponent: Component { } else { allowedReactions = .empty } - let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount == 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.areStarsReactionsEnabled) + let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount == 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.isEnabled && self.areStarsReactionsEnabled) let applyDisposable = (component.context.engine.peers.updatePeerReactionSettings(peerId: component.peerId, reactionSettings: reactionSettings) |> deliverOnMainQueue).start(error: { [weak self] error in @@ -603,7 +607,7 @@ final class PeerAllowedReactionsScreenComponent: Component { self.displayInput = false } - self.state?.updated(transition: .easeInOut(duration: 0.25)) + self.state?.updated(transition: .immediate) } } )), diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 69699a5913..1d05f23d22 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -294,7 +294,7 @@ extension ChatControllerImpl { if !hasAnonymousPeer { allPeers?.insert(SendAsPeer(peer: channel, subscribers: 0, isPremiumRequired: false), at: 0) } - } else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.messagesShouldHaveProfiles) { + } else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case .broadcast = channel.info { allPeers = peers var hasAnonymousPeer = false diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index 472af48ec3..d667fe7d33 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -375,7 +375,9 @@ extension ChatControllerImpl { } if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) { if !"".isEmpty { - self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view)) + if self.context.sharedContext.energyUsageSettings.fullTranslucency { + self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view)) + } } } }, completion: {}) @@ -390,13 +392,29 @@ extension ChatControllerImpl { guard let starsContext = self.context.starsContext else { return } - let _ = (starsContext.state + guard let peerId = self.chatLocation.peerId else { + return + } + let _ = (combineLatest( + starsContext.state, + self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId)) + ) |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] state in + |> deliverOnMainQueue).start(next: { [weak self] state, reactionSettings in guard let strongSelf = self, let balance = state?.balance else { return } + if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed { + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer { + //TODO:localize + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "Star Reactions were disabled in \(peer.debugDisplayTitle).", actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ]), in: .window(.root)) + } + return + } + if balance < 1 { controller?.dismiss(completion: { guard let strongSelf = self else { @@ -406,7 +424,7 @@ extension ChatControllerImpl { let _ = (strongSelf.context.engine.payments.starsTopUpOptions() |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] options in - guard let strongSelf, let peerId = strongSelf.chatLocation.peerId else { + guard let strongSelf else { return } guard let starsContext = strongSelf.context.starsContext else { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4c3824f63d..8c486ab361 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1686,7 +1686,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) { + if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction), strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency { strongSelf.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: strongSelf.chatDisplayNode.view)) } }, @@ -1701,13 +1701,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let starsContext = strongSelf.context.starsContext else { return } - let _ = (starsContext.state + guard let peerId = strongSelf.chatLocation.peerId else { + return + } + let _ = (combineLatest( + starsContext.state, + strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId)) + ) |> take(1) - |> deliverOnMainQueue).start(next: { [weak strongSelf] state in + |> deliverOnMainQueue).start(next: { [weak strongSelf] state, reactionSettings in guard let strongSelf, let balance = state?.balance else { return } + if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed { + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer { + //TODO:localize + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "Star Reactions were disabled in \(peer.debugDisplayTitle).", actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ]), in: .window(.root)) + } + return + } + if balance < 1 { let _ = (strongSelf.context.engine.payments.starsTopUpOptions() |> take(1) diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index 310ce9af8e..757a537c8d 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -375,101 +375,121 @@ extension ChatControllerImpl { } self.context.engine.messages.forceSendPendingSendStarsReaction(id: message.id) - let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: false) - let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, messageId: message.id, topPeers: reactionsAttribute?.topPeers ?? []) - |> deliverOnMainQueue).start(next: { [weak self] initialData in - guard let self, let initialData else { + guard let peerId = self.chatLocation.peerId else { + return + } + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] reactionSettings in + guard let self else { return } - HapticFeedback().tap() - self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, isAnonymous, isBecomingTop, transitionOut in - guard let self, amount > 0 else { + + let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: false) + let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, messageId: message.id, topPeers: reactionsAttribute?.topPeers ?? []) + |> deliverOnMainQueue).start(next: { [weak self] initialData in + guard let self, let initialData else { return } - - var sourceItemNode: ChatMessageItemView? - self.chatDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - if itemNode.item?.message.id == message.id { - sourceItemNode = itemNode - return - } - } - } - - if let itemNode = sourceItemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: .stars) { - var reactionItem: ReactionItem? - - for reaction in availableReactions.reactions { - guard let centerAnimation = reaction.centerAnimation else { - continue - } - guard let aroundAnimation = reaction.aroundAnimation else { - continue - } - if reaction.value == .stars { - reactionItem = ReactionItem( - reaction: ReactionItem.Reaction(rawValue: reaction.value), - appearAnimation: reaction.appearAnimation, - stillAnimation: reaction.selectAnimation, - listAnimation: centerAnimation, - largeListAnimation: reaction.activateAnimation, - applicationAnimation: aroundAnimation, - largeApplicationAnimation: reaction.effectAnimation, - isCustom: false - ) - break - } + HapticFeedback().tap() + self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, isAnonymous, isBecomingTop, transitionOut in + guard let self, amount > 0 else { + return } - if let reactionItem { - let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.chatDisplayNode.historyNode.takeGenericReactionEffect()) - - self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation) - - self.view.window?.addSubview(standaloneReactionAnimation.view) - standaloneReactionAnimation.frame = self.chatDisplayNode.bounds - standaloneReactionAnimation.animateOutToReaction( - context: self.context, - theme: self.presentationData.theme, - item: reactionItem, - value: .stars, - sourceView: transitionOut.sourceView, - targetView: targetView, - hideNode: false, - forceSwitchToInlineImmediately: false, - animateTargetContainer: nil, - addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in - guard let self else { - return - } - self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation) - standaloneReactionAnimation.frame = self.chatDisplayNode.bounds - self.chatDisplayNode.addSubnode(standaloneReactionAnimation) - }, - onHit: { [weak self, weak itemNode] in - guard let self else { - return - } - - if isBecomingTop { - self.chatDisplayNode.animateQuizCorrectOptionSelected() - } - - if let itemNode, let targetView = itemNode.targetReactionView(value: .stars) { - self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view)) - } - }, - completion: { [weak standaloneReactionAnimation] in - standaloneReactionAnimation?.view.removeFromSuperview() + if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed { + if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer { + //TODO:localize + self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Star Reactions were disabled in \(peer.debugDisplayTitle).", actions: [ + TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {}) + ]), in: .window(.root)) + } + return + } + + var sourceItemNode: ChatMessageItemView? + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + if itemNode.item?.message.id == message.id { + sourceItemNode = itemNode + return } - ) + } } - } - - let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), isAnonymous: isAnonymous) - self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount)) - })) + + if let itemNode = sourceItemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: .stars) { + var reactionItem: ReactionItem? + + for reaction in availableReactions.reactions { + guard let centerAnimation = reaction.centerAnimation else { + continue + } + guard let aroundAnimation = reaction.aroundAnimation else { + continue + } + if reaction.value == .stars { + reactionItem = ReactionItem( + reaction: ReactionItem.Reaction(rawValue: reaction.value), + appearAnimation: reaction.appearAnimation, + stillAnimation: reaction.selectAnimation, + listAnimation: centerAnimation, + largeListAnimation: reaction.activateAnimation, + applicationAnimation: aroundAnimation, + largeApplicationAnimation: reaction.effectAnimation, + isCustom: false + ) + break + } + } + + if let reactionItem { + let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.chatDisplayNode.historyNode.takeGenericReactionEffect()) + + self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation) + + self.view.window?.addSubview(standaloneReactionAnimation.view) + standaloneReactionAnimation.frame = self.chatDisplayNode.bounds + standaloneReactionAnimation.animateOutToReaction( + context: self.context, + theme: self.presentationData.theme, + item: reactionItem, + value: .stars, + sourceView: transitionOut.sourceView, + targetView: targetView, + hideNode: false, + forceSwitchToInlineImmediately: false, + animateTargetContainer: nil, + addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in + guard let self else { + return + } + self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation) + standaloneReactionAnimation.frame = self.chatDisplayNode.bounds + self.chatDisplayNode.addSubnode(standaloneReactionAnimation) + }, + onHit: { [weak self, weak itemNode] in + guard let self else { + return + } + + if isBecomingTop { + self.chatDisplayNode.animateQuizCorrectOptionSelected() + } + + if let itemNode, let targetView = itemNode.targetReactionView(value: .stars), self.context.sharedContext.energyUsageSettings.fullTranslucency { + self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view)) + } + }, + completion: { [weak standaloneReactionAnimation] in + standaloneReactionAnimation?.view.removeFromSuperview() + } + ) + } + } + + let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), isAnonymous: isAnonymous) + self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount)) + })) + }) }) }