Merge commit 'bf3d4cecf82886baa0c154954423e12c80984601'

This commit is contained in:
Ali 2023-02-21 22:16:28 +04:00
commit d93a143501
56 changed files with 639 additions and 336 deletions

View File

@ -8930,3 +8930,7 @@ Sorry for the inconvenience.";
"VoiceOver.Chat.NotPlayedByRecipients" = "Not played by recipients";
"VoiceOver.Chat.ReplyingToMessage" = "In reply to message: %@";
"MediaPicker.VoiceOver.Camera" = "Camera";
"ChatList.ReadAll" = "Read All";

View File

@ -277,6 +277,7 @@ public enum ResolvedUrl {
case inaccessiblePeer
case botStart(peer: Peer, payload: String)
case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?)
case gameStart(peerId: PeerId, game: String)
case channelMessage(peer: Peer, messageId: MessageId, timecode: Double?)
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
case replyThread(messageId: MessageId)

View File

@ -623,6 +623,9 @@ public protocol ChatController: ViewController {
func hintPlayNextOutgoingGift()
var isSendButtonVisible: Bool { get }
var isSelectingMessagesUpdated: ((Bool) -> Void)? { get set }
func cancelSelectingMessages()
}
public protocol ChatMessagePreviewItemNode: AnyObject {

View File

@ -96,7 +96,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
let theme = self.presentationData.theme
self.displayNode = AuthorizationSequenceSignUpControllerNode(theme: theme, strings: self.presentationData.strings, addPhoto: { [weak self] in
presentLegacyAvatarPicker(holder: currentAvatarMixin, signup: false, theme: theme, present: { c, a in
presentLegacyAvatarPicker(holder: currentAvatarMixin, signup: true, theme: theme, present: { c, a in
self?.view.endEditing(true)
self?.present(c, in: .window(.root), with: a)
}, openCurrent: nil, completion: { image in

View File

@ -968,7 +968,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.setInlineChatList(location: .forum(peerId: channel.id))
}
} else {
if let threadId = threadId {
if case let .channel(channel) = peer, channel.flags.contains(.isForum), let threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start()
strongSelf.chatListDisplayNode.clearHighlightAnimated(true)
} else {
@ -1069,7 +1069,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if case .chatList(.root) = strongSelf.location {
navigationAnimationOptions = .removeOnMasterDetails
}
if let threadId = threadId {
if case let .channel(channel) = actualPeer, channel.flags.contains(.isForum), let threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .never).start()
} else {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: .message(id: .id(messageId), highlight: true, timecode: nil), purposefulAction: {
@ -1102,7 +1102,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if case .chatList(.root) = strongSelf.location {
navigationAnimationOptions = .removeOnMasterDetails
}
if let threadId = threadId {
if case let .channel(channel) = peer, channel.flags.contains(.isForum), let threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start()
} else {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), purposefulAction: { [weak self] in
@ -1513,6 +1513,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
})))
if let filterEntries = strongSelf.tabContainerData?.0 {
for filter in filterEntries {
if case let .filter(filterId, _, unread) = filter, filterId == id {
if unread.value > 0 {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReadAll, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
guard let strongSelf = self else {
return
}
strongSelf.readAllInFilter(id: id)
})
})))
}
break
}
}
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_RemoveFolder, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { c, f in
@ -2579,6 +2599,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
}
private func readAllInFilter(id: Int32) {
guard case let .chatList(groupId) = self.chatListDisplayNode.effectiveContainerNode.location else {
return
}
for filter in self.chatListDisplayNode.mainContainerNode.availableFilters {
if case let .filter(filter) = filter, case let .filter(filterId, _, _, data) = filter, filterId == id {
let filterPredicate = chatListFilterPredicate(filter: data)
var markItems: [(groupId: EngineChatList.Group, filterPredicate: ChatListFilterPredicate?)] = []
markItems.append((groupId, filterPredicate))
for additionalGroupId in filterPredicate.includeAdditionalPeerGroupIds {
markItems.append((EngineChatList.Group(additionalGroupId), filterPredicate))
}
let _ = self.context.engine.messages.markAllChatsAsReadInteractively(items: markItems).start()
break
}
}
}
private func askForFilterRemoval(id: Int32) {
let actionSheet = ActionSheetController(presentationData: self.presentationData)

View File

@ -1381,14 +1381,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
savedMessages = true
} else {
if displayPeers.count == 1, let peer = displayPeers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = displayPeers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string
} else {
text = ""

View File

@ -2461,7 +2461,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if let strongSelf = self {
strongSelf.presentationData = presentationData
strongSelf.presentationDataPromise.set(.single(ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)))
if strongSelf.backgroundColor != nil {
strongSelf.backgroundColor = presentationData.theme.chatList.backgroundColor
}
strongSelf.listNode.forEachItemHeaderNode({ itemHeaderNode in
if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode {
itemHeaderNode.updateTheme(theme: presentationData.theme)

View File

@ -1648,6 +1648,10 @@ public final class ChatListNode: ListView {
guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false }
if let peer = peer.peer {
if peer.id.isReplies {
return false
}
switch peer {
case let .user(user):
if user.botInfo != nil {

View File

@ -175,8 +175,9 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL
}
for peerId in data.excludePeers {
if let (tag, peerCount, _, groupIdValue, isMuted) = peerTagAndCount[peerId], peerCount != 0, let groupId = groupIdValue {
var matches = true
var matches = false
if tags.contains(tag) {
matches = true
if isMuted && data.excludeMuted {
matches = false
}

View File

@ -789,10 +789,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
case .none:
break
case let .presence(presence, dateTimeFormat):
userPresence = presence
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let (string, activity) = stringAndActivityForUserPresence(strings: item.presentationData.strings, dateTimeFormat: dateTimeFormat, presence: presence, relativeTo: Int32(timestamp))
statusAttributedString = NSAttributedString(string: string, font: statusFont, textColor: activity ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
if case let .peer(peer, _) = item.peer, let peer, case let .user(user) = peer, user.botInfo != nil {
statusAttributedString = NSAttributedString(string: item.presentationData.strings.Bot_GenericBotStatus, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
} else {
userPresence = presence
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let (string, activity) = stringAndActivityForUserPresence(strings: item.presentationData.strings, dateTimeFormat: dateTimeFormat, presence: presence, relativeTo: Int32(timestamp))
statusAttributedString = NSAttributedString(string: string, font: statusFont, textColor: activity ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
}
case let .addressName(suffix):
if let addressName = peer.addressName {
let addressNameString = NSAttributedString(string: "@" + addressName, font: statusFont, textColor: item.presentationData.theme.list.itemAccentColor)

View File

@ -994,15 +994,16 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if let _ = self.presentationNode {
self.currentPresentationStateTransition = .animateOut(result: initialResult, completion: completion)
if let validLayout = self.validLayout {
if case .custom = initialResult {
if case let .custom(transition) = initialResult {
self.delayLayoutUpdate = true
Queue.mainQueue().after(0.05) {
Queue.mainQueue().after(0.1) {
self.delayLayoutUpdate = false
self.updateLayout(
layout: validLayout,
transition: .animated(duration: 0.35, curve: .easeInOut),
transition: transition,
previousActionsContainerNode: nil
)
self.isAnimatingOut = true
}
} else {
self.updateLayout(

View File

@ -80,3 +80,7 @@ public func isBoldTextEnabled() -> Signal<Bool, NoError> {
}
|> runOn(Queue.mainQueue())
}
public func isReduceTransparencyEnabled() -> Bool {
UIAccessibility.isReduceTransparencyEnabled
}

View File

@ -492,7 +492,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
let effectiveWidth = gridLayout.size.width - itemInsets.left - itemInsets.right
let itemsInRow = Int(effectiveWidth / defaultItemSize.width)
let itemsInRow = max(1, Int(effectiveWidth / defaultItemSize.width))
let itemsInRowWidth = CGFloat(itemsInRow) * defaultItemSize.width
let remainingWidth = max(0.0, effectiveWidth - itemsInRowWidth)

View File

@ -713,6 +713,8 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
itemSize.width -= 60.0
insets.left += 30.0
insets.right += 30.0
} else {
itemSize.width -= layout.safeInsets.left + layout.safeInsets.right
}
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: insets.left + layout.safeInsets.left, bottom: insets.bottom + layout.safeInsets.bottom, right: insets.right + layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })

View File

@ -1405,14 +1405,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(peers.count - 1)").string
} else {
text = ""
@ -1473,14 +1477,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(peers.count - 1)").string
} else {
text = ""
@ -1621,14 +1629,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
} else {
text = ""

View File

@ -152,7 +152,7 @@ public final class HashtagSearchController: TelegramBaseController {
}
override public func loadDisplayNode() {
self.displayNode = HashtagSearchControllerNode(context: self.context, peer: self.peer, query: self.query, navigationBar: self.navigationBar, navigationController: self.navigationController as? NavigationController)
self.displayNode = HashtagSearchControllerNode(context: self.context, controller: self, peer: self.peer, query: self.query, navigationBar: self.navigationBar, navigationController: self.navigationController as? NavigationController)
if let chatController = self.controllerNode.chatController {
chatController.parentController = self
}

View File

@ -9,6 +9,9 @@ import SegmentedControlNode
import ChatListSearchItemHeader
final class HashtagSearchControllerNode: ASDisplayNode {
private let context: AccountContext
private weak var controller: HashtagSearchController?
private let navigationBar: NavigationBar?
private let segmentedControlNode: SegmentedControlNode
@ -16,15 +19,16 @@ final class HashtagSearchControllerNode: ASDisplayNode {
let chatController: ChatController?
private let context: AccountContext
private let query: String
private var containerLayout: (ContainerViewLayout, CGFloat)?
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
private var hasValidLayout = false
init(context: AccountContext, peer: EnginePeer?, query: String, navigationBar: NavigationBar?, navigationController: NavigationController?) {
init(context: AccountContext, controller: HashtagSearchController, peer: EnginePeer?, query: String, navigationBar: NavigationBar?, navigationController: NavigationController?) {
self.context = context
self.controller = controller
self.query = query
self.navigationBar = navigationBar
@ -74,6 +78,17 @@ final class HashtagSearchControllerNode: ASDisplayNode {
}
}
}
self.chatController?.isSelectingMessagesUpdated = { [weak self] isSelecting in
if let strongSelf = self {
let button: UIBarButtonItem? = isSelecting ? UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .done, target: self, action: #selector(strongSelf.cancelPressed)) : nil
strongSelf.controller?.navigationItem.setRightBarButton(button, animated: true)
}
}
}
@objc private func cancelPressed() {
self.chatController?.cancelSelectingMessages()
}
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {

View File

@ -544,122 +544,142 @@ public final class InviteLinkViewController: ViewController {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
UIPasteboard.general.string = invite.link
self?.controller?.dismissAllTooltips()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
})))
var creatorIsBot: Signal<Bool, NoError>
if case let .link(_, _, _, _, _, adminId, _, _, _, _, _, _) = invite {
creatorIsBot = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: adminId))
|> map { peer -> Bool in
if let peer, case let .user(user) = peer, user.botInfo != nil {
return true
} else {
return false
}
}
} else {
creatorIsBot = .single(false)
}
if invite.isRevoked {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextDelete, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
let _ = (creatorIsBot
|> take(1)
|> deliverOnMainQueue).start(next: { creatorIsBot in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
dismissAction()
self?.controller?.dismiss()
if let inviteLink = invite.link {
let _ = (context.engine.peers.deletePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start()
}
self?.controller?.revokedInvitationsContext?.remove(invite)
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self?.controller?.present(controller, in: .window(.root))
UIPasteboard.general.string = invite.link
self?.controller?.dismissAllTooltips()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
})))
} else {
if !invitationAvailability(invite).isZero {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
if invite.isRevoked {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextDelete, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let parentController = strongSelf.controller else {
return
}
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let updatedPresentationData = (strongSelf.presentationData, parentController.presentationDataPromise.get())
strongSelf.controller?.present(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), in: .window(.root))
})
})))
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
dismissAction()
self?.controller?.dismiss()
if let inviteLink = invite.link {
let _ = (context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in
if case let .replace(_, newInvite) = result {
self?.controller?.invitationsContext?.add(newInvite)
}
})
let _ = (context.engine.peers.deletePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start()
}
self?.controller?.invitationsContext?.remove(invite)
let revokedInvite = invite.withUpdated(isRevoked: true)
self?.controller?.revokedInvitationsContext?.add(revokedInvite)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
self?.controller?.revokedInvitationsContext?.remove(invite)
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self?.controller?.present(controller, in: .window(.root))
})
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController)
})))
} else {
if !invitationAvailability(invite).isZero {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let parentController = strongSelf.controller else {
return
}
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let updatedPresentationData = (strongSelf.presentationData, parentController.presentationDataPromise.get())
strongSelf.controller?.present(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), in: .window(.root))
})
})))
}
if !creatorIsBot {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
self?.controller?.dismiss()
if let inviteLink = invite.link {
let _ = (context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in
if case let .replace(_, newInvite) = result {
self?.controller?.invitationsContext?.add(newInvite)
}
})
}
self?.controller?.invitationsContext?.remove(invite)
let revokedInvite = invite.withUpdated(isRevoked: true)
self?.controller?.revokedInvitationsContext?.add(revokedInvite)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self?.controller?.present(controller, in: .window(.root))
})
})))
}
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController)
})
})
let previousEntries = Atomic<[InviteLinkViewEntry]?>(value: nil)

