mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-02 12:48:45 +00:00
Camera and editor improvements
This commit is contained in:
parent
b280850bbc
commit
3c274dbf0c
@ -492,7 +492,7 @@ public enum StoryUploadResult {
|
||||
case completed
|
||||
}
|
||||
|
||||
func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy) -> Signal<StoryUploadResult, NoError> {
|
||||
private func uploadedStoryContent(account: Account, media: EngineStoryInputMedia) -> (signal: Signal<PendingMessageUploadedContentResult?, NoError>, media: Media) {
|
||||
let originalMedia: Media
|
||||
let contentToUpload: MessageContentToUpload
|
||||
|
||||
@ -571,48 +571,60 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text:
|
||||
contentSignal = signal
|
||||
}
|
||||
|
||||
return contentSignal
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<PendingMessageUploadedContentResult?, NoError> in
|
||||
return .single(nil)
|
||||
return (
|
||||
contentSignal
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<PendingMessageUploadedContentResult?, NoError> in
|
||||
return .single(nil)
|
||||
},
|
||||
originalMedia
|
||||
)
|
||||
}
|
||||
|
||||
private func apiInputPrivacyRules(privacy: EngineStoryPrivacy, transaction: Transaction) -> [Api.InputPrivacyRule] {
|
||||
var privacyRules: [Api.InputPrivacyRule]
|
||||
switch privacy.base {
|
||||
case .everyone:
|
||||
privacyRules = [.inputPrivacyValueAllowAll]
|
||||
case .contacts:
|
||||
privacyRules = [.inputPrivacyValueAllowContacts]
|
||||
case .closeFriends:
|
||||
privacyRules = [.inputPrivacyValueAllowCloseFriends]
|
||||
case .nobody:
|
||||
privacyRules = [.inputPrivacyValueDisallowAll]
|
||||
}
|
||||
var privacyUsers: [Api.InputUser] = []
|
||||
var privacyChats: [Int64] = []
|
||||
for peerId in privacy.additionallyIncludePeers {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
if let _ = peer as? TelegramUser {
|
||||
if let inputUser = apiInputUser(peer) {
|
||||
privacyUsers.append(inputUser)
|
||||
}
|
||||
} else if peer is TelegramGroup || peer is TelegramChannel {
|
||||
privacyChats.append(peer.id.id._internalGetInt64Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
if !privacyUsers.isEmpty {
|
||||
privacyRules.append(.inputPrivacyValueAllowUsers(users: privacyUsers))
|
||||
}
|
||||
if !privacyChats.isEmpty {
|
||||
privacyRules.append(.inputPrivacyValueAllowChatParticipants(chats: privacyChats))
|
||||
}
|
||||
return privacyRules
|
||||
}
|
||||
|
||||
func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy) -> Signal<StoryUploadResult, NoError> {
|
||||
let (contentSignal, originalMedia) = uploadedStoryContent(account: account, media: media)
|
||||
return contentSignal
|
||||
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
|
||||
return account.postbox.transaction { transaction -> Signal<StoryUploadResult, NoError> in
|
||||
switch result {
|
||||
case let .progress(progress):
|
||||
return .single(.progress(progress))
|
||||
case let .content(content):
|
||||
var privacyRules: [Api.InputPrivacyRule]
|
||||
switch privacy.base {
|
||||
case .everyone:
|
||||
privacyRules = [.inputPrivacyValueAllowAll]
|
||||
case .contacts:
|
||||
privacyRules = [.inputPrivacyValueAllowContacts]
|
||||
case .closeFriends:
|
||||
privacyRules = [.inputPrivacyValueAllowCloseFriends]
|
||||
case .nobody:
|
||||
privacyRules = [.inputPrivacyValueDisallowAll]
|
||||
}
|
||||
var privacyUsers: [Api.InputUser] = []
|
||||
var privacyChats: [Int64] = []
|
||||
for peerId in privacy.additionallyIncludePeers {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
if let _ = peer as? TelegramUser {
|
||||
if let inputUser = apiInputUser(peer) {
|
||||
privacyUsers.append(inputUser)
|
||||
}
|
||||
} else if peer is TelegramGroup || peer is TelegramChannel {
|
||||
privacyChats.append(peer.id.id._internalGetInt64Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
if !privacyUsers.isEmpty {
|
||||
privacyRules.append(.inputPrivacyValueAllowUsers(users: privacyUsers))
|
||||
}
|
||||
if !privacyChats.isEmpty {
|
||||
privacyRules.append(.inputPrivacyValueAllowChatParticipants(chats: privacyChats))
|
||||
}
|
||||
|
||||
switch result {
|
||||
case let .progress(progress):
|
||||
return .single(.progress(progress))
|
||||
case let .content(content):
|
||||
return account.postbox.transaction { transaction -> Signal<StoryUploadResult, NoError> in
|
||||
let privacyRules = apiInputPrivacyRules(privacy: privacy, transaction: transaction)
|
||||
switch content.content {
|
||||
case let .media(inputMedia, _):
|
||||
var flags: Int32 = 0
|
||||
@ -622,7 +634,6 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text:
|
||||
if pin {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
|
||||
if !text.isEmpty {
|
||||
flags |= 1 << 0
|
||||
apiCaption = text
|
||||
@ -678,8 +689,100 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text:
|
||||
default:
|
||||
return .complete()
|
||||
}
|
||||
default:
|
||||
return .complete()
|
||||
}
|
||||
|> switchToLatest
|
||||
default:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: Int32, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> {
|
||||
let contentSignal: Signal<PendingMessageUploadedContentResult?, NoError>
|
||||
let originalMedia: Media?
|
||||
if let media = media {
|
||||
(contentSignal, originalMedia) = uploadedStoryContent(account: account, media: media)
|
||||
} else {
|
||||
contentSignal = .single(nil)
|
||||
originalMedia = nil
|
||||
}
|
||||
|
||||
return contentSignal
|
||||
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
|
||||
if let result = result, case let .progress(progress) = result {
|
||||
return .single(.progress(progress))
|
||||
}
|
||||
|
||||
let inputMedia: Api.InputMedia?
|
||||
if let result = result, case let .content(uploadedContent) = result, case let .media(media, _) = uploadedContent.content {
|
||||
inputMedia = media
|
||||
} else {
|
||||
inputMedia = nil
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> Signal<StoryUploadResult, NoError> in
|
||||
var flags: Int32 = 0
|
||||
var apiCaption: String?
|
||||
var apiEntities: [Api.MessageEntity]?
|
||||
var privacyRules: [Api.InputPrivacyRule]?
|
||||
|
||||
if let _ = inputMedia {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
if !text.isEmpty {
|
||||
flags |= 1 << 1
|
||||
apiCaption = text
|
||||
|
||||
if !entities.isEmpty {
|
||||
flags |= 1 << 1
|
||||
|
||||
var associatedPeers: [PeerId: Peer] = [:]
|
||||
for entity in entities {
|
||||
for entityPeerId in entity.associatedPeerIds {
|
||||
if let peer = transaction.getPeer(entityPeerId) {
|
||||
associatedPeers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
apiEntities = apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary(associatedPeers))
|
||||
}
|
||||
}
|
||||
if let privacy = privacy {
|
||||
privacyRules = apiInputPrivacyRules(privacy: privacy, transaction: transaction)
|
||||
flags |= 1 << 2
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.stories.editStory(
|
||||
flags: flags,
|
||||
id: id,
|
||||
media: inputMedia,
|
||||
caption: apiCaption,
|
||||
entities: apiEntities,
|
||||
privacyRules: privacyRules
|
||||
))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<StoryUploadResult, NoError> in
|
||||
if let updates = updates {
|
||||
for update in updates.allUpdates {
|
||||
if case let .updateStory(_, story) = update {
|
||||
switch story {
|
||||
case let .storyItem(_, _, _, _, _, media, _, _):
|
||||
let (parsedMedia, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, account.peerId)
|
||||
if let parsedMedia = parsedMedia, let originalMedia = originalMedia {
|
||||
applyMediaResourceChanges(from: originalMedia, to: parsedMedia, postbox: account.postbox, force: false)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
|
||||
return .single(.completed)
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|
||||
@ -880,6 +880,10 @@ public extension TelegramEngine {
|
||||
return _internal_uploadStory(account: self.account, media: media, text: text, entities: entities, pin: pin, privacy: privacy)
|
||||
}
|
||||
|
||||
public func editStory(media: EngineStoryInputMedia?, id: Int32, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> {
|
||||
return _internal_editStory(account: self.account, media: media, id: id, text: text, entities: entities, privacy: privacy)
|
||||
}
|
||||
|
||||
public func deleteStory(id: Int32) -> Signal<Never, NoError> {
|
||||
return _internal_deleteStory(account: self.account, id: id)
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
}
|
||||
|
||||
public var baseSize: CGSize {
|
||||
let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.2)
|
||||
let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.25)
|
||||
return CGSize(width: size, height: size)
|
||||
}
|
||||
|
||||
|
||||
@ -82,6 +82,11 @@ public final class MediaEditor {
|
||||
}
|
||||
set {
|
||||
self.histogramCalculationPass.isEnabled = newValue
|
||||
if newValue {
|
||||
Queue.mainQueue().justDispatch {
|
||||
self.updateRenderChain()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -256,7 +256,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
||||
}
|
||||
|
||||
if let imagePixelBuffer {
|
||||
let image = render(width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, pixelBuffer: imagePixelBuffer, colorSpace: strongSelf.colorSpace, tintColor: tintColor)
|
||||
let image = render(width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, pixelBuffer: imagePixelBuffer, tintColor: tintColor)
|
||||
strongSelf.image = image
|
||||
}
|
||||
completion(strongSelf.image)
|
||||
@ -296,7 +296,7 @@ protocol MediaEditorComposerEntity {
|
||||
func image(for time: CMTime, frameRate: Float, completion: @escaping (CIImage?) -> Void)
|
||||
}
|
||||
|
||||
private func render(width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, pixelBuffer: CVPixelBuffer, colorSpace: CGColorSpace, tintColor: UIColor?) -> CIImage? {
|
||||
private func render(width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, pixelBuffer: CVPixelBuffer, tintColor: UIColor?) -> CIImage? {
|
||||
//let calculatedBytesPerRow = (4 * Int(width) + 31) & (~31)
|
||||
//assert(bytesPerRow == calculatedBytesPerRow)
|
||||
|
||||
@ -325,5 +325,5 @@ private func render(width: Int, height: Int, bytesPerRow: Int, data: Data, type:
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
|
||||
|
||||
return CIImage(cvPixelBuffer: pixelBuffer, options: [.colorSpace: colorSpace])
|
||||
return CIImage(cvPixelBuffer: pixelBuffer, options: [.colorSpace: deviceColorSpace])
|
||||
}
|
||||
|
||||
@ -131,3 +131,35 @@ public func storyDrafts(engine: TelegramEngine) -> Signal<[MediaEditorDraft], No
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
public func saveStorySource(engine: TelegramEngine, item: MediaEditorDraft, id: Int64) {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: id)
|
||||
let _ = engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key, item: item).start()
|
||||
}
|
||||
|
||||
public func removeStorySource(engine: TelegramEngine, id: Int64) {
|
||||
let key = EngineDataBuffer(length: 8)
|
||||
key.setInt64(0, value: id)
|
||||
let _ = engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key)
|
||||
}
|
||||
|
||||
public func moveStorySource(engine: TelegramEngine, from fromId: Int64, to toId: Int64) {
|
||||
let fromKey = EngineDataBuffer(length: 8)
|
||||
fromKey.setInt64(0, value: fromId)
|
||||
|
||||
let toKey = EngineDataBuffer(length: 8)
|
||||
toKey.setInt64(0, value: toId)
|
||||
|
||||
let _ = engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: fromKey))
|
||||
|> mapToSignal { item -> Signal<Never, NoError> in
|
||||
if let item = item?.get(MediaEditorDraft.self) {
|
||||
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: toKey, item: item)
|
||||
|> then(
|
||||
engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: fromKey)
|
||||
)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -650,14 +650,28 @@ final class MediaEditorScreenComponent: Component {
|
||||
|
||||
}
|
||||
|
||||
let timeoutValue: Int32
|
||||
var timeoutValue: String
|
||||
let timeoutSelected: Bool
|
||||
switch component.privacy {
|
||||
case let .story(_, archive):
|
||||
timeoutValue = 24
|
||||
timeoutSelected = !archive
|
||||
case let .story(_, timeout, archive):
|
||||
switch timeout {
|
||||
case 21600:
|
||||
timeoutValue = "6"
|
||||
case 43200:
|
||||
timeoutValue = "12"
|
||||
case 86400:
|
||||
timeoutValue = "24"
|
||||
case 172800:
|
||||
timeoutValue = "2d"
|
||||
default:
|
||||
timeoutValue = "24"
|
||||
}
|
||||
if archive {
|
||||
timeoutValue = "∞"
|
||||
}
|
||||
timeoutSelected = false
|
||||
case let .message(_, timeout):
|
||||
timeoutValue = timeout ?? 1
|
||||
timeoutValue = "\(timeout ?? 1)"
|
||||
timeoutSelected = timeout != nil
|
||||
}
|
||||
|
||||
@ -694,13 +708,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
guard let self, let controller = self.environment?.controller() as? MediaEditorScreen else {
|
||||
return
|
||||
}
|
||||
switch controller.state.privacy {
|
||||
case let .story(privacy, archive):
|
||||
controller.state.privacy = .story(privacy: privacy, archive: !archive)
|
||||
controller.node.presentStoryArchiveTooltip(sourceView: view)
|
||||
case .message:
|
||||
controller.presentTimeoutSetup(sourceView: view)
|
||||
}
|
||||
controller.presentTimeoutSetup(sourceView: view)
|
||||
},
|
||||
audioRecorder: nil,
|
||||
videoRecordingStatus: nil,
|
||||
@ -742,7 +750,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
|
||||
let privacyText: String
|
||||
switch component.privacy {
|
||||
case let .story(privacy, _):
|
||||
case let .story(privacy, _, _):
|
||||
switch privacy.base {
|
||||
case .everyone:
|
||||
privacyText = "Everyone"
|
||||
@ -1027,7 +1035,7 @@ private let storyDimensions = CGSize(width: 1080.0, height: 1920.0)
|
||||
private let storyMaxVideoDuration: Double = 60.0
|
||||
|
||||
public enum MediaEditorResultPrivacy: Equatable {
|
||||
case story(privacy: EngineStoryPrivacy, archive: Bool)
|
||||
case story(privacy: EngineStoryPrivacy, timeout: Int32, archive: Bool)
|
||||
case message(peers: [EnginePeer.Id], timeout: Int32?)
|
||||
}
|
||||
|
||||
@ -1070,7 +1078,7 @@ public final class MediaEditorScreen: ViewController {
|
||||
}
|
||||
|
||||
struct State {
|
||||
var privacy: MediaEditorResultPrivacy = .story(privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), archive: false)
|
||||
var privacy: MediaEditorResultPrivacy = .story(privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 86400, archive: false)
|
||||
}
|
||||
|
||||
var state = State() {
|
||||
@ -1788,7 +1796,7 @@ public final class MediaEditorScreen: ViewController {
|
||||
|
||||
private weak var storyArchiveTooltip: ViewController?
|
||||
func presentStoryArchiveTooltip(sourceView: UIView) {
|
||||
guard let controller = self.controller, case let .story(_, archive) = controller.state.privacy else {
|
||||
guard let controller = self.controller, case let .story(_, _, archive) = controller.state.privacy else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -2158,9 +2166,13 @@ public final class MediaEditorScreen: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
var archive = true
|
||||
var timeout: Int32 = 86400
|
||||
let initialPrivacy: EngineStoryPrivacy
|
||||
if case let .story(privacy, _) = self.state.privacy {
|
||||
if case let .story(privacy, timeoutValue, archiveValue) = self.state.privacy {
|
||||
initialPrivacy = privacy
|
||||
timeout = timeoutValue
|
||||
archive = archiveValue
|
||||
} else {
|
||||
initialPrivacy = EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: [])
|
||||
}
|
||||
@ -2174,7 +2186,7 @@ public final class MediaEditorScreen: ViewController {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state.privacy = .story(privacy: privacy, archive: true)
|
||||
self.state.privacy = .story(privacy: privacy, timeout: timeout, archive: archive)
|
||||
},
|
||||
editCategory: { [weak self] privacy in
|
||||
guard let self else {
|
||||
@ -2184,7 +2196,7 @@ public final class MediaEditorScreen: ViewController {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state.privacy = .story(privacy: privacy, archive: true)
|
||||
self.state.privacy = .story(privacy: privacy, timeout: timeout, archive: archive)
|
||||
self.presentPrivacySettings()
|
||||
})
|
||||
},
|
||||
@ -2265,54 +2277,104 @@ public final class MediaEditorScreen: ViewController {
|
||||
func presentTimeoutSetup(sourceView: UIView) {
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let updateTimeout: (Int32?) -> Void = { [weak self] timeout in
|
||||
let updateTimeout: (Int32?, Bool) -> Void = { [weak self] timeout, archive in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if case let .message(peers, _) = self.state.privacy {
|
||||
switch self.state.privacy {
|
||||
case let .story(privacy, _, _):
|
||||
self.state.privacy = .story(privacy: privacy, timeout: timeout ?? 86400, archive: archive)
|
||||
case let .message(peers, _):
|
||||
self.state.privacy = .message(peers: peers, timeout: timeout)
|
||||
}
|
||||
}
|
||||
|
||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
items.append(.action(ContextMenuActionItem(text: "Choose how long the media will be kept after opening.", textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||
let title: String
|
||||
switch self.state.privacy {
|
||||
case .story:
|
||||
title = "Choose how long the story will be kept."
|
||||
case .message:
|
||||
title = "Choose how long the media will be kept after opening."
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||
|
||||
switch self.state.privacy {
|
||||
case .story:
|
||||
items.append(.action(ContextMenuActionItem(text: "6 Hours", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(3600 * 6, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "12 Hours", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(3600 * 12, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "24 Hours", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(86400, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "2 Days", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(86400 * 2, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Forever", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(86400, true)
|
||||
})))
|
||||
case .message:
|
||||
items.append(.action(ContextMenuActionItem(text: "Until First View", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(1, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "3 Seconds", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(3, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "10 Seconds", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(10, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "1 Minute", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(60, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Keep Always", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(nil, false)
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Until First View", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(1)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "3 Seconds", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(3)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "10 Seconds", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(10)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "1 Minute", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(60)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Keep Always", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(nil)
|
||||
})))
|
||||
|
||||
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))
|
||||
|
||||
@ -662,6 +662,17 @@ private final class MediaToolsScreenComponent: Component {
|
||||
controller.mediaEditor.setToolValue(.highlightsTint, value: value)
|
||||
state?.updated()
|
||||
}
|
||||
},
|
||||
isTrackingUpdated: { [weak self] isTracking in
|
||||
if let self {
|
||||
let transition: Transition
|
||||
if isTracking {
|
||||
transition = .immediate
|
||||
} else {
|
||||
transition = .easeInOut(duration: 0.25)
|
||||
}
|
||||
transition.setAlpha(view: self.optionsBackgroundView, alpha: isTracking ? 0.0 : 1.0)
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
@ -867,6 +878,9 @@ public final class MediaToolsScreen: ViewController {
|
||||
}
|
||||
|
||||
func animateOutToEditor(completion: @escaping () -> Void) {
|
||||
if let mediaEditor = self.controller?.mediaEditor {
|
||||
mediaEditor.play()
|
||||
}
|
||||
if let view = self.componentHost.view as? MediaToolsScreenComponent.View {
|
||||
view.animateOutToEditor(completion: completion)
|
||||
}
|
||||
@ -921,6 +935,13 @@ public final class MediaToolsScreen: ViewController {
|
||||
sectionUpdated: { [weak self] section in
|
||||
if let self {
|
||||
self.currentSection = section
|
||||
if let mediaEditor = self.controller?.mediaEditor {
|
||||
if section == .curves {
|
||||
mediaEditor.stop()
|
||||
} else {
|
||||
mediaEditor.play()
|
||||
}
|
||||
}
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}
|
||||
|
||||
@ -110,17 +110,20 @@ final class TintComponent: Component {
|
||||
let highlightsValue: TintValue
|
||||
let shadowsValueUpdated: (TintValue) -> Void
|
||||
let highlightsValueUpdated: (TintValue) -> Void
|
||||
let isTrackingUpdated: (Bool) -> Void
|
||||
|
||||
init(
|
||||
shadowsValue: TintValue,
|
||||
highlightsValue: TintValue,
|
||||
shadowsValueUpdated: @escaping (TintValue) -> Void,
|
||||
highlightsValueUpdated: @escaping (TintValue) -> Void
|
||||
highlightsValueUpdated: @escaping (TintValue) -> Void,
|
||||
isTrackingUpdated: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.shadowsValue = shadowsValue
|
||||
self.highlightsValue = highlightsValue
|
||||
self.shadowsValueUpdated = shadowsValueUpdated
|
||||
self.highlightsValueUpdated = highlightsValueUpdated
|
||||
self.isTrackingUpdated = isTrackingUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: TintComponent, rhs: TintComponent) -> Bool {
|
||||
@ -300,6 +303,32 @@ final class TintComponent: Component {
|
||||
sizes.append(size)
|
||||
}
|
||||
|
||||
let isTrackingUpdated: (Bool) -> Void = { [weak self] isTracking in
|
||||
component.isTrackingUpdated(isTracking)
|
||||
|
||||
if let self {
|
||||
let transition: Transition
|
||||
if isTracking {
|
||||
transition = .immediate
|
||||
} else {
|
||||
transition = .easeInOut(duration: 0.25)
|
||||
}
|
||||
|
||||
let alpha: CGFloat = isTracking ? 0.0 : 1.0
|
||||
if let view = self.shadowsButton.view {
|
||||
transition.setAlpha(view: view, alpha: alpha)
|
||||
}
|
||||
if let view = self.highlightsButton.view {
|
||||
transition.setAlpha(view: view, alpha: alpha)
|
||||
}
|
||||
for color in self.colorViews {
|
||||
if let view = color.view {
|
||||
transition.setAlpha(view: view, alpha: alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sliderSize = self.slider.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
@ -321,6 +350,9 @@ final class TintComponent: Component {
|
||||
highlightsValueUpdated(state.highlightsValue.withUpdatedIntensity(value))
|
||||
}
|
||||
}
|
||||
},
|
||||
isTrackingUpdated: { isTracking in
|
||||
isTrackingUpdated(isTracking)
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
@ -44,7 +44,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
public let isRecordingLocked: Bool
|
||||
public let recordedAudioPreview: ChatRecordedMediaPreview?
|
||||
public let wasRecordingDismissed: Bool
|
||||
public let timeoutValue: Int32?
|
||||
public let timeoutValue: String?
|
||||
public let timeoutSelected: Bool
|
||||
public let displayGradient: Bool
|
||||
public let bottomInset: CGFloat
|
||||
@ -71,7 +71,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
isRecordingLocked: Bool,
|
||||
recordedAudioPreview: ChatRecordedMediaPreview?,
|
||||
wasRecordingDismissed: Bool,
|
||||
timeoutValue: Int32?,
|
||||
timeoutValue: String?,
|
||||
timeoutSelected: Bool,
|
||||
displayGradient: Bool,
|
||||
bottomInset: CGFloat
|
||||
@ -673,10 +673,9 @@ public final class MessageInputPanelComponent: Component {
|
||||
}
|
||||
|
||||
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
|
||||
func generateIcon(value: Int32) -> UIImage? {
|
||||
func generateIcon(value: String) -> UIImage? {
|
||||
let image = UIImage(bundleImageName: "Media Editor/Timeout")!
|
||||
let string = "\(value)"
|
||||
let valueString = NSAttributedString(string: "\(value)", font: Font.with(size: string.count == 1 ? 12.0 : 10.0, design: .round, weight: .semibold), textColor: .white, paragraphAlignment: .center)
|
||||
let valueString = NSAttributedString(string: value, font: Font.with(size: value.count == 1 ? 12.0 : 10.0, design: .round, weight: .semibold), textColor: .white, paragraphAlignment: .center)
|
||||
|
||||
return generateImage(image.size, contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
@ -76,6 +76,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
||||
case cachedImageRecognizedContent = 6
|
||||
case pendingInAppPurchaseState = 7
|
||||
case translationState = 10
|
||||
case storySource = 11
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificItemCacheCollectionId {
|
||||
@ -88,6 +89,7 @@ public struct ApplicationSpecificItemCacheCollectionId {
|
||||
public static let cachedImageRecognizedContent = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedImageRecognizedContent.rawValue)
|
||||
public static let pendingInAppPurchaseState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.pendingInAppPurchaseState.rawValue)
|
||||
public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue)
|
||||
public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue)
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user