mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Media caption input panel improvements
This commit is contained in:
@@ -5,6 +5,7 @@ import TextFieldComponent
|
||||
import ChatContextQuery
|
||||
import AccountContext
|
||||
import TelegramUIPreferences
|
||||
import SearchPeerMembers
|
||||
|
||||
func textInputStateContextQueryRangeAndType(inputState: TextFieldComponent.InputState) -> [(NSRange, PossibleContextQueryTypes, NSRange?)] {
|
||||
return textInputStateContextQueryRangeAndType(inputText: inputState.inputText, selectionRange: inputState.selectionRange)
|
||||
@@ -38,7 +39,7 @@ func inputContextQueries(_ inputState: TextFieldComponent.InputState) -> [ChatPr
|
||||
return result
|
||||
}
|
||||
|
||||
func contextQueryResultState(context: AccountContext, inputState: TextFieldComponent.InputState, availableTypes: [ChatPresentationInputQueryKind], currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
|
||||
func contextQueryResultState(context: AccountContext, inputState: TextFieldComponent.InputState, availableTypes: [ChatPresentationInputQueryKind], chatLocation: ChatLocation?, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
|
||||
let inputQueries = inputContextQueries(inputState).filter({ query in
|
||||
return availableTypes.contains(query.kind)
|
||||
})
|
||||
@@ -48,7 +49,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo
|
||||
for query in inputQueries {
|
||||
let previousQuery = currentQueryStates[query.kind]?.0
|
||||
if previousQuery != query {
|
||||
let signal = updatedContextQueryResultStateForQuery(context: context, inputQuery: query, previousQuery: previousQuery)
|
||||
let signal = updatedContextQueryResultStateForQuery(context: context, chatLocation: chatLocation, inputQuery: query, previousQuery: previousQuery)
|
||||
updates[query.kind] = .update(query, signal)
|
||||
}
|
||||
}
|
||||
@@ -69,7 +70,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo
|
||||
return updates
|
||||
}
|
||||
|
||||
private func updatedContextQueryResultStateForQuery(context: AccountContext, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
|
||||
private func updatedContextQueryResultStateForQuery(context: AccountContext, chatLocation: ChatLocation?, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
|
||||
switch inputQuery {
|
||||
case let .emoji(query):
|
||||
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
|
||||
@@ -149,7 +150,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
|
||||
|> castError(ChatContextQueryError.self)
|
||||
|
||||
return signal |> then(hashtags)
|
||||
case let .mention(query, _):
|
||||
case let .mention(query, types):
|
||||
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
|
||||
if let previousQuery = previousQuery {
|
||||
switch previousQuery {
|
||||
@@ -163,34 +164,79 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
|
||||
}
|
||||
|
||||
let normalizedQuery = query.lowercased()
|
||||
if normalizedQuery.isEmpty {
|
||||
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers()
|
||||
|> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
if case let .peers(peers) = recentPeers {
|
||||
let peers = peers.filter { peer in
|
||||
return peer.addressName != nil
|
||||
}.compactMap { EnginePeer($0) }
|
||||
return { _ in return .mentions(peers) }
|
||||
} else {
|
||||
return { _ in return .mentions([]) }
|
||||
}
|
||||
}
|
||||
|> castError(ChatContextQueryError.self)
|
||||
return signal |> then(peers)
|
||||
} else {
|
||||
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery)
|
||||
|> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
let peers = peersAndPresences.filter { peer in
|
||||
if let peer = peer.peer, case .user = peer, peer.addressName != nil {
|
||||
return true
|
||||
} else {
|
||||
|
||||
if let chatLocation, let peerId = chatLocation.peerId {
|
||||
let inlineBots: Signal<[(EnginePeer, Double)], NoError> = types.contains(.contextBots) ? context.engine.peers.recentlyUsedInlineBots() : .single([])
|
||||
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
|
||||
let participants = combineLatest(inlineBots, searchPeerMembers(context: context, peerId: peerId, chatLocation: chatLocation, query: query, scope: .mention))
|
||||
|> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in
|
||||
if rating < 0.14 {
|
||||
return false
|
||||
}
|
||||
}.compactMap { $0.peer }
|
||||
return { _ in return .mentions(peers) }
|
||||
if peer.indexName.matchesByTokens(normalizedQuery) {
|
||||
return true
|
||||
}
|
||||
if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}.map { $0.0 }
|
||||
|
||||
let inlineBotPeerIds = Set(filteredInlineBots.map { $0.id })
|
||||
|
||||
let filteredPeers = peers.filter { peer in
|
||||
if inlineBotPeerIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
if !types.contains(.accountPeer) && peer.id == context.account.peerId {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
var sortedPeers = filteredInlineBots
|
||||
sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in
|
||||
let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true))
|
||||
return result == .orderedAscending
|
||||
}))
|
||||
sortedPeers = sortedPeers.filter { peer in
|
||||
return !peer.displayTitle(strings: strings, displayOrder: .firstLast).isEmpty
|
||||
}
|
||||
return { _ in return .mentions(sortedPeers) }
|
||||
}
|
||||
|> castError(ChatContextQueryError.self)
|
||||
return signal |> then(peers)
|
||||
|
||||
return signal |> then(participants)
|
||||
} else {
|
||||
if normalizedQuery.isEmpty {
|
||||
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers()
|
||||
|> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
if case let .peers(peers) = recentPeers {
|
||||
let peers = peers.filter { peer in
|
||||
return peer.addressName != nil
|
||||
}.compactMap { EnginePeer($0) }
|
||||
return { _ in return .mentions(peers) }
|
||||
} else {
|
||||
return { _ in return .mentions([]) }
|
||||
}
|
||||
}
|
||||
|> castError(ChatContextQueryError.self)
|
||||
return signal |> then(peers)
|
||||
} else {
|
||||
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery)
|
||||
|> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
let peers = peersAndPresences.filter { peer in
|
||||
if let peer = peer.peer, case .user = peer, peer.addressName != nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}.compactMap { $0.peer }
|
||||
return { _ in return .mentions(peers) }
|
||||
}
|
||||
|> castError(ChatContextQueryError.self)
|
||||
return signal |> then(peers)
|
||||
}
|
||||
}
|
||||
case let .emojiSearch(query, languageCode, range):
|
||||
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|
||||
@@ -143,6 +143,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
public let disabledPlaceholder: String?
|
||||
public let isChannel: Bool
|
||||
public let storyItem: EngineStoryItem?
|
||||
public let chatLocation: ChatLocation?
|
||||
|
||||
public init(
|
||||
externalState: ExternalState,
|
||||
@@ -193,7 +194,8 @@ public final class MessageInputPanelComponent: Component {
|
||||
forceIsEditing: Bool,
|
||||
disabledPlaceholder: String?,
|
||||
isChannel: Bool,
|
||||
storyItem: EngineStoryItem?
|
||||
storyItem: EngineStoryItem?,
|
||||
chatLocation: ChatLocation?
|
||||
) {
|
||||
self.externalState = externalState
|
||||
self.context = context
|
||||
@@ -244,6 +246,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
self.disabledPlaceholder = disabledPlaceholder
|
||||
self.isChannel = isChannel
|
||||
self.storyItem = storyItem
|
||||
self.chatLocation = chatLocation
|
||||
}
|
||||
|
||||
public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool {
|
||||
@@ -346,6 +349,9 @@ public final class MessageInputPanelComponent: Component {
|
||||
if lhs.storyItem != rhs.storyItem {
|
||||
return false
|
||||
}
|
||||
if lhs.chatLocation != rhs.chatLocation {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -559,7 +565,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
if component.queryTypes.contains(.emoji) {
|
||||
availableTypes.append(.emoji)
|
||||
}
|
||||
let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, currentQueryStates: &self.contextQueryStates)
|
||||
let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, chatLocation: component.chatLocation, currentQueryStates: &self.contextQueryStates)
|
||||
|
||||
for (kind, update) in contextQueryUpdates {
|
||||
switch update {
|
||||
@@ -705,6 +711,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
return value
|
||||
}
|
||||
},
|
||||
resetScrollOnFocusChange: component.style == .media,
|
||||
formatMenuAvailability: component.isFormattingLocked ? .locked : .available,
|
||||
lockedFormatAction: {
|
||||
component.presentTextFormattingTooltip?()
|
||||
@@ -780,6 +787,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size))
|
||||
self.vibrancyEffectView.isHidden = component.style == .media
|
||||
|
||||
transition.setFrame(view: self.fieldBackgroundView, frame: fieldBackgroundFrame)
|
||||
self.fieldBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, transition: transition.containedViewLayoutTransition)
|
||||
@@ -1446,6 +1454,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
let accentColor = component.theme.chat.inputPanel.panelControlAccentColor
|
||||
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
|
||||
func generateIcon(value: String, selected: Bool) -> UIImage? {
|
||||
let image = UIImage(bundleImageName: "Media Editor/Timeout")!
|
||||
@@ -1456,7 +1465,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
context.clear(bounds)
|
||||
|
||||
if selected {
|
||||
context.setFillColor(UIColor(rgb: 0x3478f6).cgColor)
|
||||
context.setFillColor(accentColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: .zero, size: size))
|
||||
} else {
|
||||
if let cgImage = image.cgImage {
|
||||
@@ -1682,8 +1691,12 @@ public final class MessageInputPanelComponent: Component {
|
||||
|
||||
self.updateContextQueries()
|
||||
|
||||
let panelLeftInset: CGFloat = max(insets.left, 7.0)
|
||||
let panelRightInset: CGFloat = max(insets.right, 41.0)
|
||||
var panelLeftInset: CGFloat = max(insets.left, 7.0)
|
||||
var panelRightInset: CGFloat = max(insets.right, 41.0)
|
||||
if case .media = component.style {
|
||||
panelLeftInset = 0.0
|
||||
panelRightInset = 0.0
|
||||
}
|
||||
|
||||
var contextResults: ContextResultPanelComponent.Results?
|
||||
if let result = self.contextQueryResults[.mention], case let .mentions(mentions) = result, !mentions.isEmpty {
|
||||
@@ -1815,7 +1828,13 @@ public final class MessageInputPanelComponent: Component {
|
||||
containerSize: CGSize(width: availableSize.width - panelLeftInset - panelRightInset, height: availablePanelHeight)
|
||||
)
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: insets.left, y: -panelSize.height + 14.0), size: CGSize(width: panelSize.width, height: panelSize.height + 19.0))
|
||||
var panelOriginY = -panelSize.height + 14.0
|
||||
var panelHeight = panelSize.height + 19.0
|
||||
if case .media = component.style {
|
||||
panelOriginY -= 6.0
|
||||
panelHeight = panelSize.height
|
||||
}
|
||||
let panelFrame = CGRect(origin: CGPoint(x: panelLeftInset, y: panelOriginY), size: CGSize(width: panelSize.width, height: panelHeight))
|
||||
if let panelView = panel.view as? ContextResultPanelComponent.View {
|
||||
if panelView.superview == nil {
|
||||
self.insertSubview(panelView, at: 0)
|
||||
|
||||
Reference in New Issue
Block a user