View File

@ -45,8 +45,6 @@
@property (nonatomic, copy) void(^recognizedQRCode)(NSString *value, AVMetadataMachineReadableCodeObject *object);
@property (nonatomic, assign) bool compressVideo;
- (instancetype)initWithMode:(PGCameraMode)mode position:(PGCameraPosition)position;
- (void)performInitialConfigurationWithCompletion:(void (^)(void))completion;

View File

@ -49,7 +49,7 @@
@interface TGMediaVideoConversionPresetSettings : NSObject
+ (CGSize)maximumSizeForPreset:(TGMediaVideoConversionPreset)preset;
+ (NSDictionary *)videoSettingsForPreset:(TGMediaVideoConversionPreset)preset dimensions:(CGSize)dimensions;
+ (NSDictionary *)videoSettingsForPreset:(TGMediaVideoConversionPreset)preset dimensions:(CGSize)dimensions frameRate:(int32_t)frameRate;
+ (NSDictionary *)audioSettingsForPreset:(TGMediaVideoConversionPreset)preset;
@end

View File

@ -86,7 +86,7 @@ typedef enum {
- (void)setToolbarHidden:(bool)hidden animated:(bool)animated;
+ (TGPhotoEditorTab)defaultTabsForAvatarIntent;
+ (TGPhotoEditorTab)defaultTabsForAvatarIntent:(bool)hasStickers;
- (NSTimeInterval)currentTime;
- (void)setMinimalVideoDuration:(NSTimeInterval)duration;

View File

@ -314,11 +314,13 @@ const NSInteger PGCameraFrameRate = 30;
- (void)switchToBestVideoFormatForDevice:(AVCaptureDevice *)device
{
bool preferZoomableFormat = [self hasTelephotoCamera] || [self hasUltrawideCamera];
[self _reconfigureDevice:device withBlock:^(AVCaptureDevice *device)
{
NSArray *availableFormats = device.formats;
AVCaptureDeviceFormat *preferredFormat = nil;
NSMutableArray *maybeFormats = nil;
bool hasSecondaryZoomLevels = false;
int32_t maxWidth = 0;
int32_t maxHeight = 0;
for (AVCaptureDeviceFormat *format in availableFormats)
@ -329,8 +331,10 @@ const NSInteger PGCameraFrameRate = 30;
CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
if (dimensions.width >= maxWidth && dimensions.width <= 1920 && dimensions.height >= maxHeight && dimensions.height <= 1080)
{
if (dimensions.width > maxWidth)
if (dimensions.width > maxWidth) {
hasSecondaryZoomLevels = false;
maybeFormats = [[NSMutableArray alloc] init];
}
FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(format.formatDescription);
if (mediaSubType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
{
@ -347,8 +351,14 @@ const NSInteger PGCameraFrameRate = 30;
}
}
if (supportedRate)
[maybeFormats addObject:format];
if (supportedRate) {
if (iosMajorVersion() >= 16 && format.secondaryNativeResolutionZoomFactors.count > 0) {
hasSecondaryZoomLevels = true;
[maybeFormats addObject:format];
} else if (!hasSecondaryZoomLevels) {
[maybeFormats addObject:format];
}
}
}
}
}
@ -394,7 +404,11 @@ const NSInteger PGCameraFrameRate = 30;
{
AVCaptureConnection *videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
if (videoConnection.supportsVideoStabilization) {
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeStandard;
if (iosMajorVersion() >= 13) {
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeCinematicExtended;
} else {
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeCinematic;
}
}
}
@ -976,12 +990,6 @@ const NSInteger PGCameraFrameRate = 30;
NSDictionary *videoSettings = [_videoOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeMPEG4];
NSDictionary *audioSettings = [_audioOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:AVFileTypeMPEG4];
if (self.compressVideo)
{
videoSettings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:TGMediaVideoConversionPresetCompressedMedium dimensions:CGSizeMake(848, 480)];
audioSettings = [TGMediaVideoConversionPresetSettings audioSettingsForPreset:TGMediaVideoConversionPresetCompressedMedium];
}
_movieWriter = [[PGCameraMovieWriter alloc] initWithVideoTransform:TGTransformForVideoOrientation(orientation, mirrored) videoOutputSettings:videoSettings audioOutputSettings:audioSettings];
_movieWriter.finishedWithMovieAtURL = completion;
[_movieWriter startRecording];

View File

@ -919,7 +919,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
intent |= TGPhotoEditorControllerSuggestingAvatarIntent;
}
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:intent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab];
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:intent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent:!_disableStickers] selectedTab:TGPhotoEditorCropTab];
controller.editingContext = _editingContext;
controller.stickersContext = _stickersContext;
controller.dontHideStatusBar = true;

