Various fixes

This commit is contained in:
Ilya Laktyushin 2026-03-27 16:48:23 +01:00
parent fd68aed766
commit c1020d12c5
21 changed files with 681 additions and 135 deletions

View file

@ -16095,4 +16095,9 @@ Error: %8$@";
"MediaPicker.LivePhoto.Bounce" = "Bounce";
"MediaPicker.LivePhoto.LiveOff" = "Live Off";
"MediaPicker.LivePhoto.Tooltip.Live" = "Live Photo turned on";
"MediaPicker.LivePhoto.Tooltip.Loop" = "Live Photo with Loop effect turned on";
"MediaPicker.LivePhoto.Tooltip.Bounce" = "Live Photo with Bounce effect turned on";
"MediaPicker.LivePhoto.Tooltip.LiveOff" = "Live Photo turned off";
"AuthConfirmation.UnverifiedApp" = "Unverified App";

View file

@ -132,6 +132,10 @@ typedef NS_ENUM(NSUInteger, TGMediaLivePhotoMode)
- (SSignal *)highQualityPhoto;
- (void)setHighQualityPhoto:(bool)highQualityPhoto;
- (bool)isForceLivePhotoEnabled;
- (SSignal *)forceLivePhotoEnabled;
- (void)setForceLivePhotoEnabled:(bool)forceLivePhotoEnabled;
- (SSignal *)facesForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setFaces:(NSArray *)faces forItem:(NSObject<TGMediaEditableItem> *)item;

View file

