Merge commit 'eb5f6e8bc03b545d4be5de02912ecbf28c5d6107'

This commit is contained in:
Ali 2023-07-17 23:00:30 +04:00
commit 88ecedba25
6 changed files with 254 additions and 136 deletions

View File

@ -3365,7 +3365,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
timeout: privacy.timeout,
mentions: mentions,
stateContext: stateContext,
completion: { [weak self] privacy, allowScreenshots, pin in
completion: { [weak self] privacy, allowScreenshots, pin, _ in
guard let self else {
return
}
@ -3408,12 +3408,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
allowScreenshots: !isForwardingDisabled,
pin: pin,
stateContext: stateContext,
completion: { [weak self] result, isForwardingDisabled, pin in
completion: { [weak self] result, isForwardingDisabled, pin, peers in
guard let self else {
return
}
if case .closeFriends = privacy.base {
let _ = self.context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start()
self.closeFriends.set(.single(peers))
completion(EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []))
} else {
completion(result)

View File

@ -35,7 +35,7 @@ final class ShareWithPeersScreenComponent: Component {
let mentions: [String]
let categoryItems: [CategoryItem]
let optionItems: [OptionItem]
let completion: (EngineStoryPrivacy, Bool, Bool) -> Void
let completion: (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void
let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void
init(
@ -48,7 +48,7 @@ final class ShareWithPeersScreenComponent: Component {
mentions: [String],
categoryItems: [CategoryItem],
optionItems: [OptionItem],
completion: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
) {
self.context = context
@ -1552,7 +1552,8 @@ final class ShareWithPeersScreenComponent: Component {
additionallyIncludePeers: self.selectedPeers
),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
self.selectedOptions.contains(.pin),
self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? []
)
controller.dismissAllTooltips()
@ -1918,9 +1919,13 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
case let .search(query, onlyContacts):
let signal: Signal<[EngineRenderedPeer], NoError>
if onlyContacts {
signal = context.engine.contacts.searchContacts(query: query)
|> map { result in
return result.0.map { EngineRenderedPeer(peer: $0) }
signal = combineLatest(
context.engine.contacts.searchLocalPeers(query: query),
context.engine.contacts.searchContacts(query: query)
)
|> map { peers, contacts in
let contactIds = Set(contacts.0.map { $0.id })
return peers.filter { contactIds.contains($0.peerId) }
}
} else {
signal = context.engine.contacts.searchLocalPeers(query: query)
@ -1975,7 +1980,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
timeout: Int = 0,
mentions: [String] = [],
stateContext: StateContext,
completion: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
) {
self.context = context

View File

@ -1425,7 +1425,7 @@ private final class StoryContainerScreenComponent: Component {
self.state?.updated(transition: .immediate)
},
keyboardInputData: self.inputMediaNodeDataPromise.get(),
closeFriends: self.closeFriendsPromise.get(),
closeFriends: self.closeFriendsPromise,
sharedViewListsContext: self.sharedViewListsContext
)),
environment: {},

View File

@ -111,7 +111,7 @@ public final class StoryItemSetContainerComponent: Component {
public let controller: () -> ViewController?
public let toggleAmbientMode: () -> Void
public let keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>
public let closeFriends: Signal<[EnginePeer], NoError>
public let closeFriends: Promise<[EnginePeer]>
let sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext
init(
@ -144,7 +144,7 @@ public final class StoryItemSetContainerComponent: Component {
controller: @escaping () -> ViewController?,
toggleAmbientMode: @escaping () -> Void,
keyboardInputData: Signal<ChatEntityKeyboardInputNode.InputData, NoError>,
closeFriends: Signal<[EnginePeer], NoError>,
closeFriends: Promise<[EnginePeer]>,
sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext
) {
self.context = context
@ -3202,7 +3202,7 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
subject: .stories(editing: true),
initialPeerIds: Set(privacy.additionallyIncludePeers),
closeFriends: component.closeFriends
closeFriends: component.closeFriends.get()
)
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {
@ -3212,7 +3212,7 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
initialPrivacy: privacy,
stateContext: stateContext,
completion: { [weak self] privacy, _, _ in
completion: { [weak self] privacy, _, _, _ in
guard let self, let component = self.component else {
return
}
@ -3262,9 +3262,12 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
initialPrivacy: privacy,
stateContext: stateContext,
completion: { result, _, _ in
completion: { [weak self] result, _, _, peers in
if case .closeFriends = privacy.base {
let _ = context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start()
if let component = self?.component {
component.closeFriends.set(.single(peers))
}
completion(EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []))
} else {
completion(result)
@ -3632,6 +3635,132 @@ public final class StoryItemSetContainerComponent: Component {
}
}
private func getLinkedStickerPacks() -> (ContextController.Tip?, Signal<ContextController.Tip?, NoError>?) {
guard let component = self.component else {
return (nil, nil)
}
var tip: ContextController.Tip?
var tipSignal: Signal<ContextController.Tip?, NoError>?
var hasLinkedStickers = false
let media = component.slice.item.storyItem.media._asMedia()
if let image = media as? TelegramMediaImage {
hasLinkedStickers = image.flags.contains(.hasStickers)
} else if let file = media as? TelegramMediaFile {
hasLinkedStickers = file.hasLinkedStickers
}
var emojiFileIds: [Int64] = []
for entity in component.slice.item.storyItem.entities {
if case let .CustomEmoji(_, fileId) = entity.type {
emojiFileIds.append(fileId)
}
}
if !emojiFileIds.isEmpty || hasLinkedStickers, let peerReference = PeerReference(component.slice.peer._asPeer()) {
let context = component.context
tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil)
let packsPromise = Promise<[StickerPackReference]>()
if hasLinkedStickers {
packsPromise.set(context.engine.stickers.stickerPacksAttachedToMedia(media: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: media)))
} else {
packsPromise.set(.single([]))
}
let customEmojiPacksPromise = Promise<[StickerPackReference]>()
if !emojiFileIds.isEmpty {
customEmojiPacksPromise.set( context.engine.stickers.resolveInlineStickers(fileIds: emojiFileIds)
|> map { files -> [StickerPackReference] in
var packReferences: [StickerPackReference] = []
var existingIds = Set<Int64>()
for (_, file) in files {
loop: for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference {
if case let .id(id, _) = packReference, !existingIds.contains(id) {
packReferences.append(packReference)
existingIds.insert(id)
}
break loop
}
}
}
return packReferences
})
} else {
customEmojiPacksPromise.set(.single([]))
}
let action: () -> Void = { [weak self] in
if let self {
let combinedPacks = combineLatest(packsPromise.get(), customEmojiPacksPromise.get())
|> map { embeddedPackReferences, customEmojiPackReferences in
var packReferences: [StickerPackReference] = []
for packReference in embeddedPackReferences {
packReferences.append(packReference)
}
for packReference in customEmojiPackReferences {
if !packReferences.contains(packReference) {
packReferences.append(packReference)
}
}
return packReferences
}
self.sendMessageContext.openAttachedStickers(view: self, packs: combinedPacks |> take(1))
}
}
tipSignal = combineLatest(packsPromise.get(), customEmojiPacksPromise.get())
|> mapToSignal { embeddedPackReferences, customEmojiPackReferences -> Signal<ContextController.Tip?, NoError> in
var packReferences: [StickerPackReference] = []
for packReference in embeddedPackReferences {
packReferences.append(packReference)
}
for packReference in customEmojiPackReferences {
if !packReferences.contains(packReference) {
packReferences.append(packReference)
}
}
if packReferences.count > 1 {
let valueText = component.strings.Story_Context_EmbeddedStickersValue(Int32(packReferences.count))
return .single(.animatedEmoji(text: component.strings.Story_Context_EmbeddedStickers(valueText).string, arguments: nil, file: nil, action: action))
} else if let reference = packReferences.first {
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
if case let .result(info, items, _) = result {
let isEmoji = info.flags.contains(.isEmoji)
let tip: ContextController.Tip = .animatedEmoji(
text: isEmoji ? component.strings.Story_Context_EmbeddedEmojiPack(info.title).string : component.strings.Story_Context_EmbeddedStickerPack(info.title).string,
arguments: TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: .clear,
attemptSynchronous: true
),
file: items.first?.file,
action: action)
return .single(tip)
} else {
return .complete()
}
}
} else {
return .complete()
}
}
}
return (tip, tipSignal)
}
private func performMyMoreAction(sourceView: UIView, gesture: ContextGesture?) {
guard let component = self.component, let controller = component.controller() else {
return
@ -3770,66 +3899,7 @@ public final class StoryItemSetContainerComponent: Component {
})))
}
var hasLinkedStickers = false
let media = component.slice.item.storyItem.media._asMedia()
if let image = media as? TelegramMediaImage {
hasLinkedStickers = image.flags.contains(.hasStickers)
} else if let file = media as? TelegramMediaFile {
hasLinkedStickers = file.hasLinkedStickers
}
var tip: ContextController.Tip?
var tipSignal: Signal<ContextController.Tip?, NoError>?
if hasLinkedStickers, let peerReference = PeerReference(component.slice.peer._asPeer()) {
let context = component.context
tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil)
let packsPromise = Promise<[StickerPackReference]>()
packsPromise.set(context.engine.stickers.stickerPacksAttachedToMedia(media: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: media)))
let action: () -> Void = { [weak self] in
if let self {
self.sendMessageContext.openAttachedStickers(view: self, packs: packsPromise.get() |> take(1))
}
}
tipSignal = packsPromise.get()
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
if packReferences.count > 1 {
let valueText = component.strings.Story_Context_EmbeddedStickersValue(Int32(packReferences.count))
return .single(.animatedEmoji(text: component.strings.Story_Context_EmbeddedStickers(valueText).string, arguments: nil, file: nil, action: action))
} else if let reference = packReferences.first {
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
if case let .result(info, items, _) = result {
let isEmoji = info.flags.contains(.isEmoji)
let tip: ContextController.Tip = .animatedEmoji(
text: isEmoji ? component.strings.Story_Context_EmbeddedEmojiPack(info.title).string : component.strings.Story_Context_EmbeddedStickerPack(info.title).string,
arguments: TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: .clear,
attemptSynchronous: true
),
file: items.first?.file,
action: action)
return .single(tip)
} else {
return .complete()
}
}
} else {
return .complete()
}
}
}
let (tip, tipSignal) = self.getLinkedStickerPacks()
let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal)
@ -4039,66 +4109,7 @@ public final class StoryItemSetContainerComponent: Component {
})))
}
var hasLinkedStickers = false
let media = component.slice.item.storyItem.media._asMedia()
if let image = media as? TelegramMediaImage {
hasLinkedStickers = image.flags.contains(.hasStickers)
} else if let file = media as? TelegramMediaFile {
hasLinkedStickers = file.hasLinkedStickers
}
var tip: ContextController.Tip?
var tipSignal: Signal<ContextController.Tip?, NoError>?
if hasLinkedStickers {
let context = component.context
tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil)
let packsPromise = Promise<[StickerPackReference]>()
packsPromise.set(context.engine.stickers.stickerPacksAttachedToMedia(media: .standalone(media: media)))
let action: () -> Void = { [weak self] in
if let self {
self.sendMessageContext.openAttachedStickers(view: self, packs: packsPromise.get() |> take(1))
}
}
tipSignal = packsPromise.get()
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
if packReferences.count > 1 {
let valueText = component.strings.Story_Context_EmbeddedStickersValue(Int32(packReferences.count))
return .single(.animatedEmoji(text: component.strings.Story_Context_EmbeddedStickers(valueText).string, arguments: nil, file: nil, action: action))
} else if let reference = packReferences.first {
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
if case let .result(info, items, _) = result {
let isEmoji = info.flags.contains(.isEmoji)
let tip: ContextController.Tip = .animatedEmoji(
text: isEmoji ? component.strings.Story_Context_EmbeddedEmojiPack(info.title).string : component.strings.Story_Context_EmbeddedStickerPack(info.title).string,
arguments: TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: .clear,
attemptSynchronous: true
),
file: items.first?.file,
action: action)
return .single(tip)
} else {
return .complete()
}
}
} else {
return .complete()
}
}
}
let (tip, tipSignal) = self.getLinkedStickerPacks()
let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal)