View File

@ -1986,7 +1986,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
if (_intent == TGCameraControllerSignupAvatarIntent) {
intent = TGPhotoEditorControllerSignupAvatarIntent;
}
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:windowContext item:input intent:(TGPhotoEditorControllerFromCameraIntent | intent) adjustments:nil caption:nil screenImage:image availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab];
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:windowContext item:input intent:(TGPhotoEditorControllerFromCameraIntent | intent) adjustments:nil caption:nil screenImage:image availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent:_intent != TGCameraControllerSignupAvatarIntent] selectedTab:TGPhotoEditorCropTab];
controller.stickersContext = _stickersContext;
__weak TGPhotoEditorController *weakController = controller;
controller.beginTransitionIn = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView)

View File

@ -448,7 +448,7 @@
editableItem = [[TGCameraCapturedVideo alloc] initWithAsset:asset livePhoto:false];
}
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:_context item:editableItem intent:intent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab];
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:_context item:editableItem intent:intent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent:_intent != TGMediaAssetsControllerSetSignupProfilePhotoIntent] selectedTab:TGPhotoEditorCropTab];
controller.stickersContext = self.stickersContext;
controller.editingContext = self.editingContext;
controller.didFinishRenderingFullSizeImage = ^(UIImage *resultImage)

View File

@ -368,18 +368,18 @@
CMTime frameDuration30FPS = CMTimeMake(1, 30);
CMTime frameDuration = frameDuration30FPS;
if (videoTrack.nominalFrameRate > 0)
frameDuration = CMTimeMake(1, (int32_t)videoTrack.nominalFrameRate);
frameDuration = CMTimeMake(1, (int32_t)ceil(videoTrack.nominalFrameRate));
else if (CMTimeCompare(videoTrack.minFrameDuration, kCMTimeZero) == 1)
frameDuration = videoTrack.minFrameDuration;
if (CMTimeCompare(frameDuration, kCMTimeZero) != 1 || !CMTIME_IS_VALID(frameDuration) || image != nil || entityRenderer != nil || adjustments.toolsApplied)
frameDuration = frameDuration30FPS;
if (CMTimeCompare(frameDuration, frameDuration30FPS)) {
if (CMTimeCompare(frameDuration, frameDuration30FPS) && preset != TGMediaVideoConversionPresetCompressedVeryHigh) {
frameDuration = frameDuration30FPS;
}
NSInteger fps = (NSInteger)(1.0 / CMTimeGetSeconds(frameDuration));
NSInteger fps = (NSInteger)ceil(1.0 / CMTimeGetSeconds(frameDuration));
UIImage *overlayImage = nil;
if (adjustments.paintingData.imagePath != nil)
@ -536,7 +536,7 @@
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
}
NSDictionary *settings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:preset dimensions:outputDimensions];
NSDictionary *settings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:preset dimensions:outputDimensions frameRate:(int32_t)fps];
*outputSettings = settings;
*dimensions = outputDimensions;
@ -1303,7 +1303,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
};
}
+ (NSDictionary *)videoSettingsForPreset:(TGMediaVideoConversionPreset)preset dimensions:(CGSize)dimensions
+ (NSDictionary *)videoSettingsForPreset:(TGMediaVideoConversionPreset)preset dimensions:(CGSize)dimensions frameRate:(int32_t)frameRate
{
NSDictionary *videoCleanApertureSettings = @
{
@ -1324,7 +1324,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
AVVideoAverageBitRateKey: @([self _videoBitrateKbpsForPreset:preset] * 1000),
AVVideoCleanApertureKey: videoCleanApertureSettings,
AVVideoPixelAspectRatioKey: videoAspectRatioSettings,
AVVideoExpectedSourceFrameRateKey: @30
AVVideoExpectedSourceFrameRateKey: @(frameRate)
};
NSDictionary *hdVideoProperties = @
@ -1364,13 +1364,13 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
return 700;
case TGMediaVideoConversionPresetCompressedMedium:
return 1100;
return 1600;
case TGMediaVideoConversionPresetCompressedHigh:
return 2500;
return 3000;
case TGMediaVideoConversionPresetCompressedVeryHigh:
return 4000;
return 6600;
case TGMediaVideoConversionPresetVideoMessage:
return 1000;

View File

@ -93,10 +93,13 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
[_wrapperView addSubview:_fullPaintingView];
_entitiesWrapperView = [[UIView alloc] init];
_fullEntitiesView = fullEntitiesView;
_fullEntitiesView.frame = CGRectMake(0.0, 0.0, _fullEntitiesView.frame.size.width, _fullEntitiesView.frame.size.height);
_entitiesWrapperView.frame = _fullEntitiesView.frame;
if (fullEntitiesView != nil) {
_fullEntitiesView = fullEntitiesView;
_fullEntitiesView.frame = CGRectMake(0.0, 0.0, _fullEntitiesView.frame.size.width, _fullEntitiesView.frame.size.height);
_entitiesWrapperView.frame = _fullEntitiesView.frame;
} else {
_entitiesWrapperView.frame = CGRectMake(0.0, 0.0, _fullPreviewView.frame.size.width, _fullPreviewView.frame.size.height);
}
CGFloat entitiesScale = _fullPreviewView.frame.size.width / _entitiesWrapperView.frame.size.width;
_entitiesWrapperView.transform = CGAffineTransformMakeScale(entitiesScale, entitiesScale);
_entitiesWrapperView.frame = _fullPreviewView.frame;

View File

@ -951,7 +951,11 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
- (TGPhotoEditorTab)availableTabs
{
return TGPhotoEditorRotateTab | TGPhotoEditorMirrorTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab;
TGPhotoEditorTab tabs = TGPhotoEditorRotateTab | TGPhotoEditorMirrorTab | TGPhotoEditorToolsTab;
if (self.intent != TGPhotoEditorControllerSignupAvatarIntent) {
tabs |= TGPhotoEditorPaintTab;
}
return tabs;
}
- (TGPhotoEditorTab)activeTab

View File

@ -2604,15 +2604,12 @@
}]];
}
+ (TGPhotoEditorTab)defaultTabsForAvatarIntent
+ (TGPhotoEditorTab)defaultTabsForAvatarIntent:(bool)hasStickers
{
static dispatch_once_t onceToken;
static TGPhotoEditorTab avatarTabs = TGPhotoEditorNoneTab;
dispatch_once(&onceToken, ^
{
if (iosMajorVersion() >= 7)
avatarTabs = TGPhotoEditorCropTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab;
});
TGPhotoEditorTab avatarTabs = TGPhotoEditorCropTab | TGPhotoEditorToolsTab;
if (hasStickers) {
avatarTabs |= TGPhotoEditorPaintTab;
}
return avatarTabs;
}

