Merge commit '527fe4452f214b59c818526916beab563117909c'

# Conflicts:
#	Telegram/Telegram-iOS/en.lproj/Localizable.strings
This commit is contained in:
Ali 2023-07-17 13:43:39 +04:00
commit 3c16e9c215
18 changed files with 401 additions and 152 deletions

View File

@ -9419,6 +9419,9 @@ Sorry for the inconvenience.";
"StoryFeed.TooltipStoryLimitValue_any" = "%d stories";
"StoryFeed.TooltipStoryLimit" = "You can't post more than **%@** stories in **24 hours**.";
"StoryFeed.TooltipPostingDuringCall" = "You can't post stories during a call.";
"StoryFeed.TooltipPostingDuringGroupCall" = "You can't post stories during a voice chat.";
"StoryFeed.MyStory" = "My Story";
"StoryFeed.MyUploading" = "Uploading...";
@ -9593,3 +9596,121 @@ Sorry for the inconvenience.";
"Profile.AvatarStoryCount_1" = "1 Story";
"Profile.AvatarStoryCount_any" = "%d Stories";
"Story.Camera.Photo" = "Photo";
"Story.Camera.Video" = "Video";
"Story.Camera.TooltipTakePhotos" = "Take photos or videos to share with all\nyour contacts or close friends at once.";
"Story.Camera.TooltipDisableDual" = "Tap here to disable\nthe selfie camera";
"Story.Camera.TooltipDraftSaved" = "Draft Saved";
"Story.Camera.SwipeUpToZoom" = "Swipe up to zoom";
"Story.Camera.SwipeLeftToLock" = "Swipe left to lock";
"Story.Camera.SwipeLeftRelease" = "Release to lock";
"Story.Camera.SwipeRightToFlip" = "Swipe right to flip";
"Story.Camera.AccessPlaceholderTitle" = "Allow Telegram to access your camera and microphone";
"Story.Camera.AccessPlaceholderText" = "This lets you share photos and record videos.";
"Story.Camera.AccessOpenSettings" = "Open Settings";
"Story.Editor.Next" = "Next";
"Story.Editor.Done" = "Done";
"Story.Editor.DraftDiscardMedia" = "Discard Media?";
"Story.Editor.DraftDiscardDraft" = "Discard Draft?";
"Story.Editor.DraftKeepMedia" = "Save Draft";
"Story.Editor.DraftKeepDraft" = "Keep Draft";
"Story.Editor.DraftDiscaedText" = "If you go back now, you will lose any changes that you've made.";
"Story.Editor.DraftDiscard" = "Discard";
"Story.Editor.ExpirationText" = "Choose how long the story will be visible.";
"Story.Editor.ExpirationValue_1" = "1 Hour";
"Story.Editor.ExpirationValue_any" = "%d Hours";
"Story.Editor.TooltipPremiumCustomExpiration" = "Subscribe to **Telegram Premium** to make your stories disappear %@.";
"Story.Editor.TooltipPremiumMore" = "More";
"Story.Editor.InputPlaceholderAddCaption" = "Add a caption...";
"Story.Editor.TooltipMuted" = "The story will have no sound";
"Story.Editor.TooltipUnmuted" = "The story will have sound";
"Story.Editor.PreparingVideo" = "Preparing Video...";
"Story.Editor.TooltipImageSavedToPhotos" = "Image saved to Photos.";
"Story.Editor.TooltipVideoSavedToPhotos" = "Video saved to Photos.";
"Story.Editor.Uploading" = "Uploading...";
"Story.Editor.Tool.Enhance" = "Enhance";
"Story.Editor.Tool.Brightness" = "Brightness";
"Story.Editor.Tool.Contrast" = "Contrast";
"Story.Editor.Tool.Saturation" = "Saturation";
"Story.Editor.Tool.Warmth" = "Warmth";
"Story.Editor.Tool.Fade" = "Fade";
"Story.Editor.Tool.Highlights" = "Highlights";
"Story.Editor.Tool.Shadows" = "Shadows";
"Story.Editor.Tool.Vignette" = "Vignette";
"Story.Editor.Tool.Grain" = "Grain";
"Story.Editor.Tint.Shadows" = "Shadows";
"Story.Editor.Tint.Highlights" = "Highlights";
"Story.Editor.Blur.Title" = "Blur";
"Story.Editor.Blur.Off" = "Off";
"Story.Editor.Blur.Radial" = "Radial";
"Story.Editor.Blur.Linear" = "Linear";
"Story.Editor.Blur.Portrait" = "Portrait";
"Story.Editor.Curves.All" = "All";
"Story.Editor.Curves.Red" = "Red";
"Story.Editor.Curves.Green" = "Green";
"Story.Editor.Curves.Blue" = "Blue";
"Story.Privacy.AllowScreenshots" = "Allow Screenshots";
"Story.Privacy.KeepOnMyPage" = "Keep on My Page";
"Story.Privacy.KeepOnMyPageInfo" = "Keep this story on your page even after it expires in %@. Privacy settings will apply.";
"Story.Privacy.KeepOnMyPageHours_1" = "1 hour";
"Story.Privacy.KeepOnMyPageHours_any" = "%@ hours";
"Story.Privacy.TooltipSharingEnabledPublic" = "Downloading, sharing and taking screenshots will be enabled for this story.";
"Story.Privacy.TooltipSharingEnabled" = "Downloading and taking screenshots will be enabled for this story.";
"Story.Privacy.TooltipSharingDisabledPublic" = "Downloading, sharing and taking screenshots will be disabled for this story.";
"Story.Privacy.TooltipSharingDisabled" = "Downloading and taking screenshots will be disabled for this story.";
"Story.Privacy.TooltipStoryArchived" = "Users allowed to view your story will see it on your page even after it expires.";
"Story.Privacy.TooltipStoryExpires" = "The story will disappear after it expires.";
"Story.Privacy.WhoCanViewHeader" = "WHO CAN VIEW";
"Story.Privacy.ContactsHeader" = "CONTACTS";
"Story.Privacy.SearchChats" = "Search Chats";
"Story.Privacy.SearchContacts" = "Search Contacts";
"Story.Privacy.MentionRestrictedTitle" = "Privacy Restrictions";
"Story.Privacy.MentionRestrictedText" = "The privacy settings of your story will prevent some users you tagged (%@) from viewing it.";
"Story.Privacy.MentionRestrictedProceed" = "Proceed Anyway";
"Story.Privacy.CategoryEveryone" = "Everyone";
"Story.Privacy.CategoryContacts" = "Contacts";
"Story.Privacy.CategoryCloseFriends" = "Close Friends";
"Story.Privacy.CategorySelectedContacts" = "Selected Contacts";
"Story.Privacy.ExcludedPeople" = "ExcludedPeople";
"Story.Privacy.ExcludePeople" = "exclude people";
"Story.Privacy.ExcludePeopleExceptNames" = "except %@";
"Story.Privacy.ExcludePeopleExcept_1" = "except 1 person";
"Story.Privacy.ExcludePeopleExcept_any" = "except %@ people";
"Story.Privacy.EditList" = "edit list";
"Story.Privacy.People_1" = "1 person";
"Story.Privacy.People_any" = "%@ people";
"Story.Privacy.Choose" = "choose";
"Story.Privacy.EditStory" = "Edit Story";
"Story.Privacy.ShareStory" = "Share Story";
"Story.Privacy.SaveSettings" = "Save Settings";
"Story.Privacy.PostStory" = "Post Story";

