Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-06-06 15:06:22 +04:00
parent b280850bbc
commit 3c274dbf0c
11 changed files with 372 additions and 112 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -82,6 +82,11 @@ public final class MediaEditor {
}
set {
self.histogramCalculationPass.isEnabled = newValue
if newValue {
Queue.mainQueue().justDispatch {
self.updateRenderChain()
}
}
}
}

View File

@ -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])
}

View File

@ -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()
}
}
}

View File

@ -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))

View File

@ -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)))
}

View File

@ -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)
}
)
),

View File

@ -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)

View File

@ -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 {