View File

@ -34,7 +34,7 @@
}
void (^present)(UIImage *) = ^(UIImage *screenImage) {
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSuggestedAvatarIntent adjustments:nil caption:nil screenImage:screenImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab];
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSuggestedAvatarIntent adjustments:nil caption:nil screenImage:screenImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent:true] selectedTab:TGPhotoEditorCropTab];
controller.senderName = senderName;
controller.stickersContext = stickersContext;

View File

@ -695,7 +695,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
_videoTransform = [self transformForOrientation:self.orientation];
CGSize size = [TGMediaVideoConversionPresetSettings maximumSizeForPreset:preset];
NSDictionary *videoSettings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:preset dimensions:size];
NSDictionary *videoSettings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:preset dimensions:size frameRate:30];
[recorder addVideoTrackWithSourceFormatDescription:self.outputVideoFormatDescription transform:CGAffineTransformIdentity settings:videoSettings];
_recorder = recorder;

View File

@ -423,14 +423,14 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
self.isAvatar = ((adjustments as? TGVideoEditAdjustments)?.documentId ?? 0) != 0
var renderEntities: [LegacyPaintEntity] = []
if let account = account, let paintingData = adjustments.paintingData, let entitiesData = paintingData.entitiesData {
if let paintingData = adjustments.paintingData, let entitiesData = paintingData.entitiesData {
let entities = decodeDrawingEntities(data: entitiesData)
for entity in entities {
if let sticker = entity as? DrawingStickerEntity {
if let sticker = entity as? DrawingStickerEntity, let account {
renderEntities.append(LegacyPaintStickerEntity(account: account, entity: sticker))
} else if let text = entity as? DrawingTextEntity {
renderEntities.append(LegacyPaintTextEntity(entity: text))
if let renderSubEntities = text.renderSubEntities {
if let renderSubEntities = text.renderSubEntities, let account {
for entity in renderSubEntities {
renderEntities.append(LegacyPaintStickerEntity(account: account, entity: entity))
}

View File

@ -15,7 +15,7 @@ public extension EnginePeer {
} else if let _ = user.phone {
return "" //formatPhoneNumber("+\(phone)")
} else {
return ""
return "Deleted Account"
}
case let .legacyGroup(group):
return group.title

View File

@ -238,28 +238,29 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
}
let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
let controller = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer)
let sheetController = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer)
let dismissImpl = { [weak model] in
model?.dismiss(true, false)
dismissAll()
}
controller.send = {
sheetController.send = {
completed(item.asset, false, nil, {
dismissImpl()
})
}
controller.sendSilently = {
sheetController.sendSilently = {
completed(item.asset, true, nil, {
dismissImpl()
})
}
controller.schedule = {
sheetController.schedule = {
presentSchedulePicker(true, { time in
completed(item.asset, false, time, {
dismissImpl()
})
})
}
controller.sendWithTimer = {
sheetController.sendWithTimer = {
presentTimerPicker { time in
var items = selectionContext.selectedItems() ?? []
items.append(item.asset as Any)
@ -273,10 +274,10 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
})
}
}
controller.customDismissBlock = { [weak legacySheetController] in
sheetController.customDismissBlock = { [weak legacySheetController] in
legacySheetController?.dismiss()
}
legacySheetController.bind(controller: controller)
legacySheetController.bind(controller: sheetController)
present(legacySheetController, nil)
let hapticFeedback = HapticFeedback()

View File

@ -235,7 +235,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.scrollingArea = SparseItemGridScrollingArea()
self.cameraActivateAreaNode = AccessibilityAreaNode()
self.cameraActivateAreaNode.accessibilityLabel = "Camera"
self.cameraActivateAreaNode.accessibilityLabel = self.presentationData.strings.MediaPicker_VoiceOver_Camera
self.cameraActivateAreaNode.accessibilityTraits = [.button]
super.init()

View File

@ -119,6 +119,10 @@ final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
let blurredBackgroundColor = (background.inverted ? UIColor(rgb: 0xffffff, alpha: 0.1) : UIColor(rgb: 0x000000, alpha: 0.2), dateFillNeedsBlur(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper))
let blurredBackgroundNode = NavigationBackgroundNode(color: blurredBackgroundColor.0, enableBlur: blurredBackgroundColor.1)
self.blurredBackgroundNode = blurredBackgroundNode
if isReduceTransparencyEnabled() {
blurredBackgroundNode.alpha = 0.1
}
}
self.backgroundNode = ASImageNode()

View File

@ -1100,11 +1100,11 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
return
}
if updateFlags != currentFlags {
if let updateFlags, updateFlags != currentFlags {
updateState { current in
return current.withUpdatedUpdating(true)
}
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags ?? []), rank: effectiveRank) |> deliverOnMainQueue).start(error: { error in
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: effectiveRank) |> deliverOnMainQueue).start(error: { error in
updateState { current in
return current.withUpdatedUpdating(false)
}
@ -1145,7 +1145,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
}
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: {
updated(TelegramChatAdminRights(rights: updateFlags ?? []))
updated(TelegramChatAdminRights(rights: updateFlags))
dismissImpl?()
}))
} else if let updateRank = updateRank, let currentFlags = currentFlags {

View File

@ -908,7 +908,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
} else if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
selectedType = .publicChannel
} else if case .initialSetup = mode {
selectedType = .publicChannel
selectedType = .privateChannel
} else {
selectedType = .privateChannel
}
@ -1803,6 +1803,15 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
var footerItem: ItemListControllerFooterItem?
var isGroup = false
if let peer = peer as? TelegramChannel {
if case .group = peer.info {
isGroup = true
}
} else if let _ = peer as? TelegramGroup {
isGroup = true
}
var rightNavigationButton: ItemListNavigationButton?
if case .revokeNames = mode {
let count = Int32(publicChannelsToRevoke?.count ?? 0)
@ -1993,7 +2002,13 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
_ = (ApplicationSpecificNotice.getSetPublicChannelLink(accountManager: context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { showAlert in
if showAlert {
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Group_Edit_PrivatePublicLinkAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: invokeAction)]), nil)
let text: String
if isGroup {
text = presentationData.strings.Group_Edit_PrivatePublicLinkAlert
} else {
text = presentationData.strings.Channel_Edit_PrivatePublicLinkAlert
}
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: invokeAction)]), nil)
} else {
invokeAction()
}
@ -2013,16 +2028,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
if state.revokingPeerId != nil {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
}
var isGroup = false
if let peer = peer as? TelegramChannel {
if case .group = peer.info {
isGroup = true
}
} else if let _ = peer as? TelegramGroup {
isGroup = true
}
let leftNavigationButton: ItemListNavigationButton?
switch mode {
case .initialSetup:

View File

@ -13,6 +13,9 @@ import AccountContext
public func autodownloadDataSizeString(_ size: Int64, decimalSeparator: String = ".") -> String {
if size >= 1024 * 1024 * 1024 {
var remainder = (size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102)
if remainder == 10 {
remainder = 9
}
while remainder != 0 && remainder % 10 == 0 {
remainder /= 10
}
@ -24,6 +27,9 @@ public func autodownloadDataSizeString(_ size: Int64, decimalSeparator: String =
}
} else if size >= 1024 * 1024 {
var remainder = (size % (1024 * 1024)) / (1024 * 102)
if remainder == 10 {
remainder = 9
}
while remainder != 0 && remainder % 10 == 0 {
remainder /= 10
}

View File

@ -400,7 +400,7 @@ public final class PendingMessageManager {
return lhs.1.index < rhs.1.index
}) {
if case let .collectingInfo(message) = messageContext.state {
let contentToUpload = messageContentToUpload(network: strongSelf.network, postbox: strongSelf.postbox, auxiliaryMethods: strongSelf.auxiliaryMethods, transformOutgoingMessageMedia: strongSelf.transformOutgoingMessageMedia, messageMediaPreuploadManager: strongSelf.messageMediaPreuploadManager, revalidationContext: strongSelf.revalidationContext, forceReupload: messageContext.forcedReuploadOnce, isGrouped: message.groupingKey != nil, message: message)
let contentToUpload = messageContentToUpload(network: strongSelf.network, postbox: strongSelf.postbox, auxiliaryMethods: strongSelf.auxiliaryMethods, transformOutgoingMessageMedia: strongSelf.transformOutgoingMessageMedia, messageMediaPreuploadManager: strongSelf.messageMediaPreuploadManager, revalidationContext: strongSelf.revalidationContext, forceReupload: messageContext.forcedReuploadOnce, isGrouped: message.groupingKey != nil, message: message)
messageContext.contentType = contentToUpload.type
switch contentToUpload {
case let .immediate(result, type):

View File

@ -245,10 +245,11 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
}
}
case let .name(shortName):
let shortName = shortName.lowercased()
for namespace in namespaces {
for info in transaction.getItemCollectionsInfos(namespace: namespace) {
if let info = info.1 as? StickerPackCollectionInfo {
if info.shortName == shortName {
if info.shortName.lowercased() == shortName {
let items = transaction.getItemCollectionItems(collectionId: info.id)
if !items.isEmpty {
return (info, items.compactMap { $0 as? StickerPackItem }, true)

View File

@ -623,7 +623,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
let inputPanel = PresentationThemeChatInputPanel(
panelBackgroundColor: rootNavigationBar.blurredBackgroundColor,
panelBackgroundColorNoWallpaper: UIColor(rgb: 0x000000, alpha: 0.94),
panelBackgroundColorNoWallpaper: UIColor(rgb: 0x000000),
panelSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
panelControlAccentColor: UIColor(rgb: 0xffffff),
panelControlColor: UIColor(rgb: 0x808080),

View File

@ -877,7 +877,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
let inputPanel = PresentationThemeChatInputPanel(
panelBackgroundColor: rootNavigationBar.blurredBackgroundColor,
panelBackgroundColorNoWallpaper: rootNavigationBar.blurredBackgroundColor,
panelBackgroundColorNoWallpaper: UIColor(rgb: 0xffffff),
panelSeparatorColor: UIColor(rgb: 0xb2b2b2),
panelControlAccentColor: defaultDayAccentColor,
panelControlColor: UIColor(rgb: 0x858e99),

View File

@ -51,6 +51,34 @@ public func plainServiceMessageString(strings: PresentationStrings, nameDisplayO
}
}
private func peerDisplayTitles(_ peerIds: [PeerId], _ dict: SimpleDictionary<PeerId, Peer>, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
var peers: [Peer] = []
for id in peerIds {
if let peer = dict[id] {
peers.append(peer)
}
}
return peerDisplayTitles(peers, strings: strings, nameDisplayOrder: nameDisplayOrder)
}
private func peerDisplayTitles(_ peers: [Peer], strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
if peers.count == 0 {
return ""
} else {
var string = ""
var first = true
for peer in peers {
if first {
first = false
} else {
string.append(", ")
}
string.append(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder))
}
return string
}
}
public func universalServiceMessageString(presentationData: (PresentationTheme, TelegramWallpaper)?, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool, forForumOverview: Bool) -> NSAttributedString? {
var attributedString: NSAttributedString?
@ -96,9 +124,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let resultTitleString: PresentationStrings.FormattedString
if peerIds.count == 1 {
attributePeerIds.append((1, peerIds.first))
resultTitleString = strings.Notification_Invited(authorName, peerDebugDisplayTitles(peerIds, message.peers))
resultTitleString = strings.Notification_Invited(authorName, peerDisplayTitles(peerIds, message.peers, strings: strings, nameDisplayOrder: nameDisplayOrder))
} else {
resultTitleString = strings.Notification_InvitedMultiple(authorName, peerDebugDisplayTitles(peerIds, message.peers))
resultTitleString = strings.Notification_InvitedMultiple(authorName, peerDisplayTitles(peerIds, message.peers, strings: strings, nameDisplayOrder: nameDisplayOrder))
}
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
@ -115,7 +143,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
if peerIds.count == 1 {
attributePeerIds.append((1, peerIds.first))
}
attributedString = addAttributesToStringWithRanges(strings.Notification_Kicked(authorName, peerDebugDisplayTitles(peerIds, message.peers))._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
attributedString = addAttributesToStringWithRanges(strings.Notification_Kicked(authorName, peerDisplayTitles(peerIds, message.peers, strings: strings, nameDisplayOrder: nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
}
case let .photoUpdated(image):
if authorName.isEmpty || isChannel {
@ -393,10 +421,18 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
string = strings.Conversation_AutoremoveTimerRemovedUser(authorString).string
}
} else if let _ = messagePeer as? TelegramGroup {
string = strings.Conversation_AutoremoveTimerRemovedGroup
if message.author?.id == accountPeerId {
string = strings.Conversation_AutoremoveTimerRemovedUserYou
} else {
string = strings.Conversation_AutoremoveTimerRemovedGroup
}
} else if let channel = messagePeer as? TelegramChannel {
if case .group = channel.info {
string = strings.Conversation_AutoremoveTimerRemovedGroup
if message.author?.id == accountPeerId {
string = strings.Conversation_AutoremoveTimerRemovedUserYou
} else {
string = strings.Conversation_AutoremoveTimerRemovedGroup
}
} else {
string = strings.Conversation_AutoremoveTimerRemovedChannel
}
@ -644,10 +680,10 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
resultTitleString = strings.Notification_VoiceChatInvitationForYou(authorName)
} else {
attributePeerIds.append((1, peerIds.first))
resultTitleString = strings.Notification_VoiceChatInvitation(authorName, peerDebugDisplayTitles(peerIds, message.peers))
resultTitleString = strings.Notification_VoiceChatInvitation(authorName, peerDisplayTitles(peerIds, message.peers, strings: strings, nameDisplayOrder: nameDisplayOrder))
}
} else {
resultTitleString = strings.Notification_VoiceChatInvitation(authorName, peerDebugDisplayTitles(peerIds, message.peers))
resultTitleString = strings.Notification_VoiceChatInvitation(authorName, peerDisplayTitles(peerIds, message.peers, strings: strings, nameDisplayOrder: nameDisplayOrder))
}
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))

View File

@ -513,6 +513,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var updatedClosedPinnedMessageId: ((MessageId) -> Void)?
var requestedUnpinAllMessages: ((Int, MessageId) -> Void)?
public var isSelectingMessagesUpdated: ((Bool) -> Void)?
private let scrolledToMessageId = ValuePromise<ScrolledToMessageId?>(nil, ignoreRepeated: true)
private var scrolledToMessageIdValue: ScrolledToMessageId? = nil {
didSet {
@ -2625,14 +2627,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(peers.count - 1)").string
} else {
text = ""
@ -3057,6 +3063,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
strongSelf.commitPurposefulAction()
var isScheduledMessages = false
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
isScheduledMessages = true
}
guard !isScheduledMessages else {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
|> deliverOnMainQueue).start(next: { message in
guard let strongSelf = self, let message = message else {
@ -4222,7 +4238,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}
let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationSend, action: {
completion()
@ -4234,15 +4250,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else {
return
}
var isChannel = false
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
isChannel = true
}
let peerName = EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
presentConfirmation(peerName, isChannel, {
if case .user = peerType {
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peer.id).start()
controller?.dismiss()
})
} else {
var isChannel = false
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
isChannel = true
}
let peerName = EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
presentConfirmation(peerName, isChannel, {
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peer.id).start()
controller?.dismiss()
})
}
}
createNewGroupImpl = { [weak controller] in
switch peerType {
@ -11437,6 +11457,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.recordingActivityPromise.set(recordingActivityValue)
}
if (self.presentationInterfaceState.interfaceState.selectionState == nil) != (updatedChatPresentationInterfaceState.interfaceState.selectionState == nil) {
self.isSelectingMessagesUpdated?(updatedChatPresentationInterfaceState.interfaceState.selectionState != nil)
}
self.presentationInterfaceState = updatedChatPresentationInterfaceState
self.updateSlowmodeStatus()
@ -11660,6 +11684,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}), in: .current)
}
public func cancelSelectingMessages() {
self.navigationButtonAction(.cancelMessageSelection)
}
private func navigationButtonAction(_ action: ChatNavigationButtonAction) {
switch action {
case .spacer, .toggleInfoPanel:
@ -14587,7 +14615,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId
if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, threadId: self.chatLocation.threadId, botId: results.botId, result: result, replyToMessageId: replyMessageId, hideVia: hideVia, silentPosting: silentPosting) {
if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, threadId: self.chatLocation.threadId, botId: results.botId, result: result, replyToMessageId: replyMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime) {
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
if let strongSelf = self {
strongSelf.chatDisplayNode.collapseInput()
@ -15943,14 +15971,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
savedMessages = true
} else {
if displayPeers.count == 1, let peer = displayPeers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = displayPeers.first {
let peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string
} else {
text = ""

View File

@ -2627,7 +2627,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
context: self.context,
currentInputData: inputMediaNodeData,
updatedInputData: self.inputMediaNodeDataPromise.get(),
defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty || self.openStickersBeginWithEmoji,
defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || self.openStickersBeginWithEmoji,
controllerInteraction: self.controllerInteraction,
interfaceInteraction: self.interfaceInteraction,
chatPeerId: peerId

View File

@ -354,47 +354,50 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
let isTextEmpty = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0
if chatPresentationInterfaceState.interfaceState.forwardMessageIds == nil {
if isTextEmpty && chatPresentationInterfaceState.hasScheduledMessages {
accessoryItems.append(.scheduledMessages)
let hasForward = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil
if isTextEmpty && chatPresentationInterfaceState.hasScheduledMessages && !hasForward {
accessoryItems.append(.scheduledMessages)
}
var stickersEnabled = true
var stickersAreEmoji = !isTextEmpty
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(peer) {
accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting))
}
var stickersEnabled = true
let stickersAreEmoji = !isTextEmpty
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(peer) {
accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting))
}
if peer.hasBannedPermission(.banSendStickers) != nil {
stickersEnabled = false
}
} else if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
if peer.hasBannedPermission(.banSendStickers) {
stickersEnabled = false
}
if peer.hasBannedPermission(.banSendStickers) != nil {
stickersEnabled = false
}
if isTextEmpty && chatPresentationInterfaceState.hasBots && chatPresentationInterfaceState.hasBotCommands {
accessoryItems.append(.commands)
}
if !canSendTextMessages {
if stickersEnabled && !stickersAreEmoji {
accessoryItems.append(.input(isEnabled: true, inputMode: .stickers))
}
} else {
if stickersEnabled {
accessoryItems.append(.input(isEnabled: true, inputMode: stickersAreEmoji ? .emoji : .stickers))
} else {
accessoryItems.append(.input(isEnabled: true, inputMode: .emoji))
}
}
if isTextEmpty, let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id {
accessoryItems.append(.botInput(isEnabled: true, inputMode: .bot))
} else if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
if peer.hasBannedPermission(.banSendStickers) {
stickersEnabled = false
}
}
if isTextEmpty && chatPresentationInterfaceState.hasBots && chatPresentationInterfaceState.hasBotCommands && !hasForward {
accessoryItems.append(.commands)
}
if !canSendTextMessages {
if stickersEnabled && !stickersAreEmoji && !hasForward {
accessoryItems.append(.input(isEnabled: true, inputMode: .stickers))
}
} else {
stickersAreEmoji = stickersAreEmoji || hasForward
if stickersEnabled {
accessoryItems.append(.input(isEnabled: true, inputMode: stickersAreEmoji ? .emoji : .stickers))
} else {
accessoryItems.append(.input(isEnabled: true, inputMode: .emoji))
}
}
if isTextEmpty, let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id {
accessoryItems.append(.botInput(isEnabled: true, inputMode: .bot))
}
return ChatTextInputPanelState(accessoryItems: accessoryItems, contextPlaceholder: contextPlaceholder, mediaRecordingState: chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState)
}
}

View File

@ -53,6 +53,50 @@ private final class ChatMessageBubbleClippingNode: ASDisplayNode {
}
}
private func hasCommentButton(item: ChatMessageItem) -> Bool {
let firstMessage = item.content.firstMessage
var hasDiscussion = false
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
hasDiscussion = true
}
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id {
hasDiscussion = false
}
if firstMessage.adAttribute != nil {
hasDiscussion = false
}
if hasDiscussion {
var canComment = false
if case .pinnedMessages = item.associatedData.subject {
canComment = false
} else if firstMessage.id.namespace == Namespaces.Message.Local {
canComment = true
} else {
for attribute in firstMessage.attributes {
if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId {
switch item.associatedData.channelDiscussionGroup {
case .unknown:
canComment = true
case let .known(groupId):
canComment = groupId == commentsPeerId
}
break
}
}
}
if canComment {
return true
}
} else if firstMessage.id.peerId.isReplies {
return true
}
return false
}
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], Bool, Bool) {
var result: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] = []
var skipText = false
@ -65,7 +109,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
var needReactions = true
var disableComments = false
var hasSeparateCommentsButton = false
outer: for (message, itemAttributes) in item.content {
for attribute in message.attributes {
@ -87,7 +131,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
if isVideo {
if file.isInstantVideo {
disableComments = true
hasSeparateCommentsButton = true
result.append((message, ChatMessageInstantVideoBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
} else {
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), message.text.isEmpty {
@ -222,43 +266,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
needReactions = false
}
if !isAction && !disableComments && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) {
var hasDiscussion = false
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
hasDiscussion = true
}
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id {
hasDiscussion = false
}
if firstMessage.adAttribute != nil {
hasDiscussion = false
}
if hasDiscussion {
var canComment = false
if case .pinnedMessages = item.associatedData.subject {
canComment = false
} else if firstMessage.id.namespace == Namespaces.Message.Local {
canComment = true
} else {
for attribute in firstMessage.attributes {
if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId {
switch item.associatedData.channelDiscussionGroup {
case .unknown:
canComment = true
case let .known(groupId):
canComment = groupId == commentsPeerId
}
break
}
}
}
if canComment {
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
}
} else if firstMessage.id.peerId.isReplies {
if !isAction && !hasSeparateCommentsButton && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) {
if hasCommentButton(item: item) {
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
}
}
@ -1292,6 +1301,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if !needsShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo, !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) {
needsShareButton = true
}
var mayHaveSeparateCommentsButton = false
if !needsShareButton {
loop: for media in item.message.media {
if media is TelegramMediaGame || media is TelegramMediaInvoice {
@ -1307,12 +1317,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if media is TelegramMediaAction {
needsShareButton = false
break loop
} else if let media = media as? TelegramMediaFile, media.isInstantVideo {
mayHaveSeparateCommentsButton = true
break loop
}
}
}
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
needsShareButton = false
if (item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected()) {
if mayHaveSeparateCommentsButton && hasCommentButton(item: item) {
} else {
needsShareButton = false
}
}
}

View File

@ -238,7 +238,6 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
var updatedReplyBackgroundNode: NavigationBackgroundNode?
var updatedInstantVideoBackgroundImage: UIImage?
let instantVideoBackgroundImage: UIImage?
@ -556,17 +555,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
let effectiveAudioTranscriptionState = updatedAudioTranscriptionState ?? audioTranscriptionState
if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil {
if let currentReplyBackgroundNode = currentReplyBackgroundNode {
updatedReplyBackgroundNode = currentReplyBackgroundNode
} else {
updatedReplyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper))
}
updatedReplyBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate)
}
return (result, { [weak self] layoutData, animation in
if let strongSelf = self {
strongSelf.item = item
@ -575,6 +564,17 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.secretProgressIcon = secretProgressIcon
strongSelf.automaticDownload = automaticDownload
var updatedReplyBackgroundNode: NavigationBackgroundNode?
if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil {
if let currentReplyBackgroundNode = currentReplyBackgroundNode {
updatedReplyBackgroundNode = currentReplyBackgroundNode
} else {
updatedReplyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper))
}
updatedReplyBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate)
}
if let updatedAudioTranscriptionState = updatedAudioTranscriptionState {
strongSelf.audioTranscriptionState = updatedAudioTranscriptionState
strongSelf.updateTranscriptionExpanded?(strongSelf.audioTranscriptionState)
@ -928,7 +928,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.addSubnode(viaBotNode)
}
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? 11.0 : (width - messageInfoSize.width - bubbleEdgeInset - 9.0 + 10.0)), y: 8.0), size: viaBotLayout.size)
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (displayVideoFrame.maxX - width + 6.0) : (width - messageInfoSize.width - bubbleEdgeInset - 9.0 + 10.0)), y: 8.0), size: viaBotLayout.size)
animation.animator.updateFrame(layer: viaBotNode.layer, frame: viaBotFrame, completion: nil)
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height)
@ -1226,6 +1226,30 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap:
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
if let item = self.item {
for attribute in item.message.attributes {
if let attribute = attribute as? InlineBotMessageAttribute {
var botAddressName: String?
if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName {
botAddressName = addressName
} else {
botAddressName = attribute.title
}
if let botAddressName = botAddressName {
item.controllerInteraction.updateInputState { textInputState in
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
}
item.controllerInteraction.updateInputMode { _ in
return .text
}
return
}
}
}
}
}
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
if let item = self.item {
for attribute in item.message.attributes {
@ -1315,6 +1339,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
return playbackNode.view
}
}
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(point), !viaBotNode.alpha.isZero {
return self.view
}
if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(point), !forwardInfoNode.alpha.isZero {
return self.view
}

