Merge commit 'bc1598bb926aee4267a278857463f8ce8ac1dec9'

This commit is contained in:
Isaac 2025-10-26 22:06:03 +04:00
commit 24260d3652
9 changed files with 76 additions and 123 deletions

View File

@ -102,7 +102,7 @@ final class CameraDeviceContext {
return 30.0
}
switch DeviceModel.current {
case .iPhone15ProMax, .iPhone14ProMax, .iPhone13ProMax, .iPhone16ProMax:
case .iPhone15ProMax, .iPhone14ProMax, .iPhone13ProMax, .iPhone16ProMax, .iPhone17Pro, .iPhone17ProMax:
return 60.0
default:
return 30.0

View File

@ -132,7 +132,6 @@ final class ComposePollScreenComponent: Component {
private var reactionSelectionControl: ComponentView<Empty>?
private var isUpdating: Bool = false
private var ignoreScrolling: Bool = false
private var previousHadInputHeight: Bool = false
private var component: ComposePollScreenComponent?
@ -467,21 +466,6 @@ final class ComposePollScreenComponent: Component {
self.endEditing(true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
}
}
private func updateScrolling(transition: ComponentTransition) {
let navigationAlphaDistance: CGFloat = 16.0
let navigationAlpha: CGFloat = max(0.0, min(1.0, self.scrollView.contentOffset.y / navigationAlphaDistance))
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
}
}
func isPanGestureEnabled() -> Bool {
if self.inputMediaNode != nil {
return false
@ -1627,7 +1611,6 @@ final class ComposePollScreenComponent: Component {
}
self.previousHadInputHeight = (inputHeight > 0.0)
self.ignoreScrolling = true
let previousBounds = self.scrollView.bounds
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
@ -1762,9 +1745,6 @@ final class ComposePollScreenComponent: Component {
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
}
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
if isEditing {
if let controller = environment.controller() as? ComposePollScreen {

View File

@ -23,7 +23,9 @@ open class SwitchNode: ASDisplayNode {
public var frameColor = UIColor(rgb: 0xe0e0e0) {
didSet {
if self.isNodeLoaded {
(self.view as! UISwitch).tintColor = self.frameColor
if oldValue != self.frameColor {
(self.view as! UISwitch).tintColor = self.frameColor
}
}
}
}
@ -37,7 +39,9 @@ open class SwitchNode: ASDisplayNode {
public var contentColor = UIColor(rgb: 0x42d451) {
didSet {
if self.isNodeLoaded {
(self.view as! UISwitch).onTintColor = self.contentColor
if oldValue != self.contentColor {
(self.view as! UISwitch).onTintColor = self.contentColor
}
}
}
}

View File

@ -129,8 +129,17 @@ final class VideoAdComponent: Component {
self.imageNode.setSignal(signal)
}
let color = UIColor(rgb: 0x64d2ff)
if self.adIcon == nil {
self.adIcon = generateAdIcon(color: color, strings: component.strings)
}
let leftInset: CGFloat = media != nil ? 51.0 : 16.0
let rightInset: CGFloat = 60.0
var titleRightInset: CGFloat = 0.0
if let adIcon = self.adIcon {
titleRightInset += adIcon.size.width + 16.0
}
let titleSize = self.title.update(
transition: .immediate,
@ -138,7 +147,7 @@ final class VideoAdComponent: Component {
MultilineTextComponent(text: .plain(NSAttributedString(string: titleString, font: Font.semibold(14.0), textColor: .white)))
),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: availableSize.height)
containerSize: CGSize(width: availableSize.width - leftInset - rightInset - titleRightInset, height: availableSize.height)
)
let textColor = UIColor.white
@ -194,11 +203,6 @@ final class VideoAdComponent: Component {
textView.frame = textFrame
}
let color = UIColor(rgb: 0x64d2ff)
if self.adIcon == nil {
self.adIcon = generateAdIcon(color: color, strings: component.strings)
}
let buttonSize = self.button.update(
transition: .immediate,
component: AnyComponent(

View File

@ -85,7 +85,6 @@ final class ComposeTodoScreenComponent: Component {
private let doneButton = ComponentView<Empty>()
private var isUpdating: Bool = false
private var ignoreScrolling: Bool = false
private var previousHadInputHeight: Bool = false
private var component: ComposeTodoScreenComponent?
@ -381,25 +380,10 @@ final class ComposeTodoScreenComponent: Component {
return true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.endEditing(true)
}
private func updateScrolling(transition: ComponentTransition) {
let navigationAlphaDistance: CGFloat = 16.0
let navigationAlpha: CGFloat = max(0.0, min(1.0, self.scrollView.contentOffset.y / navigationAlphaDistance))
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
}
}
func isPanGestureEnabled() -> Bool {
if self.inputMediaNode != nil {
return false
@ -1511,7 +1495,6 @@ final class ComposeTodoScreenComponent: Component {
}
self.previousHadInputHeight = (inputHeight > 0.0)
self.ignoreScrolling = true
let previousBounds = self.scrollView.bounds
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
@ -1548,9 +1531,6 @@ final class ComposeTodoScreenComponent: Component {
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
}
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
if isEditing {
if let controller = environment.controller() as? ComposeTodoScreen {
@ -1662,14 +1642,6 @@ final class ComposeTodoScreenComponent: Component {
self.addSubview(doneButtonView)
}
transition.setFrame(view: doneButtonView, frame: doneButtonFrame)
if isValid {
doneButtonView.layer.filters = []
} else {
if (doneButtonView.layer.filters ?? []).isEmpty, let monochrome = CALayer.monochrome() {
doneButtonView.layer.filters = [monochrome]
}
}
}
if let currentEditingTag = self.currentEditingTag, previousEditingTag !== currentEditingTag, self.currentInputMode != .keyboard {

View File

@ -1,49 +0,0 @@
#include <metal_stdlib>
#include "EditorCommon.h"
#include "EditorUtils.h"
using namespace metal;
kernel void morphologyMaximumFilter(texture2d<float, access::sample> inputTexture [[texture(0)]],
texture2d<float, access::write> outputTexture [[texture(1)]],
constant float& radius [[buffer(0)]],
uint2 gid [[thread_position_in_grid]]) {
uint2 size = uint2(inputTexture.get_width(), inputTexture.get_height());
uint2 pos = gid;
float maxIntensity = 0.0;
int kernelRadius = int(radius);
for (int y = -kernelRadius; y <= kernelRadius; ++y) {
for (int x = -kernelRadius; x <= kernelRadius; ++x) {
uint2 samplePos = pos + uint2(x, y);
if (samplePos.x >= 0 && samplePos.y >= 0 && samplePos.x < size.x && samplePos.y < size.y) {
float intensity = inputTexture.read(samplePos).a;
if (intensity > maxIntensity) {
maxIntensity = intensity;
}
}
}
}
outputTexture.write(maxIntensity, gid);
}
fragment half4 stickerOutlineFragmentShader(RasterizerData in [[stage_in]],
texture2d<half, access::sample> sourceTexture [[texture(0)]],
texture2d<half, access::sample> maskTexture [[texture(1)]]
)
{
constexpr sampler colorSampler(min_filter::linear, mag_filter::linear, address::clamp_to_zero);
constexpr sampler maskSampler(min_filter::linear, mag_filter::linear, address::clamp_to_zero);
half4 color = sourceTexture.sample(colorSampler, in.texCoord);
half intensity = maskTexture.sample(maskSampler, in.texCoord).r;
half4 result = half4(intensity, intensity, intensity, max(color.a, intensity));
result.r = mix(result.r, color.r, color.a);
result.g = mix(result.g, color.g, color.a);
result.b = mix(result.b, color.b, color.a);
return result;
}

View File

@ -85,6 +85,7 @@ public final class SearchInputPanelComponent: Component {
public final class View: UIView, UITextFieldDelegate {
private let edgeEffectView: EdgeEffectView
private let containerView: GlassBackgroundContainerView
private let backgroundView: GlassBackgroundView
private let icon = ComponentView<Empty>()
@ -106,13 +107,15 @@ public final class SearchInputPanelComponent: Component {
override init(frame: CGRect) {
self.edgeEffectView = EdgeEffectView()
self.containerView = GlassBackgroundContainerView()
self.backgroundView = GlassBackgroundView()
self.textField = TextField()
super.init(frame: frame)
self.addSubview(self.edgeEffectView)
self.addSubview(self.backgroundView)
self.addSubview(self.containerView)
self.containerView.contentView.addSubview(self.backgroundView)
}
required init?(coder: NSCoder) {
@ -196,7 +199,7 @@ public final class SearchInputPanelComponent: Component {
let fieldFrame = CGRect(origin: CGPoint(x: edgeInsets.left, y: edgeInsets.top), size: CGSize(width: availableSize.width - edgeInsets.left - edgeInsets.right - fieldHeight - buttonSpacing, height: fieldHeight))
let cancelButtonFrame = CGRect(origin: CGPoint(x: edgeInsets.left + fieldFrame.width + buttonSpacing, y: edgeInsets.top), size: CGSize(width: fieldHeight, height: fieldHeight))
self.backgroundView.update(size: fieldFrame.size, cornerRadius: fieldFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: backgroundColor), transition: transition)
self.backgroundView.update(size: fieldFrame.size, cornerRadius: fieldFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: backgroundColor), isInteractive: true, transition: transition)
transition.setFrame(view: self.backgroundView, frame: fieldFrame)
let fieldSideInset: CGFloat = 41.0
@ -299,7 +302,7 @@ public final class SearchInputPanelComponent: Component {
)
if let cancelButtonView = self.cancelButton.view {
if cancelButtonView.superview == nil {
self.addSubview(cancelButtonView)
self.containerView.contentView.addSubview(cancelButtonView)
}
transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
}
@ -313,6 +316,9 @@ public final class SearchInputPanelComponent: Component {
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: edgeColor, blur: true, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition)
transition.setFrame(view: self.containerView, frame: CGRect(origin: .zero, size: size))
self.containerView.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition)
return size
}
}

View File

@ -42,6 +42,16 @@ func openWebAppImpl(
presentationData = context.sharedContext.currentPresentationData.with({ $0 })
}
var skipTermsOfService = skipTermsOfService
if let whiteListedBots = context.currentAppConfiguration.with({ $0 }).data?["whitelisted_bots"] as? [Double] {
let botId = botPeer.id.id._internalGetInt64Value()
for bot in whiteListedBots {
if Int64(bot) == botId {
skipTermsOfService = true
}
}
}
let botName: String
let botAddress: String
let botVerified: Bool
@ -530,6 +540,16 @@ public extension ChatControllerImpl {
peerId = botPeer.id
}
var skipTermsOfService = false
if let whiteListedBots = context.currentAppConfiguration.with({ $0 }).data?["whitelisted_bots"] as? [Double] {
let botId = botPeer.id.id._internalGetInt64Value()
for bot in whiteListedBots {
if Int64(bot) == botId {
skipTermsOfService = true
}
}
}
chatController?.attachmentController?.dismiss(animated: true, completion: nil)
let updatedPresentationData = chatController?.updatedPresentationData
@ -657,17 +677,21 @@ public extension ChatControllerImpl {
openBotApp(false, false, appSettings)
}
} else {
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
openBotApp(allowWrite, false, appSettings)
}, showMore: chatController == nil ? nil : { [weak chatController] in
if let chatController {
chatController.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil)
}
}, openTerms: {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: parentController?.navigationController as? NavigationController, dismissInput: {})
})
parentController?.present(controller, in: .window(.root))
if skipTermsOfService {
openBotApp(true, false, appSettings)
} else {
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
openBotApp(allowWrite, false, appSettings)
}, showMore: chatController == nil ? nil : { [weak chatController] in
if let chatController {
chatController.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil)
}
}, openTerms: {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: parentController?.navigationController as? NavigationController, dismissInput: {})
})
parentController?.present(controller, in: .window(.root))
}
}
} else {
openBotApp(false, false, appSettings)

View File

@ -1378,14 +1378,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
let _ = (self.context.engine.messages.attachMenuBots()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak self] attachMenuBots in
guard let self else {
guard let self, let controller = self.controller else {
return
}
let currentTimestamp = CACurrentMediaTime()
var fillData = false
let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) })
if isAttachMenu || attachMenuBot != nil {
if isAttachMenu || attachMenuBot != nil || controller.isWhiteListedBot {
if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 {
self.webView?.lastTouchTimestamp = nil
fillData = true
@ -3492,6 +3492,18 @@ public final class WebAppController: ViewController, AttachmentContainable {
return false
}
private var isWhiteListedBot: Bool {
if let whiteListedBots = self.context.currentAppConfiguration.with({ $0 }).data?["whitelisted_bots"] as? [Double] {
let botId = self.botId.id._internalGetInt64Value()
for bot in whiteListedBots {
if Int64(bot) == botId {
return true
}
}
}
return false
}
public func beforeMaximize(navigationController: NavigationController, completion: @escaping () -> Void) {
switch self.source {
case .generic, .settings: