From 59d9b02c8c239a1b4ef33d67ea7b7b5869bb63f5 Mon Sep 17 00:00:00 2001 From: Lev Poznyakov Date: Thu, 9 Apr 2026 19:28:14 +0400 Subject: [PATCH 1/3] videoControlsInFooter --- .../Sources/SGSettingsController.swift | 4 ++ .../Sources/SimpleSettings.swift | 7 ++- .../ChatItemGalleryFooterContentNode.swift | 52 +++++++++++++++- .../Items/UniversalVideoGalleryItem.swift | 32 ++++++++++ .../VideoPlaybackControlsComponent.swift | 60 +++++++++++++++---- 5 files changed, 139 insertions(+), 16 deletions(-) diff --git a/Swiftgram/SGSettingsUI/Sources/SGSettingsController.swift b/Swiftgram/SGSettingsUI/Sources/SGSettingsController.swift index da487fa1c9..662c7493cc 100644 --- a/Swiftgram/SGSettingsUI/Sources/SGSettingsController.swift +++ b/Swiftgram/SGSettingsUI/Sources/SGSettingsController.swift @@ -106,6 +106,7 @@ private enum SGBoolSetting: String { case nyStyleSnow case nyStyleLightning case tabBarSearchEnabled + case videoControlsInFooter } private enum SGOneFromManySetting: String { @@ -305,6 +306,7 @@ private func SGControllerEntries(presentationData: PresentationData, callListSet id.increment(10000) entries.append(.header(id: id.count, section: .other, text: strings.Appearance_Other.uppercased(), badge: nil)) + entries.append(.toggle(id: id.count, section: .other, settingName: .videoControlsInFooter, value: SGSimpleSettings.shared.videoControlsInFooter, text: i18n("Settings.videoControlsInFooter", lang), enabled: true)) entries.append(.toggle(id: id.count, section: .other, settingName: .swipeForVideoPIP, value: SGSimpleSettings.shared.videoPIPSwipeDirection == SGSimpleSettings.VideoPIPSwipeDirection.up.rawValue, text: i18n("Settings.swipeForVideoPIP", lang), enabled: true)) entries.append(.notice(id: id.count, section: .other, text: i18n("Settings.swipeForVideoPIP.Notice", lang))) entries.append(.toggle(id: id.count, section: .other, settingName: .hideChannelBottomButton, value: !SGSimpleSettings.shared.hideChannelBottomButton, text: i18n("Settings.showChannelBottomButton", lang), enabled: true)) @@ -521,6 +523,8 @@ public func sgSettingsController(context: AccountContext/*, focusOnItemTag: Int? case .nyStyleLightning: SGSimpleSettings.shared.nyStyle = value ? SGSimpleSettings.NYStyle.lightning.rawValue : SGSimpleSettings.NYStyle.default.rawValue simplePromise.set(true) // Trigger update for 'enabled' field of other toggles + case .videoControlsInFooter: + SGSimpleSettings.shared.videoControlsInFooter = value } }, updateSliderValue: { setting, value in switch (setting) { diff --git a/Swiftgram/SGSimpleSettings/Sources/SimpleSettings.swift b/Swiftgram/SGSimpleSettings/Sources/SimpleSettings.swift index 2db47deb51..d2e2522ba9 100644 --- a/Swiftgram/SGSimpleSettings/Sources/SimpleSettings.swift +++ b/Swiftgram/SGSimpleSettings/Sources/SimpleSettings.swift @@ -176,6 +176,7 @@ public class SGSimpleSettings { case warnOnStoriesOpen case showProfileId case sendWithReturnKey + case videoControlsInFooter } public enum DownloadSpeedBoostValues: String, CaseIterable { @@ -328,7 +329,8 @@ public class SGSimpleSettings { Keys.hideStories.rawValue: false, Keys.warnOnStoriesOpen.rawValue: false, Keys.showProfileId.rawValue: true, - Keys.sendWithReturnKey.rawValue: false + Keys.sendWithReturnKey.rawValue: false, + Keys.videoControlsInFooter.rawValue: false, ] public static let groupDefaultValues: [String: Any] = [ @@ -592,6 +594,9 @@ public class SGSimpleSettings { @UserDefault(key: Keys.tabBarSearchEnabled.rawValue) public var tabBarSearchEnabled: Bool + + @UserDefault(key: Keys.videoControlsInFooter.rawValue) + public var videoControlsInFooter: Bool } extension SGSimpleSettings { diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index a673be983b..7b562f057f 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -8,6 +8,7 @@ import SwiftSignalKit import Photos import TelegramPresentationData import TelegramUIPreferences +import SGSimpleSettings import TextFormat import TelegramStringFormatting import AccountContext @@ -32,6 +33,7 @@ import TelegramNotices import SolidRoundedButtonNode import UrlHandling import GlassControls +import VideoPlaybackControlsComponent import ComponentFlow import ComponentDisplayAdapters import EdgeEffect @@ -154,6 +156,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll private var buttonNode: SolidRoundedButtonNode? private var buttonIconNode: ASImageNode? private let buttonPanel = ComponentView() + private let playbackButtonPanel = ComponentView() private var textSelectionNode: TextSelectionNode? @@ -285,6 +288,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll self.statusButtonNode.isHidden = true self.statusNode.isHidden = true } + self.requestLayout?(.animated(duration: 0.2, curve: .easeInOut)) } } } @@ -1306,6 +1310,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll var leftControlItems: [GlassControlGroupComponent.Item] = [] var rightControlItems: [GlassControlGroupComponent.Item] = [] var centerControlItems: [GlassControlGroupComponent.Item] = [] + let videoControlsInFooter = SGSimpleSettings.shared.videoControlsInFooter + let playbackControlsVisible = videoControlsInFooter && (!self.playbackControlButton.isHidden || self.hasSeekControls) if let buttonsState = self.buttonsState { if buttonsState.displayActionButton { @@ -1320,7 +1326,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } )) } - if buttonsState.displayPictureInPictureButton { + if !videoControlsInFooter && buttonsState.displayPictureInPictureButton { centerControlItems.append(GlassControlGroupComponent.Item( id: AnyHashable("pip"), content: .icon("Media Gallery/PictureInPictureButton"), @@ -1332,7 +1338,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } )) } - if let settingsButtonState = buttonsState.settingsButtonState { + if !videoControlsInFooter, let settingsButtonState = buttonsState.settingsButtonState { centerControlItems.append(GlassControlGroupComponent.Item( id: AnyHashable("settings"), content: .customIcon(id: AnyHashable("settings"), component: AnyComponent(SettingsNavigationIconComponent( @@ -1419,6 +1425,32 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll centerControlItems = [] } + var playbackPanelSize = CGSize() + if playbackControlsVisible { + playbackPanelSize = self.playbackButtonPanel.update( + transition: ComponentTransition(transition), + component: AnyComponent(VideoPlaybackControlsComponent( + layoutParams: .init(sideButtonSize: 44.0, centerButtonSize: 44.0, spacing: 12.0), + isFooter: true, + isVisible: playbackControlsVisible, + isPlaying: !self.currentIsPaused, + displaySeekControls: self.hasSeekControls, + togglePlayback: { [weak self] in + self?.playbackControlPressed() + }, + seek: { [weak self] isForward in + if isForward { + self?.forwardButtonPressed() + } else { + self?.backwardButtonPressed() + } + } + )), + environment: {}, + containerSize: CGSize(width: width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0) + ) + } + let buttonPanelSize = self.buttonPanel.update( transition: ComponentTransition(transition), component: AnyComponent(GlassControlPanelComponent( @@ -1458,6 +1490,16 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - buttonPanelInsets.bottom - 44.0 + floorToScreenPixels((44.0 - 44.0) * 0.5)), size: CGSize(width: 44.0, height: 44.0)) self.playPauseIconNode.frame = self.playbackControlButton.bounds.offsetBy(dx: 2.0, dy: -2.0) + if playbackControlsVisible, let playbackButtonPanelView = self.playbackButtonPanel.view { + if playbackButtonPanelView.superview == nil { + self.contentNode.view.addSubview(playbackButtonPanelView) + } + let playbackPanelFrame = CGRect(origin: CGPoint(x: floor((width - playbackPanelSize.width) * 0.5), y: buttonPanelFrame.minY), size: playbackPanelSize) + ComponentTransition(transition).setFrame(view: playbackButtonPanelView, frame: playbackPanelFrame) + } else if let playbackButtonPanelView = self.playbackButtonPanel.view, playbackButtonPanelView.superview != nil { + playbackButtonPanelView.removeFromSuperview() + } + let statusSize = CGSize(width: 28.0, height: 28.0) transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: floor((width - statusSize.width) / 2.0), y: panelHeight - bottomInset - statusSize.height - 8.0), size: statusSize)) @@ -1517,6 +1559,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll if let buttonPanelView = self.buttonPanel.view { buttonPanelView.alpha = 1.0 } + if let playbackButtonPanelView = self.playbackButtonPanel.view { + playbackButtonPanelView.alpha = 1.0 + } self.backwardButton.alpha = self.hasSeekControls ? 1.0 : 0.0 self.forwardButton.alpha = self.hasSeekControls ? 1.0 : 0.0 @@ -1546,6 +1591,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll if let buttonPanelView = self.buttonPanel.view { buttonPanelView.alpha = 0.0 } + if let playbackButtonPanelView = self.playbackButtonPanel.view { + playbackButtonPanelView.alpha = 0.0 + } self.backwardButton.alpha = 0.0 self.forwardButton.alpha = 0.0 diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 2e7a455f46..02d41c60f7 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1219,6 +1219,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if !self.areControlsVisible || hideControls { playbackControlsIsVisible = false } + if SGSimpleSettings.shared.videoControlsInFooter { + playbackControlsIsVisible = false + } let playbackControlsSize = self.playbackControls.update( transition: ComponentTransition(transition), @@ -1228,6 +1231,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { centerButtonSize: 92.0, spacing: 30.0 ), + isFooter: false, isVisible: playbackControlsIsVisible, isPlaying: playbackControlsIsPlaying, displaySeekControls: playbackControlsIsSeekable, @@ -3354,6 +3358,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { var topItems: [ContextMenuItem] = [] var items: [ContextMenuItem] = [] + var hasVideoControlItems = false if isSettings { let sliderValuePromise = ValuePromise(nil) @@ -3485,7 +3490,34 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } } else { + if SGSimpleSettings.shared.videoControlsInFooter && strongSelf.hasPictureInPicture { + hasVideoControlItems = true + items.append(.action(ContextMenuActionItem(text: "Picture in Picture", textColor: .primary, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/PictureInPictureButton"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + self?.pictureInPictureButtonPressed() + }))) + } + if SGSimpleSettings.shared.videoControlsInFooter && strongSelf.settingsButtonState != nil { + hasVideoControlItems = true + items.append(.action(ContextMenuActionItem(text: "Settings", textColor: .primary, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + Queue.mainQueue().after(0.12, { + guard let self else { + return + } + self.openMoreMenu(sourceView: self.moreBarButton.referenceNode.view, gesture: nil, adMessage: nil, isSettings: true, actionsOnTop: false) + }) + }))) + } + if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() && !item.peerIsCopyProtected && message.paidContent == nil { + if hasVideoControlItems { + items.append(.separator) + } items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_MenuSaveToGallery, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in guard let self else { c?.dismiss(result: .default, completion: nil) diff --git a/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift b/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift index 4e92cd3b51..e890a6ec8e 100644 --- a/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift +++ b/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift @@ -22,6 +22,7 @@ public final class VideoPlaybackControlsComponent: Component { } let layoutParams: LayoutParams + let isFooter: Bool let isVisible: Bool let isPlaying: Bool let displaySeekControls: Bool @@ -30,6 +31,7 @@ public final class VideoPlaybackControlsComponent: Component { public init( layoutParams: LayoutParams, + isFooter: Bool = false, isVisible: Bool, isPlaying: Bool, displaySeekControls: Bool, @@ -37,6 +39,7 @@ public final class VideoPlaybackControlsComponent: Component { seek: @escaping (Bool) -> Void ) { self.layoutParams = layoutParams + self.isFooter = isFooter self.isVisible = isVisible self.isPlaying = isPlaying self.displaySeekControls = displaySeekControls @@ -48,6 +51,9 @@ public final class VideoPlaybackControlsComponent: Component { if lhs.layoutParams != rhs.layoutParams { return false } + if lhs.isFooter != rhs.isFooter { + return false + } if lhs.isVisible != rhs.isVisible { return false } @@ -62,6 +68,7 @@ public final class VideoPlaybackControlsComponent: Component { public final class View: UIView { private let backgroundContainer: GlassBackgroundContainerView + private let groupBackgroundView: GlassBackgroundView private let leftButtonBackgroundView: GlassBackgroundView private let leftIconView: PlaybackIconView private let rightButtonBackgroundView: GlassBackgroundView @@ -71,9 +78,11 @@ public final class VideoPlaybackControlsComponent: Component { private var component: VideoPlaybackControlsComponent? private weak var state: EmptyComponentState? + private var videoControlsInFooterBackground = false public override init(frame: CGRect) { self.backgroundContainer = GlassBackgroundContainerView() + self.groupBackgroundView = GlassBackgroundView() self.leftButtonBackgroundView = GlassBackgroundView() self.leftIconView = PlaybackIconView(isForward: false) @@ -89,6 +98,7 @@ public final class VideoPlaybackControlsComponent: Component { super.init(frame: frame) + self.addSubview(self.groupBackgroundView) self.addSubview(self.backgroundContainer) self.backgroundContainer.contentView.addSubview(self.leftButtonBackgroundView) @@ -144,6 +154,20 @@ public final class VideoPlaybackControlsComponent: Component { return result } + private func updateControlsHost(isFooter: Bool) { + if self.videoControlsInFooterBackground == isFooter { + return + } + self.videoControlsInFooterBackground = isFooter + + let targetView = isFooter ? self.groupBackgroundView.contentView : self.backgroundContainer.contentView + targetView.addSubview(self.leftButtonBackgroundView) + targetView.addSubview(self.rightButtonBackgroundView) + targetView.addSubview(self.centerButtonBackgroundView) + + self.backgroundContainer.isUserInteractionEnabled = !isFooter + } + func update(component: VideoPlaybackControlsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let isVisibleChanged = self.component?.isVisible != component.isVisible @@ -152,12 +176,20 @@ public final class VideoPlaybackControlsComponent: Component { self.isUserInteractionEnabled = component.isVisible - let containerInset: CGFloat = 32.0 + var containerInset: CGFloat = 32.0 + let isFooter = component.isFooter + if isFooter { + containerInset = 0.0 + } + self.updateControlsHost(isFooter: isFooter) - let size = CGSize(width: component.layoutParams.sideButtonSize * 2.0 + component.layoutParams.centerButtonSize + component.layoutParams.spacing * 2.0, height: component.layoutParams.centerButtonSize) + let size = CGSize( + width: component.displaySeekControls ? (component.layoutParams.sideButtonSize * 2.0 + component.layoutParams.centerButtonSize + component.layoutParams.spacing * 2.0) : component.layoutParams.centerButtonSize, + height: component.layoutParams.centerButtonSize + ) let leftButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize)).offsetBy(dx: containerInset, dy: containerInset) - let centerButtonFrame = CGRect(origin: CGPoint(x: component.layoutParams.sideButtonSize + component.layoutParams.spacing, y: floorToScreenPixels((size.height - component.layoutParams.centerButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.centerButtonSize, height: component.layoutParams.centerButtonSize)).offsetBy(dx: containerInset, dy: containerInset) + let centerButtonFrame = CGRect(origin: CGPoint(x: component.displaySeekControls ? (component.layoutParams.sideButtonSize + component.layoutParams.spacing) : 0.0, y: floorToScreenPixels((size.height - component.layoutParams.centerButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.centerButtonSize, height: component.layoutParams.centerButtonSize)).offsetBy(dx: containerInset, dy: containerInset) let rightButtonFrame = CGRect(origin: CGPoint(x: size.width - component.layoutParams.sideButtonSize, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize)).offsetBy(dx: containerInset, dy: containerInset) if isVisibleChanged && !transition.animation.isImmediate { @@ -170,28 +202,30 @@ public final class VideoPlaybackControlsComponent: Component { let areSideButtonsVisible = component.isVisible && component.displaySeekControls let buttonsTintColor: GlassBackgroundView.TintColor = .init(kind: .custom(style: .clear, color: UIColor(white: 0.0, alpha: 0.2))) + transition.setFrame(view: self.groupBackgroundView, frame: CGRect(origin: CGPoint(), size: size)) + self.groupBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: isFooter, isVisible: component.isVisible && isFooter, transition: transition) transition.setFrame(view: self.leftButtonBackgroundView, frame: leftButtonFrame) - self.leftButtonBackgroundView.update(size: leftButtonFrame.size, cornerRadius: leftButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: areSideButtonsVisible, transition: transition) + self.leftButtonBackgroundView.update(size: leftButtonFrame.size, cornerRadius: leftButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: isFooter ? false : areSideButtonsVisible, transition: transition) transition.setFrame(view: self.leftIconView, frame: CGRect(origin: CGPoint(), size: leftButtonFrame.size)) - self.leftIconView.update(size: leftButtonFrame.size) + self.leftIconView.update(size: leftButtonFrame.size, isFooter: isFooter) transition.setAlpha(view: self.leftIconView, alpha: areSideButtonsVisible ? 1.0 : 0.0) transition.setBlur(layer: self.leftIconView.layer, radius: areSideButtonsVisible ? 0.0 : 10.0) self.leftButtonBackgroundView.isUserInteractionEnabled = areSideButtonsVisible transition.setFrame(view: self.rightButtonBackgroundView, frame: rightButtonFrame) - self.rightButtonBackgroundView.update(size: rightButtonFrame.size, cornerRadius: rightButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: areSideButtonsVisible, transition: transition) + self.rightButtonBackgroundView.update(size: rightButtonFrame.size, cornerRadius: rightButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: isFooter ? false : areSideButtonsVisible, transition: transition) transition.setFrame(view: self.rightIconView, frame: CGRect(origin: CGPoint(), size: rightButtonFrame.size)) - self.rightIconView.update(size: rightButtonFrame.size) + self.rightIconView.update(size: rightButtonFrame.size, isFooter: isFooter) transition.setAlpha(view: self.rightIconView, alpha: areSideButtonsVisible ? 1.0 : 0.0) transition.setBlur(layer: self.rightIconView.layer, radius: areSideButtonsVisible ? 0.0 : 10.0) self.rightButtonBackgroundView.isUserInteractionEnabled = areSideButtonsVisible transition.setFrame(view: self.centerButtonBackgroundView, frame: centerButtonFrame) - self.centerButtonBackgroundView.update(size: centerButtonFrame.size, cornerRadius: centerButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: component.isVisible, transition: transition) + self.centerButtonBackgroundView.update(size: centerButtonFrame.size, cornerRadius: centerButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: isFooter ? false : component.isVisible, transition: transition) let centerButtonIconNode: PlayPauseIconNode - let centerIconFactor: CGFloat = 0.9 + let centerIconFactor: CGFloat = isFooter ? 0.84 : 0.9 let centerButtonIconSize = CGSize(width: centerButtonFrame.width * centerIconFactor, height: centerButtonFrame.height * centerIconFactor) if let current = self.centerButtonIconNode, current.size == centerButtonIconSize { centerButtonIconNode = current @@ -206,7 +240,7 @@ public final class VideoPlaybackControlsComponent: Component { self.centerButtonBackgroundView.contentView.addSubview(centerButtonIconNode.view) centerButtonIconNode.enqueueState(component.isPlaying ? .pause : .play, animated: false) } - transition.setFrame(view: centerButtonIconNode.view, frame: centerButtonIconSize.centered(in: CGRect(origin: CGPoint(), size: centerButtonFrame.size)).offsetBy(dx: component.isPlaying ? 0.0 : 5.0, dy: 0.0)) + transition.setFrame(view: centerButtonIconNode.view, frame: centerButtonIconSize.centered(in: CGRect(origin: CGPoint(), size: centerButtonFrame.size)).offsetBy(dx: component.isPlaying || isFooter ? 0.0 : 5.0, dy: 0.0)) centerButtonIconNode.enqueueState(component.isPlaying ? .pause : .play, animated: !transition.animation.isImmediate && component.isVisible && !isVisibleChanged) transition.setAlpha(view: centerButtonIconNode.view, alpha: component.isVisible ? 1.0 : 0.0) transition.setBlur(layer: centerButtonIconNode.layer, radius: component.isVisible ? 0.0 : 10.0) @@ -322,16 +356,16 @@ private final class PlaybackIconView: HighlightTrackingButton { fatalError("init(coder:) has not been implemented") } - func update(size: CGSize) { + func update(size: CGSize, isFooter: Bool) { if let image = self.backgroundIconView.image { - let factor: CGFloat = 1.4 + let factor: CGFloat = isFooter ? 1.12 : 1.4 self.backgroundIconView.frame = CGSize(width: floor(image.size.width * factor), height: floor(image.size.height * factor)).centered(in: CGRect(origin: CGPoint(), size: size)) } let textSize = self.text.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "15", font: Font.with(size: 16.0, design: .round, weight: .semibold, traits: []), textColor: .white)) + text: .plain(NSAttributedString(string: "15", font: Font.with(size: isFooter ? 11.0 : 16.0, design: .round, weight: .semibold, traits: []), textColor: .white)) )), environment: {}, containerSize: size From 54aa08ef7f9292c235262eae24b224606317e4e4 Mon Sep 17 00:00:00 2001 From: Lev Poznyakov Date: Thu, 9 Apr 2026 22:50:47 +0400 Subject: [PATCH 2/3] ai translations --- Swiftgram/SGStrings/Strings/af.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/ar.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/ca.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/cs.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/da.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/de.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/el.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/es.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/fa.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/fi.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/fr.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/he.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/hi.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/hu.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/id.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/it.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/ja.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/km.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/ko.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/ku.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/nl.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/no.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/pl.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/pt.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/ro.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/ru.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/si.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/sk.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/sr.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/sv.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/tr.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/uk.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/uz.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/vi.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/zh-hans.lproj/SGLocalizable.strings | 1 + Swiftgram/SGStrings/Strings/zh-hant.lproj/SGLocalizable.strings | 1 + 37 files changed, 37 insertions(+) diff --git a/Swiftgram/SGStrings/Strings/af.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/af.lproj/SGLocalizable.strings index 5acfe970d5..50e09ef39b 100644 --- a/Swiftgram/SGStrings/Strings/af.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/af.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP met Veeg"; "Settings.swipeForVideoPIP.Notice" = "As geaktiveer, sal die veeg van die video dit in Prent-in-Prent modus oopmaak."; +"Settings.videoControlsInFooter" = "Videokontroles in voetskrif"; diff --git a/Swiftgram/SGStrings/Strings/ar.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/ar.lproj/SGLocalizable.strings index 41f1168454..d347973a51 100644 --- a/Swiftgram/SGStrings/Strings/ar.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/ar.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "فيديو PIP مع السحب"; "Settings.swipeForVideoPIP.Notice" = "إذا تم تمكينه، سيفتح سحب الفيديو في وضع الصورة في الصورة."; +"Settings.videoControlsInFooter" = "عناصر تحكم الفيديو في التذييل"; diff --git a/Swiftgram/SGStrings/Strings/ca.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/ca.lproj/SGLocalizable.strings index a48c45fa92..599b69876d 100644 --- a/Swiftgram/SGStrings/Strings/ca.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/ca.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Vídeo PIP amb desplaçament"; "Settings.swipeForVideoPIP.Notice" = "Si està habilitat, desplaçar el vídeo l'obrirà en mode Imatge en Imatge."; +"Settings.videoControlsInFooter" = "Controls de vídeo al peu de pàgina"; diff --git a/Swiftgram/SGStrings/Strings/cs.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/cs.lproj/SGLocalizable.strings index 05cf6ed482..ce03d5470b 100644 --- a/Swiftgram/SGStrings/Strings/cs.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/cs.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP s přetahováním"; "Settings.swipeForVideoPIP.Notice" = "Pokud je povoleno, poslání videa jej otevře v režimu Obraz v obraze."; +"Settings.videoControlsInFooter" = "Ovládání videa v zápatí"; diff --git a/Swiftgram/SGStrings/Strings/da.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/da.lproj/SGLocalizable.strings index cb0c4174db..9e371635a2 100644 --- a/Swiftgram/SGStrings/Strings/da.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/da.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP med Swipe"; "Settings.swipeForVideoPIP.Notice" = "Hvis aktiveret, vil sletning af video åbne den i billede-i-billede-tilstand."; +"Settings.videoControlsInFooter" = "Videokontroller i sidefoden"; diff --git a/Swiftgram/SGStrings/Strings/de.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/de.lproj/SGLocalizable.strings index 726e485dcf..b2d0cbb123 100644 --- a/Swiftgram/SGStrings/Strings/de.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/de.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP mit Wischen"; "Settings.swipeForVideoPIP.Notice" = "Wenn aktiviert, öffnet das Wischen des Videos es im Bild-in-Bild-Modus."; +"Settings.videoControlsInFooter" = "Videosteuerung in der Fußzeile"; diff --git a/Swiftgram/SGStrings/Strings/el.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/el.lproj/SGLocalizable.strings index 010c2fd7ed..48e227bd72 100644 --- a/Swiftgram/SGStrings/Strings/el.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/el.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Βίντεο PIP με Swipe"; "Settings.swipeForVideoPIP.Notice" = "Αν είναι ενεργοποιημένο, το σ swipe video θα το ανοίξει σε λειτουργία Εικόνα μέσα στην Εικόνα."; +"Settings.videoControlsInFooter" = "Στοιχεία ελέγχου βίντεο στο υποσέλιδο"; diff --git a/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings index 0383460b65..07c14f5570 100644 --- a/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings @@ -178,6 +178,7 @@ "Settings.swipeForVideoPIP" = "Video PIP with Swipe"; "Settings.swipeForVideoPIP.Notice" = "If enabled, swiping video will open it in Picture-in-Picture mode."; +"Settings.videoControlsInFooter" = "Video controls in footer"; "SessionBackup.Title" = "Accounts Backup"; "SessionBackup.Sessions.Title" = "Sessions"; diff --git a/Swiftgram/SGStrings/Strings/es.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/es.lproj/SGLocalizable.strings index 4fcb6aee08..59e6ff5c0c 100644 --- a/Swiftgram/SGStrings/Strings/es.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/es.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP con deslizamiento"; "Settings.swipeForVideoPIP.Notice" = "Si está habilitado, deslizar el video lo abrirá en modo imagen en imagen."; +"Settings.videoControlsInFooter" = "Controles de video en el pie de página"; diff --git a/Swiftgram/SGStrings/Strings/fa.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/fa.lproj/SGLocalizable.strings index 1581d63536..44720b7ff6 100644 --- a/Swiftgram/SGStrings/Strings/fa.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/fa.lproj/SGLocalizable.strings @@ -7,3 +7,4 @@ "Settings.ShowProfileID" = "نمایش ایدی پروفایل"; "Settings.Translation.QuickTranslateButton" = "دکمه ترجمه سریع"; "ContextMenu.SaveToCloud" = "ذخیره در فضای ابری"; +"Settings.videoControlsInFooter" = "کنترل‌های ویدیو در پایین صفحه"; diff --git a/Swiftgram/SGStrings/Strings/fi.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/fi.lproj/SGLocalizable.strings index 3e7ea96fbf..8f2671b829 100644 --- a/Swiftgram/SGStrings/Strings/fi.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/fi.lproj/SGLocalizable.strings @@ -228,3 +228,4 @@ "Paywall.Error.Title" = "Virhe"; "PayWall.ValidationError" = "Vahvistusvirhe"; "PayWall.ValidationError.TryAgain" = "Ostovahvistuksessa tapahtui jokin virhe. Ei hätää! Yritä palauttaa ostot hieman myöhemmin."; +"Settings.videoControlsInFooter" = "Videon säätimet alatunnisteessa"; diff --git a/Swiftgram/SGStrings/Strings/fr.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/fr.lproj/SGLocalizable.strings index adc9a1b3d3..9e727ae816 100644 --- a/Swiftgram/SGStrings/Strings/fr.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/fr.lproj/SGLocalizable.strings @@ -135,3 +135,4 @@ "Settings.forceBuiltInMic.Notice" = "Si activé, l'application utilisera uniquement le microphone de l'appareil même si des écouteurs sont connectés."; "Settings.hideChannelBottomButton" = "Masquer le panneau inférieur du canal"; +"Settings.videoControlsInFooter" = "Contrôles vidéo dans le pied de page"; diff --git a/Swiftgram/SGStrings/Strings/he.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/he.lproj/SGLocalizable.strings index eb4562b7c8..2c58ecf1a4 100644 --- a/Swiftgram/SGStrings/Strings/he.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/he.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "וידאו PIP עם החלקה"; "Settings.swipeForVideoPIP.Notice" = "אם מופעל, החלקת הווידאו תפתח אותו במצב תמונה בתוך תמונה."; +"Settings.videoControlsInFooter" = "פקדי וידאו בכותרת התחתונה"; diff --git a/Swiftgram/SGStrings/Strings/hi.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/hi.lproj/SGLocalizable.strings index 6adc148a1d..2727962951 100644 --- a/Swiftgram/SGStrings/Strings/hi.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/hi.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "वीडियो PIP स्वाइप के साथ"; "Settings.swipeForVideoPIP.Notice" = "यदि सक्षम है, तो वीडियो को स्वाइप करने से यह चित्र-इन-चित्र मोड में खोला जाएगा।"; +"Settings.videoControlsInFooter" = "फ़ुटर में वीडियो नियंत्रण"; diff --git a/Swiftgram/SGStrings/Strings/hu.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/hu.lproj/SGLocalizable.strings index d357bb69b6..1e94074179 100644 --- a/Swiftgram/SGStrings/Strings/hu.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/hu.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Videó PIP a húzással"; "Settings.swipeForVideoPIP.Notice" = "Ha engedélyezve van, a videó húzása képet-képben üzemmódban nyitja meg."; +"Settings.videoControlsInFooter" = "Videóvezérlők a láblécben"; diff --git a/Swiftgram/SGStrings/Strings/id.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/id.lproj/SGLocalizable.strings index 44ba7a11a2..9cc654d9b0 100644 --- a/Swiftgram/SGStrings/Strings/id.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/id.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP dengan Geser"; "Settings.swipeForVideoPIP.Notice" = "Jika diaktifkan, menggeser video akan membukanya dalam mode Gambar-dalam-Gambar."; +"Settings.videoControlsInFooter" = "Kontrol video di footer"; diff --git a/Swiftgram/SGStrings/Strings/it.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/it.lproj/SGLocalizable.strings index ca32eafb8c..58a1a44699 100644 --- a/Swiftgram/SGStrings/Strings/it.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/it.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP con scorrimento"; "Settings.swipeForVideoPIP.Notice" = "Se abilitato, scorrendo il video si aprirà in modalità Picture-in-Picture."; +"Settings.videoControlsInFooter" = "Controlli video nel piè di pagina"; diff --git a/Swiftgram/SGStrings/Strings/ja.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/ja.lproj/SGLocalizable.strings index afe45d6566..5bea3b4328 100644 --- a/Swiftgram/SGStrings/Strings/ja.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/ja.lproj/SGLocalizable.strings @@ -244,3 +244,4 @@ "PayWall.ValidationError" = "検証エラー"; "PayWall.ValidationError.TryAgain" = "購入の検証中に問題が発生しました。心配しないでください!後で購入を復元してみてください。"; "PayWall.ValidationError.Expired" = "サブスクリプションの有効期限が切れました。Pro機能へのアクセスを取り戻すには、再度サブスクリプションを登録してください。"; +"Settings.videoControlsInFooter" = "フッター内の動画コントロール"; diff --git a/Swiftgram/SGStrings/Strings/km.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/km.lproj/SGLocalizable.strings index 928cf393a6..8a4ba9453d 100644 --- a/Swiftgram/SGStrings/Strings/km.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/km.lproj/SGLocalizable.strings @@ -6,3 +6,4 @@ "Settings.HidePhoneInSettingsUI" = "លាក់លេខទូរសព្ទក្នុងការកំណត់"; "Settings.Folders.BottomTab" = "ថតឯបាត"; "ContextMenu.SaveToCloud" = "រក្សាទុកទៅពពក"; +"Settings.videoControlsInFooter" = "ការគ្រប់គ្រងវីដេអូនៅផ្នែកខាងក្រោម"; diff --git a/Swiftgram/SGStrings/Strings/ko.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/ko.lproj/SGLocalizable.strings index 501a5f64b4..6998cd6db4 100644 --- a/Swiftgram/SGStrings/Strings/ko.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/ko.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "비디오 PIP 스와이프"; "Settings.swipeForVideoPIP.Notice" = "설정이 활성화되면 비디오를 스와이프하면 화면 속 화면 모드로 열립니다."; +"Settings.videoControlsInFooter" = "바닥글의 비디오 컨트롤"; diff --git a/Swiftgram/SGStrings/Strings/ku.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/ku.lproj/SGLocalizable.strings index 62ac20a89c..6ad1662bf6 100644 --- a/Swiftgram/SGStrings/Strings/ku.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/ku.lproj/SGLocalizable.strings @@ -8,3 +8,4 @@ "Settings.Translation.QuickTranslateButton" = "دوگمەی وەرگێڕانی خێرا"; "Settings.Folders.BottomTab" = "بوخچەکان لە خوارەوە"; "ContextMenu.SaveToCloud" = "هەڵگرتن لە کڵاود"; +"Settings.videoControlsInFooter" = "کۆنترۆڵی ڤیدیۆ لە بەشی خوارەوە"; diff --git a/Swiftgram/SGStrings/Strings/nl.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/nl.lproj/SGLocalizable.strings index d80e6ca49e..006efe31f4 100644 --- a/Swiftgram/SGStrings/Strings/nl.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/nl.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP met veeg"; "Settings.swipeForVideoPIP.Notice" = "Als ingeschakeld, opent het swipen van video het in de modus Beeld-in-Beeld."; +"Settings.videoControlsInFooter" = "Videobediening in de voettekst"; diff --git a/Swiftgram/SGStrings/Strings/no.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/no.lproj/SGLocalizable.strings index 5fd16d5c62..04952edee5 100644 --- a/Swiftgram/SGStrings/Strings/no.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/no.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP med sveip"; "Settings.swipeForVideoPIP.Notice" = "Hvis aktivert, vil sveipingen av video åpne den i bilde-i-bilde-modus."; +"Settings.videoControlsInFooter" = "Videokontroller i bunnteksten"; diff --git a/Swiftgram/SGStrings/Strings/pl.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/pl.lproj/SGLocalizable.strings index 9efb5c2247..dede328e3e 100644 --- a/Swiftgram/SGStrings/Strings/pl.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/pl.lproj/SGLocalizable.strings @@ -243,3 +243,4 @@ "PayWall.ValidationError" = "Błąd weryfikacji"; "PayWall.ValidationError.TryAgain" = "Coś poszło nie tak podczas weryfikacji zakupu. Nie martw się! Spróbuj przywrócić zakupy trochę później."; "PayWall.ValidationError.Expired" = "Twoja subskrypcja wygasła. Subskrybuj ponownie, aby odzyskać dostęp do funkcji Pro."; +"Settings.videoControlsInFooter" = "Elementy sterujące wideo w stopce"; diff --git a/Swiftgram/SGStrings/Strings/pt.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/pt.lproj/SGLocalizable.strings index 6f8830a32e..28dde29ce4 100644 --- a/Swiftgram/SGStrings/Strings/pt.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/pt.lproj/SGLocalizable.strings @@ -261,3 +261,4 @@ "AppBadge.Title" = "Emblema do Aplicativo"; "AppBadge.Notice" = "Personalizar Emblema do Aplicativo Exibido nas capturas de tela"; +"Settings.videoControlsInFooter" = "Controles de vídeo no rodapé"; diff --git a/Swiftgram/SGStrings/Strings/ro.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/ro.lproj/SGLocalizable.strings index ccb2ad1e46..1924ae4e7e 100644 --- a/Swiftgram/SGStrings/Strings/ro.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/ro.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP cu gestul de glisare"; "Settings.swipeForVideoPIP.Notice" = "Dacă este activat, glisarea video va deschide în modul imagine în imagine."; +"Settings.videoControlsInFooter" = "Controale video în subsol"; diff --git a/Swiftgram/SGStrings/Strings/ru.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/ru.lproj/SGLocalizable.strings index 0488ecf5f3..3587c97484 100644 --- a/Swiftgram/SGStrings/Strings/ru.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/ru.lproj/SGLocalizable.strings @@ -272,3 +272,4 @@ "Settings.NY.Style.snow" = "Снег"; "Settings.NY.Style.lightning" = "Молнии"; "Settings.NY.Notice" = "Доступны только на ограниченное время!"; +"Settings.videoControlsInFooter" = "Элементы управления видео в футере"; diff --git a/Swiftgram/SGStrings/Strings/si.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/si.lproj/SGLocalizable.strings index 869c70ba7e..1d438bf2ed 100644 --- a/Swiftgram/SGStrings/Strings/si.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/si.lproj/SGLocalizable.strings @@ -1,2 +1,3 @@ "Settings.Tabs.Header" = "පටිති"; "ContextMenu.SaveToCloud" = "මේඝයට සුරකින්න"; +"Settings.videoControlsInFooter" = "පාදකයේ වීඩියෝ පාලක"; diff --git a/Swiftgram/SGStrings/Strings/sk.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/sk.lproj/SGLocalizable.strings index 77376339e3..c5769ee6cc 100644 --- a/Swiftgram/SGStrings/Strings/sk.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/sk.lproj/SGLocalizable.strings @@ -2,3 +2,4 @@ "Settings.Tabs.ShowContacts" = "Zobraziť kontakty"; "Settings.Tabs.ShowNames" = "Zobraziť názvy záložiek"; "ContextMenu.SaveToCloud" = "Uložiť na Cloud"; +"Settings.videoControlsInFooter" = "Ovládanie videa v pätičke"; diff --git a/Swiftgram/SGStrings/Strings/sr.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/sr.lproj/SGLocalizable.strings index c71efa9f16..98aafbfb66 100644 --- a/Swiftgram/SGStrings/Strings/sr.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/sr.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Видео PIP са свлачење"; "Settings.swipeForVideoPIP.Notice" = "Ако је омогућено, померање видеа ће га отворити у режиму слике у слици."; +"Settings.videoControlsInFooter" = "Контроле видеа у подножју"; diff --git a/Swiftgram/SGStrings/Strings/sv.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/sv.lproj/SGLocalizable.strings index de9ed08295..1a74239037 100644 --- a/Swiftgram/SGStrings/Strings/sv.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/sv.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP med svep"; "Settings.swipeForVideoPIP.Notice" = "Om aktiverat, kommer svepning av video att öppna det i bild-i-bild-läge."; +"Settings.videoControlsInFooter" = "Videokontroller i sidfoten"; diff --git a/Swiftgram/SGStrings/Strings/tr.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/tr.lproj/SGLocalizable.strings index 7f1b643ec7..3cf5a09884 100644 --- a/Swiftgram/SGStrings/Strings/tr.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/tr.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Videoyu kaydırarak PIP"; "Settings.swipeForVideoPIP.Notice" = "Eğer etkinleştirildi ise videoyu kaydırmak, Piksel içinde Piksel modunda açılacaktır."; +"Settings.videoControlsInFooter" = "Alt bilgide video kontrolleri"; diff --git a/Swiftgram/SGStrings/Strings/uk.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/uk.lproj/SGLocalizable.strings index 405fcfb869..a3a6c80bab 100644 --- a/Swiftgram/SGStrings/Strings/uk.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/uk.lproj/SGLocalizable.strings @@ -243,3 +243,4 @@ "PayWall.ValidationError" = "Помилка валідації"; "PayWall.ValidationError.TryAgain" = "Щось пішло не так під час перевірки покупки. Не хвилюйтеся! Спробуйте відновити покупки трохи пізніше."; "PayWall.ValidationError.Expired" = "Ваша підписка застаріла. Підпишіться, щоб відновити доступ до Pro-можливостей."; +"Settings.videoControlsInFooter" = "Елементи керування відео у футері"; diff --git a/Swiftgram/SGStrings/Strings/uz.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/uz.lproj/SGLocalizable.strings index cfab47bc31..68608a4dde 100644 --- a/Swiftgram/SGStrings/Strings/uz.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/uz.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP bilan Surish"; "Settings.swipeForVideoPIP.Notice" = "Agar yoqilgan bo'lsa, videoni surish uni Tasvir ichida Tasvir rejimida ochadi."; +"Settings.videoControlsInFooter" = "Futerda video boshqaruvlari"; diff --git a/Swiftgram/SGStrings/Strings/vi.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/vi.lproj/SGLocalizable.strings index 8878463be8..30b9424e39 100644 --- a/Swiftgram/SGStrings/Strings/vi.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/vi.lproj/SGLocalizable.strings @@ -150,3 +150,4 @@ "Settings.swipeForVideoPIP" = "Video PIP với Vuốt"; "Settings.swipeForVideoPIP.Notice" = "Nếu được kích hoạt, việc vuốt video sẽ mở nó ở chế độ Hình trong hình."; +"Settings.videoControlsInFooter" = "Điều khiển video ở chân trang"; diff --git a/Swiftgram/SGStrings/Strings/zh-hans.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/zh-hans.lproj/SGLocalizable.strings index 460aabbfaa..2c2875384b 100644 --- a/Swiftgram/SGStrings/Strings/zh-hans.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/zh-hans.lproj/SGLocalizable.strings @@ -247,3 +247,4 @@ "PayWall.ValidationError" = "验证错误"; "PayWall.ValidationError.TryAgain" = "购买验证过程中出现问题。不用担心!稍后再试恢复购买。"; "PayWall.ValidationError.Expired" = "您的订阅已过期。再次订阅以重新获得专业版功能。"; +"Settings.videoControlsInFooter" = "页脚中的视频控件"; diff --git a/Swiftgram/SGStrings/Strings/zh-hant.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/zh-hant.lproj/SGLocalizable.strings index cc3963ac1f..4eff49b7d0 100644 --- a/Swiftgram/SGStrings/Strings/zh-hant.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/zh-hant.lproj/SGLocalizable.strings @@ -247,3 +247,4 @@ "PayWall.ValidationError" = "驗證錯誤"; "PayWall.ValidationError.TryAgain" = "在購買驗證過程中出錯。別擔心!稍後再試恢復購買。"; "PayWall.ValidationError.Expired" = "您的訂閱已過期。請重新訂閱以恢復訪問 Pro 功能。"; +"Settings.videoControlsInFooter" = "頁腳中的影片控制項"; From 5ebd7ccf2194116167e4e36d5d9afa511e6427c2 Mon Sep 17 00:00:00 2001 From: Lev Poznyakov Date: Sat, 11 Apr 2026 14:47:53 +0400 Subject: [PATCH 3/3] fix: fullscreen button & play offset --- .../Sources/ChatItemGalleryFooterContentNode.swift | 6 +++++- .../Sources/Items/UniversalVideoGalleryItem.swift | 10 ++++++++++ .../Sources/VideoPlaybackControlsComponent.swift | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 7b562f057f..94f00c3af0 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -193,6 +193,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll private var codeHighlightState: (id: EngineMessage.Id, specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)? + var displayFullscreenButtonInItems: Bool { + return self.buttonsState?.displayFullscreenButton == true + } + var playbackControl: (() -> Void)? var seekBackward: ((Double) -> Void)? var seekForward: ((Double) -> Void)? @@ -1394,7 +1398,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } )) } - if buttonsState.displayFullscreenButton && !metrics.isTablet { + if !videoControlsInFooter && buttonsState.displayFullscreenButton && !metrics.isTablet { centerControlItems.append(GlassControlGroupComponent.Item( id: AnyHashable("fullscreen"), content: .icon(isLandscape ? "Chat/Context Menu/Collapse" : "Chat/Context Menu/Expand"), diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 02d41c60f7..93a300b4c5 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -3513,6 +3513,16 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { }) }))) } + let isLandscape = strongSelf.bounds.width > strongSelf.bounds.height + if SGSimpleSettings.shared.videoControlsInFooter && strongSelf.footerContentNode.displayFullscreenButtonInItems { + hasVideoControlItems = true + items.append(.action(ContextMenuActionItem(text: isLandscape ? "Collapse" : "Fullscreen", textColor: .primary, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: isLandscape ? "Chat/Context Menu/Collapse" : "Chat/Context Menu/Expand"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + self?.footerContentNode.fullscreenButtonPressed() + }))) + } if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() && !item.peerIsCopyProtected && message.paidContent == nil { if hasVideoControlItems { diff --git a/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift b/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift index e890a6ec8e..55e338b610 100644 --- a/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift +++ b/submodules/TelegramUI/Components/VideoPlaybackControlsComponent/Sources/VideoPlaybackControlsComponent.swift @@ -240,7 +240,7 @@ public final class VideoPlaybackControlsComponent: Component { self.centerButtonBackgroundView.contentView.addSubview(centerButtonIconNode.view) centerButtonIconNode.enqueueState(component.isPlaying ? .pause : .play, animated: false) } - transition.setFrame(view: centerButtonIconNode.view, frame: centerButtonIconSize.centered(in: CGRect(origin: CGPoint(), size: centerButtonFrame.size)).offsetBy(dx: component.isPlaying || isFooter ? 0.0 : 5.0, dy: 0.0)) + transition.setFrame(view: centerButtonIconNode.view, frame: centerButtonIconSize.centered(in: CGRect(origin: CGPoint(), size: centerButtonFrame.size)).offsetBy(dx: isFooter ? (component.isPlaying ? 0.0 : 2.0) : (component.isPlaying ? 0.0 : 5.0), dy: 0.0)) centerButtonIconNode.enqueueState(component.isPlaying ? .pause : .play, animated: !transition.animation.isImmediate && component.isVisible && !isVisibleChanged) transition.setAlpha(view: centerButtonIconNode.view, alpha: component.isVisible ? 1.0 : 0.0) transition.setBlur(layer: centerButtonIconNode.layer, radius: component.isVisible ? 0.0 : 10.0)