mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-29 09:01:05 +00:00
UI improvements
This commit is contained in:
parent
e943444b48
commit
a7c9ee2de7
@ -233,6 +233,31 @@ public final class EmojiSuggestionsComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func item(at point: CGPoint) -> (CALayer, TelegramMediaFile)? {
|
||||||
|
let location = self.convert(point, to: self.scrollView)
|
||||||
|
if self.scrollView.bounds.contains(location) {
|
||||||
|
var closestFile: (file: TelegramMediaFile, layer: CALayer, distance: CGFloat)?
|
||||||
|
for (_, itemLayer) in self.visibleLayers {
|
||||||
|
guard let file = itemLayer.file else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let distance = abs(location.x - itemLayer.position.x)
|
||||||
|
if let (_, _, currentDistance) = closestFile {
|
||||||
|
if distance < currentDistance {
|
||||||
|
closestFile = (file, itemLayer, distance)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closestFile = (file, itemLayer, distance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let (file, itemLayer, _) = closestFile {
|
||||||
|
return (itemLayer, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
let location = recognizer.location(in: self.scrollView)
|
let location = recognizer.location(in: self.scrollView)
|
||||||
|
@ -196,7 +196,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) {
|
if case .group = channel.info, isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) {
|
||||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,17 +30,17 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
|||||||
self.presentationInterfaceState = interfaceState
|
self.presentationInterfaceState = interfaceState
|
||||||
}
|
}
|
||||||
|
|
||||||
let bannedPermission: (Int32, Bool)?
|
var bannedPermission: (Int32, Bool)?
|
||||||
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||||
bannedPermission = channel.hasBannedPermission(.banSendText)
|
if let value = channel.hasBannedPermission(.banSendText) {
|
||||||
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup {
|
bannedPermission = value
|
||||||
if group.hasBannedPermission(.banSendText) {
|
} else if !channel.hasPermission(.sendSomething) {
|
||||||
|
bannedPermission = (Int32.max, false)
|
||||||
|
}
|
||||||
|
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup {
|
||||||
|
if !group.hasPermission(.sendSomething) {
|
||||||
bannedPermission = (Int32.max, false)
|
bannedPermission = (Int32.max, false)
|
||||||
} else {
|
|
||||||
bannedPermission = nil
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
bannedPermission = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var iconImage: UIImage?
|
var iconImage: UIImage?
|
||||||
|
@ -30,6 +30,9 @@ import ComponentFlow
|
|||||||
import EmojiSuggestionsComponent
|
import EmojiSuggestionsComponent
|
||||||
import AudioToolbox
|
import AudioToolbox
|
||||||
import ChatControllerInteraction
|
import ChatControllerInteraction
|
||||||
|
import UndoUI
|
||||||
|
import PremiumUI
|
||||||
|
import StickerPeekUI
|
||||||
|
|
||||||
private let accessoryButtonFont = Font.medium(14.0)
|
private let accessoryButtonFont = Font.medium(14.0)
|
||||||
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
|
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
|
||||||
@ -2571,6 +2574,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
viewForOverlayContent.addSubview(currentEmojiSuggestionView)
|
viewForOverlayContent.addSubview(currentEmojiSuggestionView)
|
||||||
|
|
||||||
currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
|
||||||
|
self.installEmojiSuggestionPreviewGesture(hostView: currentEmojiSuggestionView)
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalPosition = textInputNode.textView.convert(currentEmojiSuggestion.localPosition, to: self.view)
|
let globalPosition = textInputNode.textView.convert(currentEmojiSuggestion.localPosition, to: self.view)
|
||||||
@ -2693,6 +2698,277 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func installEmojiSuggestionPreviewGesture(hostView: UIView) {
|
||||||
|
let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
|
||||||
|
guard let self else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return self.emojiSuggestionPeekContentAtPoint(point: point)
|
||||||
|
}, present: { [weak self] content, sourceView, sourceRect in
|
||||||
|
guard let strongSelf = self, let context = strongSelf.context else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
|
||||||
|
return (sourceView, sourceRect)
|
||||||
|
})
|
||||||
|
//strongSelf.peekController = controller
|
||||||
|
strongSelf.interfaceInteraction?.presentController(controller, nil)
|
||||||
|
return controller
|
||||||
|
}, updateContent: { [weak self] content in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = strongSelf
|
||||||
|
})
|
||||||
|
hostView.addGestureRecognizer(peekRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func emojiSuggestionPeekContentAtPoint(point: CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? {
|
||||||
|
guard let presentationInterfaceState = self.presentationInterfaceState else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let chatPeerId = presentationInterfaceState.renderedPeer?.peer?.id else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let context = self.context else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var maybeFile: TelegramMediaFile?
|
||||||
|
var maybeItemLayer: CALayer?
|
||||||
|
|
||||||
|
if let currentEmojiSuggestionView = self.currentEmojiSuggestionView?.componentView as? EmojiSuggestionsComponent.View {
|
||||||
|
if let (itemLayer, file) = currentEmojiSuggestionView.item(at: point) {
|
||||||
|
maybeFile = file
|
||||||
|
maybeItemLayer = itemLayer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let file = maybeFile else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let itemLayer = maybeItemLayer else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = chatPeerId
|
||||||
|
let _ = file
|
||||||
|
let _ = itemLayer
|
||||||
|
|
||||||
|
var collectionId: ItemCollectionId?
|
||||||
|
for attribute in file.attributes {
|
||||||
|
if case let .CustomEmoji(_, _, _, packReference) = attribute {
|
||||||
|
switch packReference {
|
||||||
|
case let .id(id, _):
|
||||||
|
collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
|
||||||
|
if let collectionId {
|
||||||
|
bubbleUpEmojiOrStickersets.append(collectionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
let accountPeerId = context.account.peerId
|
||||||
|
|
||||||
|
let _ = bubbleUpEmojiOrStickersets
|
||||||
|
let _ = context
|
||||||
|
let _ = accountPeerId
|
||||||
|
|
||||||
|
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId))
|
||||||
|
|> map { peer -> Bool in
|
||||||
|
var hasPremium = false
|
||||||
|
if case let .user(user) = peer, user.isPremium {
|
||||||
|
hasPremium = true
|
||||||
|
}
|
||||||
|
return hasPremium
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> map { [weak self, weak itemLayer] hasPremium -> (UIView, CGRect, PeekControllerContent)? in
|
||||||
|
guard let strongSelf = self, let itemLayer = itemLayer else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = strongSelf
|
||||||
|
let _ = itemLayer
|
||||||
|
|
||||||
|
var menuItems: [ContextMenuItem] = []
|
||||||
|
menuItems.removeAll()
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let _ = presentationData
|
||||||
|
|
||||||
|
var isLocked = false
|
||||||
|
if !hasPremium {
|
||||||
|
isLocked = file.isPremiumEmoji
|
||||||
|
if isLocked && chatPeerId == context.account.peerId {
|
||||||
|
isLocked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let interaction = strongSelf.interfaceInteraction {
|
||||||
|
let _ = interaction
|
||||||
|
|
||||||
|
let sendEmoji: (TelegramMediaFile) -> Void = { file in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = "."
|
||||||
|
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
||||||
|
loop: for attribute in file.attributes {
|
||||||
|
switch attribute {
|
||||||
|
case let .CustomEmoji(_, _, displayText, stickerPackReference):
|
||||||
|
text = displayText
|
||||||
|
|
||||||
|
var packId: ItemCollectionId?
|
||||||
|
if case let .id(id, _) = stickerPackReference {
|
||||||
|
packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id)
|
||||||
|
}
|
||||||
|
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file)
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let emojiAttribute {
|
||||||
|
controller.controllerInteraction?.sendEmoji(text, emojiAttribute, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let setStatus: (TelegramMediaFile) -> Void = { file in
|
||||||
|
guard let self, let context = self.context else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start()
|
||||||
|
|
||||||
|
var animateInAsReplacement = false
|
||||||
|
animateInAsReplacement = false
|
||||||
|
/*if let currentUndoOverlayController = strongSelf.currentUndoOverlayController {
|
||||||
|
currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation()
|
||||||
|
strongSelf.currentUndoOverlayController = nil
|
||||||
|
animateInAsReplacement = true
|
||||||
|
}*/
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Your emoji status has been updated.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false })
|
||||||
|
//strongSelf.currentUndoOverlayController = controller
|
||||||
|
controller.controllerInteraction?.presentController(undoController, nil)
|
||||||
|
}
|
||||||
|
let copyEmoji: (TelegramMediaFile) -> Void = { file in
|
||||||
|
var text = "."
|
||||||
|
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
||||||
|
loop: for attribute in file.attributes {
|
||||||
|
switch attribute {
|
||||||
|
case let .CustomEmoji(_, _, displayText, _):
|
||||||
|
text = displayText
|
||||||
|
|
||||||
|
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file)
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = emojiAttribute {
|
||||||
|
storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: "Send Emoji", icon: { theme in
|
||||||
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) {
|
||||||
|
return generateImage(image.size, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
if let cgImage = image.cgImage {
|
||||||
|
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, action: { _, f in
|
||||||
|
sendEmoji(file)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: "Set as Status", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Smile"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasPremium {
|
||||||
|
setStatus(file)
|
||||||
|
} else {
|
||||||
|
var replaceImpl: ((ViewController) -> Void)?
|
||||||
|
let controller = PremiumDemoScreen(context: context, subject: .animatedEmoji, action: {
|
||||||
|
let controller = PremiumIntroScreen(context: context, source: .animatedEmoji)
|
||||||
|
replaceImpl?(controller)
|
||||||
|
})
|
||||||
|
replaceImpl = { [weak controller] c in
|
||||||
|
controller?.replace(with: c)
|
||||||
|
}
|
||||||
|
strongSelf.interfaceInteraction?.getNavigationController()?.pushViewController(controller)
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(text: "Copy Emoji", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
copyEmoji(file)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
if menuItems.isEmpty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let interfaceInteraction = self.interfaceInteraction else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
let _ = interfaceInteraction
|
||||||
|
|
||||||
|
let controller = PremiumIntroScreen(context: context, source: .stickers)
|
||||||
|
//let _ = controller
|
||||||
|
|
||||||
|
interfaceInteraction.getNavigationController()?.pushViewController(controller)
|
||||||
|
})
|
||||||
|
let _ = content
|
||||||
|
//return nil
|
||||||
|
|
||||||
|
return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateTextNodeText(animated: Bool) {
|
private func updateTextNodeText(animated: Bool) {
|
||||||
var inputHasText = false
|
var inputHasText = false
|
||||||
var hideMicButton = false
|
var hideMicButton = false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user