View File

@ -975,6 +975,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .groupBotStart:
break
case .gameStart:
break
case let .channelMessage(peer, messageId, timecode):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), subject: .message(id: .id(messageId), highlight: true, timecode: timecode)))

View File

@ -379,11 +379,11 @@ private func calculateTextFieldRealInsets(presentationInterfaceState: ChatPresen
return UIEdgeInsets(top: 4.5 + top, left: 0.0, bottom: 5.5 + bottom, right: right)
}
private var currentTextInputBackgroundImage: (UIColor, UIColor, CGFloat, UIImage)?
private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackgroundColor: UIColor?, strokeColor: UIColor, diameter: CGFloat) -> UIImage? {
private var currentTextInputBackgroundImage: (UIColor, UIColor, CGFloat, CGFloat, UIImage)?
private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackgroundColor: UIColor?, strokeColor: UIColor, diameter: CGFloat, strokeWidth: CGFloat) -> UIImage? {
if let backgroundColor = backgroundColor, let current = currentTextInputBackgroundImage {
if current.0.isEqual(backgroundColor) && current.1.isEqual(strokeColor) && current.2.isEqual(to: diameter) {
return current.3
if current.0.isEqual(backgroundColor) && current.1.isEqual(strokeColor) && current.2.isEqual(to: diameter) && current.3.isEqual(to: strokeWidth) {
return current.4
}
}
@ -401,13 +401,12 @@ private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackground
context.setBlendMode(.normal)
context.setStrokeColor(strokeColor.cgColor)
let strokeWidth: CGFloat = UIScreenPixel
context.setLineWidth(strokeWidth)
context.strokeEllipse(in: CGRect(x: strokeWidth / 2.0, y: strokeWidth / 2.0, width: diameter - strokeWidth, height: diameter - strokeWidth))
})?.stretchableImage(withLeftCapWidth: Int(diameter) / 2, topCapHeight: Int(diameter) / 2)
if let image = image {
if let backgroundColor = backgroundColor {
currentTextInputBackgroundImage = (backgroundColor, strokeColor, diameter, image)
currentTextInputBackgroundImage = (backgroundColor, strokeColor, diameter, strokeWidth, image)
}
return image
} else {
@ -1290,7 +1289,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
if self.theme == nil || !self.theme!.chat.inputPanel.inputTextColor.isEqual(interfaceState.theme.chat.inputPanel.inputTextColor) {
let textColor = interfaceState.theme.chat.inputPanel.inputTextColor
let tintColor = interfaceState.theme.list.itemAccentColor
let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize)
if let textInputNode = self.textInputNode {
@ -1302,12 +1300,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
textInputNode.selectedRange = range
}
textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(baseFontSize), NSAttributedString.Key.foregroundColor.rawValue: textColor]
textInputNode.tintColor = tintColor
self.updateSpoiler()
}
}
let tintColor = interfaceState.theme.list.itemAccentColor
if let textInputNode = self.textInputNode, tintColor != textInputNode.tintColor {
textInputNode.tintColor = tintColor
textInputNode.tintColorDidChange()
}
let keyboardAppearance = interfaceState.theme.rootController.keyboardColor.keyboardAppearance
if let textInputNode = self.textInputNode, textInputNode.keyboardAppearance != keyboardAppearance, textInputNode.isFirstResponder() {
if textInputNode.isCurrentlyEmoji() {
@ -1332,15 +1335,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics)
let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight
let strokeWidth: CGFloat
let backgroundColor: UIColor
if case let .color(color) = interfaceState.chatWallpaper, UIColor(rgb: color).isEqual(interfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) {
backgroundColor = interfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper
strokeWidth = 1.0 - UIScreenPixel
} else {
backgroundColor = interfaceState.theme.chat.inputPanel.panelBackgroundColor
strokeWidth = UIScreenPixel
}
self.textInputBackgroundNode.image = textInputBackgroundImage(backgroundColor: backgroundColor, inputBackgroundColor: nil, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight)
self.transparentTextInputBackgroundImage = textInputBackgroundImage(backgroundColor: nil, inputBackgroundColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight)
self.textInputBackgroundNode.image = textInputBackgroundImage(backgroundColor: backgroundColor, inputBackgroundColor: nil, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight, strokeWidth: strokeWidth)
self.transparentTextInputBackgroundImage = textInputBackgroundImage(backgroundColor: nil, inputBackgroundColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight, strokeWidth: strokeWidth)
self.textInputContainerBackgroundNode.image = generateStretchableFilledCircleImage(diameter: minimalInputHeight, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor)
self.searchLayoutClearImageNode.image = PresentationResourcesChat.chatInputTextFieldClearImage(interfaceState.theme)

View File

@ -162,6 +162,28 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
dismissInput()
navigationController?.pushViewController(controller)
case let .gameStart(botPeerId, game):
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages], hasContactSelector: false, title: presentationData.strings.Bot_AddToChat_Title, selectForumThreads: true))
controller.peerSelected = { [weak controller] peer, _ in
let _ = peer.id
let _ = botPeerId
let _ = game
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text: String
if let peer = peer as? TelegramUser {
text = presentationData.strings.Target_ShareGameConfirmationPrivate(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
} else {
text = presentationData.strings.Target_ShareGameConfirmationGroup(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
}
let alertController = textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.RequestPeer_SelectionConfirmationSend, action: {
controller?.dismiss()
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})])
present(alertController, nil)
}
dismissInput()
navigationController?.pushViewController(controller)
case let .channelMessage(peer, messageId, timecode):
openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil))
case let .replyThreadMessage(replyThreadMessage, messageId):

