Stories improvements

This commit is contained in:
Ilya Laktyushin 2023-06-24 00:21:45 +04:00
parent 0c091f6c1b
commit 9368d93053
28 changed files with 1200 additions and 156 deletions

View File

@ -899,7 +899,7 @@ public protocol SharedAccountContext: AnyObject {
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode) -> ViewController func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController
func makeDebugSettingsController(context: AccountContext?) -> ViewController? func makeDebugSettingsController(context: AccountContext?) -> ViewController?

View File

@ -2566,10 +2566,42 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return return
} }
let _ = self.context.engine.peers.togglePeerStoriesMuted(peerId: peer.id).start() let _ = self.context.engine.peers.togglePeerStoriesMuted(peerId: peer.id).start()
let iconColor = UIColor.white
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if isMuted {
self.present(UndoOverlayController(
presentationData: presentationData,
content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will now get a notification whenever **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
} else {
self.present(UndoOverlayController(
presentationData: presentationData,
content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will no longer receive a notification when **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
}
}))) })))
items.append(.action(ContextMenuActionItem(text: "Archive", icon: { theme in items.append(.action(ContextMenuActionItem(text: "Hide", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToContacts"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
@ -2577,6 +2609,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return return
} }
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true) self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true)
}))) })))
} }

View File

