mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '527fe4452f214b59c818526916beab563117909c'
# Conflicts: # Telegram/Telegram-iOS/en.lproj/Localizable.strings
This commit is contained in:
commit
3c16e9c215
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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 = ""
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)),
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user