Various improvements

This commit is contained in:
Ilya Laktyushin 2024-11-29 21:58:17 +04:00
parent 43741a3b20
commit f81443e438
11 changed files with 230 additions and 117 deletions

View File

@ -13299,3 +13299,16 @@ Sorry for the inconvenience.";
"WebApp.ShareMessage.Share" = "Share With...";
"Notification.Gift" = "Gift";
"WebBrowser.PassExistsError" = "This pass is already added to Wallet.";
"Chat.VideoProcessingInfo" = "The video will be published once converted and optimized.";
"Camera.CollageManagementTooltip" = "Tap a tile to delete or reorder it.";
"Camera.CollageReorderingInfo" = "Hold and drag tiles to reorder them.";
"MediaPicker.InvertCaptionTooltip" = "Tap here to move caption up.";
"MediaPicker.InvertCaption.Updated.Up.Title" = "Caption moved up";
"MediaPicker.InvertCaption.Updated.Up.Text" = "Text will be shown above the media.";
"MediaPicker.InvertCaption.Updated.Down.Title" = "Caption moved down";
"MediaPicker.InvertCaption.Updated.Down.Text" = "Text will be shown below the media.";

View File

@ -896,8 +896,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
if let data = try? Data(contentsOf: url), let pass = try? PKPass(data: data) {
let passLibrary = PKPassLibrary()
if passLibrary.containsPass(pass) {
//TODO:localize
let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: "This pass is already added to Wallet.", actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {})])
let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: self.presentationData.strings.WebBrowser_PassExistsError, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {})])
self.present(alertController, nil)
} else if let controller = PKAddPassesViewController(pass: pass) {
self.getNavigationController()?.view.window?.rootViewController?.present(controller, animated: true)

View File

@ -438,6 +438,7 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
icon = nil
isUserInteractionEnabled = action != nil
case let .starsReactions(topCount):
//TODO:localize
self.action = nil
self.text = "Send \(topCount) or more to highlight your profile"
self.targetSelectionIndex = nil
@ -445,14 +446,13 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
isUserInteractionEnabled = action != nil
case .videoProcessing:
self.action = nil
self.text = "The video will be published once converted and optimized."
self.text = self.presentationData.strings.Chat_VideoProcessingInfo
self.targetSelectionIndex = nil
icon = nil
isUserInteractionEnabled = action != nil
case .collageReordering:
//TODO:localize
self.action = nil
self.text = "Hold and drag tiles to reorder them."
self.text = self.presentationData.strings.Camera_CollageReorderingInfo
self.targetSelectionIndex = nil
icon = UIImage(bundleImageName: "Chat/Context Menu/Tip")
}

View File

@ -199,6 +199,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case dismissedBusinessIntroBadge = 72
case dismissedBusinessLinksBadge = 73
case dismissedBusinessChatbotsBadge = 74
case captionAboveMediaTooltip = 75
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -534,6 +535,10 @@ private struct ApplicationSpecificNoticeKeys {
static func dismissedBusinessChatbotsBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessChatbotsBadge.key)
}
static func captionAboveMediaTooltip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key)
}
}
public struct ApplicationSpecificNotice {
@ -2246,4 +2251,31 @@ public struct ApplicationSpecificNotice {
}
|> take(1)
}
public static func getCaptionAboveMediaTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip())?.get(ApplicationSpecificCounterNotice.self) {
return value.value
} else {
return 0
}
}
}
public static func incrementCaptionAboveMediaTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
return accountManager.transaction { transaction -> Int in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
let previousValue = currentValue
currentValue += Int32(count)
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip(), entry)
}
return Int(previousValue)
}
}
}

View File