@ -370,6 +370,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
let highlightedPackId = controller.highlightedPackId let highlightedPackId = controller.highlightedPackId
let forceTheme = controller.forceTheme
self.disposable = (combineLatest(queue: .mainQueue(), self.disposable = (combineLatest(queue: .mainQueue(),
mappedFeatured, mappedFeatured,
self.additionalPacks.get(), self.additionalPacks.get(),
@ -377,6 +378,11 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
context.sharedContext.presentationData context.sharedContext.presentationData
) )
|> map { featuredEntries, additionalPacks, view, presentationData -> FeaturedTransition in |> map { featuredEntries, additionalPacks, view, presentationData -> FeaturedTransition in
var presentationData = presentationData
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
var installedPacks = Set<ItemCollectionId>() var installedPacks = Set<ItemCollectionId>()
if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView {
if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] {
@ -801,6 +807,7 @@ public final class FeaturedStickersScreen: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
fileprivate let forceTheme: PresentationTheme?
private let _ready = Promise<Bool>() private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> { override public var ready: Promise<Bool> {
@ -809,12 +816,18 @@ public final class FeaturedStickersScreen: ViewController {
fileprivate var searchNavigationNode: SearchNavigationContentNode? fileprivate var searchNavigationNode: SearchNavigationContentNode?
public init(context: AccountContext, highlightedPackId: ItemCollectionId?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil) { public init(context: AccountContext, highlightedPackId: ItemCollectionId?, forceTheme: PresentationTheme? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil) {
self.context = context self.context = context
self.highlightedPackId = highlightedPackId self.highlightedPackId = highlightedPackId
self.sendSticker = sendSticker self.sendSticker = sendSticker
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } var presentationData = context.sharedContext.currentPresentationData.with { $0 }
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
self.presentationData = presentationData
self.forceTheme = forceTheme
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
@ -833,6 +846,11 @@ public final class FeaturedStickersScreen: ViewController {
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self { if let strongSelf = self {
let previous = strongSelf.presentationData let previous = strongSelf.presentationData
var presentationData = presentationData
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
strongSelf.presentationData = presentationData strongSelf.presentationData = presentationData
if previous.theme !== presentationData.theme || previous.strings !== presentationData.strings { if previous.theme !== presentationData.theme || previous.strings !== presentationData.strings {

View File

@ -242,7 +242,7 @@ private func archivedStickerPacksControllerEntries(context: AccountContext, pres
return entries return entries
} }
public func archivedStickerPacksController(context: AccountContext, mode: ArchivedStickerPacksControllerMode, archived: [ArchivedStickerPackItem]?, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void) -> ViewController { public func archivedStickerPacksController(context: AccountContext, mode: ArchivedStickerPacksControllerMode, archived: [ArchivedStickerPackItem]?, forceTheme: PresentationTheme? = nil, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void) -> ViewController {
let statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true) let statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ArchivedStickerPacksControllerState()) let stateValue = Atomic(value: ArchivedStickerPacksControllerState())
let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in
@ -279,6 +279,11 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
let installedStickerPacks = Promise<CombinedView>() let installedStickerPacks = Promise<CombinedView>()
installedStickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) installedStickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]))
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
var presentStickerPackController: ((StickerPackCollectionInfo) -> Void)? var presentStickerPackController: ((StickerPackCollectionInfo) -> Void)?
let arguments = ArchivedStickerPacksControllerArguments(context: context, openStickerPack: { info in let arguments = ArchivedStickerPacksControllerArguments(context: context, openStickerPack: { info in
@ -325,8 +330,6 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
return .complete() return .complete()
} }
|> deliverOnMainQueue).start(next: { info, items in |> deliverOnMainQueue).start(next: { info, items in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var animateInAsReplacement = false var animateInAsReplacement = false
if let navigationController = navigationControllerImpl?() { if let navigationController = navigationControllerImpl?() {
for controller in navigationController.overlayControllers { for controller in navigationController.overlayControllers {
@ -419,6 +422,11 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, installedStickerPacks.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, installedStickerPacks.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue)
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, packs, installedView, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, packs, installedView, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in
var presentationData = presentationData
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
var stickerSettings = StickerSettings.defaultSettings var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) { if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) {
stickerSettings = value stickerSettings = value

View File

@ -616,7 +616,7 @@ private func installedStickerPacksControllerEntries(context: AccountContext, pre
return entries return entries
} }
public func installedStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, archivedPacks: [ArchivedStickerPackItem]? = nil, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void = { _ in }, focusOnItemTag: InstalledStickerPacksEntryTag? = nil) -> ViewController { public func installedStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, archivedPacks: [ArchivedStickerPackItem]? = nil, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void = { _ in }, focusOnItemTag: InstalledStickerPacksEntryTag? = nil, forceTheme: PresentationTheme? = nil) -> ViewController {
let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(mode == .modal).withUpdatedSelectedPackIds(mode == .modal ? Set() : nil) let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(mode == .modal).withUpdatedSelectedPackIds(mode == .modal ? Set() : nil)
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
@ -624,6 +624,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
statePromise.set(stateValue.modify { f($0) }) statePromise.set(stateValue.modify { f($0) })
} }
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)?
var navigateToChatControllerImpl: ((PeerId) -> Void)? var navigateToChatControllerImpl: ((PeerId) -> Void)?
@ -650,7 +655,6 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
} }
} }
}, removePack: { archivedItem in }, removePack: { archivedItem in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData) let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
@ -734,15 +738,15 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
} }
})) }))
}, openMasks: { }, openMasks: {
pushControllerImpl?(installedStickerPacksController(context: context, mode: .masks, archivedPacks: archivedPacks, updatedPacks: { _ in})) pushControllerImpl?(installedStickerPacksController(context: context, mode: .masks, archivedPacks: archivedPacks, updatedPacks: { _ in }, forceTheme: forceTheme))
}, openEmoji: { }, openEmoji: {
pushControllerImpl?(installedStickerPacksController(context: context, mode: .emoji, archivedPacks: archivedPacks, updatedPacks: { _ in})) pushControllerImpl?(installedStickerPacksController(context: context, mode: .emoji, archivedPacks: archivedPacks, updatedPacks: { _ in }, forceTheme: forceTheme))
}, openQuickReaction: { }, openQuickReaction: {
pushControllerImpl?(quickReactionSetupController( pushControllerImpl?(quickReactionSetupController(
context: context context: context
)) ))
}, openFeatured: { }, openFeatured: {
pushControllerImpl?(FeaturedStickersScreen(context: context, highlightedPackId: nil)) pushControllerImpl?(FeaturedStickersScreen(context: context, highlightedPackId: nil, forceTheme: forceTheme))
}, openArchived: { archived in }, openArchived: { archived in
let archivedMode: ArchivedStickerPacksControllerMode let archivedMode: ArchivedStickerPacksControllerMode
switch mode { switch mode {
@ -753,12 +757,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
default: default:
archivedMode = .stickers archivedMode = .stickers
} }
pushControllerImpl?(archivedStickerPacksController(context: context, mode: archivedMode, archived: archived, updatedPacks: { packs in pushControllerImpl?(archivedStickerPacksController(context: context, mode: archivedMode, archived: archived, forceTheme: forceTheme, updatedPacks: { packs in
archivedPromise.set(.single(packs)) archivedPromise.set(.single(packs))
updatedPacks(packs) updatedPacks(packs)
})) }))
}, openSuggestOptions: { }, openSuggestOptions: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData) let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
@ -889,6 +892,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, temporaryPackOrder, featuredAndArchived, sharedData, quickReaction, availableReactions, emojiCount -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, view, temporaryPackOrder, featuredAndArchived, sharedData, quickReaction, availableReactions, emojiCount -> (ItemListControllerState, (ItemListNodeState, Any)) in
var presentationData = presentationData
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
var stickerSettings = StickerSettings.defaultSettings var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) { if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) {
stickerSettings = value stickerSettings = value

View File

@ -609,7 +609,11 @@ private func apiInputPrivacyRules(privacy: EngineStoryPrivacy, transaction: Tran
case .closeFriends: case .closeFriends:
privacyRules = [.inputPrivacyValueAllowCloseFriends] privacyRules = [.inputPrivacyValueAllowCloseFriends]
case .nobody: case .nobody:
privacyRules = [.inputPrivacyValueDisallowAll] if privacy.additionallyIncludePeers.isEmpty {
privacyRules = [.inputPrivacyValueDisallowAll]
} else {
privacyRules = []
}
} }
var privacyUsers: [Api.InputUser] = [] var privacyUsers: [Api.InputUser] = []
var privacyChats: [Int64] = [] var privacyChats: [Int64] = []
@ -833,7 +837,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
} }
} }
func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: Int32, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> { func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: Int32, text: String?, entities: [MessageTextEntity]?, privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> {
let contentSignal: Signal<PendingMessageUploadedContentResult?, NoError> let contentSignal: Signal<PendingMessageUploadedContentResult?, NoError>
let originalMedia: Media? let originalMedia: Media?
if let media = media { if let media = media {
@ -865,13 +869,11 @@ func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: In
if let _ = inputMedia { if let _ = inputMedia {
flags |= 1 << 0 flags |= 1 << 0
} }
if !text.isEmpty { if let text = text {
flags |= 1 << 1 flags |= 1 << 1
apiCaption = text apiCaption = text
if !entities.isEmpty { if let entities = entities {
flags |= 1 << 1
var associatedPeers: [PeerId: Peer] = [:] var associatedPeers: [PeerId: Peer] = [:]
for entity in entities { for entity in entities {
for entityPeerId in entity.associatedPeerIds { for entityPeerId in entity.associatedPeerIds {
@ -1184,6 +1186,10 @@ extension Stories.StoredItem {
for id in users { for id in users {
additionalPeerIds.append(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id))) additionalPeerIds.append(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id)))
} }
case let .privacyValueDisallowUsers(users):
for id in users {
additionalPeerIds.append(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id)))
}
case let .privacyValueAllowChatParticipants(chats): case let .privacyValueAllowChatParticipants(chats):
for id in chats { for id in chats {
if let peer = transaction.getPeer(EnginePeer.Id(namespace: Namespaces.Peer.CloudGroup, id: EnginePeer.Id.Id._internalFromInt64Value(id))) { if let peer = transaction.getPeer(EnginePeer.Id(namespace: Namespaces.Peer.CloudGroup, id: EnginePeer.Id.Id._internalFromInt64Value(id))) {

View File

@ -939,7 +939,7 @@ public extension TelegramEngine {
_internal_cancelStoryUpload(account: self.account, stableId: stableId) _internal_cancelStoryUpload(account: self.account, stableId: stableId)
} }
public func editStory(media: EngineStoryInputMedia?, id: Int32, text: String, entities: [MessageTextEntity], privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> { 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) return _internal_editStory(account: self.account, media: media, id: id, text: text, entities: entities, privacy: privacy)
} }

View File

@ -88,6 +88,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
let presentGlobalOverlayController: (ViewController, Any?) -> Void let presentGlobalOverlayController: (ViewController, Any?) -> Void
let getNavigationController: () -> NavigationController? let getNavigationController: () -> NavigationController?
let requestLayout: (ContainedViewLayoutTransition) -> Void let requestLayout: (ContainedViewLayoutTransition) -> Void
public var forceTheme: PresentationTheme?
public init( public init(
sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool, sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool,
@ -1333,6 +1334,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
interaction.getNavigationController()?.pushViewController(FeaturedStickersScreen( interaction.getNavigationController()?.pushViewController(FeaturedStickersScreen(
context: context, context: context,
highlightedPackId: featuredStickerPack.info.id, highlightedPackId: featuredStickerPack.info.id,
forceTheme: interaction.forceTheme,
sendSticker: { [weak interaction] fileReference, sourceNode, sourceRect in sendSticker: { [weak interaction] fileReference, sourceNode, sourceRect in
guard let interaction else { guard let interaction else {
return false return false
@ -1369,7 +1371,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
guard let interaction else { guard let interaction else {
return return
} }
let controller = context.sharedContext.makeInstalledStickerPacksController(context: context, mode: .modal) let controller = context.sharedContext.makeInstalledStickerPacksController(context: context, mode: .modal, forceTheme: interaction.forceTheme)
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
interaction.getNavigationController()?.pushViewController(controller) interaction.getNavigationController()?.pushViewController(controller)
}, },
@ -1381,6 +1383,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
interaction.getNavigationController()?.pushViewController(FeaturedStickersScreen( interaction.getNavigationController()?.pushViewController(FeaturedStickersScreen(
context: context, context: context,
highlightedPackId: nil, highlightedPackId: nil,
forceTheme: interaction.forceTheme,
sendSticker: { [weak interaction] fileReference, sourceNode, sourceRect in sendSticker: { [weak interaction] fileReference, sourceNode, sourceRect in
guard let interaction else { guard let interaction else {
return false return false

View File

@ -417,22 +417,22 @@ final class MediaEditorScreenComponent: Component {
} }
if let view = self.saveButton.view { if let view = self.saveButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) view.layer.animateAlpha(from: 0.0, to: view.alpha, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
} }
if let view = self.muteButton.view { if let view = self.muteButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) view.layer.animateAlpha(from: 0.0, to: view.alpha, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
} }
if let view = self.settingsButton.view { if let view = self.settingsButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) view.layer.animateAlpha(from: 0.0, to: view.alpha, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
} }
if let view = self.privacyButton.view { if let view = self.privacyButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) view.layer.animateAlpha(from: 0.0, to: view.alpha, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
} }
@ -603,6 +603,12 @@ final class MediaEditorScreenComponent: Component {
let environment = environment[ViewControllerComponentContainer.Environment.self].value let environment = environment[ViewControllerComponentContainer.Environment.self].value
self.environment = environment self.environment = environment
if self.component == nil {
if let controller = environment.controller() as? MediaEditorScreen {
self.inputPanelExternalState.initialText = controller.initialCaption
}
}
self.component = component self.component = component
self.state = state self.state = state
@ -1051,7 +1057,8 @@ final class MediaEditorScreenComponent: Component {
transition.setAlpha(view: inputPanelView, alpha: isEditingTextEntity || component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0) transition.setAlpha(view: inputPanelView, alpha: isEditingTextEntity || component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0)
} }
let privacyText: String let additionalPeersCount = component.privacy.privacy.additionallyIncludePeers.count
var privacyText: String
switch component.privacy.privacy.base { switch component.privacy.privacy.base {
case .everyone: case .everyone:
privacyText = "Everyone" privacyText = "Everyone"
@ -1059,8 +1066,16 @@ final class MediaEditorScreenComponent: Component {
privacyText = "Close Friends" privacyText = "Close Friends"
case .contacts: case .contacts:
privacyText = "Contacts" privacyText = "Contacts"
if additionalPeersCount > 0 {
privacyText += " (-\(additionalPeersCount))"
}
case .nobody: case .nobody:
privacyText = "Selected Contacts" privacyText = "Selected Contacts"
if additionalPeersCount > 0 {
privacyText += " (\(additionalPeersCount))"
} else {
privacyText = "Only You"
}
} }
let displayTopButtons = !(self.inputPanelExternalState.isEditing || isEditingTextEntity || component.isDisplayingTool) let displayTopButtons = !(self.inputPanelExternalState.isEditing || isEditingTextEntity || component.isDisplayingTool)
@ -1549,6 +1564,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
private var isDismissed = false private var isDismissed = false
private var isDismissBySwipeSuppressed = false private var isDismissBySwipeSuppressed = false
fileprivate var hasAnyChanges = false
private var presentationData: PresentationData private var presentationData: PresentationData
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
@ -1698,7 +1715,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let isSavingAvailable: Bool let isSavingAvailable: Bool
switch subject { switch subject {
case .image, .video: case .image, .video:
isSavingAvailable = true isSavingAvailable = !controller.isEditingStory
default: default:
isSavingAvailable = false isSavingAvailable = false
} }
@ -1776,6 +1793,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if !isSavingAvailable && controller.previousSavedValues == nil { if !isSavingAvailable && controller.previousSavedValues == nil {
controller.previousSavedValues = values controller.previousSavedValues = values
} else { } else {
self.hasAnyChanges = true
controller.isSavingAvailable = true controller.isSavingAvailable = true
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut)) controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
} }
@ -2258,11 +2277,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
completion() completion()
}) })
} else { } else {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false) if controller.isEditingStory {
self.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false, completion: { _ in
self.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: self.bounds.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in completion()
completion() })
}) } else {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false)
self.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: self.bounds.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
}
} }
} }
@ -2566,6 +2591,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let stickerEntity = DrawingStickerEntity(content: .file(file)) let stickerEntity = DrawingStickerEntity(content: .file(file))
self.interaction?.insertEntity(stickerEntity) self.interaction?.insertEntity(stickerEntity)
self.hasAnyChanges = true
self.controller?.isSavingAvailable = true self.controller?.isSavingAvailable = true
self.controller?.requestLayout(transition: .immediate) self.controller?.requestLayout(transition: .immediate)
} }
@ -2585,6 +2611,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, animation: .none, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white)) let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, animation: .none, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white))
self.interaction?.insertEntity(textEntity) self.interaction?.insertEntity(textEntity)
self.hasAnyChanges = true
self.controller?.isSavingAvailable = true self.controller?.isSavingAvailable = true
self.controller?.requestLayout(transition: .immediate) self.controller?.requestLayout(transition: .immediate)
return return
@ -2817,17 +2844,22 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case videoFile(path: String) case videoFile(path: String)
case asset(localIdentifier: String) case asset(localIdentifier: String)
} }
case image(image: UIImage, dimensions: PixelDimensions, caption: NSAttributedString?) case image(image: UIImage, dimensions: PixelDimensions)
case video(video: VideoResult, coverImage: UIImage?, values: MediaEditorValues, duration: Double, dimensions: PixelDimensions, caption: NSAttributedString?) case video(video: VideoResult, coverImage: UIImage?, values: MediaEditorValues, duration: Double, dimensions: PixelDimensions)
} }
fileprivate let context: AccountContext fileprivate let context: AccountContext
fileprivate let subject: Signal<Subject?, NoError> fileprivate let subject: Signal<Subject?, NoError>
fileprivate let isEditingStory: Bool
fileprivate let initialCaption: NSAttributedString?
fileprivate let initialPrivacy: EngineStoryPrivacy?
fileprivate let transitionIn: TransitionIn? fileprivate let transitionIn: TransitionIn?
fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut? fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut?
public var cancelled: (Bool) -> Void = { _ in } public var cancelled: (Bool) -> Void = { _ in }
public var completion: (Int64, MediaEditorScreen.Result, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _ in } public var completion: (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy , @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _, _ in }
public var dismissed: () -> Void = { } public var dismissed: () -> Void = { }
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
@ -2835,12 +2867,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
public init( public init(
context: AccountContext, context: AccountContext,
subject: Signal<Subject?, NoError>, subject: Signal<Subject?, NoError>,
isEditing: Bool,
initialCaption: NSAttributedString? = nil,
initialPrivacy: EngineStoryPrivacy? = nil,
transitionIn: TransitionIn?, transitionIn: TransitionIn?,
transitionOut: @escaping (Bool, Bool?) -> TransitionOut?, transitionOut: @escaping (Bool, Bool?) -> TransitionOut?,
completion: @escaping (Int64, MediaEditorScreen.Result, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void completion: @escaping (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
self.isEditingStory = isEditing
self.initialCaption = initialCaption
self.initialPrivacy = initialPrivacy
self.transitionIn = transitionIn self.transitionIn = transitionIn
self.transitionOut = transitionOut self.transitionOut = transitionOut
self.completion = completion self.completion = completion
@ -2851,6 +2889,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
if let initialPrivacy {
self.state.privacy = MediaEditorResultPrivacy(privacy: initialPrivacy, timeout: 86400, archive: false)
}
self.automaticallyControlPresentationContextLayout = false self.automaticallyControlPresentationContextLayout = false
self.navigationPresentation = .flatModal self.navigationPresentation = .flatModal
@ -2894,6 +2936,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
ShareWithPeersScreen( ShareWithPeersScreen(
context: self.context, context: self.context,
initialPrivacy: initialPrivacy, initialPrivacy: initialPrivacy,
timeout: timeout,
stateContext: stateContext, stateContext: stateContext,
completion: { [weak self] privacy in completion: { [weak self] privacy in
guard let self else { guard let self else {
@ -2928,6 +2971,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
ShareWithPeersScreen( ShareWithPeersScreen(
context: self.context, context: self.context,
initialPrivacy: privacy, initialPrivacy: privacy,
timeout: 0,
stateContext: stateContext, stateContext: stateContext,
completion: { [weak self] result in completion: { [weak self] result in
guard let self else { guard let self else {
@ -2935,8 +2979,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
if case .closeFriends = privacy.base { if case .closeFriends = privacy.base {
let _ = self.context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start() let _ = self.context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start()
completion(EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []))
} else {
completion(result)
} }
completion(result)
}, },
editCategory: { _ in } editCategory: { _ in }
) )
@ -3051,6 +3097,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
func isEligibleForDraft() -> Bool { func isEligibleForDraft() -> Bool {
if self.isEditingStory {
return false
}
guard let mediaEditor = self.node.mediaEditor else { guard let mediaEditor = self.node.mediaEditor else {
return false return false
} }
@ -3235,13 +3284,26 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
let caption = self.getCaption() let caption = self.getCaption()
let randomId: Int64 let randomId: Int64
if case let .draft(_, id) = subject, let id { if case let .draft(_, id) = subject, let id {
randomId = id randomId = id
} else { } else {
randomId = Int64.random(in: .min ... .max) randomId = Int64.random(in: .min ... .max)
} }
if self.isEditingStory && !self.node.hasAnyChanges {
self.completion(randomId, nil, caption, self.state.privacy, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss()
Queue.mainQueue().justDispatch {
finished()
}
})
})
return
}
if mediaEditor.resultIsVideo { if mediaEditor.resultIsVideo {
var firstFrame: Signal<UIImage?, NoError> var firstFrame: Signal<UIImage?, NoError>
let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60)) let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
@ -3357,7 +3419,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if let self { if let self {
makeEditorImageComposition(account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in makeEditorImageComposition(account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
if let self { if let self {
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions, caption: caption), self.state.privacy, { [weak self] finished in self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss() self?.dismiss()
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
@ -3379,7 +3441,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
makeEditorImageComposition(account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in makeEditorImageComposition(account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in
if let self, let resultImage { if let self, let resultImage {
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size), caption: caption), self.state.privacy, { [weak self] finished in self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss() self?.dismiss()
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {

View File

@ -268,7 +268,7 @@ private final class MediaToolsScreenComponent: Component {
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true) view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay) view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: delay) view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: delay)
delay += 0.05 delay += 0.03
} }
} }

View File

@ -116,6 +116,7 @@ final class VideoScrubberComponent: Component {
private let leftHandleView = HandleView() private let leftHandleView = HandleView()
private let rightHandleView = HandleView() private let rightHandleView = HandleView()
private let borderView = UIImageView() private let borderView = UIImageView()
private let zoneView = HandleView()
private let cursorView = HandleView() private let cursorView = HandleView()
private let transparentFramesContainer = UIView() private let transparentFramesContainer = UIView()
@ -202,11 +203,13 @@ final class VideoScrubberComponent: Component {
self.addSubview(self.transparentFramesContainer) self.addSubview(self.transparentFramesContainer)
self.addSubview(self.opaqueFramesContainer) self.addSubview(self.opaqueFramesContainer)
self.addSubview(self.zoneView)
self.addSubview(self.leftHandleView) self.addSubview(self.leftHandleView)
self.addSubview(self.rightHandleView) self.addSubview(self.rightHandleView)
self.addSubview(self.borderView) self.addSubview(self.borderView)
self.addSubview(self.cursorView) self.addSubview(self.cursorView)
self.zoneView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleZoneHandlePan(_:))))
self.leftHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleLeftHandlePan(_:)))) self.leftHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleLeftHandlePan(_:))))
self.rightHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleRightHandlePan(_:)))) self.rightHandleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleRightHandlePan(_:))))
self.cursorView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePositionHandlePan(_:)))) self.cursorView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePositionHandlePan(_:))))
@ -225,6 +228,42 @@ final class VideoScrubberComponent: Component {
self.displayLink?.invalidate() self.displayLink?.invalidate()
} }
@objc private func handleZoneHandlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let component = self.component else {
return
}
let translation = gestureRecognizer.translation(in: self)
let start = handleWidth / 2.0
let end = self.frame.width - handleWidth / 2.0
let length = end - start
let delta = translation.x / length
let duration = component.endPosition - component.startPosition
let startValue = max(0.0, min(component.duration - duration, component.startPosition + delta))
let endValue = startValue + duration
var transition: Transition = .immediate
switch gestureRecognizer.state {
case .began, .changed:
self.isPanningTrimHandle = true
component.trimUpdated(startValue, endValue, false, false)
if case .began = gestureRecognizer.state {
transition = .easeInOut(duration: 0.25)
}
case .ended, .cancelled:
self.isPanningTrimHandle = false
component.trimUpdated(startValue, endValue, false, true)
transition = .easeInOut(duration: 0.25)
default:
break
}
gestureRecognizer.setTranslation(.zero, in: self)
self.state?.updated(transition: transition)
}
@objc private func handleLeftHandlePan(_ gestureRecognizer: UIPanGestureRecognizer) { @objc private func handleLeftHandlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let component = self.component else { guard let component = self.component else {
return return
@ -413,6 +452,9 @@ final class VideoScrubberComponent: Component {
let rightHandleFrame = CGRect(origin: CGPoint(x: max(leftHandleFrame.maxX, rightHandlePosition - handleWidth / 2.0), y: 0.0), size: CGSize(width: handleWidth, height: scrubberSize.height)) let rightHandleFrame = CGRect(origin: CGPoint(x: max(leftHandleFrame.maxX, rightHandlePosition - handleWidth / 2.0), y: 0.0), size: CGSize(width: handleWidth, height: scrubberSize.height))
transition.setFrame(view: self.rightHandleView, frame: rightHandleFrame) transition.setFrame(view: self.rightHandleView, frame: rightHandleFrame)
let zoneFrame = CGRect(x: leftHandleFrame.maxX, y: 0.0, width: rightHandleFrame.minX - leftHandleFrame.maxX, height: scrubberSize.height)
transition.setFrame(view: self.zoneView, frame: zoneFrame)
if self.isPanningPositionHandle || !component.isPlaying { if self.isPanningPositionHandle || !component.isPlaying {
self.positionAnimation = nil self.positionAnimation = nil

View File

@ -118,7 +118,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery) let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery)
|> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in |> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let peers = peersAndPresences.filter { peer in let peers = peersAndPresences.filter { peer in
if let peer = peer.peer, case .user = peer { if let peer = peer.peer, case .user = peer, peer.addressName != nil {
return true return true
} else { } else {
return false return false

View File

@ -30,6 +30,8 @@ public final class MessageInputPanelComponent: Component {
public fileprivate(set) var hasText: Bool = false public fileprivate(set) var hasText: Bool = false
public fileprivate(set) var isKeyboardHidden: Bool = false public fileprivate(set) var isKeyboardHidden: Bool = false
public var initialText: NSAttributedString?
public fileprivate(set) var insertText: (NSAttributedString) -> Void = { _ in } public fileprivate(set) var insertText: (NSAttributedString) -> Void = { _ in }
public fileprivate(set) var deleteBackward: () -> Void = { } public fileprivate(set) var deleteBackward: () -> Void = { }
@ -366,6 +368,11 @@ public final class MessageInputPanelComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
if let initialText = component.externalState.initialText {
component.externalState.initialText = nil
self.textFieldExternalState.initialText = initialText
}
let hasMediaRecording = component.audioRecorder != nil || component.videoRecordingStatus != nil let hasMediaRecording = component.audioRecorder != nil || component.videoRecordingStatus != nil
let hasMediaEditing = component.recordedAudioPreview != nil let hasMediaEditing = component.recordedAudioPreview != nil

View File

@ -25,6 +25,7 @@ final class ShareWithPeersScreenComponent: Component {
let context: AccountContext let context: AccountContext
let stateContext: ShareWithPeersScreen.StateContext let stateContext: ShareWithPeersScreen.StateContext
let initialPrivacy: EngineStoryPrivacy let initialPrivacy: EngineStoryPrivacy
let timeout: Int
let categoryItems: [CategoryItem] let categoryItems: [CategoryItem]
let completion: (EngineStoryPrivacy) -> Void let completion: (EngineStoryPrivacy) -> Void
let editCategory: (EngineStoryPrivacy) -> Void let editCategory: (EngineStoryPrivacy) -> Void
@ -34,6 +35,7 @@ final class ShareWithPeersScreenComponent: Component {
context: AccountContext, context: AccountContext,
stateContext: ShareWithPeersScreen.StateContext, stateContext: ShareWithPeersScreen.StateContext,
initialPrivacy: EngineStoryPrivacy, initialPrivacy: EngineStoryPrivacy,
timeout: Int,
categoryItems: [CategoryItem], categoryItems: [CategoryItem],
completion: @escaping (EngineStoryPrivacy) -> Void, completion: @escaping (EngineStoryPrivacy) -> Void,
editCategory: @escaping (EngineStoryPrivacy) -> Void, editCategory: @escaping (EngineStoryPrivacy) -> Void,
@ -42,6 +44,7 @@ final class ShareWithPeersScreenComponent: Component {
self.context = context self.context = context
self.stateContext = stateContext self.stateContext = stateContext
self.initialPrivacy = initialPrivacy self.initialPrivacy = initialPrivacy
self.timeout = timeout
self.categoryItems = categoryItems self.categoryItems = categoryItems
self.completion = completion self.completion = completion
self.editCategory = editCategory self.editCategory = editCategory
@ -58,6 +61,9 @@ final class ShareWithPeersScreenComponent: Component {
if lhs.initialPrivacy != rhs.initialPrivacy { if lhs.initialPrivacy != rhs.initialPrivacy {
return false return false
} }
if lhs.timeout != rhs.timeout {
return false
}
if lhs.categoryItems != rhs.categoryItems { if lhs.categoryItems != rhs.categoryItems {
return false return false
} }
@ -237,6 +243,7 @@ final class ShareWithPeersScreenComponent: Component {
private var selectedPeers: [EnginePeer.Id] = [] private var selectedPeers: [EnginePeer.Id] = []
private var selectedCategories = Set<CategoryId>() private var selectedCategories = Set<CategoryId>()
private var selectedPeersByCategory: [CategoryId: [EnginePeer.Id]] = [:]
private var component: ShareWithPeersScreenComponent? private var component: ShareWithPeersScreenComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@ -447,7 +454,8 @@ final class ShareWithPeersScreenComponent: Component {
let sectionTitle: String let sectionTitle: String
if section.id == 0 { if section.id == 0 {
sectionTitle = "WHO CAN VIEW FOR 24 HOURS" let hours = component.timeout / 3600
sectionTitle = "WHO CAN VIEW FOR \(hours) HOURS"
} else { } else {
if case .chats = component.stateContext.subject { if case .chats = component.stateContext.subject {
sectionTitle = "CHATS" sectionTitle = "CHATS"
@ -522,8 +530,14 @@ final class ShareWithPeersScreenComponent: Component {
if self.selectedCategories.contains(categoryId) { if self.selectedCategories.contains(categoryId) {
} else { } else {
self.selectedPeers = [] self.selectedPeers = []
self.selectedCategories.removeAll() self.selectedCategories.removeAll()
self.selectedCategories.insert(categoryId) self.selectedCategories.insert(categoryId)
if self.selectedPeers.isEmpty && categoryId == .selectedContacts {
component.editCategory(EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []))
controller.dismiss()
}
} }
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring))) self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
}, },
@ -1233,7 +1247,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
self.initialPeerIds = Set(selectedPeers.map { $0.id }) self.initialPeerIds = Set(selectedPeers.map { $0.id })
} else { } else {
for peer in contactList.peers { for peer in contactList.peers {
if case let .user(user) = peer, initialPeerIds.contains(user.id) { if case let .user(user) = peer, initialPeerIds.contains(user.id), !user.isDeleted {
selectedPeers.append(peer) selectedPeers.append(peer)
} }
} }
@ -1249,7 +1263,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
}) })
var peers: [EnginePeer] = [] var peers: [EnginePeer] = []
peers = contactList.peers.filter { !self.initialPeerIds.contains($0.id) && $0.id != context.account.peerId }.sorted(by: { lhs, rhs in peers = contactList.peers.filter { !self.initialPeerIds.contains($0.id) && $0.id != context.account.peerId && !$0.isDeleted }.sorted(by: { lhs, rhs in
let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: .firstLast) let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: .firstLast)
if result == .orderedSame { if result == .orderedSame {
return lhs.id < rhs.id return lhs.id < rhs.id
@ -1297,7 +1311,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
private var isDismissed: Bool = false private var isDismissed: Bool = false
public init(context: AccountContext, initialPrivacy: EngineStoryPrivacy, stateContext: StateContext, completion: @escaping (EngineStoryPrivacy) -> Void, editCategory: @escaping (EngineStoryPrivacy) -> Void, secondaryAction: @escaping () -> Void = {}) { public init(context: AccountContext, initialPrivacy: EngineStoryPrivacy, timeout: Int, stateContext: StateContext, completion: @escaping (EngineStoryPrivacy) -> Void, editCategory: @escaping (EngineStoryPrivacy) -> Void, secondaryAction: @escaping () -> Void = {}) {
self.context = context self.context = context
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = [] var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
@ -1328,7 +1342,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
title: "Selected Contacts", title: "Selected Contacts",
icon: "Chat List/Filters/Group", icon: "Chat List/Filters/Group",
iconColor: .violet, iconColor: .violet,
actionTitle: "edit list" actionTitle: "choose"
)) ))
} }
@ -1336,6 +1350,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
context: context, context: context,
stateContext: stateContext, stateContext: stateContext,
initialPrivacy: initialPrivacy, initialPrivacy: initialPrivacy,
timeout: timeout,
categoryItems: categoryItems, categoryItems: categoryItems,
completion: completion, completion: completion,
editCategory: editCategory, editCategory: editCategory,

View File

@ -65,6 +65,8 @@ swift_library(
"//submodules/OverlayStatusController", "//submodules/OverlayStatusController",
"//submodules/Utils/VolumeButtons", "//submodules/Utils/VolumeButtons",
"//submodules/TelegramUI/Components/PeerReportScreen", "//submodules/TelegramUI/Components/PeerReportScreen",
"//submodules/LocalMediaResources",
"//submodules/SaveToCameraRoll",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -25,6 +25,9 @@ import PresentationDataUtils
import PeerReportScreen import PeerReportScreen
import ChatEntityKeyboardInputNode import ChatEntityKeyboardInputNode
import TextFieldComponent import TextFieldComponent
import TextFormat
import LocalMediaResources
import SaveToCameraRoll
public final class StoryItemSetContainerComponent: Component { public final class StoryItemSetContainerComponent: Component {
public final class ExternalState { public final class ExternalState {
@ -578,7 +581,8 @@ public final class StoryItemSetContainerComponent: Component {
return true return true
} }
if let navigationController = component.controller()?.navigationController as? NavigationController { if let navigationController = component.controller()?.navigationController as? NavigationController {
if !(navigationController.topViewController is StoryContainerScreen) { let topViewController = navigationController.topViewController
if !(topViewController is StoryContainerScreen) && !(topViewController is MediaEditorScreen) {
return true return true
} }
} }
@ -1222,7 +1226,9 @@ public final class StoryItemSetContainerComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: component.slice.additionalPeerData.isMuted ? "Notify" : "Not Notify", icon: { theme in
let isMuted = component.slice.additionalPeerData.isMuted
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Not Notify", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -1232,6 +1238,38 @@ public final class StoryItemSetContainerComponent: Component {
} }
let _ = component.context.engine.peers.togglePeerStoriesMuted(peerId: component.slice.peer.id).start() let _ = component.context.engine.peers.togglePeerStoriesMuted(peerId: component.slice.peer.id).start()
let iconColor = UIColor.white
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
if isMuted {
self.component?.presentController(UndoOverlayController(
presentationData: presentationData,
content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will now get a notification whenever **\(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), nil)
} else {
self.component?.presentController(UndoOverlayController(
presentationData: presentationData,
content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will no longer receive a notification when **\(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), nil)
}
}))) })))
var isHidden = false var isHidden = false
@ -1240,7 +1278,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
items.append(.action(ContextMenuActionItem(text: isHidden ? "Unhide \(component.slice.peer.compactDisplayTitle)" : "Hide \(component.slice.peer.compactDisplayTitle)", icon: { theme in items.append(.action(ContextMenuActionItem(text: isHidden ? "Unhide \(component.slice.peer.compactDisplayTitle)" : "Hide \(component.slice.peer.compactDisplayTitle)", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/MoveToChats" : "Chat/Context Menu/MoveToContacts"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -1359,7 +1397,7 @@ public final class StoryItemSetContainerComponent: Component {
self.sendMessageContext.updateInputMediaNode(inputPanel: self.inputPanel, availableSize: availableSize, bottomInset: component.safeInsets.bottom, inputHeight: component.inputHeight, effectiveInputHeight: inputHeight, metrics: component.metrics, deviceMetrics: component.deviceMetrics, transition: transition) self.sendMessageContext.updateInputMediaNode(inputPanel: self.inputPanel, availableSize: availableSize, bottomInset: component.safeInsets.bottom, inputHeight: component.inputHeight, effectiveInputHeight: inputHeight, metrics: component.metrics, deviceMetrics: component.deviceMetrics, transition: transition)
let bottomContentInsetWithoutInput = bottomContentInset //let bottomContentInsetWithoutInput = bottomContentInset
var viewListInset: CGFloat = 0.0 var viewListInset: CGFloat = 0.0
var inputPanelBottomInset: CGFloat var inputPanelBottomInset: CGFloat
@ -1505,14 +1543,10 @@ public final class StoryItemSetContainerComponent: Component {
let privacyText: String let privacyText: String
switch component.slice.item.storyItem.privacy?.base { switch component.slice.item.storyItem.privacy?.base {
case .closeFriends: case .closeFriends:
if additionalCount != 0 { privacyText = "Close Friends"
privacyText = "Close Friends (+\(additionalCount)"
} else {
privacyText = "Close Friends"
}
case .contacts: case .contacts:
if additionalCount != 0 { if additionalCount != 0 {
privacyText = "Contacts (+\(additionalCount)" privacyText = "Contacts (-\(additionalCount))"
} else { } else {
privacyText = "Contacts" privacyText = "Contacts"
} }
@ -1565,8 +1599,8 @@ public final class StoryItemSetContainerComponent: Component {
let _ = component.context.engine.messages.updateStoriesArePinned(ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).start() let _ = component.context.engine.messages.updateStoriesArePinned(ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).start()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
if component.slice.item.storyItem.isPinned { if component.slice.item.storyItem.isPinned {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
self.component?.presentController(UndoOverlayController( self.component?.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .info(title: nil, text: "Story removed from your profile", timeout: nil), content: .info(title: nil, text: "Story removed from your profile", timeout: nil),
@ -1575,7 +1609,6 @@ public final class StoryItemSetContainerComponent: Component {
action: { _ in return false } action: { _ in return false }
), nil) ), nil)
} else { } else {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
self.component?.presentController(UndoOverlayController( self.component?.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .info(title: "Story saved to your profile", text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil), content: .info(title: "Story saved to your profile", text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil),
@ -1833,7 +1866,8 @@ public final class StoryItemSetContainerComponent: Component {
transition.setFrame(layer: self.topContentGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: gradientHeight))) transition.setFrame(layer: self.topContentGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: gradientHeight)))
transition.setAlpha(layer: self.topContentGradientLayer, alpha: (component.hideUI || self.displayViewList) ? 0.0 : 1.0) transition.setAlpha(layer: self.topContentGradientLayer, alpha: (component.hideUI || self.displayViewList) ? 0.0 : 1.0)
let itemLayout = ItemLayout(size: CGSize(width: contentFrame.width, height: availableSize.height - component.containerInsets.top - 44.0 - bottomContentInsetWithoutInput)) let itemSize = CGSize(width: contentFrame.width, height: floorToScreenPixels(contentFrame.width * 1.77778))
let itemLayout = ItemLayout(size: itemSize) //ItemLayout(size: CGSize(width: contentFrame.width, height: availableSize.height - component.containerInsets.top - 44.0 - bottomContentInsetWithoutInput))
self.itemLayout = itemLayout self.itemLayout = itemLayout
let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize) let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize)
@ -2236,12 +2270,17 @@ public final class StoryItemSetContainerComponent: Component {
return contentSize return contentSize
} }
private func openItemPrivacySettings() { private func openItemPrivacySettings(initialPrivacy: EngineStoryPrivacy? = nil) {
guard let context = self.component?.context, let privacy = self.component?.slice.item.storyItem.privacy else { guard let context = self.component?.context else {
return return
} }
let stateContext = ShareWithPeersScreen.StateContext(context: context, subject: .stories) let privacy = initialPrivacy ?? self.component?.slice.item.storyItem.privacy
guard let privacy else {
return
}
let stateContext = ShareWithPeersScreen.StateContext(context: context, subject: .stories, initialPeerIds: Set(privacy.additionallyIncludePeers))
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else { guard let self else {
return return
@ -2249,6 +2288,7 @@ public final class StoryItemSetContainerComponent: Component {
let controller = ShareWithPeersScreen( let controller = ShareWithPeersScreen(
context: context, context: context,
initialPrivacy: privacy, initialPrivacy: privacy,
timeout: 86400,
stateContext: stateContext, stateContext: stateContext,
completion: { [weak self] privacy in completion: { [weak self] privacy in
guard let self, let component = self.component else { guard let self, let component = self.component else {
@ -2259,10 +2299,15 @@ public final class StoryItemSetContainerComponent: Component {
self.updateIsProgressPaused() self.updateIsProgressPaused()
}, },
editCategory: { [weak self] privacy in editCategory: { [weak self] privacy in
guard let self, let component = self.component else { guard let self else {
return return
} }
let _ = component.context.engine.messages.editStoryPrivacy(id: component.slice.item.storyItem.id, privacy: privacy).start() self.openItemPrivacyCategory(privacy: privacy, completion: { [weak self] privacy in
guard let self else {
return
}
self.openItemPrivacySettings(initialPrivacy: privacy)
})
} }
) )
self.component?.controller()?.push(controller) self.component?.controller()?.push(controller)
@ -2272,6 +2317,38 @@ public final class StoryItemSetContainerComponent: Component {
}) })
} }
private func openItemPrivacyCategory(privacy: EngineStoryPrivacy, completion: @escaping (EngineStoryPrivacy) -> Void) {
guard let context = self.component?.context else {
return
}
let stateContext = ShareWithPeersScreen.StateContext(context: context, subject: .contacts(privacy.base), initialPeerIds: Set(privacy.additionallyIncludePeers))
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {
return
}
let controller = ShareWithPeersScreen(
context: context,
initialPrivacy: privacy,
timeout: 86400,
stateContext: stateContext,
completion: { result in
if case .closeFriends = privacy.base {
let _ = context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start()
completion(EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []))
} else {
completion(result)
}
},
editCategory: { _ in }
)
self.component?.controller()?.push(controller)
self.privacyController = controller
self.updateIsProgressPaused()
})
}
private func navigateToPeer(peer: EnginePeer, messageId: EngineMessage.Id? = nil) { private func navigateToPeer(peer: EnginePeer, messageId: EngineMessage.Id? = nil) {
guard let component = self.component else { guard let component = self.component else {
return return
@ -2300,48 +2377,227 @@ public final class StoryItemSetContainerComponent: Component {
} }
private func openStoryEditing() { private func openStoryEditing() {
guard let context = self.component?.context, let id = self.component?.slice.item.storyItem.id else { guard let context = self.component?.context, let item = self.component?.slice.item.storyItem else {
return return
} }
let _ = (getStorySource(engine: context.engine, id: Int64(id)) let id = item.id
|> deliverOnMainQueue).start(next: { [weak self] source in
guard let self else { self.isEditingStory = true
return self.updateIsProgressPaused()
let subject: Signal<MediaEditorScreen.Subject?, NoError>
// if let source {
// subject = .single(.draft(source, Int64(id)))
// } else {
let media = item.media._asMedia()
subject = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: .standalone(media: media))
|> mapToSignal { (value, isImage) -> Signal<MediaEditorScreen.Subject?, NoError> in
guard case let .data(data) = value, data.complete else {
return .complete()
} }
self.isEditingStory = true if let image = UIImage(contentsOfFile: data.path) {
self.updateIsProgressPaused() return .single(nil)
|> then(
if let source { .single(.image(image, PixelDimensions(image.size), nil, .bottomRight))
var updateProgressImpl: ((Float) -> Void)? |> delay(0.2, queue: Queue.mainQueue())
let controller = MediaEditorScreen( )
context: context, } else {
subject: .single(.draft(source, Int64(id))), return .single(.video(data.path, nil, nil, nil, PixelDimensions(width: 720, height: 1280), .bottomRight))
transitionIn: nil, }
transitionOut: { _, _ in return nil }, }
completion: { [weak self] _, mediaResult, privacy, commit in
switch mediaResult { var updateProgressImpl: ((Float) -> Void)?
case let .image(image, dimensions, caption): let controller = MediaEditorScreen(
if let imageData = compressImageToJPEG(image, quality: 0.6) { context: context,
let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData), id: id, text: caption?.string ?? "", entities: [], privacy: privacy.privacy) subject: subject,
|> deliverOnMainQueue).start(next: { [weak self] result in isEditing: true,
switch result { initialCaption: chatInputStateStringWithAppliedEntities(item.text, entities: item.entities),
case let .progress(progress): initialPrivacy: item.privacy,
updateProgressImpl?(progress) transitionIn: nil,
case .completed: transitionOut: { _, _ in return nil },
Queue.mainQueue().after(0.1) { completion: { [weak self] _, mediaResult, caption, privacy, commit in
if let self { guard let self else {
self.isEditingStory = false return
self.rewindCurrentItem() }
self.updateIsProgressPaused() let entities = generateChatInputTextEntities(caption)
} var updatedText: String?
commit({}) var updatedEntities: [MessageTextEntity]?
} var updatedPrivacy: EngineStoryPrivacy?
} if caption.string != item.text || entities != item.entities {
}) updatedText = caption.string
updatedEntities = entities
}
if privacy.privacy != item.privacy {
updatedPrivacy = privacy.privacy
}
if let mediaResult {
switch mediaResult {
case let .image(image, dimensions):
if let imageData = compressImageToJPEG(image, quality: 0.7) {
let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
switch result {
case let .progress(progress):
updateProgressImpl?(progress)
case .completed:
Queue.mainQueue().after(0.1) {
self.isEditingStory = false
self.rewindCurrentItem()
self.updateIsProgressPaused()
commit({})
}
}
})
}
case let .video(content, firstFrameImage, values, duration, dimensions):
if let valuesData = try? JSONEncoder().encode(values) {
let data = MemoryBuffer(data: valuesData)
let digest = MemoryBuffer(data: data.md5Digest())
let adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
let resource: TelegramMediaResource
switch content {
case let .imageFile(path):
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
case let .videoFile(path):
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
case let .asset(localIdentifier):
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
}
let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let _ = (context.engine.messages.editStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
switch result {
case let .progress(progress):
updateProgressImpl?(progress)
case .completed:
Queue.mainQueue().after(0.1) {
self.isEditingStory = false
self.rewindCurrentItem()
self.updateIsProgressPaused()
commit({})
}
}
})
}
}
} else if updatedText != nil || updatedPrivacy != nil {
let _ = (context.engine.messages.editStory(media: nil, id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in
switch result {
case .completed:
Queue.mainQueue().after(0.1) {
if let self {
self.isEditingStory = false
self.rewindCurrentItem()
self.updateIsProgressPaused()
}
commit({})
} }
default: default:
break break
// case let .video(content, _, values, duration, dimensions, caption): }
})
} else {
self.isEditingStory = false
self.rewindCurrentItem()
self.updateIsProgressPaused()
commit({})
}
}
)
controller.dismissed = { [weak self] in
self?.isEditingStory = false
self?.updateIsProgressPaused()
}
self.component?.controller()?.push(controller)
updateProgressImpl = { [weak controller] progress in
controller?.updateEditProgress(progress)
}
// }
// let _ = (getStorySource(engine: context.engine, id: Int64(id))
// |> deliverOnMainQueue).start(next: { [weak self] source in
// guard let self else {
// return
// }
//
// self.isEditingStory = true
// self.updateIsProgressPaused()
//
// let subject: Signal<MediaEditorScreen.Subject?, NoError>
// if let source {
// subject = .single(.draft(source, Int64(id)))
// } else {
// let media = item.media._asMedia()
// subject = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: .standalone(media: media))
// |> mapToSignal { (value, isImage) -> Signal<MediaEditorScreen.Subject?, NoError> in
// guard case let .data(data) = value, data.complete else {
// return .complete()
// }
// if let image = UIImage(contentsOfFile: data.path) {
// return .single(.image(image, PixelDimensions(image.size), nil, .bottomRight))
// } else {
// return .single(.video(data.path, nil, nil, nil, PixelDimensions(width: 720, height: 1280), .bottomRight))
// }
// }
// }
//
// var updateProgressImpl: ((Float) -> Void)?
// let controller = MediaEditorScreen(
// context: context,
// subject: subject,
// isEditing: true,
// transitionIn: nil,
// transitionOut: { _, _ in return nil },
// completion: { [weak self] randomId, mediaResult, caption, privacy, commit in
// let entities = generateChatInputTextEntities(caption)
// var updatedText: String?
// var updatedEntities: [MessageTextEntity]?
// var updatedPrivacy: EngineStoryPrivacy?
// if caption.string != item.text || entities != item.entities {
// updatedText = caption.string
// updatedEntities = entities
// }
// if privacy.privacy != item.privacy {
// updatedPrivacy = privacy.privacy
// }
//
// if let mediaResult {
// switch mediaResult {
// case let .image(image, dimensions, caption):
// if let imageData = compressImageToJPEG(image, quality: 0.7) {
// let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
// |> deliverOnMainQueue).start(next: { [weak self] result in
// switch result {
// case let .progress(progress):
// updateProgressImpl?(progress)
// case .completed:
// Queue.mainQueue().after(0.1) {
// if let self {
// self.isEditingStory = false
// self.rewindCurrentItem()
// self.updateIsProgressPaused()
// }
// commit({})
// }
// }
// })
// }
// case let .video(content, firstFrameImage, values, duration, dimensions, caption):
// let adjustments: VideoMediaResourceAdjustments // let adjustments: VideoMediaResourceAdjustments
// if let valuesData = try? JSONEncoder().encode(values) { // if let valuesData = try? JSONEncoder().encode(values) {
// let data = MemoryBuffer(data: valuesData) // let data = MemoryBuffer(data: valuesData)
@ -2357,41 +2613,62 @@ public final class StoryItemSetContainerComponent: Component {
// case let .asset(localIdentifier): // case let .asset(localIdentifier):
// resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments)) // resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
// } // }
// if case let .story(storyPrivacy, period, pin) = privacy { // let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
// let _ = (context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource), text: caption?.string ?? "", entities: [], pin: pin, privacy: storyPrivacy, period: period, randomId: randomId) //
// |> deliverOnMainQueue).start(next: { [weak chatListController] result in // let _ = (context.engine.messages.editStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
// if let chatListController { // |> deliverOnMainQueue).start(next: { [weak self] result in
// switch result { // switch result {
// case let .progress(progress): // case let .progress(progress):
// let _ = progress // updateProgressImpl?(progress)
// break // case .completed:
// case .completed: // Queue.mainQueue().after(0.1) {
// Queue.mainQueue().after(0.1) { // if let self {
// commit() // self.isEditingStory = false
// } // self.rewindCurrentItem()
// self.updateIsProgressPaused()
// } // }
// commit({})
// } // }
// })
// Queue.mainQueue().justDispatch {
// commit({ [weak chatListController] in
// chatListController?.animateStoryUploadRipple()
// })
// } // }
// } // })
// } // }
} // }
} // } else if updatedText != nil || updatedPrivacy != nil {
) // let _ = (context.engine.messages.editStory(media: nil, id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
controller.dismissed = { [weak self] in // |> deliverOnMainQueue).start(next: { [weak self] result in
self?.isEditingStory = false // switch result {
self?.updateIsProgressPaused() // case .completed:
} // Queue.mainQueue().after(0.1) {
self.component?.controller()?.push(controller) // if let self {
updateProgressImpl = { [weak controller] progress in // self.isEditingStory = false
controller?.updateEditProgress(progress) // self.rewindCurrentItem()
} // self.updateIsProgressPaused()
} // }
}) // commit({})
// }
// default:
// break
// }
// })
// } else {
// if let self {
// self.isEditingStory = false
// self.rewindCurrentItem()
// self.updateIsProgressPaused()
// }
// commit({})
// }
// }
// )
// controller.dismissed = { [weak self] in
// self?.isEditingStory = false
// self?.updateIsProgressPaused()
// }
// self.component?.controller()?.push(controller)
// updateProgressImpl = { [weak controller] progress in
// controller?.updateEditProgress(progress)
// }
// })
} }
} }

View File

@ -169,6 +169,7 @@ final class StoryItemSetContainerSendMessage {
} }
} }
) )
self.inputMediaInteraction?.forceTheme = defaultDarkColorPresentationTheme
} }
func toggleInputMode() { func toggleInputMode() {

View File

@ -603,8 +603,7 @@ final class StoryItemSetViewListComponent: Component {
moreAction: { [weak self] sourceView, gesture in moreAction: { [weak self] sourceView, gesture in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }
component.moreAction(sourceView, gesture) component.moreAction(sourceView, gesture)
} }
)), )),

View File

@ -20,6 +20,7 @@ public final class TextFieldComponent: Component {
public final class ExternalState { public final class ExternalState {
public fileprivate(set) var isEditing: Bool = false public fileprivate(set) var isEditing: Bool = false
public fileprivate(set) var hasText: Bool = false public fileprivate(set) var hasText: Bool = false
public var initialText: NSAttributedString?
public init() { public init() {
} }
@ -92,6 +93,17 @@ public final class TextFieldComponent: Component {
public struct InputState { public struct InputState {
public var inputText: NSAttributedString public var inputText: NSAttributedString
public var selectionRange: Range<Int> public var selectionRange: Range<Int>
public init(inputText: NSAttributedString, selectionRange: Range<Int>) {
self.inputText = inputText
self.selectionRange = selectionRange
}
public init(inputText: NSAttributedString) {
self.inputText = inputText
let length = inputText.length
self.selectionRange = length ..< length
}
} }
public final class View: UIView, UITextViewDelegate, UIScrollViewDelegate { public final class View: UIView, UITextViewDelegate, UIScrollViewDelegate {
@ -104,8 +116,6 @@ public final class TextFieldComponent: Component {
private var customEmojiContainerView: CustomEmojiContainerView? private var customEmojiContainerView: CustomEmojiContainerView?
private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
//private var inputState = InputState(inputText: NSAttributedString(), selectionRange: 0 ..< 0)
private var inputState: InputState { private var inputState: InputState {
let selectionRange: Range<Int> = self.textView.selectedRange.location ..< (self.textView.selectedRange.location + self.textView.selectedRange.length) let selectionRange: Range<Int> = self.textView.selectedRange.location ..< (self.textView.selectedRange.location + self.textView.selectedRange.length)
return InputState(inputText: stateAttributedStringForText(self.textView.attributedText ?? NSAttributedString()), selectionRange: selectionRange) return InputState(inputText: stateAttributedStringForText(self.textView.attributedText ?? NSAttributedString()), selectionRange: selectionRange)
@ -445,6 +455,13 @@ public final class TextFieldComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
if let initialText = component.externalState.initialText {
component.externalState.initialText = nil
self.updateInputState { _ in
return TextFieldComponent.InputState(inputText: initialText)
}
}
if self.emojiViewProvider == nil { if self.emojiViewProvider == nil {
self.emojiViewProvider = { [weak self] emoji in self.emojiViewProvider = { [weak self] emoji in
guard let component = self?.component else { guard let component = self?.component else {

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "movetochats_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,191 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.335022 2.295349 cm
1.000000 1.000000 1.000000 scn
6.142120 15.779264 m
7.480694 17.157986 9.452373 18.039629 11.664999 18.039629 c
15.777071 18.039629 19.000000 15.023025 19.000000 11.431902 c
19.000000 7.840779 15.777071 4.824175 11.664999 4.824175 c
11.037781 4.824175 10.429922 4.895034 9.850378 5.027961 c
9.469110 5.115411 9.178260 4.904372 9.078142 4.829156 c
8.976803 4.753022 8.861000 4.645836 8.759948 4.552301 c
8.759891 4.552247 l
8.759882 4.552238 l
8.699350 4.496364 l
8.446663 4.264504 8.099366 3.961192 7.552770 3.654654 c
7.352402 3.542285 7.115053 3.463325 6.872348 3.411900 c
6.929290 3.488448 6.986062 3.569141 7.041155 3.653596 c
7.389184 4.187105 7.489088 4.765461 7.466393 5.236623 c
7.448723 5.603467 7.137013 5.886529 6.770169 5.868859 c
6.403325 5.851189 6.120263 5.539478 6.137933 5.172635 c
6.149623 4.929938 6.096569 4.639868 5.927217 4.380261 c
5.748320 4.106021 5.559394 3.900901 5.381646 3.707916 c
5.381536 3.707796 l
5.337605 3.660100 5.294358 3.613144 5.252113 3.566068 c
5.201833 3.510037 5.145585 3.445241 5.095825 3.378344 c
5.050557 3.317486 4.981599 3.217117 4.936990 3.090534 c
4.889143 2.954765 4.849132 2.725258 4.970338 2.484573 c
5.083405 2.260048 5.273345 2.155308 5.385351 2.108326 c
5.486843 2.065754 5.591929 2.046288 5.659274 2.035854 c
5.739243 2.023464 5.827882 2.014999 5.920462 2.010006 c
6.106138 1.999989 6.331503 2.002670 6.575747 2.024937 c
7.054850 2.068615 7.665498 2.193001 8.203329 2.494621 c
8.876777 2.872299 9.310046 3.251671 9.598545 3.516390 c
9.688824 3.599475 l
9.724767 3.632614 9.754330 3.659869 9.779459 3.682659 c
10.387117 3.559157 11.018166 3.494175 11.664999 3.494175 c
16.389484 3.494175 20.329998 6.989792 20.329998 11.431902 c
20.329998 15.874012 16.389484 19.369629 11.664999 19.369629 c
9.098855 19.369629 6.781081 18.346710 5.187877 16.705719 c
4.932044 16.442213 4.938264 16.021204 5.201771 15.765370 c
5.465278 15.509537 5.886287 15.515757 6.142120 15.779264 c
h
5.843703 3.353676 m
5.847516 3.352829 l
5.844881 3.353352 5.843657 3.353655 5.843703 3.353676 c
h
6.159736 2.580116 m
6.157165 2.576271 l
6.157100 2.576252 6.157888 2.577515 6.159736 2.580116 c
h
9.892341 3.775630 m
9.895166 3.777505 9.896662 3.778707 9.896752 3.778999 c
9.896927 3.779562 9.891918 3.776763 9.881212 3.768926 c
9.886010 3.771608 9.889739 3.773905 9.892341 3.775630 c
h
20.929787 12.381819 m
20.751503 12.702913 20.867275 13.107740 21.188370 13.286022 c
21.509464 13.464306 21.914291 13.348534 22.092573 13.027439 c
22.691111 11.949450 23.030014 10.727039 23.030014 9.431902 c
23.030014 6.938105 21.869383 4.889858 19.940552 3.448150 c
19.940319 3.447509 l
19.919962 3.390541 19.898252 3.296368 19.892189 3.174892 c
19.880058 2.931812 19.932896 2.640709 20.102795 2.380260 c
20.281693 2.106022 20.470617 1.900900 20.648365 1.707916 c
20.692333 1.660179 20.735619 1.613182 20.777899 1.566067 c
20.828178 1.510036 20.884426 1.445240 20.934187 1.378344 c
20.979456 1.317486 21.048412 1.217117 21.093021 1.090534 c
21.140869 0.954765 21.180880 0.725258 21.059673 0.484573 c
20.946608 0.260048 20.756666 0.155308 20.644661 0.108326 c
20.543169 0.065756 20.438084 0.046288 20.370739 0.035854 c
20.290770 0.023464 20.202129 0.014999 20.109550 0.010004 c
19.923874 -0.000013 19.698509 0.002670 19.454264 0.024937 c
18.975163 0.068615 18.364513 0.193001 17.826683 0.494623 c
17.153234 0.872299 16.719965 1.251671 16.431467 1.516390 c
16.341227 1.599442 l
16.340408 1.600197 l
16.339453 1.601078 l
16.304291 1.633493 16.275278 1.660238 16.250553 1.682659 c
15.642897 1.559156 15.011847 1.494175 14.365013 1.494175 c
13.738460 1.494175 13.126695 1.555145 12.536637 1.671227 c
12.176275 1.742119 11.941614 2.091721 12.012507 2.452084 c
12.083401 2.812447 12.433002 3.047108 12.793365 2.976213 c
13.299057 2.876730 13.824845 2.824175 14.365013 2.824175 c
14.992231 2.824175 15.600090 2.895035 16.179634 3.027962 c
16.560902 3.115410 16.851753 2.904373 16.951870 2.829155 c
17.053213 2.753019 17.169022 2.645828 17.270073 2.552288 c
17.270130 2.552237 l
17.330662 2.496365 l
17.583349 2.264503 17.930645 1.961193 18.477242 1.654654 c
18.677610 1.542286 18.914959 1.463326 19.157663 1.411900 c
19.100721 1.488449 19.043949 1.569141 18.988857 1.653595 c
18.639727 2.188793 18.540283 2.769123 18.563843 3.241184 c
18.575624 3.477282 18.618753 3.701606 18.687880 3.895054 c
18.746796 4.059927 18.860445 4.305161 19.081284 4.466843 c
20.750088 5.688613 21.700012 7.375656 21.700012 9.431902 c
21.700012 10.491108 21.423624 11.492397 20.929787 12.381819 c
h
19.953547 3.480234 m
19.956055 3.485239 l
19.957342 3.487652 19.958071 3.488707 19.958145 3.488573 c
19.958214 3.488447 19.957697 3.487259 19.956514 3.485152 c
19.953547 3.480234 l
h
16.133259 1.778999 m
16.133430 1.778439 16.138786 1.774525 16.148804 1.768925 c
16.138100 1.776760 16.133089 1.779560 16.133259 1.778999 c
h
20.186310 1.353676 m
20.182497 1.352829 l
20.185133 1.353352 20.186356 1.353657 20.186310 1.353676 c
h
19.870279 0.580111 m
19.872847 0.576271 l
19.872919 0.576242 19.872131 0.577499 19.870279 0.580111 c
h
5.559548 10.869629 m
3.694774 12.734404 l
3.435075 12.994102 3.435075 13.415156 3.694774 13.674854 c
3.954473 13.934553 4.375527 13.934553 4.635226 13.674854 c
7.635226 10.674855 l
7.894925 10.415156 7.894925 9.994102 7.635226 9.734403 c
4.635226 6.734404 l
4.375527 6.474705 3.954473 6.474705 3.694774 6.734404 c
3.435075 6.994102 3.435075 7.415156 3.694774 7.674854 c
5.559548 9.539629 l
0.665000 9.539629 l
0.297730 9.539629 0.000000 9.837359 0.000000 10.204629 c
0.000000 10.571898 0.297730 10.869629 0.665000 10.869629 c
5.559548 10.869629 l
h
f*
n
Q
endstream
endobj
3 0 obj
5655
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000005745 00000 n
0000005768 00000 n
0000005941 00000 n
0000006015 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
6074
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "movetocontacts_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,115 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.335022 3.335083 cm
1.000000 1.000000 1.000000 scn
11.664982 15.999956 m
9.732011 15.999956 7.974946 15.253136 6.664206 14.031046 c
6.395582 13.780590 5.974784 13.795319 5.724329 14.063942 c
5.473873 14.332565 5.488601 14.753363 5.757225 15.003819 c
7.304427 16.446377 9.382257 17.329956 11.664982 17.329956 c
16.450529 17.329956 20.329983 13.450504 20.329983 8.664956 c
20.329983 3.879409 16.450529 -0.000046 11.664982 -0.000046 c
9.467892 -0.000046 7.460371 0.818539 5.933279 2.166380 c
5.881387 2.212180 5.830048 2.258594 5.779273 2.305608 c
5.521967 2.543855 5.495585 2.938190 5.710920 3.207923 c
6.184868 3.968952 7.955641 6.329956 11.664997 6.329956 c
13.847623 6.329956 15.371711 5.506486 16.347773 4.669862 c
16.690248 4.376310 16.964067 4.082387 17.175648 3.823866 c
18.311298 5.115568 18.999981 6.809836 18.999981 8.664956 c
18.999981 12.715965 15.715990 15.999956 11.664982 15.999956 c
h
16.207489 2.905417 m
16.032719 3.127470 15.793164 3.393527 15.482220 3.660050 c
14.708282 4.323426 13.482370 4.999956 11.664997 4.999956 c
9.138727 4.999956 7.731341 3.681140 7.121977 2.905769 c
8.371500 1.918601 9.948810 1.329956 11.664982 1.329956 c
13.380391 1.329956 14.958264 1.918815 16.207489 2.905417 c
h
11.665061 12.499956 m
10.651619 12.499956 9.830061 11.678398 9.830061 10.664956 c
9.830061 9.651514 10.651619 8.829956 11.665061 8.829956 c
12.678503 8.829956 13.500061 9.651514 13.500061 10.664956 c
13.500061 11.678398 12.678503 12.499956 11.665061 12.499956 c
h
8.500061 10.664956 m
8.500061 12.412937 9.917080 13.829956 11.665061 13.829956 c
13.413042 13.829956 14.830061 12.412937 14.830061 10.664956 c
14.830061 8.916975 13.413042 7.499956 11.665061 7.499956 c
9.917080 7.499956 8.500061 8.916975 8.500061 10.664956 c
h
5.559548 7.999956 m
3.694774 6.135181 l
3.435075 5.875484 3.435075 5.454429 3.694774 5.194731 c
3.954473 4.935032 4.375527 4.935032 4.635226 5.194731 c
7.635226 8.194730 l
7.894925 8.454429 7.894925 8.875484 7.635226 9.135182 c
4.635226 12.135181 l
4.375527 12.394880 3.954473 12.394880 3.694774 12.135181 c
3.435075 11.875484 3.435075 11.454429 3.694774 11.194731 c
5.559548 9.329956 l
0.665000 9.329956 l
0.297730 9.329956 0.000000 9.032226 0.000000 8.664956 c
0.000000 8.297687 0.297730 7.999956 0.665000 7.999956 c
5.559548 7.999956 l
h
f*
n
Q
endstream
endobj
3 0 obj
2353
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002443 00000 n
0000002466 00000 n
0000002639 00000 n
0000002713 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2772
%%EOF

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "Stories.svg", "filename" : "stories_30.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -1,5 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="7" fill="#FF2D55"/>
<path d="M15 23C10.5817 23 7 19.4183 7 15C7 10.5817 10.5817 7 15 7" stroke="white" stroke-width="2" stroke-linecap="round"/>
<circle cx="15" cy="15" r="8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-dasharray="1.32 4.32"/>
</svg>

Before

Width:  |  Height:  |  Size: 397 B

View File

@ -0,0 +1,221 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.996078 0.172549 0.333333 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 6.000000 6.000000 cm
1.000000 1.000000 1.000000 scn
8.819326 15.997714 m
8.879366 15.999235 8.939594 16.000000 9.000000 16.000000 c
9.217377 16.000000 9.432181 15.990122 9.644025 15.970834 c
10.194035 15.920755 10.680502 16.326031 10.730579 16.876040 c
10.780658 17.426048 10.375383 17.912516 9.825374 17.962595 c
9.553321 17.987366 9.278004 18.000000 9.000000 18.000000 c
4.029437 18.000000 0.000000 13.970563 0.000000 9.000000 c
0.000000 6.631216 0.915133 4.476173 2.411141 2.869128 c
2.583210 2.684289 2.762962 2.506699 2.949891 2.336866 c
4.481421 0.945414 6.494651 0.074747 8.709856 0.004589 c
8.806196 0.001537 8.902919 0.000000 9.000000 0.000000 c
9.278004 0.000000 9.553321 0.012634 9.825375 0.037405 c
10.375384 0.087484 10.780659 0.573952 10.730580 1.123960 c
10.680502 1.673969 10.194035 2.079245 9.644025 2.029166 c
9.432181 2.009878 9.217377 2.000000 9.000000 2.000000 c
8.935744 2.000000 8.871692 2.000866 8.807852 2.002586 c
7.071224 2.049389 5.492391 2.728685 4.293358 3.818473 c
4.126952 3.969717 3.967862 4.128867 3.816680 4.295331 c
2.725542 5.496764 2.046374 7.079132 2.002286 8.819326 c
2.000765 8.879366 2.000000 8.939594 2.000000 9.000000 c
2.000000 9.065163 2.000890 9.130116 2.002660 9.194851 c
2.051733 10.990423 2.776973 12.617154 3.932920 13.829583 c
4.027086 13.928350 4.124111 14.024369 4.223862 14.117506 c
5.433672 15.247099 7.044405 15.952745 8.819326 15.997714 c
h
14.753499 15.921051 m
14.328968 16.274309 13.698444 16.216534 13.345185 15.792002 c
12.991926 15.367472 13.049703 14.736948 13.474234 14.383689 c
13.804405 14.108947 14.108946 13.804406 14.383688 13.474234 c
14.736948 13.049704 15.367471 12.991926 15.792002 13.345186 c
16.216534 13.698445 16.274309 14.328969 15.921049 14.753500 c
15.568322 15.177391 15.177390 15.568323 14.753499 15.921051 c
h
17.962595 9.825375 m
17.912516 10.375383 17.426048 10.780659 16.876040 10.730579 c
16.326031 10.680502 15.920755 10.194035 15.970834 9.644025 c
15.990122 9.432182 16.000000 9.217377 16.000000 9.000000 c
16.000000 8.782623 15.990122 8.567819 15.970834 8.355975 c
15.920755 7.805965 16.326031 7.319498 16.876040 7.269421 c
17.426048 7.219342 17.912516 7.624617 17.962595 8.174626 c
17.987366 8.446679 18.000000 8.721996 18.000000 9.000000 c
18.000000 9.278004 17.987366 9.553321 17.962595 9.825375 c
h
15.921050 3.246501 m
16.274311 3.671032 16.216534 4.301556 15.792003 4.654815 c
15.367472 5.008074 14.736948 4.950297 14.383689 4.525766 c
14.108947 4.195595 13.804406 3.891054 13.474235 3.616312 c
13.049704 3.263052 12.991927 2.632529 13.345186 2.207998 c
13.698445 1.783466 14.328969 1.725691 14.753500 2.078951 c
15.177391 2.431678 15.568323 2.822610 15.921050 3.246501 c
h
f*
n
Q
endstream
endobj
2 0 obj
3662
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
endstream
endobj
4 0 obj
944
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000003920 00000 n
0000003943 00000 n
0000005135 00000 n
0000005157 00000 n
0000005455 00000 n
0000005557 00000 n
0000005578 00000 n
0000005751 00000 n
0000005825 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
5885
%%EOF

View File

@ -1846,8 +1846,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData)
} }
public func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode) -> ViewController { public func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController {
return installedStickerPacksController(context: context, mode: mode) return installedStickerPacksController(context: context, mode: mode, forceTheme: forceTheme)
} }
} }

View File

@ -330,6 +330,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
let controller = MediaEditorScreen( let controller = MediaEditorScreen(
context: context, context: context,
subject: subject, subject: subject,
isEditing: false,
transitionIn: transitionIn, transitionIn: transitionIn,
transitionOut: { finished, isNew in transitionOut: { finished, isNew in
if finished, let transitionOut = transitionOut(finished), let destinationView = transitionOut.destinationView { if finished, let transitionOut = transitionOut(finished), let destinationView = transitionOut.destinationView {
@ -347,8 +348,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} else { } else {
return nil return nil
} }
}, completion: { [weak self] randomId, mediaResult, privacy, commit in }, completion: { [weak self] randomId, mediaResult, caption, privacy, commit in
guard let self else { guard let self, let mediaResult else {
dismissCameraImpl?() dismissCameraImpl?()
commit({}) commit({})
return return
@ -357,16 +358,15 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
if let chatListController = self.chatListController as? ChatListControllerImpl { if let chatListController = self.chatListController as? ChatListControllerImpl {
chatListController.scrollToStories() chatListController.scrollToStories()
switch mediaResult { switch mediaResult {
case let .image(image, dimensions, caption): case let .image(image, dimensions):
if let imageData = compressImageToJPEG(image, quality: 0.7) { if let imageData = compressImageToJPEG(image, quality: 0.7) {
let text = caption ?? NSAttributedString() let entities = generateChatInputTextEntities(caption)
let entities = generateChatInputTextEntities(text) self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: text.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
commit({}) commit({})
} }
} }
case let .video(content, firstFrameImage, values, duration, dimensions, caption): case let .video(content, firstFrameImage, values, duration, dimensions):
let adjustments: VideoMediaResourceAdjustments let adjustments: VideoMediaResourceAdjustments
if let valuesData = try? JSONEncoder().encode(values) { if let valuesData = try? JSONEncoder().encode(values) {
let data = MemoryBuffer(data: valuesData) let data = MemoryBuffer(data: valuesData)
@ -383,9 +383,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments)) resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
} }
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let text = caption ?? NSAttributedString() let entities = generateChatInputTextEntities(caption)
let entities = generateChatInputTextEntities(text) self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: text.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
commit({}) commit({})
} }