View File

@ -464,6 +464,7 @@ public protocol PresentationCallManager: AnyObject {
var currentCallSignal: Signal<PresentationCall?, NoError> { get }
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get }
var hasActiveCall: Bool { get }
var hasActiveGroupCall: Bool { get }
func requestCall(context: AccountContext, peerId: EnginePeer.Id, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
func joinGroupCall(context: AccountContext, peerId: EnginePeer.Id, invite: String?, requestJoinAsPeerId: ((@escaping (EnginePeer.Id?) -> Void) -> Void)?, initialCall: EngineGroupCallDescription, endCurrentIfAny: Bool) -> JoinGroupCallManagerResult

View File

@ -2536,6 +2536,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
fileprivate func openStoryCamera(fromList: Bool) {
var reachedCountLimit = false
var premiumNeeded = false
var hasActiveCall = false
var hasActiveGroupCall = false
let storiesCountLimit = self.context.userLimits.maxExpiringStoriesCount
if let rawStorySubscriptions = self.rawStorySubscriptions, let accountItem = rawStorySubscriptions.accountItem {
@ -2555,7 +2557,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
break
}
if reachedCountLimit || premiumNeeded {
if let callManager = self.context.sharedContext.callManager {
if callManager.hasActiveGroupCall {
hasActiveGroupCall = true
} else if callManager.hasActiveCall {
hasActiveCall = true
}
}
if reachedCountLimit || premiumNeeded || hasActiveCall || hasActiveGroupCall {
if let componentView = self.chatListHeaderView() {
var sourceFrame: CGRect?
if fromList {
@ -2577,6 +2587,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} else if reachedCountLimit {
let valueText = self.presentationData.strings.StoryFeed_TooltipStoryLimitValue(Int32(storiesCountLimit))
text = self.presentationData.strings.StoryFeed_TooltipStoryLimit(valueText).string
} else if hasActiveCall {
text = self.presentationData.strings.StoryFeed_TooltipPostingDuringCall
} else if hasActiveGroupCall {
text = self.presentationData.strings.StoryFeed_TooltipPostingDuringGroupCall
} else {
text = ""
}

View File

@ -1169,24 +1169,24 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
self.applyItemNodeAsCurrent(id: .all, itemNode: itemNode)
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
guard let strongSelf = self, strongSelf.availableFilters.count > 1 || strongSelf.controller?.isStoryPostingAvailable == true else {
guard let self, self.availableFilters.count > 1 || (self.controller?.isStoryPostingAvailable == true && !(self.context.sharedContext.callManager?.hasActiveCall ?? false)) else {
return []
}
guard case .chatList(.root) = strongSelf.location else {
guard case .chatList(.root) = self.location else {
return []
}
switch strongSelf.currentItemNode.visibleContentOffset() {
switch self.currentItemNode.visibleContentOffset() {
case let .known(value):
if value < -strongSelf.currentItemNode.tempTopInset {
if value < -self.currentItemNode.tempTopInset {
return []
}
case .none, .unknown:
break
}
if !strongSelf.currentItemNode.isNavigationInAFinalState {
if !self.currentItemNode.isNavigationInAFinalState {
return []
}
if strongSelf.availableFilters.count > 1 {
if self.availableFilters.count > 1 {
return [.leftCenter, .rightCenter]
} else {
return [.rightEdge]
@ -1267,7 +1267,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
}
if case .compact = layout.metrics.widthClass, self.controller?.isStoryPostingAvailable == true {
if case .compact = layout.metrics.widthClass, self.controller?.isStoryPostingAvailable == true && !(self.context.sharedContext.callManager?.hasActiveCall ?? false) {
let cameraIsAlreadyOpened = self.controller?.hasStoryCameraTransition ?? false
if selectedIndex <= 0 && translation.x > 0.0 {
transitionFraction = 0.0

View File

@ -86,6 +86,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return self.currentCall != nil || self.currentGroupCall != nil
}
public var hasActiveGroupCall: Bool {
return self.currentGroupCall != nil
}
private let currentCallPromise = Promise<PresentationCall?>(nil)
public var currentCallSignal: Signal<PresentationCall?, NoError> {
return self.currentCallPromise.get()

View File

@ -934,13 +934,13 @@ private final class CameraScreenComponent: CombinedComponent {
case .none:
hintText = " "
case .zoom:
hintText = "Swipe up to zoom"
hintText = environment.strings.Story_Camera_SwipeUpToZoom
case .lock:
hintText = "Swipe left to lock"
hintText = environment.strings.Story_Camera_SwipeLeftToLock
case .releaseLock:
hintText = "Release to lock"
hintText = environment.strings.Story_Camera_SwipeLeftRelease
case .flip:
hintText = "Swipe right to flip"
hintText = environment.strings.Story_Camera_SwipeRightToFlip
}
if let hintText {
let hintLabel = hintLabel.update(
@ -967,6 +967,7 @@ private final class CameraScreenComponent: CombinedComponent {
let modeControl = modeControl.update(
component: ModeComponent(
isTablet: isTablet,
strings: environment.strings,
availableModes: [.photo, .video],
currentMode: component.cameraState.mode,
updatedMode: { [weak state] mode in
@ -1948,7 +1949,7 @@ public class CameraScreen: ViewController {
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 29.0), size: CGSize())
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Draft Saved"), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipDraftSaved), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .ignore
})
self.controller?.present(controller, in: .current)
@ -1963,7 +1964,7 @@ public class CameraScreen: ViewController {
let location = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let accountManager = self.context.sharedContext.accountManager
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Tap here to disable\nthe selfie camera"), textAlignment: .center, location: .point(location, .right), displayDuration: .custom(5.0), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipDisableDual), textAlignment: .center, location: .point(location, .right), displayDuration: .custom(5.0), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in
if containerFrame.contains(point) {
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start()
return .dismiss(consume: true)
@ -1983,7 +1984,7 @@ public class CameraScreen: ViewController {
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize())
let accountManager = self.context.sharedContext.accountManager
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { [weak self] point, containerFrame in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipTakePhotos), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { [weak self] point, containerFrame in
if containerFrame.contains(point) {
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
Queue.mainQueue().justDispatch {

View File

@ -3,14 +3,15 @@ import UIKit
import Display
import ComponentFlow
import MultilineTextComponent
import TelegramPresentationData
extension CameraMode {
var title: String {
func title(strings: PresentationStrings) -> String {
switch self {
case .photo:
return "Photo"
return strings.Story_Camera_Photo
case .video:
return "Video"
return strings.Story_Camera_Video
}
}
}
@ -19,6 +20,7 @@ private let buttonSize = CGSize(width: 55.0, height: 44.0)
final class ModeComponent: Component {
let isTablet: Bool
let strings: PresentationStrings
let availableModes: [CameraMode]
let currentMode: CameraMode
let updatedMode: (CameraMode) -> Void
@ -26,12 +28,14 @@ final class ModeComponent: Component {
init(
isTablet: Bool,
strings: PresentationStrings,
availableModes: [CameraMode],
currentMode: CameraMode,
updatedMode: @escaping (CameraMode) -> Void,
tag: AnyObject?
) {
self.isTablet = isTablet
self.strings = strings
self.availableModes = availableModes
self.currentMode = currentMode
self.updatedMode = updatedMode
@ -42,6 +46,9 @@ final class ModeComponent: Component {
if lhs.isTablet != rhs.isTablet {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.availableModes != rhs.availableModes {
return false
}
@ -145,7 +152,7 @@ final class ModeComponent: Component {
updatedMode(mode)
}
itemView.update(value: mode.title, selected: mode == component.currentMode)
itemView.update(value: mode.title(strings: component.strings), selected: mode == component.currentMode)
itemView.bounds = CGRect(origin: .zero, size: itemFrame.size)
if isTablet {

View File

@ -55,9 +55,6 @@ final class PlaceholderComponent: Component {
super.init(frame: frame)
self.backgroundColor = UIColor(rgb: 0x1c1c1e)
// if #available(iOS 13.0, *) {
// self.layer.cornerCurve = .continuous
// }
}
required init?(coder: NSCoder) {
@ -71,9 +68,10 @@ final class PlaceholderComponent: Component {
let sideInset: CGFloat = 36.0
let animationHeight: CGFloat = 120.0
let title: String = "Allow Telegram to access your camera and microphone"
let text: String = "This lets you share photos and record videos."
let buttonTitle: String = "Open Settings"
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let title = presentationData.strings.Story_Camera_AccessPlaceholderTitle
let text = presentationData.strings.Story_Camera_AccessPlaceholderText
let buttonTitle = presentationData.strings.Story_Camera_AccessOpenSettings
let animationSize = self.animation.update(
transition: .immediate,

View File

@ -309,6 +309,25 @@ public final class MediaEditor {
}
}
public func replaceSource(_ image: UIImage, additionalImage: UIImage?, time: CMTime) {
func fixImageOrientation(_ image: UIImage) -> UIImage {
UIGraphicsBeginImageContext(image.size)
image.draw(at: .zero)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage ?? image
}
let image = fixImageOrientation(image)
guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice, let texture = loadTexture(image: image, device: device) else {
return
}
let additionalImage = additionalImage.flatMap { fixImageOrientation($0) }
let additionalTexture = additionalImage.flatMap { loadTexture(image: $0, device: device) }
self.renderer.consumeTexture(texture, additionalTexture: additionalTexture, time: time, render: true)
}
private var volumeFade: SwiftSignalKit.Timer?
private func setupSource() {
guard let renderTarget = self.previewView else {
@ -633,6 +652,23 @@ public final class MediaEditor {
}
}
public func seek(_ position: Double, completion: @escaping () -> Void) {
guard let player = self.player else {
completion()
return
}
player.pause()
self.additionalPlayer?.pause()
let targetPosition = CMTime(seconds: position, preferredTimescale: CMTimeScale(60.0))
player.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: { _ in
Queue.mainQueue().async {
completion()
}
})
self.additionalPlayer?.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero)
}
public var isPlaying: Bool {
return (self.player?.rate ?? 0.0) > 0.0
}

View File

@ -87,6 +87,9 @@ final class MediaEditorRenderer: TextureConsumer {
private var textureCache: CVMetalTextureCache?
private var currentTexture: MTLTexture?
private var currentAdditionalTexture: MTLTexture?
private var currentTime: CMTime = .zero
private var currentPixelBuffer: VideoPixelBuffer?
private var currentAdditionalPixelBuffer: VideoPixelBuffer?
@ -174,8 +177,8 @@ final class MediaEditorRenderer: TextureConsumer {
self.renderPasses.forEach { $0.setup(device: device, library: library) }
}
public var displayEnabled = true
var renderPassedEnabled = true
var needsDisplay = false
func renderFrame() {
@ -200,7 +203,13 @@ final class MediaEditorRenderer: TextureConsumer {
}
var texture: MTLTexture
if let currentTexture = self.currentTexture {
if let currentAdditionalTexture = self.currentAdditionalTexture, let currentTexture = self.currentTexture {
if let result = self.videoFinishPass.process(input: currentTexture, secondInput: currentAdditionalTexture, timestamp: self.currentTime, device: device, commandBuffer: commandBuffer) {
texture = result
} else {
texture = currentTexture
}
} else if let currentTexture = self.currentTexture {
texture = currentTexture
} else if let currentPixelBuffer = self.currentPixelBuffer, let currentAdditionalPixelBuffer = self.currentAdditionalPixelBuffer, let videoTexture = self.videoInputPass.processPixelBuffer(currentPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer), let additionalVideoTexture = self.additionalVideoInputPass.processPixelBuffer(currentAdditionalPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer) {
if let result = self.videoFinishPass.process(input: videoTexture, secondInput: additionalVideoTexture, timestamp: currentPixelBuffer.timestamp, device: device, commandBuffer: commandBuffer) {
@ -237,7 +246,7 @@ final class MediaEditorRenderer: TextureConsumer {
}
commandBuffer.commit()
if let renderTarget = self.renderTarget {
if let renderTarget = self.renderTarget, self.displayEnabled {
if self.needsDisplay {
self.didRenderFrame()
} else {
@ -299,6 +308,19 @@ final class MediaEditorRenderer: TextureConsumer {
}
}
func consumeTexture(_ texture: MTLTexture, additionalTexture: MTLTexture?, time: CMTime, render: Bool) {
if render {
self.willRenderFrame()
}
self.currentTexture = texture
self.currentAdditionalTexture = additionalTexture
self.currentTime = time
if render {
self.renderFrame()
}
}
var previousPresentationTimestamp: CMTime?
func consumeVideoPixelBuffer(pixelBuffer: VideoPixelBuffer, additionalPixelBuffer: VideoPixelBuffer?, render: Bool) {
self.willRenderFrame()

View File

@ -4,6 +4,7 @@ import Display
import ComponentFlow
import LegacyComponents
import MediaEditor
import TelegramPresentationData
private final class BlurModeComponent: Component {
typealias EnvironmentType = Empty
@ -115,17 +116,20 @@ private final class BlurModeComponent: Component {
final class BlurComponent: Component {
typealias EnvironmentType = Empty
let strings: PresentationStrings
let value: BlurValue
let hasPortrait: Bool
let valueUpdated: (BlurValue) -> Void
let isTrackingUpdated: (Bool) -> Void
init(
strings: PresentationStrings,
value: BlurValue,
hasPortrait: Bool,
valueUpdated: @escaping (BlurValue) -> Void,
isTrackingUpdated: @escaping (Bool) -> Void
) {
self.strings = strings
self.value = value
self.hasPortrait = hasPortrait
self.valueUpdated = valueUpdated
@ -133,6 +137,9 @@ final class BlurComponent: Component {
}
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.value != rhs.value {
return false
}
@ -190,7 +197,7 @@ final class BlurComponent: Component {
transition: transition,
component: AnyComponent(
Text(
text: "Blur",
text: component.strings.Story_Editor_Blur_Title,
font: Font.regular(14.0),
color: UIColor(rgb: 0x808080)
)
@ -212,7 +219,7 @@ final class BlurComponent: Component {
Button(
content: AnyComponent(
BlurModeComponent(
title: "Off",
title: component.strings.Story_Editor_Blur_Off,
icon: self.offImage,
isSelected: state.value.mode == .off
)
@ -233,7 +240,7 @@ final class BlurComponent: Component {
Button(
content: AnyComponent(
BlurModeComponent(
title: "Radial",
title: component.strings.Story_Editor_Blur_Radial,
icon: self.radialImage,
isSelected: state.value.mode == .radial
)
@ -254,7 +261,7 @@ final class BlurComponent: Component {
Button(
content: AnyComponent(
BlurModeComponent(
title: "Linear",
title: component.strings.Story_Editor_Blur_Linear,
icon: self.linearImage,
isSelected: state.value.mode == .linear
)
@ -275,7 +282,7 @@ final class BlurComponent: Component {
Button(
content: AnyComponent(
BlurModeComponent(
title: "Portrait",
title: component.strings.Story_Editor_Blur_Portrait,
icon: self.portraitImage,
isSelected: state.value.mode == .portrait
)

View File

@ -5,6 +5,7 @@ import ComponentFlow
import LegacyComponents
import MediaEditor
import MultilineTextComponent
import TelegramPresentationData
private class HistogramView: UIView {
private var size: CGSize?
@ -75,18 +76,24 @@ class CurvesInternalState {
final class CurvesComponent: Component {
typealias EnvironmentType = Empty
let strings: PresentationStrings
let histogram: MediaEditorHistogram?
let internalState: CurvesInternalState
init(
strings: PresentationStrings,
histogram: MediaEditorHistogram?,
internalState: CurvesInternalState
) {
self.strings = strings
self.histogram = histogram
self.internalState = internalState
}
static func ==(lhs: CurvesComponent, rhs: CurvesComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.histogram != rhs.histogram {
return false
}
@ -145,7 +152,7 @@ final class CurvesComponent: Component {
Button(
content: AnyComponent(
Text(
text: "All",
text: component.strings.Story_Editor_Curves_All,
font: Font.regular(14.0),
color: state.section == .all ? .white : UIColor(rgb: 0x808080)
)
@ -173,7 +180,7 @@ final class CurvesComponent: Component {
Button(
content: AnyComponent(
Text(
text: "Red",
text: component.strings.Story_Editor_Curves_Red,
font: Font.regular(14.0),
color: state.section == .red ? .white : UIColor(rgb: 0x808080)
)
@ -201,7 +208,7 @@ final class CurvesComponent: Component {
Button(
content: AnyComponent(
Text(
text: "Green",
text: component.strings.Story_Editor_Curves_Green,
font: Font.regular(14.0),
color: state.section == .green ? .white : UIColor(rgb: 0x808080)
)
@ -229,7 +236,7 @@ final class CurvesComponent: Component {
Button(
content: AnyComponent(
Text(
text: "Blue",
text: component.strings.Story_Editor_Curves_Blue,
font: Font.regular(14.0),
color: state.section == .blue ? .white : UIColor(rgb: 0x808080)
)

View File

@ -725,9 +725,9 @@ final class MediaEditorScreenComponent: Component {
transition.setAlpha(view: cancelButtonView, alpha: component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0)
}
var doneButtonTitle = "NEXT"
var doneButtonTitle = environment.strings.Story_Editor_Next
if let controller = environment.controller() as? MediaEditorScreen, controller.isEditingStory {
doneButtonTitle = "DONE"
doneButtonTitle = environment.strings.Story_Editor_Done
}
let doneButtonSize = self.doneButton.update(
@ -736,7 +736,7 @@ final class MediaEditorScreenComponent: Component {
content: AnyComponent(DoneButtonComponent(
backgroundColor: UIColor(rgb: 0x007aff),
icon: UIImage(bundleImageName: "Media Editor/Next")!,
title: doneButtonTitle)),
title: doneButtonTitle.uppercased())),
action: {
guard let controller = environment.controller() as? MediaEditorScreen else {
return
@ -1097,7 +1097,7 @@ final class MediaEditorScreenComponent: Component {
theme: environment.theme,
strings: environment.strings,
style: .editor,
placeholder: "Add a caption...",
placeholder: environment.strings.Story_Editor_InputPlaceholderAddCaption,
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
queryTypes: [.mention],
alwaysDarkWhenHasText: false,
@ -1274,20 +1274,20 @@ final class MediaEditorScreenComponent: Component {
var privacyText: String
switch component.privacy.privacy.base {
case .everyone:
privacyText = "Everyone"
privacyText = environment.strings.Story_ContextPrivacy_LabelEveryone
case .closeFriends:
privacyText = "Close Friends"
privacyText = environment.strings.Story_ContextPrivacy_LabelCloseFriends
case .contacts:
privacyText = "Contacts"
if additionalPeersCount > 0 {
privacyText += " (-\(additionalPeersCount))"
privacyText = environment.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalPeersCount)").string
} else {
privacyText = environment.strings.Story_ContextPrivacy_LabelContacts
}
case .nobody:
privacyText = "Selected Contacts"
if additionalPeersCount > 0 {
privacyText += " (\(additionalPeersCount))"
privacyText = environment.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalPeersCount))
} else {
privacyText = "Only You"
privacyText = environment.strings.Story_ContextPrivacy_LabelOnlyMe
}
}
@ -2641,7 +2641,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? "The story will have no sound" : "The story will have sound"), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? self.presentationData.strings.Story_Editor_TooltipMuted : self.presentationData.strings.Story_Editor_TooltipUnmuted), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .ignore
})
self.muteTooltip = tooltipController
@ -2664,9 +2664,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let text: String
let isVideo = self.mediaEditor?.resultIsVideo ?? false
if isVideo {
text = "Video saved to Photos."
text = self.presentationData.strings.Story_Editor_TooltipVideoSavedToPhotos
} else {
text = "Image saved to Photos."
text = self.presentationData.strings.Story_Editor_TooltipImageSavedToPhotos
}
if let tooltipController = self.saveTooltip {
@ -2690,8 +2690,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
let text = "Uploading..."
let text = self.presentationData.strings.Story_Editor_Uploading
if let tooltipController = self.saveTooltip {
tooltipController.content = .progress(text, progress)
} else {
@ -2718,8 +2717,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
let text = "Preparing video..."
let text = self.presentationData.strings.Story_Editor_PreparingVideo
if let tooltipController = self.saveTooltip {
tooltipController.content = .progress(text, progress)
} else {
@ -3081,7 +3079,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
transition: transition,
component: AnyComponent(
ToolValueComponent(
title: "Enhance",
title: environment.strings.Story_Editor_Tool_Enhance,
value: "\(Int(abs(enhanceValue) * 100.0))"
)
),
@ -3441,14 +3439,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, isForwardingDisabled: self.state.privacy.isForwardingDisabled, pin: self.state.privacy.pin)
}
let title = "Choose how long the story will be visible."
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let title = presentationData.strings.Story_Editor_ExpirationText
let currentValue = self.state.privacy.timeout
let currentArchived = self.state.privacy.pin
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
items.append(.action(ContextMenuActionItem(text: "6 Hours", icon: { theme in
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(6), icon: { theme in
if !hasPremium {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
} else {
@ -3463,7 +3462,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self?.presentTimeoutPremiumSuggestion(3600 * 6)
}
})))
items.append(.action(ContextMenuActionItem(text: "12 Hours", icon: { theme in
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(12), icon: { theme in
if !hasPremium {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
} else {
@ -3478,14 +3477,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self?.presentTimeoutPremiumSuggestion(3600 * 12)
}
})))
items.append(.action(ContextMenuActionItem(text: "24 Hours", icon: { theme in
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(24), icon: { theme in
return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(86400)
})))
items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(48), icon: { theme in
if !hasPremium {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
} else {
@ -3501,7 +3500,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
})))
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
self.present(contextController, in: .window(.root))
}
@ -3510,10 +3508,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let timeoutString = presentationData.strings.MuteExpires_Hours(max(1, timeout / (60 * 60)))
let text = "Subscribe to **Telegram Premium** to make your stories disappear \(timeoutString)."
let text = presentationData.strings.Story_Editor_TooltipPremiumCustomExpiration(timeoutString).string
let context = self.context
let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: "More"), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: presentationData.strings.Story_Editor_TooltipPremiumMore), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
if case .undo = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
self.push(controller)
@ -3549,23 +3547,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let title: String
let save: String
if case .draft = self.node.subject {
title = "Discard Draft?"
save = "Keep Draft"
title = presentationData.strings.Story_Editor_DraftDiscardDraft
save = presentationData.strings.Story_Editor_DraftKeepDraft
} else {
title = "Discard Media?"
save = "Save Draft"
title = presentationData.strings.Story_Editor_DraftDiscardMedia
save = presentationData.strings.Story_Editor_DraftKeepMedia
}
let theme = defaultDarkPresentationTheme
let controller = textAlertController(
context: self.context,
forceTheme: theme,
title: title,
text: "If you go back now, you will lose any changes that you've made.",
text: presentationData.strings.Story_Editor_DraftDiscaedText,
actions: [
TextAlertAction(type: .destructiveAction, title: "Discard", action: { [weak self] in
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Story_Editor_DraftDiscard, action: { [weak self] in
if let self {
self.requestDismiss(saveDraft: false, animated: true)
}
@ -3575,7 +3574,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.requestDismiss(saveDraft: true, animated: true)
}
}),
TextAlertAction(type: .genericAction, title: "Cancel", action: {
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})
],
@ -3715,8 +3714,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.dismissAllTooltips()
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: false)
mediaEditor.requestRenderFrame()
mediaEditor.stop()
mediaEditor.invalidate()
self.node.entitiesView.invalidate()
@ -3777,7 +3775,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
if mediaEditor.resultIsVideo {
var firstFrame: Signal<UIImage?, NoError>
var firstFrame: Signal<(UIImage?, UIImage?), NoError>
let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
let videoResult: Result.VideoResult
@ -3791,8 +3789,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
videoResult = .imageFile(path: tempImagePath)
duration = 5.0
firstFrame = .single(image)
case let .video(path, _, _, _, _, _, durationValue, _, _):
firstFrame = .single((image, nil))
case let .video(path, _, _, additionalPath, _, _, durationValue, _, _):
videoResult = .videoFile(path: path)
if let videoTrimRange = mediaEditor.values.videoTrimRange {
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
@ -3800,12 +3798,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
duration = durationValue
}
firstFrame = Signal<UIImage?, NoError> { subscriber in
let _ = additionalPath
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
let avAsset = AVURLAsset(url: URL(fileURLWithPath: path))
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.appliesPreferredTrackTransform = true
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage))
subscriber.putNext((UIImage(cgImage: cgImage), nil))
subscriber.putCompletion()
}
})
@ -3824,14 +3825,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} else {
duration = 5.0
}
firstFrame = Signal<UIImage?, NoError> { subscriber in
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
if asset.mediaType == .video {
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
if let avAsset {
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.appliesPreferredTrackTransform = true
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage))
subscriber.putNext((UIImage(cgImage: cgImage), nil))
subscriber.putCompletion()
}
})
@ -3842,7 +3844,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
options.deliveryMode = .highQualityFormat
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
if let image {
subscriber.putNext(image)
subscriber.putNext((image, nil))
subscriber.putCompletion()
}
}
@ -3857,12 +3859,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} else {
duration = min(draft.duration ?? 5.0, storyMaxVideoDuration)
}
firstFrame = Signal<UIImage?, NoError> { subscriber in
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
let avAsset = AVURLAsset(url: URL(fileURLWithPath: draft.fullPath()))
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.appliesPreferredTrackTransform = true
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage))
subscriber.putNext((UIImage(cgImage: cgImage), nil))
subscriber.putCompletion()
}
})
@ -3875,21 +3878,34 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
duration = 5.0
if let image = UIImage(contentsOfFile: draft.fullPath()) {
firstFrame = .single(image)
firstFrame = .single((image, nil))
} else {
firstFrame = .single(UIImage())
firstFrame = .single((UIImage(), nil))
}
}
}
if let resultImage = mediaEditor.resultImage {
firstFrame = .single(resultImage)
}
let _ = (firstFrame
|> deliverOnMainQueue).start(next: { [weak self] image in
|> deliverOnMainQueue).start(next: { [weak self] image, additionalImage in
if let self {
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
var currentImage = mediaEditor.resultImage
if let image {
mediaEditor.replaceSource(image, additionalImage: additionalImage, time: firstFrameTime)
if let updatedImage = mediaEditor.resultImage {
currentImage = updatedImage
}
}
var inputImage: UIImage
if let currentImage {
inputImage = currentImage
} else if let image {
inputImage = image
} else {
inputImage = UIImage()
}
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
if let self {
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in

View File

@ -335,6 +335,7 @@ private final class MediaToolsScreenComponent: Component {
self.component = component
self.state = state
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let isTablet: Bool
if case .regular = environment.metrics.widthClass {
isTablet = true
@ -577,7 +578,7 @@ private final class MediaToolsScreenComponent: Component {
var tools: [AdjustmentTool] = [
AdjustmentTool(
key: .enhance,
title: "Enhance",
title: presentationData.strings.Story_Editor_Tool_Enhance,
value: mediaEditor?.getToolValue(.enhance) as? Float ?? 0.0,
minValue: 0.0,
maxValue: 1.0,
@ -585,7 +586,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .brightness,
title: "Brightness",
title: presentationData.strings.Story_Editor_Tool_Brightness,
value: mediaEditor?.getToolValue(.brightness) as? Float ?? 0.0,
minValue: -1.0,
maxValue: 1.0,
@ -593,7 +594,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .contrast,
title: "Contrast",
title: presentationData.strings.Story_Editor_Tool_Contrast,
value: mediaEditor?.getToolValue(.contrast) as? Float ?? 0.0,
minValue: -1.0,
maxValue: 1.0,
@ -601,7 +602,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .saturation,
title: "Saturation",
title: presentationData.strings.Story_Editor_Tool_Saturation,
value: mediaEditor?.getToolValue(.saturation) as? Float ?? 0.0,
minValue: -1.0,
maxValue: 1.0,
@ -609,7 +610,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .warmth,
title: "Warmth",
title: presentationData.strings.Story_Editor_Tool_Warmth,
value: mediaEditor?.getToolValue(.warmth) as? Float ?? 0.0,
minValue: -1.0,
maxValue: 1.0,
@ -617,7 +618,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .fade,
title: "Fade",
title: presentationData.strings.Story_Editor_Tool_Fade,
value: mediaEditor?.getToolValue(.fade) as? Float ?? 0.0,
minValue: 0.0,
maxValue: 1.0,
@ -625,7 +626,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .highlights,
title: "Highlights",
title: presentationData.strings.Story_Editor_Tool_Highlights,
value: mediaEditor?.getToolValue(.highlights) as? Float ?? 0.0,
minValue: -1.0,
maxValue: 1.0,
@ -633,7 +634,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .shadows,
title: "Shadows",
title: presentationData.strings.Story_Editor_Tool_Shadows,
value: mediaEditor?.getToolValue(.shadows) as? Float ?? 0.0,
minValue: -1.0,
maxValue: 1.0,
@ -641,7 +642,7 @@ private final class MediaToolsScreenComponent: Component {
),
AdjustmentTool(
key: .vignette,
title: "Vignette",
title: presentationData.strings.Story_Editor_Tool_Vignette,
value: mediaEditor?.getToolValue(.vignette) as? Float ?? 0.0,
minValue: 0.0,
maxValue: 1.0,
@ -660,7 +661,7 @@ private final class MediaToolsScreenComponent: Component {
if !component.mediaEditor.sourceIsVideo {
tools.insert(AdjustmentTool(
key: .grain,
title: "Grain",
title: presentationData.strings.Story_Editor_Tool_Grain,
value: mediaEditor?.getToolValue(.grain) as? Float ?? 0.0,
minValue: 0.0,
maxValue: 1.0,
@ -721,6 +722,7 @@ private final class MediaToolsScreenComponent: Component {
optionsSize = self.toolOptions.update(
transition: optionsTransition,
component: AnyComponent(TintComponent(
strings: presentationData.strings,
shadowsValue: mediaEditor?.getToolValue(.shadowsTint) as? TintValue ?? TintValue.initial,
highlightsValue: mediaEditor?.getToolValue(.highlightsTint) as? TintValue ?? TintValue.initial,
shadowsValueUpdated: { [weak state] value in
@ -778,6 +780,7 @@ private final class MediaToolsScreenComponent: Component {
optionsSize = self.toolOptions.update(
transition: optionsTransition,
component: AnyComponent(BlurComponent(
strings: presentationData.strings,
value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial,
hasPortrait: mediaEditor?.hasPortraitMask ?? false,
valueUpdated: { [weak state] value in
@ -853,6 +856,7 @@ private final class MediaToolsScreenComponent: Component {
optionsSize = self.toolOptions.update(
transition: optionsTransition,
component: AnyComponent(CurvesComponent(
strings: presentationData.strings,
histogram: state.histogram,
internalState: internalState
)),

View File

@ -218,10 +218,12 @@ final class StoryPreviewComponent: Component {
}
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(Text(
text: "My story",
text: presentationData.strings.Story_HeaderYourStory,
font: Font.medium(14.0),
color: .white
)),
@ -240,7 +242,6 @@ final class StoryPreviewComponent: Component {
transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size))
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let inputPanelSize = self.inputPanel.update(
transition: transition,
component: AnyComponent(MessageInputPanelComponent(
@ -249,7 +250,7 @@ final class StoryPreviewComponent: Component {
theme: presentationData.theme,
strings: presentationData.strings,
style: .story,
placeholder: "Reply Privately...",
placeholder: presentationData.strings.Story_InputPlaceholderReplyPrivately,
maxLength: nil,
queryTypes: [],
alwaysDarkWhenHasText: false,

View File

@ -4,6 +4,7 @@ import Display
import ComponentFlow
import LegacyComponents
import MediaEditor
import TelegramPresentationData
private final class TintColorComponent: Component {
typealias EnvironmentType = Empty
@ -106,6 +107,7 @@ final class TintComponent: Component {
typealias EnvironmentType = Empty
let strings: PresentationStrings
let shadowsValue: TintValue
let highlightsValue: TintValue
let shadowsValueUpdated: (TintValue) -> Void
@ -113,12 +115,14 @@ final class TintComponent: Component {
let isTrackingUpdated: (Bool) -> Void
init(
strings: PresentationStrings,
shadowsValue: TintValue,
highlightsValue: TintValue,
shadowsValueUpdated: @escaping (TintValue) -> Void,
highlightsValueUpdated: @escaping (TintValue) -> Void,
isTrackingUpdated: @escaping (Bool) -> Void
) {
self.strings = strings
self.shadowsValue = shadowsValue
self.highlightsValue = highlightsValue
self.shadowsValueUpdated = shadowsValueUpdated
@ -127,6 +131,9 @@ final class TintComponent: Component {
}
static func ==(lhs: TintComponent, rhs: TintComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.highlightsValue != rhs.highlightsValue {
return false
}
@ -185,7 +192,7 @@ final class TintComponent: Component {
Button(
content: AnyComponent(
Text(
text: "Shadows",
text: component.strings.Story_Editor_Tint_Shadows,
font: Font.regular(14.0),
color: state.section == .shadows ? .white : UIColor(rgb: 0x808080)
)
@ -213,7 +220,7 @@ final class TintComponent: Component {
Button(
content: AnyComponent(
Text(
text: "Highlights",
text: component.strings.Story_Editor_Tint_Highlights,
font: Font.regular(14.0),
color: state.section == .highlights ? .white : UIColor(rgb: 0x808080)
)

View File

@ -346,26 +346,26 @@ public final class MediaRecordingPanelComponent: Component {
let cancelTextSize = self.cancelText.update(
transition: .immediate,
component: AnyComponent(Text(text: "Slide to cancel", font: Font.regular(15.0), color: UIColor(rgb: 0xffffff, alpha: 0.3))),
component: AnyComponent(Text(text: component.strings.Conversation_SlideToCancel, font: Font.regular(15.0), color: UIColor(rgb: 0xffffff, alpha: 0.3))),
environment: {},
containerSize: CGSize(width: max(30.0, availableSize.width - 100.0), height: 44.0)
)
let _ = self.vibrancyCancelText.update(
transition: .immediate,
component: AnyComponent(Text(text: "Slide to cancel", font: Font.regular(15.0), color: .white)),
component: AnyComponent(Text(text: component.strings.Conversation_SlideToCancel, font: Font.regular(15.0), color: .white)),
environment: {},
containerSize: CGSize(width: max(30.0, availableSize.width - 100.0), height: 44.0)
)
let cancelButtonTextSize = self.cancelButtonText.update(
transition: .immediate,
component: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: .white)),
component: AnyComponent(Text(text: component.strings.Common_Cancel, font: Font.regular(17.0), color: .white)),
environment: {},
containerSize: CGSize(width: max(30.0, availableSize.width - 100.0), height: 44.0)
)
let _ = self.vibrancyCancelButtonText.update(
transition: .immediate,
component: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: .clear)),
component: AnyComponent(Text(text: component.strings.Common_Cancel, font: Font.regular(17.0), color: .clear)),
environment: {},
containerSize: CGSize(width: max(30.0, availableSize.width - 100.0), height: 44.0)
)

View File

@ -504,32 +504,33 @@ final class ShareWithPeersScreenComponent: Component {
let animationName: String
let text: String
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
switch optionId {
case .screenshot:
if self.selectedOptions.contains(.screenshot) {
if self.selectedCategories.contains(.everyone) {
animationName = "anim_savemedia"
text = "Downloading, sharing and taking screenshots will be enabled for this story."
text = presentationData.strings.Story_Privacy_TooltipSharingEnabledPublic
} else {
animationName = "anim_savemedia"
text = "Downloading and taking screenshots will be enabled for this story."
text = presentationData.strings.Story_Privacy_TooltipSharingEnabled
}
} else {
if self.selectedCategories.contains(.everyone) {
animationName = "premium_unlock"
text = "Downloading, sharing and taking screenshots will be disabled for this story."
text = presentationData.strings.Story_Privacy_TooltipSharingDisabledPublic
} else {
animationName = "premium_unlock"
text = "Downloading and taking screenshots will be disabled for this story."
text = presentationData.strings.Story_Privacy_TooltipSharingDisabled
}
}
case .pin:
if self.selectedOptions.contains(.pin) {
animationName = "anim_profileadd"
text = "Users allowed to view your story will see it on your page event after it expires."
text = presentationData.strings.Story_Privacy_TooltipStoryArchived
} else {
animationName = "anim_autoremove_on"
text = "The story will disappear after it expires."
text = presentationData.strings.Story_Privacy_TooltipStoryExpires
}
}
@ -563,7 +564,7 @@ final class ShareWithPeersScreenComponent: Component {
guard let stateValue = self.effectiveStateValue else {
return
}
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
topOffset = max(0.0, topOffset)
transition.setTransform(layer: self.backgroundView.layer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
@ -652,9 +653,9 @@ final class ShareWithPeersScreenComponent: Component {
let sectionTitle: String
if section.id == 0 {
sectionTitle = "WHO CAN VIEW"
sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader
} else if section.id == 1 {
sectionTitle = "CONTACTS"
sectionTitle = environment.strings.Story_Privacy_ContactsHeader
} else {
sectionTitle = ""
}
@ -927,10 +928,12 @@ final class ShareWithPeersScreenComponent: Component {
self.visibleSectionFooters[section.id] = sectionFooter
}
let footerValue = environment.strings.Story_Privacy_KeepOnMyPageHours(Int32(component.timeout / 3600))
let footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string
let footerSize = sectionFooter.update(
transition: sectionFooterTransition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Keep this story on your page even after it expires in \( component.timeout / 3600 ) hours. Privacy settings will apply.", font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)),
text: .plain(NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)),
maximumNumberOfLines: 0,
lineSpacing: 0.2
)),
@ -1255,9 +1258,9 @@ final class ShareWithPeersScreenComponent: Component {
let placeholder: String
switch component.stateContext.subject {
case .chats:
placeholder = "Search Chats"
placeholder = environment.strings.Story_Privacy_SearchChats
default:
placeholder = "Search Contacts"
placeholder = environment.strings.Story_Privacy_SearchContacts
}
self.navigationTextField.parentState = state
navigationTextFieldSize = self.navigationTextField.update(
@ -1406,7 +1409,7 @@ final class ShareWithPeersScreenComponent: Component {
let navigationLeftButtonSize = self.navigationLeftButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
action: { [weak self] in
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
return
@ -1426,26 +1429,26 @@ final class ShareWithPeersScreenComponent: Component {
}
navigationButtonsWidth += navigationLeftButtonSize.width + navigationSideInset
var actionButtonTitle = "Save Settings"
var actionButtonTitle = environment.strings.Story_Privacy_SaveSettings
let title: String
switch component.stateContext.subject {
case let .stories(editing):
if editing {
title = "Edit Story"
title = environment.strings.Story_Privacy_EditStory
} else {
title = "Share Story"
actionButtonTitle = "Post Story"
title = environment.strings.Story_Privacy_ShareStory
actionButtonTitle = environment.strings.Story_Privacy_PostStory
}
case .chats:
title = "Send as a Message"
title = ""
case let .contacts(category):
switch category {
case .closeFriends:
title = "Close Friends"
title = environment.strings.Story_Privacy_CategoryCloseFriends
case .contacts:
title = "Excluded People"
title = environment.strings.Story_Privacy_ExcludePeople
case .nobody:
title = "Selected Contacts"
title = environment.strings.Story_Privacy_CategorySelectedContacts
case .everyone:
title = ""
}
@ -1525,7 +1528,7 @@ final class ShareWithPeersScreenComponent: Component {
isEnabled: true,
displaysProgress: false,
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() as? ShareWithPeersScreen else {
guard let self, let component = self.component, let environment = self.environment, let controller = self.environment?.controller() as? ShareWithPeersScreen else {
return
}
@ -1561,13 +1564,13 @@ final class ShareWithPeersScreenComponent: Component {
let alertController = textAlertController(
context: component.context,
forceTheme: defaultDarkColorPresentationTheme,
title: "Privacy Restrictions",
text: "The privacy settings of your story will prevent some users you tagged (\( usernamesString )) from viewing it.",
title: environment.strings.Story_Privacy_MentionRestrictedTitle,
text: environment.strings.Story_Privacy_MentionRestrictedText(usernamesString).string,
actions: [
TextAlertAction(type: .defaultAction, title: "Proceed Anyway", action: {
TextAlertAction(type: .defaultAction, title: environment.strings.Story_Privacy_MentionRestrictedProceed, action: {
proceed()
}),
TextAlertAction(type: .genericAction, title: "Cancel", action: {})
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {})
],
actionLayout: .vertical
)
@ -1984,7 +1987,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
if case let .stories(editing) = stateContext.subject {
categoryItems.append(ShareWithPeersScreenComponent.CategoryItem(
id: .everyone,
title: "Everyone",
title: presentationData.strings.Story_Privacy_CategoryEveryone,
icon: "Chat List/Filters/Channel",
iconColor: .blue,
actionTitle: nil
@ -1995,65 +1998,65 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
peerNames = String(peers.map { $0.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) }.joined(separator: ", "))
}
var contactsSubtitle = "exclude people"
var contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
if initialPrivacy.base == .contacts, initialPrivacy.additionallyIncludePeers.count > 0 {
if initialPrivacy.additionallyIncludePeers.count == 1 {
if !peerNames.isEmpty {
contactsSubtitle = "except \(peerNames)"
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
} else {
contactsSubtitle = "except 1 person"
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExcept(1)
}
} else {
if !peerNames.isEmpty {
contactsSubtitle = "except \(peerNames)"
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
} else {
contactsSubtitle = "except \(initialPrivacy.additionallyIncludePeers.count) people"
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExcept(Int32(initialPrivacy.additionallyIncludePeers.count))
}
}
}
categoryItems.append(ShareWithPeersScreenComponent.CategoryItem(
id: .contacts,
title: "Contacts",
title: presentationData.strings.Story_Privacy_CategoryContacts,
icon: "Chat List/Tabs/IconContacts",
iconColor: .yellow,
actionTitle: contactsSubtitle
))
var closeFriendsSubtitle = "edit list"
var closeFriendsSubtitle = presentationData.strings.Story_Privacy_EditList
if let peers = stateContext.stateValue?.closeFriendsPeers, !peers.isEmpty {
if peers.count > 2 {
closeFriendsSubtitle = "\(peers.count) people"
closeFriendsSubtitle = presentationData.strings.Story_Privacy_People(Int32(peers.count))
} else {
closeFriendsSubtitle = String(peers.map { $0.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) }.joined(separator: ", "))
}
}
categoryItems.append(ShareWithPeersScreenComponent.CategoryItem(
id: .closeFriends,
title: "Close Friends",
title: presentationData.strings.Story_Privacy_CategoryCloseFriends,
icon: "Call/StarHighlighted",
iconColor: .green,
actionTitle: closeFriendsSubtitle
))
var selectedContactsSubtitle = "choose"
var selectedContactsSubtitle = presentationData.strings.Story_Privacy_Choose
if initialPrivacy.base == .nobody, initialPrivacy.additionallyIncludePeers.count > 0 {
if initialPrivacy.additionallyIncludePeers.count == 1 {
if !peerNames.isEmpty {
selectedContactsSubtitle = peerNames
} else {
selectedContactsSubtitle = "1 person"
selectedContactsSubtitle = presentationData.strings.Story_Privacy_People(1)
}
} else {
if !peerNames.isEmpty {
selectedContactsSubtitle = peerNames
} else {
selectedContactsSubtitle = "\(initialPrivacy.additionallyIncludePeers.count) people"
selectedContactsSubtitle = presentationData.strings.Story_Privacy_People(Int32(initialPrivacy.additionallyIncludePeers.count))
}
}
}
categoryItems.append(ShareWithPeersScreenComponent.CategoryItem(
id: .selectedContacts,
title: "Selected Contacts",
title: presentationData.strings.Story_Privacy_CategorySelectedContacts,
icon: "Chat List/Filters/Group",
iconColor: .violet,
actionTitle: selectedContactsSubtitle
@ -2062,12 +2065,12 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
if !editing {
optionItems.append(ShareWithPeersScreenComponent.OptionItem(
id: .screenshot,
title: "Allow Screenshots"
title: presentationData.strings.Story_Privacy_AllowScreenshots
))
optionItems.append(ShareWithPeersScreenComponent.OptionItem(
id: .pin,
title: "Keep on My Page"
title: presentationData.strings.Story_Privacy_KeepOnMyPage
))
}
}