@ -568,19 +568,33 @@ private final class CameraScreenComponent: CombinedComponent {
}
func toggleCollageCamera() {
guard let controller = self.getController(), let _ = controller.camera else {
guard let controller = self.getController(), let camera = controller.camera else {
return
}
let currentTimestamp = CACurrentMediaTime()
if let lastDualCameraTimestamp = self.lastDualCameraTimestamp, currentTimestamp - lastDualCameraTimestamp < 1.5 {
return
}
if let lastFlipTimestamp = self.lastFlipTimestamp, currentTimestamp - lastFlipTimestamp < 1.0 {
return
}
self.lastDualCameraTimestamp = currentTimestamp
controller.node.dismissAllTooltips()
if controller.cameraState.isDualCameraEnabled {
camera.setDualCameraEnabled(false)
}
if controller.cameraState.isCollageEnabled {
self.displayingCollageSelection = !self.displayingCollageSelection
self.updated(transition: .spring(duration: 0.3))
} else {
let isEnabled = !controller.cameraState.isCollageEnabled
self.displayingCollageSelection = isEnabled
controller.updateCameraState({ $0.updatedIsCollageEnabled(isEnabled).updatedCollageProgress(0.0) }, transition: .spring(duration: 0.3))
controller.updateCameraState({
$0.updatedIsCollageEnabled(isEnabled).updatedCollageProgress(0.0).updatedIsDualCameraEnabled(false)
}, transition: .spring(duration: 0.3))
}
self.hapticFeedback.impact(.light)
}
@ -635,9 +649,8 @@ private final class CameraScreenComponent: CombinedComponent {
self.displayingCollageSelection = false
self.updated(transition: .spring(duration: 0.3))
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Tap a tile to delete or reorder it.", timeout: 3.0, customUndoText: nil), elevatedLayout: false, action: { _ in
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Camera_CollageManagementTooltip, timeout: 2.0, customUndoText: nil), elevatedLayout: false, action: { _ in
return true
})
controller.present(tooltipController, in: .current)
@ -1279,68 +1292,66 @@ private final class CameraScreenComponent: CombinedComponent {
nextButtonX -= dualButton.size.width + 16.0
}
if !component.cameraState.isDualCameraEnabled {
let collageButton = collageButton.update(
component: CameraButton(
content: AnyComponentWithIdentity(
id: "collage",
component: AnyComponent(
CollageIconComponent(
grid: component.cameraState.collageGrid,
crossed: false,
isSelected: component.cameraState.isCollageEnabled,
tintColor: controlsTintColor
)
let collageButton = collageButton.update(
component: CameraButton(
content: AnyComponentWithIdentity(
id: "collage",
component: AnyComponent(
CollageIconComponent(
grid: component.cameraState.collageGrid,
crossed: false,
isSelected: component.cameraState.isCollageEnabled,
tintColor: controlsTintColor
)
),
action: { [weak state] in
if let state {
state.toggleCollageCamera()
}
)
),
action: { [weak state] in
if let state {
state.toggleCollageCamera()
}
).tagged(collageButtonTag),
availableSize: CGSize(width: 40.0, height: 40.0),
}
).tagged(collageButtonTag),
availableSize: CGSize(width: 40.0, height: 40.0),
transition: .immediate
)
var collageButtonX = nextButtonX
if rightMostButtonWidth.isZero {
collageButtonX = availableSize.width - topControlInset - collageButton.size.width / 2.0 - 5.0
}
context.add(collageButton
.position(CGPoint(x: collageButtonX, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + collageButton.size.height / 2.0 + 2.0))
.appear(.default(scale: true))
.disappear(.default(scale: true))
.shadow(Shadow(color: UIColor(white: 0.0, alpha: 0.25), radius: 3.0, offset: .zero))
)
nextButtonX -= collageButton.size.width
if state.displayingCollageSelection {
let collageCarousel = collageCarousel.update(
component: CollageIconCarouselComponent(
grids: collageGrids.filter { $0 != component.cameraState.collageGrid },
selected: { [weak state] grid in
state?.updateCollageGrid(grid)
}
),
availableSize: CGSize(width: nextButtonX + 4.0, height: 40.0),
transition: .immediate
)
var collageButtonX = nextButtonX
if rightMostButtonWidth.isZero {
collageButtonX = availableSize.width - topControlInset - collageButton.size.width / 2.0 - 5.0
}
context.add(collageButton
.position(CGPoint(x: collageButtonX, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + collageButton.size.height / 2.0 + 2.0))
.appear(.default(scale: true))
.disappear(.default(scale: true))
.shadow(Shadow(color: UIColor(white: 0.0, alpha: 0.25), radius: 3.0, offset: .zero))
context.add(collageCarousel
.position(CGPoint(x: collageCarousel.size.width / 2.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + collageCarousel.size.height / 2.0 + 2.0))
.appear(ComponentTransition.Appear({ _, view, transition in
if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate {
view.animateIn()
}
}))
.disappear(ComponentTransition.Disappear({ view, transition, completion in
if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate {
view.animateOut(completion: completion)
} else {
completion()
}
}))
)
nextButtonX -= collageButton.size.width
if state.displayingCollageSelection {
let collageCarousel = collageCarousel.update(
component: CollageIconCarouselComponent(
grids: collageGrids.filter { $0 != component.cameraState.collageGrid },
selected: { [weak state] grid in
state?.updateCollageGrid(grid)
}
),
availableSize: CGSize(width: nextButtonX + 4.0, height: 40.0),
transition: .immediate
)
context.add(collageCarousel
.position(CGPoint(x: collageCarousel.size.width / 2.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + collageCarousel.size.height / 2.0 + 2.0))
.appear(ComponentTransition.Appear({ _, view, transition in
if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate {
view.animateIn()
}
}))
.disappear(ComponentTransition.Disappear({ view, transition, completion in
if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate {
view.animateOut(completion: completion)
} else {
completion()
}
}))
)
}
}
}
}

View File

@ -299,7 +299,7 @@ final class CameraVideoLayer: MetalEngineSubjectLayer, MetalEngineSubject {
encoder.setFragmentTexture(blurredTexture, index: 0)
var brightness: Float = 0.85
var brightness: Float = 0.95
var saturation: Float = 1.3
var overlay: SIMD4<Float> = SIMD4<Float>()
encoder.setFragmentBytes(&brightness, length: 4, index: 0)

View File

@ -24,6 +24,7 @@ swift_library(
"//submodules/UndoUI",
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView",
"//submodules/TelegramNotices",
],
visibility = [
"//visibility:public",

View File

@ -15,6 +15,7 @@ import ContextUI
import TooltipUI
import LegacyMessageInputPanelInputView
import UndoUI
import TelegramNotices
public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
private let context: AccountContext
@ -357,7 +358,6 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
}
private func toggleIsCaptionAbove() {
//TODO:localize
self.currentIsCaptionAbove = !self.currentIsCaptionAbove
self.captionIsAboveUpdated?(self.currentIsCaptionAbove)
self.update(transition: .animated(duration: 0.3, curve: .spring))
@ -366,8 +366,8 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let title = self.currentIsCaptionAbove ? "Caption moved up" : "Caption moved down"
let text = self.currentIsCaptionAbove ? "Text will be shown above the media." : "Text will be shown below the media."
let title = self.currentIsCaptionAbove ? presentationData.strings.MediaPicker_InvertCaption_Updated_Up_Title : presentationData.strings.MediaPicker_InvertCaption_Updated_Down_Title
let text = self.currentIsCaptionAbove ? presentationData.strings.MediaPicker_InvertCaption_Updated_Up_Text : presentationData.strings.MediaPicker_InvertCaption_Updated_Down_Title
let animationName = self.currentIsCaptionAbove ? "message_preview_sort_above" : "message_preview_sort_below"
let controller = UndoOverlayController(
@ -498,30 +498,43 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
}
self.dismissAllTooltips()
let parentFrame = superview.convert(superview.bounds, to: nil)
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX + 2.0, y: absoluteFrame.minY + 6.0), size: CGSize())
//TODO:localize
let text = "Tap here to move caption up."
let tooltipController = TooltipScreen(
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .plain(text: text),
balancedTextLayout: false,
style: .customBlur(UIColor(rgb: 0x18181a), 4.0),
arrowStyle: .small,
icon: nil,
location: .point(location, .bottom),
displayDuration: .default,
inset: 4.0,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
let _ = (ApplicationSpecificNotice.getCaptionAboveMediaTooltip(accountManager: self.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] count in
guard let self else {
return
}
)
self.tooltipController = tooltipController
self.present(tooltipController)
if count > 2 {
return
}
let parentFrame = superview.convert(superview.bounds, to: nil)
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX + 2.0, y: absoluteFrame.minY + 6.0), size: CGSize())
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let tooltipController = TooltipScreen(
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .plain(text: presentationData.strings.MediaPicker_InvertCaptionTooltip),
balancedTextLayout: false,
style: .customBlur(UIColor(rgb: 0x18181a), 4.0),
arrowStyle: .small,
icon: nil,
location: .point(location, .bottom),
displayDuration: .default,
inset: 4.0,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.tooltipController = tooltipController
self.present(tooltipController)
let _ = ApplicationSpecificNotice.incrementCaptionAboveMediaTooltip(accountManager: self.context.sharedContext.accountManager).start()
})
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

View File

@ -1686,10 +1686,17 @@ public final class MediaEditor {
public func setupCollage(_ items: [MediaEditor.Subject.VideoCollageItem]) {
let longestItem = longestCollageItem(items)
var collage: [MediaEditorValues.VideoCollageItem] = []
var index = 0
var passedFirstVideo = false
var mainVideoIsMuted = false
for item in items {
var content: MediaEditorValues.VideoCollageItem.Content
var isVideo = false
if item.content == longestItem?.content {
content = .main
isVideo = true
} else {
switch item.content {
case let .image(image):
@ -1700,23 +1707,38 @@ public final class MediaEditor {
content = .imageFile(path: tempImagePath)
case let .video(path, _):
content = .videoFile(path: path)
isVideo = true
case let .asset(asset):
content = .asset(localIdentifier: asset.localIdentifier, isVideo: asset.mediaType == .video)
isVideo = asset.mediaType == .video
}
}
collage.append(MediaEditorValues.VideoCollageItem(
let item = MediaEditorValues.VideoCollageItem(
content: content,
frame: item.frame,
videoTrimRange: nil,
videoOffset: nil,
videoVolume: nil
))
videoVolume: passedFirstVideo ? 0.0 : nil
)
collage.append(item)
if isVideo {
passedFirstVideo = true
}
index += 1
if item.content == .main, let videoVolume = item.videoVolume, videoVolume.isZero {
mainVideoIsMuted = true
}
}
self.updateValues(mode: .skipRendering) { values in
return values.withUpdatedCollage(collage)
var values = values.withUpdatedCollage(collage)
if mainVideoIsMuted {
values = values.withUpdatedVideoVolume(0.0)
}
return values
}
self.setupAdditionalVideoPlayback()
self.updateAdditionalVideoPlaybackRange()
}
@ -1766,21 +1788,20 @@ public final class MediaEditor {
private func setupAdditionalVideoPlayback() {
if !self.values.collage.isEmpty {
var signals: [Signal<(UniversalTextureSource.Input, AVPlayer?), NoError>] = []
var signals: [Signal<(UniversalTextureSource.Input, AVPlayer?, CGFloat?), NoError>] = []
for item in self.values.collage {
switch item.content {
case .main:
break
case let .imageFile(path):
if let image = UIImage(contentsOfFile: path) {
signals.append(.single((.image(image, item.frame), nil)))
signals.append(.single((.image(image, item.frame), nil, nil)))
}
case let .videoFile(path):
let asset = AVURLAsset(url: URL(fileURLWithPath: path))
let player = self.makePlayer(asset: asset)
if let playerItem = player.currentItem {
signals.append(.single((.video(playerItem, item.frame), player)))
signals.append(.single((.video(playerItem, item.frame), player, item.videoVolume)))
}
case let .asset(localIdentifier, _):
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
@ -1798,7 +1819,7 @@ public final class MediaEditor {
}
let player = self.makePlayer(asset: avAsset)
if let playerItem = player.currentItem {
subscriber.putNext((.video(playerItem, item.frame), player))
subscriber.putNext((.video(playerItem, item.frame), player, item.videoVolume))
}
subscriber.putCompletion()
})
@ -1817,9 +1838,12 @@ public final class MediaEditor {
var additionalInputs: [UniversalTextureSource.Input] = []
var additionalPlayers: [AVPlayer] = []
for (input, player) in results {
for (input, player, volume) in results {
additionalInputs.append(input)
if let player {
if let volume {
player.volume = Float(volume)
}
additionalPlayers.append(player)
}
}
@ -1861,23 +1885,41 @@ public final class MediaEditor {
}
public func collageItemIndexForTrackId(_ trackId: Int32) -> Int? {
var collageIndex = -1
var trackIndex = -1
var trackIdToIndex: [Int32: Int] = [:]
var index = 0
var trackIndex: Int32 = 0
for item in self.values.collage {
if case .main = item.content {
trackIndex += 1
} else if case .videoFile = item.content {
trackIndex += 1
} else if case .asset(_, true) = item.content {
trackIndex += 1
}
collageIndex += 1
if trackIndex == trackId {
return collageIndex
trackIdToIndex[0] = index
} else {
if item.content.isVideo {
trackIndex += 1
trackIdToIndex[trackIndex] = index
}
}
index += 1
}
return nil
return trackIdToIndex[trackId]
// var collageIndex = -1
// var trackIndex = -1
// for item in self.values.collage {
// if case .main = item.content {
// trackIndex += 1
// } else if case .videoFile = item.content {
// trackIndex += 1
// } else if case .asset(_, true) = item.content {
// trackIndex += 1
// }
// collageIndex += 1
//
// if trackIndex == trackId {
// return collageIndex
// }
// }
// return nil
}
public func playerIndexForTrackId(_ trackId: Int32) -> Int? {

View File

@ -41,6 +41,8 @@ extension MediaEditorScreenImpl {
return false
} else if case .empty = subject, !self.node.hasAnyChanges && !self.node.drawingView.internalState.canUndo {
return false
} else if case .videoCollage = subject {
return false
}
}
return true

View File

@ -4579,7 +4579,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
guard let mediaEditor = self.mediaEditor else {
return
}
let isVideo = trackId != 2
let isVideo = trackId != 1000
let actionTitle: String = isVideo ? self.presentationData.strings.MediaEditor_RemoveVideo : self.presentationData.strings.MediaEditor_RemoveAudio
let isCollage = !mediaEditor.values.collage.isEmpty