View File

@ -2466,7 +2466,7 @@ final class StoryItemSetContainerSendMessage {
})
}
func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, navigation: ChatControllerInteractionNavigateToPeer = .info, sourceMessageId: MessageId? = nil) {
func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) {
guard let component = view.component, let parentController = component.controller() else {
return
}

View File

@ -0,0 +1,101 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import AccountContext
import TelegramCore
import AsyncDisplayKit
import AvatarNode
final class StoryPrivacyIconComponent: Component {
enum Privacy {
case everyone
case closeFriends
case contacts
case selectedContacts
}
let privacy: Privacy
let isEditable: Bool
init(privacy: Privacy, isEditable: Bool) {
self.privacy = privacy
self.isEditable = isEditable
}
static func ==(lhs: StoryPrivacyIconComponent, rhs: StoryPrivacyIconComponent) -> Bool {
if lhs.privacy != rhs.privacy {
return false
}
if lhs.isEditable != rhs.isEditable {
return false
}
return true
}
final class View: UIImageView {
private var component: StoryPrivacyIconComponent?
private weak var state: EmptyComponentState?
func update(component: StoryPrivacyIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
self.state = state
let size = CGSize(width: component.isEditable ? 40.0 : 24.0, height: 24.0)
self.image = generateImage(size, rotatedContext: { size, context in
let path: CGPath
if size.width == size.height {
path = CGPath(ellipseIn: CGRect(origin: .zero, size: size), transform: nil)
} else {
path = CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: size.height / 2.0, cornerHeight: size.height / 2.0, transform: nil)
}
context.addPath(path)
context.clip()
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor]
let icon: UIImage
switch component.privacy {
case .everyone:
colors = [UIColor(rgb: 0x4faaff).cgColor, UIColor(rgb: 0x017aff).cgColor]
icon = UIImage()
case .closeFriends:
colors = [UIColor(rgb: 0x87d93a).cgColor, UIColor(rgb: 0x31b73b).cgColor]
icon = UIImage()
case .contacts:
colors = [UIColor(rgb: 0xc36eff).cgColor, UIColor(rgb: 0x8c61fa).cgColor]
icon = UIImage()
case .selectedContacts:
colors = [UIColor(rgb: 0xffb643).cgColor, UIColor(rgb: 0xf69a36).cgColor]
icon = UIImage()
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
if let cgImage = icon.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: icon.size))
}
if component.isEditable {
let arrowIcon = UIImage()
if let cgImage = arrowIcon.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: icon.size))
}
}
})
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}