View File

@ -86,14 +86,18 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
} else {
text = ""

View File

@ -2495,7 +2495,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if actions.options.contains(.deleteGlobally) {
let globalTitle: String
if isChannel {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone
} else if let personalPeerName = personalPeerName {
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string
} else {
@ -8000,14 +8000,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
savedMessages = true
} else {
if displayPeers.count == 1, let peer = displayPeers.first {
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
} else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last {
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string
} else if let peer = displayPeers.first {
let peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string
} else {
text = ""
@ -8921,7 +8925,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let offsetY = self.scrollNode.view.contentOffset.y
if self.isSettings, !(self.controller?.movingInHierarchy == true) {
let bottomOffsetY = max(0.0, self.scrollNode.view.contentSize.height + min(83.0, self.scrollNode.view.contentInset.bottom) - offsetY - self.scrollNode.frame.height)
var bottomInset = self.scrollNode.view.contentInset.bottom
if let layout = self.validLayout?.0, case .compact = layout.metrics.widthClass {
bottomInset = min(83.0, bottomInset)
}
let bottomOffsetY = max(0.0, self.scrollNode.view.contentSize.height + bottomInset - offsetY - self.scrollNode.frame.height)
let backgroundAlpha: CGFloat = min(30.0, bottomOffsetY) / 30.0
if let tabBarController = self.controller?.parent as? TabBarController {

View File

@ -67,6 +67,7 @@ public enum ParsedInternalPeerUrlParameter {
case botStart(String)
case groupBotStart(String, ResolvedBotAdminRights?)
case attachBotStart(String, String?)
case gameStart(String)
case channelMessage(Int32, Double?)
case replyThread(Int32, Int32)
case voiceChat(String?)
@ -212,7 +213,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
}
return .peer(.name(peerName), .groupBotStart(value, botAdminRights))
} else if queryItem.name == "game" {
return nil
return .peer(.name(peerName), .gameStart(value))
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
return .peer(.name(peerName), .voiceChat(value))
} else if queryItem.name == "startattach" {
@ -582,6 +583,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return .single(.botStart(peer: peer, payload: payload))
case let .groupBotStart(payload, adminRights):
return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights))
case let .gameStart(game):
return .single(.gameStart(peerId: peer.id, game: game))
case let .attachBotStart(name, payload):
return context.engine.peers.resolvePeerByName(name: name)
|> take(1)

View File

@ -36,7 +36,7 @@ func presentLegacyWebSearchEditor(context: AccountContext, theme: PresentationTh
let paintStickersContext = LegacyPaintStickersContext(context: context)
let controller = TGPhotoEditorController(context: legacyController.context, item: item, intent: TGPhotoEditorControllerAvatarIntent, adjustments: nil, caption: nil, screenImage: screenImage ?? UIImage(), availableTabs: TGPhotoEditorController.defaultTabsForAvatarIntent(), selectedTab: .cropTab)!
let controller = TGPhotoEditorController(context: legacyController.context, item: item, intent: TGPhotoEditorControllerAvatarIntent, adjustments: nil, caption: nil, screenImage: screenImage ?? UIImage(), availableTabs: TGPhotoEditorController.defaultTabs(forAvatarIntent: true), selectedTab: .cropTab)!
controller.stickersContext = paintStickersContext
legacyController.bind(controller: controller)