@ -144,12 +144,15 @@
SPipe *_captionAbovePipe;
SPipe *_highQualityPhotoPipe;
SPipe *_livePhotoModePipe;
SPipe *_forceLivePhotoPipe;
NSAttributedString *_forcedCaption;
bool _captionAbove;
bool _highQualityPhoto;
bool _forceLivePhotoEnabled;
}
@end
@ -227,6 +230,7 @@
_captionAbovePipe = [[SPipe alloc] init];
_highQualityPhotoPipe = [[SPipe alloc] init];
_livePhotoModePipe = [[SPipe alloc] init];
_forceLivePhotoPipe = [[SPipe alloc] init];
}
return self;
}
@ -989,6 +993,27 @@
_highQualityPhotoPipe.sink(@(highQualityPhoto));
}
- (bool)isForceLivePhotoEnabled {
return _forceLivePhotoEnabled;
}
- (SSignal *)forceLivePhotoEnabled
{
__weak TGMediaEditingContext *weakSelf = self;
SSignal *updateSignal = [_forceLivePhotoPipe.signalProducer() map:^NSNumber *(NSNumber *update)
{
__strong TGMediaEditingContext *strongSelf = weakSelf;
return @(strongSelf->_forceLivePhotoEnabled);
}];
return [[SSignal single:@(_forceLivePhotoEnabled)] then:updateSignal];
}
- (void)setForceLivePhotoEnabled:(bool)forceLivePhotoEnabled
{
_forceLivePhotoEnabled = forceLivePhotoEnabled;
_forceLivePhotoPipe.sink(@(forceLivePhotoEnabled));
}
- (SSignal *)facesForItem:(NSObject<TGMediaEditableItem> *)item
{

View file

@ -56,6 +56,7 @@ swift_library(
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/GlassControls",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/Components/BundleIconComponent",

View file

@ -3,6 +3,9 @@ import UIKit
import Display
import LegacyComponents
import ComponentFlow
import SwiftSignalKit
import ViewControllerComponent
import MultilineTextComponent
import GlassBackgroundComponent
import ContextUI
import TelegramPresentationData
@ -55,21 +58,25 @@ final class LivePhotoButton: UIView, TGLivePhotoButton {
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_LivePhoto_Live, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveOn"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
self?.presentTooltip(.live)
self?.modeUpdated?(.live)
f(.default)
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_LivePhoto_Loop, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveLoop"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
self?.presentTooltip(.loop)
self?.modeUpdated?(.loop)
f(.default)
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_LivePhoto_Bounce, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveBounce"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
self?.presentTooltip(.bounce)
self?.modeUpdated?(.bounce)
f(.default)
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_LivePhoto_LiveOff, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveOff"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
self?.presentTooltip(.off)
self?.modeUpdated?(.off)
f(.default)
@ -79,6 +86,48 @@ final class LivePhotoButton: UIView, TGLivePhotoButton {
self.present?(contextController, nil)
}
private func presentTooltip(_ mode: TGMediaLivePhotoMode) {
guard self.mode != mode else {
return
}
let iconName: String
let text: String
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
switch mode {
case .live:
iconName = "Media Editor/LiveLargeOn"
text = presentationData.strings.MediaPicker_LivePhoto_Tooltip_Live
case .loop:
iconName = "Media Editor/LiveLargeLoop"
text = presentationData.strings.MediaPicker_LivePhoto_Tooltip_Loop
case .bounce:
iconName = "Media Editor/LiveLargeBounce"
text = presentationData.strings.MediaPicker_LivePhoto_Tooltip_Bounce
case .off:
iconName = "Media Editor/LiveLargeOff"
text = presentationData.strings.MediaPicker_LivePhoto_Tooltip_LiveOff
default:
iconName = ""
text = ""
}
let controller = TooltipInfoScreen(
context: self.context,
content: AnyComponent(HStack([
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
BundleIconComponent(name: iconName, tintColor: .white)
)),
AnyComponentWithIdentity(id: "label", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)))
))
], spacing: 11.0)),
alignment: .center
)
self.present?(controller, nil)
}
func updateFrame(_ frame: CGRect) {
let transition: ContainedViewLayoutTransition
if self.frame.width.isZero {
@ -185,3 +234,165 @@ private final class LivePhotoReferenceContentSource: ContextReferenceContentSour
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}
private final class TooltipInfoContent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let content: AnyComponent<Empty>
let alignment: TooltipInfoScreen.Alignment
init(
context: AccountContext,
content: AnyComponent<Empty>,
alignment: TooltipInfoScreen.Alignment
) {
self.context = context
self.content = content
self.alignment = alignment
}
static func ==(lhs: TooltipInfoContent, rhs: TooltipInfoContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.content != rhs.content {
return false
}
if lhs.alignment != rhs.alignment {
return false
}
return true
}
public final class View: UIView {
private let backgroundView = GlassBackgroundView()
private let contentView = ComponentView<Empty>()
private var component: TooltipInfoContent?
private var environment: EnvironmentType?
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.backgroundView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var isDismissed = false
func dismiss() {
guard !self.isDismissed, let controller = self.environment?.controller() else {
return
}
self.isDismissed = true
let transition = ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut))
transition.setBlur(layer: self.backgroundView.layer, radius: 10.0)
self.backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
controller.dismiss()
})
}
func update(component: TooltipInfoContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
let environment = environment[ViewControllerComponentContainer.Environment.self].value
let insets = UIEdgeInsets(top: 12.0, left: 10.0, bottom: 12.0, right: 18.0)
let contentSize = self.contentView.update(
transition: .immediate,
component: component.content,
environment: {},
containerSize: availableSize
)
if let contentView = self.contentView.view {
if contentView.superview == nil {
self.backgroundView.contentView.addSubview(contentView)
}
contentView.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: contentSize)
}
let backgroundSize = CGSize(width: contentSize.width + insets.left + insets.right, height: contentSize.height + insets.top + insets.bottom)
self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: true, tintColor: .init(kind: .panel), transition: .immediate)
let backgroundFrame: CGRect
switch component.alignment {
case .center:
backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - backgroundSize.width) / 2.0), y: floor((availableSize.height - backgroundSize.height) / 2.0)), size: backgroundSize)
case .bottom:
backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - backgroundSize.width) / 2.0), y: availableSize.height - backgroundSize.height - environment.additionalInsets.bottom - backgroundSize.height - 38.0), size: backgroundSize)
}
self.backgroundView.frame = backgroundFrame
self.environment = environment
if self.component == nil {
self.backgroundView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
self.backgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
Queue.mainQueue().after(2.5) {
self.dismiss()
}
}
self.component = component
return availableSize
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
self.dismiss()
return nil
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class TooltipInfoScreen: ViewControllerComponentContainer {
enum Alignment {
case center
case bottom
}
private let context: AccountContext
public init(
context: AccountContext,
content: AnyComponent<Empty>,
alignment: Alignment
) {
self.context = context
super.init(
context: context,
component: TooltipInfoContent(
context: context,
content: content,
alignment: alignment
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.navigationPresentation = .flatModal
self.automaticallyControlPresentationContextLayout = false
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
}

View file

@ -111,6 +111,7 @@ final class MediaPickerGridItemNode: GridItemNode {
var currentMediaState: (TGMediaSelectableItem, Int)?
var currentAssetState: (PHFetchResult<PHAsset>, Int)?
var currentAsset: PHAsset?
var isLivePhoto = false
var currentDraftState: (MediaEditorDraft, Int)?
var enableAnimations: Bool = true
@ -175,6 +176,7 @@ final class MediaPickerGridItemNode: GridItemNode {
self.typeIconNode.displaysAsynchronously = false
self.typeIconNode.displayWithoutProcessing = true
self.typeIconNode.isLayerBacked = true
self.typeIconNode.contentMode = .center
self.durationNode = ImmediateTextNode()
self.durationNode.isLayerBacked = true
@ -547,6 +549,27 @@ final class MediaPickerGridItemNode: GridItemNode {
}
self.imageNode.setSignal(imageSignal)
if self.currentDraftState != nil {
self.currentDraftState = nil
}
var typeIcon: UIImage?
var duration: String?
if asset.mediaType == .video {
if asset.mediaSubtypes.contains(.videoHighFrameRate) {
typeIcon = UIImage(bundleImageName: "Media Editor/MediaSlomo")
} else if asset.mediaSubtypes.contains(.videoTimelapse) {
typeIcon = UIImage(bundleImageName: "Media Editor/MediaTimelapse")
} else {
typeIcon = UIImage(bundleImageName: "Media Editor/MediaVideo")
}
duration = stringForDuration(Int32(asset.duration))
} else {
if asset.mediaSubtypes.contains(.photoLive), self.interaction?.displayLivePhotoIcon == true {
self.isLivePhoto = true
}
}
let spoilerSignal = Signal<Bool, NoError> { subscriber in
if let signal = editingContext.spoilerSignal(forIdentifier: asset.localIdentifier) {
let disposable = signal.start(next: { next in
@ -579,35 +602,55 @@ final class MediaPickerGridItemNode: GridItemNode {
}
}
self.spoilerDisposable.set((combineLatest(spoilerSignal, priceSignal, self.selectionPromise.get())
|> deliverOnMainQueue).start(next: { [weak self] hasSpoiler, price, selectionState in
guard let strongSelf = self else {
let livePhotoModeSignal: Signal<TGMediaLivePhotoMode?, NoError>
if self.isLivePhoto {
let itemSignal = Signal<TGMediaLivePhotoMode?, NoError> { subscriber in
if let signal = editingContext.livePhotoMode(forIdentifier: asset.localIdentifier) {
let disposable = signal.start(next: { next in
subscriber.putNext((next as? UInt).flatMap { TGMediaLivePhotoMode(rawValue: $0) })
}, error: { _ in
}, completed: nil)!
return ActionDisposable {
disposable.dispose()
}
} else {
return EmptyDisposable
}
}
let forceSignal = Signal<Bool?, NoError> { subscriber in
if let signal = editingContext.forceLivePhotoEnabled() {
let disposable = signal.start(next: { next in
subscriber.putNext((next as? Bool) ?? false)
}, error: { _ in
}, completed: nil)!
return ActionDisposable {
disposable.dispose()
}
} else {
return EmptyDisposable
}
}
livePhotoModeSignal = combineLatest(itemSignal, forceSignal)
|> map { item, _ in
return item
}
} else {
livePhotoModeSignal = .single(nil)
}
self.spoilerDisposable.set((combineLatest(spoilerSignal, priceSignal, livePhotoModeSignal, self.selectionPromise.get())
|> deliverOnMainQueue).start(next: { [weak self] hasSpoiler, price, livePhotoMode, selectionState in
guard let self else {
return
}
strongSelf.updateHasSpoiler(hasSpoiler, price: selectionState.selected ? price : nil, isSingle: selectionState.count == 1 || selectionState.index == 1)
self.updateHasSpoiler(hasSpoiler, price: selectionState.selected ? price : nil, isSingle: selectionState.count == 1 || selectionState.index == 1)
self.updateLivePhotoMode(livePhotoMode)
}))
if self.currentDraftState != nil {
self.currentDraftState = nil
}
var typeIcon: UIImage?
var duration: String?
if asset.mediaType == .video {
if asset.mediaSubtypes.contains(.videoHighFrameRate) {
typeIcon = UIImage(bundleImageName: "Media Editor/MediaSlomo")
} else if asset.mediaSubtypes.contains(.videoTimelapse) {
typeIcon = UIImage(bundleImageName: "Media Editor/MediaTimelapse")
} else {
typeIcon = UIImage(bundleImageName: "Media Editor/MediaVideo")
}
duration = stringForDuration(Int32(asset.duration))
} else {
if asset.mediaSubtypes.contains(.photoLive) && interaction.displayLivePhotoIcon {
typeIcon = UIImage(bundleImageName: "Chat/Message/LivePhoto")
}
}
if asset.isFavorite {
typeIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Favorite"), color: .white)
}
@ -616,7 +659,7 @@ final class MediaPickerGridItemNode: GridItemNode {
if self.leftShadowNode.supernode == nil {
self.addSubnode(self.leftShadowNode)
}
} else if self.leftShadowNode.supernode != nil {
} else if !self.isLivePhoto, self.leftShadowNode.supernode != nil {
self.leftShadowNode.removeFromSupernode()
}
@ -633,7 +676,7 @@ final class MediaPickerGridItemNode: GridItemNode {
if self.typeIconNode.supernode == nil {
self.addSubnode(self.typeIconNode)
}
} else if self.typeIconNode.supernode != nil {
} else if !self.isLivePhoto, self.typeIconNode.supernode != nil {
self.typeIconNode.removeFromSupernode()
}
@ -714,6 +757,48 @@ final class MediaPickerGridItemNode: GridItemNode {
}
}
private var livePhotoIconName: String?
private func updateLivePhotoMode(_ mode: TGMediaLivePhotoMode?) {
guard self.isLivePhoto, self.interaction?.displayLivePhotoIcon == true else {
return
}
var effectiveMode: TGMediaLivePhotoMode
if let mode, mode != .off {
effectiveMode = mode
} else {
if self.interaction?.editingState.isForceLivePhotoEnabled() == true {
effectiveMode = .live
} else {
effectiveMode = .off
}
}
var iconName: String
switch effectiveMode {
case .off:
iconName = "Chat/Message/LivePhotoOff"
default:
iconName = "Chat/Message/LivePhoto"
}
if self.livePhotoIconName != iconName {
self.livePhotoIconName = iconName
if self.leftShadowNode.supernode == nil {
if self.typeIconNode.supernode != nil {
self.insertSubnode(self.leftShadowNode, belowSubnode: self.typeIconNode)
} else {
self.addSubnode(self.leftShadowNode)
}
}
self.typeIconNode.image = UIImage(bundleImageName: iconName)
if self.typeIconNode.supernode == nil {
self.addSubnode(self.typeIconNode)
}
self.setNeedsLayout()
}
}
override func layout() {
super.layout()

View file

@ -33,7 +33,9 @@ import ComponentFlow
import BundleIconComponent
import LottieComponent
import GlassBarButtonComponent
import GlassControls
import AlertComponent
import MultilineTextComponent
final class MediaPickerInteraction {
let downloadManager: AssetDownloadManager
@ -237,6 +239,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private let titleView: MediaPickerTitleView
private let cancelButtonNode: WebAppCancelButtonNode
private var buttons: ComponentView<Empty>?
private var cancelButton: ComponentView<Empty>?
private var rightButton: ComponentView<Empty>?
private let moreButtonPlayOnce = ActionSlot<Void>()
@ -1164,7 +1167,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
private func updateSelectionState(animated: Bool = false, updateLayout: Bool = true) {
fileprivate func updateSelectionState(animated: Bool = false, updateLayout: Bool = true) {
self.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? MediaPickerGridItemNode {
itemNode.updateSelectionState(animated: animated)
@ -2102,6 +2105,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if case .glass = style {
self.cancelButton = ComponentView()
self.buttons = ComponentView()
}
self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings)
@ -2635,8 +2639,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let useGlassButtons = (isBack || !self.controllerNode.scrolledToTop) && !self.controllerNode.isSwitchingAssetGroup
let barButtonSideInset: CGFloat = 16.0
let barButtonSize = CGSize(width: 44.0, height: 44.0)
var buttonTransition = ComponentTransition.easeInOut(duration: 0.25)
if case let .animated(duration, _) = transition, duration > 0.25 {
buttonTransition = .easeInOut(duration: duration)
@ -2659,124 +2663,248 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
transition.updateAlpha(layer: self.controllerNode.topEdgeEffectView.layer, alpha: self.controllerNode.scrolledExactlyToTop && self.controllerNode.currentDisplayMode == .all ? 0.0 : 1.0)
self.controllerNode.topEdgeEffectView.update(content: topEdgeColor, blur: true, alpha: 0.8, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
if let cancelButton = self.cancelButton {
if cancelButton.view == nil {
buttonTransition = .immediate
}
let cancelButtonSize = cancelButton.update(
transition: buttonTransition,
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: nil,
isDark: self.presentationData.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
BundleIconComponent(
name: isBack ? "Navigation/Back" : "Navigation/Close",
tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
self?.cancelPressed()
let leftControlItems: [GlassControlGroupComponent.Item] = [
GlassControlGroupComponent.Item(
id: AnyHashable(isBack ? "back" : "close"),
content: .icon(isBack ? "Navigation/Back" : "Navigation/Close"),
action: { [weak self] in
guard let self else {
return
}
)),
environment: {},
containerSize: barButtonSize
)
let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset + layout.safeInsets.left, y: barButtonSideInset), size: cancelButtonSize)
if let view = cancelButton.view {
if view.superview == nil {
self.view.addSubview(view)
self.cancelPressed()
}
)
]
var rightControlItems: [GlassControlGroupComponent.Item] = []
if moreIsVisible, case .glass = self.style {
if self.hasSelectButton {
rightControlItems.append(GlassControlGroupComponent.Item(
id: AnyHashable("select"),
content: .text(self.presentationData.strings.Common_Select),
action: { [weak self] in
guard let self else {
return
}
self.selectPressed()
}
))
} else {
rightControlItems.append(GlassControlGroupComponent.Item(
id: AnyHashable("more"),
content: .animation("anim_morewide"),
action: { [weak self] in
guard let self else {
return
}
if let controlsView = self.buttons?.view as? GlassControlPanelComponent.View, let rightItemView = controlsView.rightItemView, let sourceView = rightItemView.itemView(id: AnyHashable("more")) {
self.searchOrMorePressed(view: sourceView, gesture: nil)
}
}
))
}
var hasLivePhoto = false
if let selectionContext = self.interaction?.selectionState, let editingContext = self.interaction?.editingState {
for case let item as TGMediaEditableItem in selectionContext.selectedItems() {
if let item = item as? TGMediaAsset, let asset = item.backingAsset {
if asset.mediaSubtypes.contains(.photoLive) {
hasLivePhoto = true
break
}
}
}
if hasLivePhoto {
let isForceLivePhoto = editingContext.isForceLivePhotoEnabled()
rightControlItems.insert(GlassControlGroupComponent.Item(
id: AnyHashable(isForceLivePhoto ? "livePhoto_on" : "livePhoto_off"),
content: .icon(isForceLivePhoto ? "Media Editor/LiveOn" : "Media Editor/LiveOff"),
action: { [weak self, weak editingContext] in
guard let self, let editingContext else {
return
}
editingContext.setForceLivePhotoEnabled(!isForceLivePhoto)
self.controllerNode.updateSelectionState()
let iconName: String
let text: String
if !isForceLivePhoto {
iconName = "Media Editor/LiveLargeOn"
text = self.presentationData.strings.MediaPicker_LivePhoto_Tooltip_Live
} else {
iconName = "Media Editor/LiveLargeOff"
text = self.presentationData.strings.MediaPicker_LivePhoto_Tooltip_LiveOff
}
let controller = TooltipInfoScreen(
context: self.context,
content: AnyComponent(HStack([
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
BundleIconComponent(name: iconName, tintColor: .white)
)),
AnyComponentWithIdentity(id: "label", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)))
))
], spacing: 11.0)),
alignment: .bottom
)
self.present(controller, in: .current)
}
), at: 0)
}
view.bounds = CGRect(origin: .zero, size: cancelButtonFrame.size)
view.center = cancelButtonFrame.center
}
}
if moreIsVisible, case .glass = self.style {
let moreButton: ComponentView<Empty>
if let current = self.rightButton {
moreButton = current
} else {
moreButton = ComponentView<Empty>()
self.rightButton = moreButton
if let buttons = self.buttons {
if buttons.view == nil {
buttonTransition = .immediate
}
let buttonComponent: AnyComponent<Empty>
let rightButtonSize: CGSize
if self.hasSelectButton {
buttonComponent = AnyComponent(GlassBarButtonComponent(
size: nil,
backgroundColor: nil,
isDark: self.presentationData.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "select", component: AnyComponent(
Text(text: self.presentationData.strings.Common_Select, font: Font.semibold(17.0), color: self.presentationData.theme.chat.inputPanel.panelControlColor)
)),
action: { [weak self] _ in
self?.selectPressed()
self?.moreButtonPlayOnce.invoke(Void())
})
)
rightButtonSize = CGSize(width: 160.0, height: 44.0)
} else {
buttonComponent = AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: nil,
isDark: self.presentationData.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_morewide"
),
color: self.presentationData.theme.chat.inputPanel.panelControlColor,
size: CGSize(width: 34.0, height: 34.0),
playOnce: self.moreButtonPlayOnce
)
)),
action: { [weak self] view in
self?.searchOrMorePressed(view: view, gesture: nil)
self?.moreButtonPlayOnce.invoke(Void())
})
)
rightButtonSize = barButtonSize
}
let moreButtonSize = moreButton.update(
let buttonsSize = buttons.update(
transition: buttonTransition,
component: buttonComponent,
component: AnyComponent(GlassControlPanelComponent(
theme: self.presentationData.theme,
leftItem: GlassControlPanelComponent.Item(
items: leftControlItems,
background: .panel
),
centralItem: nil,
rightItem: rightControlItems.isEmpty ? nil : GlassControlPanelComponent.Item(
items: rightControlItems,
background: .panel
),
centerAlignmentIfPossible: true,
isDark: self.presentationData.theme.overallDarkAppearance
)),
environment: {},
containerSize: rightButtonSize
containerSize: CGSize(width: layout.size.width - barButtonSideInset * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: 44.0)
)
let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset - layout.safeInsets.right, y: barButtonSideInset), size: moreButtonSize)
if let view = moreButton.view {
let buttonsFrame = CGRect(origin: CGPoint(x: barButtonSideInset + layout.safeInsets.left, y: barButtonSideInset), size: buttonsSize)
if let view = buttons.view {
if view.superview == nil {
self.view.addSubview(view)
if transition.isAnimated {
let transition = ComponentTransition(transition)
transition.animateAlpha(view: view, from: 0.0, to: 1.0)
transition.animateScale(view: view, from: 0.1, to: 1.0)
}
}
view.bounds = CGRect(origin: .zero, size: moreButtonFrame.size)
view.center = moreButtonFrame.center
}
} else if let moreButton = self.rightButton {
self.rightButton = nil
if let view = moreButton.view {
if transition.isAnimated {
let transition = ComponentTransition(transition)
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
view.removeFromSuperview()
})
transition.animateScale(view: view, from: 1.0, to: 0.1)
} else {
view.removeFromSuperview()
}
view.bounds = CGRect(origin: .zero, size: buttonsFrame.size)
view.center = buttonsFrame.center
}
}
// if let cancelButton = self.cancelButton {
// if cancelButton.view == nil {
// buttonTransition = .immediate
// }
// let cancelButtonSize = cancelButton.update(
// transition: buttonTransition,
// component: AnyComponent(GlassBarButtonComponent(
// size: barButtonSize,
// backgroundColor: nil,
// isDark: self.presentationData.theme.overallDarkAppearance,
// state: .glass,
// component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
// BundleIconComponent(
// name: isBack ? "Navigation/Back" : "Navigation/Close",
// tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor
// )
// )),
// action: { [weak self] _ in
// self?.cancelPressed()
// }
// )),
// environment: {},
// containerSize: barButtonSize
// )
// let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset + layout.safeInsets.left, y: barButtonSideInset), size: cancelButtonSize)
// if let view = cancelButton.view {
// if view.superview == nil {
// self.view.addSubview(view)
// }
// view.bounds = CGRect(origin: .zero, size: cancelButtonFrame.size)
// view.center = cancelButtonFrame.center
// }
// }
//
// if moreIsVisible, case .glass = self.style {
// let moreButton: ComponentView<Empty>
// if let current = self.rightButton {
// moreButton = current
// } else {
// moreButton = ComponentView<Empty>()
// self.rightButton = moreButton
// }
//
// let buttonComponent: AnyComponent<Empty>
// let rightButtonSize: CGSize
// if self.hasSelectButton {
// buttonComponent = AnyComponent(GlassBarButtonComponent(
// size: nil,
// backgroundColor: nil,
// isDark: self.presentationData.theme.overallDarkAppearance,
// state: .glass,
// component: AnyComponentWithIdentity(id: "select", component: AnyComponent(
// Text(text: self.presentationData.strings.Common_Select, font: Font.semibold(17.0), color: self.presentationData.theme.chat.inputPanel.panelControlColor)
// )),
// action: { [weak self] _ in
// self?.selectPressed()
// })
// )
// rightButtonSize = CGSize(width: 160.0, height: 44.0)
// } else {
// buttonComponent = AnyComponent(GlassBarButtonComponent(
// size: barButtonSize,
// backgroundColor: nil,
// isDark: self.presentationData.theme.overallDarkAppearance,
// state: .glass,
// component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
// LottieComponent(
// content: LottieComponent.AppBundleContent(
// name: "anim_morewide"
// ),
// color: self.presentationData.theme.chat.inputPanel.panelControlColor,
// size: CGSize(width: 34.0, height: 34.0),
// playOnce: self.moreButtonPlayOnce
// )
// )),
// action: { [weak self] view in
// self?.searchOrMorePressed(view: view, gesture: nil)
// self?.moreButtonPlayOnce.invoke(Void())
// })
// )
// rightButtonSize = barButtonSize
// }
//
// let moreButtonSize = moreButton.update(
// transition: buttonTransition,
// component: buttonComponent,
// environment: {},
// containerSize: rightButtonSize
// )
// let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset - layout.safeInsets.right, y: barButtonSideInset), size: moreButtonSize)
// if let view = moreButton.view {
// if view.superview == nil {
// self.view.addSubview(view)
// if transition.isAnimated {
// let transition = ComponentTransition(transition)
// transition.animateAlpha(view: view, from: 0.0, to: 1.0)
// transition.animateScale(view: view, from: 0.1, to: 1.0)
// }
// }
// view.bounds = CGRect(origin: .zero, size: moreButtonFrame.size)
// view.center = moreButtonFrame.center
// }
// } else if let moreButton = self.rightButton {
// self.rightButton = nil
// if let view = moreButton.view {
// if transition.isAnimated {
// let transition = ComponentTransition(transition)
// view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
// view.removeFromSuperview()
// })
// transition.animateScale(view: view, from: 1.0, to: 0.1)
// } else {
// view.removeFromSuperview()
// }
// }
// }
if case .assets(_, .story) = self.subject {
if self.selectionCount > 0 {
let text = self.presentationData.strings.MediaPicker_CreateStory(self.selectionCount)

View file

@ -317,6 +317,14 @@ public final class GlassControlPanelComponent: Component {
return availableSize
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if result === self {
return nil
}
return result
}
}
public func makeView() -> View {

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "livecorner.pdf",
"filename" : "livemini.pdf",
"idiom" : "universal"
}
],

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "liveoffmini.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "bounce (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "loop (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "liveoff (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "live (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -9,6 +9,7 @@ import AccountContext
import ShareController
import UndoUI
import AttachmentFileController
import LegacyMediaPickerUI
final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayerController {
private let context: AccountContext
@ -156,9 +157,24 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
guard let self, let navigationController = self.parentNavigationController else {
return
}
var dismissImpl: (() -> Void)?
let controller = makeAttachmentFileControllerImpl(
context: self.context,
mode: .audio(.savedMusic),
presentFiles: { [weak self] in
guard let self else {
return
}
dismissImpl?()
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let controller = legacyICloudFilePicker(theme: presentationData.theme, mode: .import, documentTypes: ["public.mp3", "public.mpeg-4-audio", "public.aac-audio", "org.xiph.flac"], completion: { urls in
guard let url = urls.first else {
return
}
print(url)
})
self.present(controller, in: .window(.root))
},
send: { [weak self] mediaReferences, _, _, _ in
guard let self, let reference = mediaReferences.first?.concrete(TelegramMediaFile.self) else {
return
@ -168,6 +184,9 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
) as! AttachmentFileControllerImpl
controller.navigationPresentation = .modal
navigationController.pushViewController(controller)
dismissImpl = { [weak controller] in
controller?.dismiss()
}
},
getParentController: { [weak self] in
return self