mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
11a4dbf0a0
commit
ec8bd9acdd
24 changed files with 570 additions and 371 deletions
|
|
@ -16355,3 +16355,7 @@ Error: %8$@";
|
|||
"Chat.ContextMenu.OpenInApp" = "Open In-App";
|
||||
|
||||
"CreatePoll.Link.Description" = "Attach a link to this option.";
|
||||
|
||||
"ChatList.ClearSearchHistory.Confirm" = "Clear";
|
||||
|
||||
"ChatList.RemoveFolderConfirmationTitle" = "Remove %@?";
|
||||
|
|
|
|||
|
|
@ -1125,7 +1125,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
}, setupEditMessage: { _, _ in
|
||||
}, beginMessageSelection: { _, _ in
|
||||
}, cancelMessageSelection: { _ in
|
||||
}, deleteSelectedMessages: {
|
||||
}, deleteSelectedMessages: { _ in
|
||||
}, reportSelectedMessages: {
|
||||
}, reportMessages: { _, _ in
|
||||
}, blockMessageAuthor: { _, _ in
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ swift_library(
|
|||
"//submodules/SearchBarNode:SearchBarNode",
|
||||
"//submodules/ChatListSearchRecentPeersNode:ChatListSearchRecentPeersNode",
|
||||
"//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader",
|
||||
"//submodules/TelegramUI/Components/SectionTitleContextItem",
|
||||
"//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager",
|
||||
"//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager",
|
||||
"//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode",
|
||||
|
|
|
|||
|
|
@ -4487,11 +4487,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let filter = filters.first(where: { $0.id == id }) else {
|
||||
guard let filter = filters.first(where: { $0.id == id }), case let .filter(_, title, _, data) = filter else {
|
||||
return
|
||||
}
|
||||
|
||||
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||
if data.isShared {
|
||||
let _ = (combineLatest(
|
||||
self.context.engine.data.get(
|
||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))),
|
||||
|
|
@ -4573,24 +4573,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
})
|
||||
} else {
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: self.presentationData.strings.ChatList_RemoveFolderConfirmation),
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
apply()
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
let alertController = textAlertController(context: context, title: self.presentationData.strings.ChatList_RemoveFolderConfirmationTitle(title.text).string, text: self.presentationData.strings.ChatList_RemoveFolderConfirmation, actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
||||
}),
|
||||
TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.ChatList_RemoveFolderAction, action: {
|
||||
apply()
|
||||
})
|
||||
])
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
self.present(alertController, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,13 +461,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||
let _ = (context.engine.peers.currentChatListFilters()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { filters in
|
||||
guard let filter = filters.first(where: { $0.id == id }) else {
|
||||
guard let filter = filters.first(where: { $0.id == id }), case let .filter(_, title, _, data) = filter else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||
if data.isShared {
|
||||
let _ = (combineLatest(
|
||||
context.engine.data.get(
|
||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))),
|
||||
|
|
@ -539,31 +539,21 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||
}
|
||||
})
|
||||
} else {
|
||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: presentationData.strings.ChatList_RemoveFolderConfirmation),
|
||||
ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == id }) {
|
||||
filters.remove(at: index)
|
||||
}
|
||||
return filters
|
||||
let alertController = textAlertController(context: context, title: presentationData.strings.ChatList_RemoveFolderConfirmationTitle(title.text).string, text: presentationData.strings.ChatList_RemoveFolderConfirmation, actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
}),
|
||||
TextAlertAction(type: .destructiveAction, title: presentationData.strings.ChatList_RemoveFolderAction, action: {
|
||||
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == id }) {
|
||||
filters.remove(at: index)
|
||||
}
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
return filters
|
||||
}
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
})
|
||||
])
|
||||
presentControllerImpl?(actionSheet)
|
||||
presentControllerImpl?(alertController)
|
||||
}
|
||||
})
|
||||
}, updateDisplayTags: { value in
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import PremiumUI
|
|||
import AvatarNode
|
||||
import StoryContainerScreen
|
||||
import ChatListSearchFiltersContainerNode
|
||||
import SectionTitleContextItem
|
||||
import EdgeEffect
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
|
@ -53,7 +54,7 @@ final class ChatListSearchInteraction {
|
|||
let openDisabledPeer: (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void
|
||||
let openMessage: (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void
|
||||
let openUrl: (String) -> Void
|
||||
let clearRecentSearch: () -> Void
|
||||
let clearRecentSearch: (ASDisplayNode) -> Void
|
||||
let addContact: (String) -> Void
|
||||
let toggleMessageSelection: (EngineMessage.Id, Bool) -> Void
|
||||
let messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)
|
||||
|
|
@ -67,7 +68,7 @@ final class ChatListSearchInteraction {
|
|||
let dismissSearch: () -> Void
|
||||
let openAdInfo: (ASDisplayNode, AdPeer) -> Void
|
||||
|
||||
init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set<EngineMessage.Id>?, openStories: ((EnginePeer.Id, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void, dismissSearch: @escaping () -> Void, openAdInfo: @escaping (ASDisplayNode, AdPeer) -> Void) {
|
||||
init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping (ASDisplayNode) -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set<EngineMessage.Id>?, openStories: ((EnginePeer.Id, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void, dismissSearch: @escaping () -> Void, openAdInfo: @escaping (ASDisplayNode, AdPeer) -> Void) {
|
||||
self.openPeer = openPeer
|
||||
self.openDisabledPeer = openDisabledPeer
|
||||
self.openMessage = openMessage
|
||||
|
|
@ -88,6 +89,20 @@ final class ChatListSearchInteraction {
|
|||
}
|
||||
}
|
||||
|
||||
private final class ChatListSearchClearRecentReferenceContentSource: ContextReferenceContentSource {
|
||||
private let sourceNode: ASDisplayNode
|
||||
|
||||
let keepInPlace: Bool = true
|
||||
|
||||
init(sourceNode: ASDisplayNode) {
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .bottom)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ChatListSearchContainerNodeSearchState: Equatable {
|
||||
var selectedMessageIds: Set<EngineMessage.Id>?
|
||||
|
||||
|
|
@ -241,30 +256,34 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||
self?.dismissInput()
|
||||
}, contentContext: nil, progress: nil, completion: nil)
|
||||
}, progress: nil, alertDisplayUpdated: nil, concealedAlertOption: nil)
|
||||
}, clearRecentSearch: { [weak self] in
|
||||
}, clearRecentSearch: { [weak self] sourceNode in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
let presentationData = strongSelf.presentationData
|
||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: presentationData.strings.ChatList_ClearSearchHistory),
|
||||
ActionSheetButtonItem(title: presentationData.strings.WebSearch_RecentSectionClear, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
let items: [ContextMenuItem] = [
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.ChatList_ClearSearchHistory, textFont: .small, icon: { _ in return nil }, action: emptyAction)),
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.ChatList_ClearSearchHistory_Confirm, textColor: .destructive, icon: { theme in
|
||||
return nil
|
||||
}, action: { [weak self] c, _ in
|
||||
let clearImpl = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.engine.peers.clearRecentlySearchedPeers()
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
}
|
||||
let _ = (strongSelf.context.engine.peers.clearRecentlySearchedPeers()
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
})
|
||||
]), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
if let c {
|
||||
c.dismiss(completion: clearImpl)
|
||||
} else {
|
||||
clearImpl()
|
||||
}
|
||||
}))
|
||||
]
|
||||
let controller = makeContextController(presentationData: presentationData, source: .reference(ChatListSearchClearRecentReferenceContentSource(sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||
strongSelf.dismissInput()
|
||||
strongSelf.present?(actionSheet, nil)
|
||||
strongSelf.present?(controller, nil)
|
||||
}, addContact: { phoneNumber in
|
||||
addContact?(phoneNumber)
|
||||
}, toggleMessageSelection: { [weak self] messageId, selected in
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||
peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void,
|
||||
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
||||
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
||||
clearRecentlySearchedPeers: @escaping () -> Void,
|
||||
clearRecentlySearchedPeers: @escaping (ASDisplayNode) -> Void,
|
||||
deletePeer: @escaping (EnginePeer.Id) -> Void,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
|
|
@ -294,8 +294,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||
} else if case .globalPosts = key {
|
||||
header = ChatListSearchItemHeader(type: .text(strings.ChatList_HeaderPublicPosts, 0), theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||
} else {
|
||||
header = ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: { _ in
|
||||
clearRecentlySearchedPeers()
|
||||
header = ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: { sourceNode in
|
||||
clearRecentlySearchedPeers(sourceNode)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1334,7 +1334,7 @@ private func chatListSearchContainerPreparedRecentTransition(
|
|||
peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void,
|
||||
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
||||
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
||||
clearRecentlySearchedPeers: @escaping () -> Void,
|
||||
clearRecentlySearchedPeers: @escaping (ASDisplayNode) -> Void,
|
||||
deletePeer: @escaping (EnginePeer.Id) -> Void,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
|
|
@ -4524,8 +4524,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
} else {
|
||||
gesture?.cancel()
|
||||
}
|
||||
}, clearRecentlySearchedPeers: {
|
||||
interaction.clearRecentSearch()
|
||||
}, clearRecentlySearchedPeers: { sourceNode in
|
||||
interaction.clearRecentSearch(sourceNode)
|
||||
}, deletePeer: { peerId in
|
||||
let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).startStandalone()
|
||||
}, animationCache: strongSelf.animationCache, animationRenderer: strongSelf.animationRenderer, openStories: { peerId, avatarNode in
|
||||
|
|
|
|||
|
|
@ -5096,11 +5096,11 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
nextTitleIconOrigin += 7.0
|
||||
let titleBadgeFrame = CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: titleFrame.minY + floor((titleFrame.height - titleBadgeLayout.size.height) * 0.5)), size: titleBadgeLayout.size)
|
||||
nextTitleIconOrigin += titleBadgeLayout.size.width + 4.0
|
||||
titleBadgeNode.frame = titleBadgeFrame
|
||||
transition.updateFrame(node: titleBadgeNode, frame: titleBadgeFrame)
|
||||
|
||||
var titleBadgeBackgroundFrame = titleBadgeFrame.insetBy(dx: -4.0, dy: -2.0)
|
||||
titleBadgeBackgroundFrame.size.height -= 1.0
|
||||
backgroundView.frame = titleBadgeBackgroundFrame
|
||||
transition.updateFrame(view: backgroundView, frame: titleBadgeBackgroundFrame)
|
||||
if item.presentationData.theme.overallDarkAppearance {
|
||||
backgroundView.tintColor = theme.titleColor.withMultipliedAlpha(0.1)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||
public let setupEditMessage: (EngineMessage.Id?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void
|
||||
public let beginMessageSelection: ([EngineMessage.Id], @escaping (ContainedViewLayoutTransition) -> Void) -> Void
|
||||
public let cancelMessageSelection: (ContainedViewLayoutTransition) -> Void
|
||||
public let deleteSelectedMessages: () -> Void
|
||||
public let deleteSelectedMessages: (UIView?) -> Void
|
||||
public let reportSelectedMessages: () -> Void
|
||||
public let reportMessages: ([EngineRawMessage], ContextControllerProtocol?) -> Void
|
||||
public let blockMessageAuthor: (EngineRawMessage, ContextControllerProtocol?) -> Void
|
||||
|
|
@ -206,7 +206,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||
setupEditMessage: @escaping (EngineMessage.Id?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
|
||||
beginMessageSelection: @escaping ([EngineMessage.Id], @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
|
||||
cancelMessageSelection: @escaping (ContainedViewLayoutTransition) -> Void,
|
||||
deleteSelectedMessages: @escaping () -> Void,
|
||||
deleteSelectedMessages: @escaping (UIView?) -> Void,
|
||||
reportSelectedMessages: @escaping () -> Void,
|
||||
reportMessages: @escaping ([EngineRawMessage], ContextControllerProtocol?) -> Void,
|
||||
blockMessageAuthor: @escaping (EngineRawMessage, ContextControllerProtocol?) -> Void,
|
||||
|
|
@ -475,7 +475,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||
}, setupEditMessage: { _, _ in
|
||||
}, beginMessageSelection: { _, _ in
|
||||
}, cancelMessageSelection: { _ in
|
||||
}, deleteSelectedMessages: {
|
||||
}, deleteSelectedMessages: { _ in
|
||||
}, reportSelectedMessages: {
|
||||
}, reportMessages: { _, _ in
|
||||
}, blockMessageAuthor: { _, _ in
|
||||
|
|
|
|||
|
|
@ -16,12 +16,24 @@ private let dateFont = Font.regular(12.0)
|
|||
|
||||
public final class GalleryTitleView: UIView, NavigationBarTitleView {
|
||||
public final class Content: Equatable {
|
||||
let message: EngineMessage
|
||||
let message: EngineMessage?
|
||||
let authorTitle: String?
|
||||
let dateTitle: String?
|
||||
let title: String?
|
||||
let action: (() -> Void)?
|
||||
|
||||
|
||||
public init(message: EngineMessage, title: String?, action: (() -> Void)?) {
|
||||
self.message = message
|
||||
self.authorTitle = nil
|
||||
self.dateTitle = nil
|
||||
self.title = title
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public init(authorTitle: String?, dateTitle: String?, title: String?, action: (() -> Void)?) {
|
||||
self.message = nil
|
||||
self.authorTitle = authorTitle
|
||||
self.dateTitle = dateTitle
|
||||
self.title = title
|
||||
self.action = action
|
||||
}
|
||||
|
|
@ -30,6 +42,12 @@ public final class GalleryTitleView: UIView, NavigationBarTitleView {
|
|||
if lhs.message != rhs.message {
|
||||
return false
|
||||
}
|
||||
if lhs.authorTitle != rhs.authorTitle {
|
||||
return false
|
||||
}
|
||||
if lhs.dateTitle != rhs.dateTitle {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
|
|
@ -114,18 +132,38 @@ public final class GalleryTitleView: UIView, NavigationBarTitleView {
|
|||
public func setContent(content: Content?) {
|
||||
self.content = content
|
||||
|
||||
self.backgroundContainer.isHidden = self.content == nil
|
||||
let authorNameText: String?
|
||||
let dateText: String?
|
||||
|
||||
if let content {
|
||||
let authorNameText = stringForFullAuthorName(message: content.message, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, accountPeerId: self.context.account.peerId).first ?? ""
|
||||
let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: content.message.timestamp).string
|
||||
|
||||
self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white)
|
||||
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: UIColor(white: 1.0, alpha: 0.5))
|
||||
if let message = content.message {
|
||||
authorNameText = stringForFullAuthorName(message: message, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, accountPeerId: self.context.account.peerId).first ?? ""
|
||||
dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: message.timestamp).string
|
||||
} else {
|
||||
authorNameText = content.authorTitle
|
||||
dateText = content.dateTitle
|
||||
}
|
||||
} else {
|
||||
authorNameText = nil
|
||||
dateText = nil
|
||||
}
|
||||
|
||||
|
||||
if let authorNameText {
|
||||
self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white)
|
||||
} else {
|
||||
self.authorNameNode.attributedText = nil
|
||||
}
|
||||
|
||||
if let dateText {
|
||||
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: UIColor(white: 1.0, alpha: 0.5))
|
||||
} else {
|
||||
self.dateNode.attributedText = nil
|
||||
}
|
||||
|
||||
self.backgroundContainer.isHidden = (authorNameText?.isEmpty ?? true) && (dateText?.isEmpty ?? true)
|
||||
|
||||
self.titleString = content?.title
|
||||
|
||||
|
||||
if !self.bounds.isEmpty {
|
||||
let _ = self.updateLayout(availableSize: self.bounds.size, transition: .immediate)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ swift_library(
|
|||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
|
|
@ -22,6 +24,7 @@ swift_library(
|
|||
"//submodules/PhotoResources:PhotoResources",
|
||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||
"//submodules/ShareController:ShareController",
|
||||
"//submodules/TelegramUI/Components/GlassControls",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI",
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import TelegramCore
|
|||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import GalleryUI
|
||||
import ContextUI
|
||||
import LegacyComponents
|
||||
import LegacyMediaPickerUI
|
||||
import SaveToCameraRoll
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
import AppBundle
|
||||
|
||||
public enum AvatarGalleryEntryId: Hashable {
|
||||
case topImage
|
||||
|
|
@ -20,6 +22,20 @@ public enum AvatarGalleryEntryId: Hashable {
|
|||
case resource(String)
|
||||
}
|
||||
|
||||
private final class AvatarGalleryContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let sourceView: UIView
|
||||
private let actionsOnTop: Bool
|
||||
|
||||
init(sourceView: UIView, actionsOnTop: Bool = false) {
|
||||
self.sourceView = sourceView
|
||||
self.actionsOnTop = actionsOnTop
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: self.actionsOnTop ? .top : .bottom)
|
||||
}
|
||||
}
|
||||
|
||||
private func avatarGalleryEntryMatchesImage(_ entry: AvatarGalleryEntry, _ image: TelegramMediaImage) -> Bool {
|
||||
guard
|
||||
let entryRepresentation = largestImageRepresentation(entry.representations.map({ $0.representation })),
|
||||
|
|
@ -535,12 +551,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
|||
}
|
||||
|
||||
if strongSelf.isViewLoaded {
|
||||
strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: sourceCorners, delete: strongSelf.canDelete ? {
|
||||
self?.deleteEntry(entry)
|
||||
strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: sourceCorners, delete: strongSelf.canDelete ? { [weak self] sourceView in
|
||||
self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil)
|
||||
} : nil, setMain: { [weak self] in
|
||||
self?.setMainEntry(entry)
|
||||
}, edit: { [weak self] in
|
||||
self?.editEntry(entry)
|
||||
}, edit: { [weak self] sourceView, gesture in
|
||||
self?.editEntry(entry, sourceView: sourceView, gesture: gesture)
|
||||
})
|
||||
}), centralItemIndex: strongSelf.centralEntryIndex, synchronous: !isFirstTime)
|
||||
|
||||
|
|
@ -704,12 +720,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
|||
}
|
||||
|
||||
let presentationData = self.presentationData
|
||||
self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in
|
||||
self?.deleteEntry(entry)
|
||||
self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] sourceView in
|
||||
self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil)
|
||||
} : nil, setMain: { [weak self] in
|
||||
self?.setMainEntry(entry)
|
||||
}, edit: { [weak self] in
|
||||
self?.editEntry(entry)
|
||||
}, edit: { [weak self] sourceView, gesture in
|
||||
self?.editEntry(entry, sourceView: sourceView, gesture: gesture)
|
||||
}) }), centralItemIndex: self.centralEntryIndex)
|
||||
|
||||
self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in
|
||||
|
|
@ -823,12 +839,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
|||
|
||||
private func replaceEntries(_ entries: [AvatarGalleryEntry]) {
|
||||
self.galleryNode.currentThumbnailContainerNode?.updateSynchronously = true
|
||||
self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in
|
||||
self?.deleteEntry(entry)
|
||||
self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] sourceView in
|
||||
self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil)
|
||||
} : nil, setMain: { [weak self] in
|
||||
self?.setMainEntry(entry)
|
||||
}, edit: { [weak self] in
|
||||
self?.editEntry(entry)
|
||||
}, edit: { [weak self] sourceView, gesture in
|
||||
self?.editEntry(entry, sourceView: sourceView, gesture: gesture)
|
||||
}) }), centralItemIndex: 0, synchronous: true)
|
||||
self.entries = entries
|
||||
self.galleryNode.currentThumbnailContainerNode?.updateSynchronously = false
|
||||
|
|
@ -895,158 +911,189 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
|||
}
|
||||
}
|
||||
|
||||
private func editEntry(_ rawEntry: AvatarGalleryEntry) {
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
let dismissAction: () -> Void = { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
}
|
||||
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
self?.openAvatarSetup?({ [weak self] in
|
||||
self?.dismissImmediately()
|
||||
private func editEntry(_ rawEntry: AvatarGalleryEntry, sourceView rawSourceView: UIView, gesture: ContextGesture?) {
|
||||
let presentationData = self.presentationData
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Settings_SetNewProfilePhotoOrVideo, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c?.dismiss(completion: { [weak self] in
|
||||
self?.openAvatarSetup?({ [weak self] in
|
||||
self?.dismissImmediately()
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
})))
|
||||
|
||||
var isFallback = false
|
||||
if case let .image(_, _, _, _, _, _, _, _, _, _, isFallbackValue, _) = rawEntry {
|
||||
isFallback = isFallbackValue
|
||||
}
|
||||
|
||||
|
||||
if self.peer.id == self.context.account.peerId, let position = rawEntry.indexData?.position, position > 0 || isFallback {
|
||||
let title: String
|
||||
if let _ = rawEntry.videoRepresentations.last {
|
||||
title = self.presentationData.strings.ProfilePhoto_SetMainVideo
|
||||
title = presentationData.strings.ProfilePhoto_SetMainVideo
|
||||
} else {
|
||||
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
|
||||
title = presentationData.strings.ProfilePhoto_SetMainPhoto
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
self?.setMainEntry(rawEntry)
|
||||
}))
|
||||
items.append(.action(ContextMenuActionItem(text: title, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c?.dismiss(completion: { [weak self] in
|
||||
self?.setMainEntry(rawEntry)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
let deleteTitle: String
|
||||
if let _ = rawEntry.videoRepresentations.last {
|
||||
deleteTitle = self.presentationData.strings.Settings_RemoveVideo
|
||||
deleteTitle = presentationData.strings.Settings_RemoveVideo
|
||||
} else {
|
||||
deleteTitle = self.presentationData.strings.GroupInfo_SetGroupPhotoDelete
|
||||
deleteTitle = presentationData.strings.GroupInfo_SetGroupPhotoDelete
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
self?.deleteEntry(rawEntry)
|
||||
}))
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
self.view.endEditing(true)
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
|
||||
private func deleteEntry(_ rawEntry: AvatarGalleryEntry) {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let proceed = {
|
||||
var entry = rawEntry
|
||||
if case .topImage = entry, !self.entries.isEmpty {
|
||||
entry = self.entries[0]
|
||||
items.append(.action(ContextMenuActionItem(text: deleteTitle, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
guard let self, let c else {
|
||||
return
|
||||
}
|
||||
|
||||
self.removedEntry?(rawEntry)
|
||||
|
||||
var focusOnItem: Int?
|
||||
var updatedEntries = self.entries
|
||||
var replaceItems = false
|
||||
var dismiss = false
|
||||
|
||||
switch entry {
|
||||
case .topImage:
|
||||
if self.peer.id == self.context.account.peerId {
|
||||
let items: [ContextMenuItem] = [
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Settings_RemoveConfirmation, textColor: .destructive, icon: { _ in
|
||||
return nil
|
||||
}, action: { [weak self] c, _ in
|
||||
if let c {
|
||||
c.dismiss(completion: { [weak self] in
|
||||
self?.performDeleteEntry(rawEntry)
|
||||
})
|
||||
} else {
|
||||
if entry == self.entries.first {
|
||||
let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
|
||||
dismiss = true
|
||||
} else {
|
||||
if let index = self.entries.firstIndex(of: entry) {
|
||||
self.entries.remove(at: index)
|
||||
self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false))
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .image(_, reference, _, _, _, _, _, messageId, _, _, isFallback, _):
|
||||
if self.peer.id == self.context.account.peerId {
|
||||
if isFallback {
|
||||
let _ = self.context.engine.accountData.updateFallbackPhoto(resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
|
||||
} else if let reference = reference {
|
||||
let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start()
|
||||
}
|
||||
|
||||
if entry == self.entries.first {
|
||||
dismiss = true
|
||||
} else {
|
||||
if let index = self.entries.firstIndex(of: entry) {
|
||||
replaceItems = true
|
||||
updatedEntries.remove(at: index)
|
||||
focusOnItem = index - 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let messageId = messageId {
|
||||
let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: [messageId], type: .forEveryone).start()
|
||||
}
|
||||
|
||||
if entry == self.entries.first {
|
||||
let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
|
||||
dismiss = true
|
||||
} else {
|
||||
if let index = self.entries.firstIndex(of: entry) {
|
||||
replaceItems = true
|
||||
updatedEntries.remove(at: index)
|
||||
focusOnItem = index - 1
|
||||
}
|
||||
}
|
||||
self?.performDeleteEntry(rawEntry)
|
||||
}
|
||||
}
|
||||
|
||||
if replaceItems {
|
||||
updatedEntries = normalizeEntries(updatedEntries)
|
||||
self.galleryNode.pager.replaceItems(updatedEntries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in
|
||||
self?.deleteEntry(entry)
|
||||
} : nil, setMain: { [weak self] in
|
||||
self?.setMainEntry(entry)
|
||||
}, edit: { [weak self] in
|
||||
self?.editEntry(entry)
|
||||
}) }), centralItemIndex: focusOnItem, synchronous: true)
|
||||
self.entries = updatedEntries
|
||||
}
|
||||
if dismiss {
|
||||
self._hiddenMedia.set(.single(nil))
|
||||
Queue.mainQueue().after(0.2) {
|
||||
self.dismiss(forceAway: true)
|
||||
}))
|
||||
]
|
||||
c.pushItems(items: .single(ContextController.Items(content: .list(items))))
|
||||
})))
|
||||
|
||||
self.view.endEditing(true)
|
||||
|
||||
let sourceView = self.navigationBar?.navigationButtonContextContainer(sourceView: rawSourceView) ?? rawSourceView
|
||||
let contextController = makeContextController(
|
||||
presentationData: presentationData.withUpdated(theme: defaultDarkColorPresentationTheme),
|
||||
source: .reference(AvatarGalleryContextReferenceContentSource(sourceView: sourceView)),
|
||||
items: .single(ContextController.Items(content: .list(items))),
|
||||
gesture: gesture
|
||||
)
|
||||
self.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
private func presentDeleteEntryConfirmation(_ rawEntry: AvatarGalleryEntry, sourceView: UIView, gesture: ContextGesture?) {
|
||||
self.view.endEditing(true)
|
||||
|
||||
let items: [ContextMenuItem] = [
|
||||
.action(ContextMenuActionItem(text: self.presentationData.strings.Settings_RemoveConfirmation, textColor: .destructive, icon: { _ in
|
||||
return nil
|
||||
}, action: { [weak self] c, _ in
|
||||
if let c {
|
||||
c.dismiss(completion: { [weak self] in
|
||||
self?.performDeleteEntry(rawEntry)
|
||||
})
|
||||
} else {
|
||||
self?.performDeleteEntry(rawEntry)
|
||||
}
|
||||
} else {
|
||||
if let firstEntry = self.entries.first {
|
||||
self._hiddenMedia.set(.single(firstEntry))
|
||||
}
|
||||
}
|
||||
}
|
||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
let items: [ActionSheetItem] = [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Settings_RemoveConfirmation, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
proceed()
|
||||
})
|
||||
}))
|
||||
]
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
let contextController = makeContextController(
|
||||
presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme),
|
||||
source: .reference(AvatarGalleryContextReferenceContentSource(sourceView: sourceView, actionsOnTop: true)),
|
||||
items: .single(ContextController.Items(content: .list(items))),
|
||||
gesture: gesture
|
||||
)
|
||||
self.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
private func performDeleteEntry(_ rawEntry: AvatarGalleryEntry) {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
var entry = rawEntry
|
||||
if case .topImage = entry, !self.entries.isEmpty {
|
||||
entry = self.entries[0]
|
||||
}
|
||||
|
||||
self.removedEntry?(rawEntry)
|
||||
|
||||
var focusOnItem: Int?
|
||||
var updatedEntries = self.entries
|
||||
var replaceItems = false
|
||||
var dismiss = false
|
||||
|
||||
switch entry {
|
||||
case .topImage:
|
||||
if self.peer.id == self.context.account.peerId {
|
||||
} else {
|
||||
if entry == self.entries.first {
|
||||
let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
|
||||
dismiss = true
|
||||
} else {
|
||||
if let index = self.entries.firstIndex(of: entry) {
|
||||
self.entries.remove(at: index)
|
||||
self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false))
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .image(_, reference, _, _, _, _, _, messageId, _, _, isFallback, _):
|
||||
if self.peer.id == self.context.account.peerId {
|
||||
if isFallback {
|
||||
let _ = self.context.engine.accountData.updateFallbackPhoto(resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
|
||||
} else if let reference = reference {
|
||||
let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start()
|
||||
}
|
||||
|
||||
if entry == self.entries.first {
|
||||
dismiss = true
|
||||
} else {
|
||||
if let index = self.entries.firstIndex(of: entry) {
|
||||
replaceItems = true
|
||||
updatedEntries.remove(at: index)
|
||||
focusOnItem = index - 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let messageId = messageId {
|
||||
let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: [messageId], type: .forEveryone).start()
|
||||
}
|
||||
|
||||
if entry == self.entries.first {
|
||||
let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
|
||||
dismiss = true
|
||||
} else {
|
||||
if let index = self.entries.firstIndex(of: entry) {
|
||||
replaceItems = true
|
||||
updatedEntries.remove(at: index)
|
||||
focusOnItem = index - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if replaceItems {
|
||||
updatedEntries = normalizeEntries(updatedEntries)
|
||||
self.galleryNode.pager.replaceItems(updatedEntries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] sourceView in
|
||||
self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil)
|
||||
} : nil, setMain: { [weak self] in
|
||||
self?.setMainEntry(entry)
|
||||
}, edit: { [weak self] sourceView, gesture in
|
||||
self?.editEntry(entry, sourceView: sourceView, gesture: gesture)
|
||||
}) }), centralItemIndex: focusOnItem, synchronous: true)
|
||||
self.entries = updatedEntries
|
||||
}
|
||||
if dismiss {
|
||||
self._hiddenMedia.set(.single(nil))
|
||||
Queue.mainQueue().after(0.2) {
|
||||
self.dismiss(forceAway: true)
|
||||
}
|
||||
} else {
|
||||
if let firstEntry = self.entries.first {
|
||||
self._hiddenMedia.set(.single(firstEntry))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,11 @@ import AsyncDisplayKit
|
|||
import Display
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Photos
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import GalleryUI
|
||||
import AppBundle
|
||||
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
||||
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
|
||||
|
||||
private let nameFont = Font.medium(15.0)
|
||||
private let dateFont = Font.regular(14.0)
|
||||
import ComponentFlow
|
||||
import GlassControls
|
||||
|
||||
enum AvatarGalleryItemFooterContent {
|
||||
case info
|
||||
|
|
@ -27,24 +19,19 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
private var strings: PresentationStrings
|
||||
private var dateTimeFormat: PresentationDateTimeFormat
|
||||
|
||||
private let deleteButton: UIButton
|
||||
private let actionButton: UIButton
|
||||
private let nameNode: ASTextNode
|
||||
private let dateNode: ASTextNode
|
||||
private let buttonPanel = ComponentView<Empty>()
|
||||
private let mainNode: ASTextNode
|
||||
private let setMainButton: HighlightableButtonNode
|
||||
|
||||
private var currentNameText: String?
|
||||
private var currentDateText: String?
|
||||
private var currentTypeText: String?
|
||||
private var displayActionButton: Bool = true
|
||||
|
||||
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
var delete: (() -> Void)? {
|
||||
var delete: ((UIView) -> Void)? {
|
||||
didSet {
|
||||
self.deleteButton.isHidden = self.delete == nil
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,25 +43,6 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.strings = presentationData.strings
|
||||
self.dateTimeFormat = presentationData.dateTimeFormat
|
||||
|
||||
self.deleteButton = UIButton()
|
||||
self.deleteButton.isHidden = true
|
||||
|
||||
self.actionButton = UIButton()
|
||||
|
||||
self.deleteButton.setImage(deleteImage, for: [.normal])
|
||||
self.actionButton.setImage(actionImage, for: [.normal])
|
||||
|
||||
self.nameNode = ASTextNode()
|
||||
self.nameNode.maximumNumberOfLines = 1
|
||||
self.nameNode.isUserInteractionEnabled = false
|
||||
self.nameNode.displaysAsynchronously = false
|
||||
|
||||
self.dateNode = ASTextNode()
|
||||
self.dateNode.maximumNumberOfLines = 1
|
||||
self.dateNode.isUserInteractionEnabled = false
|
||||
self.dateNode.displaysAsynchronously = false
|
||||
|
||||
self.setMainButton = HighlightableButtonNode()
|
||||
self.setMainButton.isHidden = true
|
||||
|
|
@ -86,36 +54,18 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|||
|
||||
super.init()
|
||||
|
||||
self.view.addSubview(self.deleteButton)
|
||||
self.view.addSubview(self.actionButton)
|
||||
|
||||
self.addSubnode(self.nameNode)
|
||||
self.addSubnode(self.dateNode)
|
||||
self.addSubnode(self.setMainButton)
|
||||
self.addSubnode(self.mainNode)
|
||||
|
||||
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
|
||||
self.setMainButton.addTarget(self, action: #selector(self.setMainButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
func setEntry(_ entry: AvatarGalleryEntry, content: AvatarGalleryItemFooterContent) {
|
||||
var nameText: String?
|
||||
var dateText: String?
|
||||
var typeText: String?
|
||||
var buttonText: String?
|
||||
var canShare = true
|
||||
switch entry {
|
||||
case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, isFallback, _):
|
||||
if date != 0 || isFallback {
|
||||
nameText = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
|
||||
}
|
||||
if let date = date, date != 0 {
|
||||
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string
|
||||
} else if isFallback {
|
||||
dateText = !videoRepresentations.isEmpty ? self.strings.ProfilePhoto_PublicVideo : self.strings.ProfilePhoto_PublicPhoto
|
||||
}
|
||||
|
||||
case let .image(_, _, _, videoRepresentations, peer, _, _, _, _, _, _, _):
|
||||
if (!videoRepresentations.isEmpty) {
|
||||
typeText = self.strings.ProfilePhoto_MainVideo
|
||||
buttonText = self.strings.ProfilePhoto_SetMainVideo
|
||||
|
|
@ -131,23 +81,6 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|||
break
|
||||
}
|
||||
|
||||
if self.currentNameText != nameText || self.currentDateText != dateText {
|
||||
self.currentNameText = nameText
|
||||
self.currentDateText = dateText
|
||||
|
||||
if let nameText = nameText {
|
||||
self.nameNode.attributedText = NSAttributedString(string: nameText, font: nameFont, textColor: .white)
|
||||
} else {
|
||||
self.nameNode.attributedText = nil
|
||||
}
|
||||
|
||||
if let dateText = dateText {
|
||||
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
|
||||
} else {
|
||||
self.dateNode.attributedText = nil
|
||||
}
|
||||
}
|
||||
|
||||
if self.currentTypeText != typeText {
|
||||
self.currentTypeText = typeText
|
||||
|
||||
|
|
@ -159,17 +92,16 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|||
}
|
||||
}
|
||||
|
||||
self.actionButton.isHidden = !canShare
|
||||
if self.displayActionButton != canShare {
|
||||
self.displayActionButton = canShare
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
|
||||
switch content {
|
||||
case .info:
|
||||
self.nameNode.isHidden = false
|
||||
self.dateNode.isHidden = false
|
||||
self.mainNode.isHidden = true
|
||||
self.setMainButton.isHidden = true
|
||||
case let .own(isMainPhoto):
|
||||
self.nameNode.isHidden = true
|
||||
self.dateNode.isHidden = true
|
||||
self.mainNode.isHidden = !isMainPhoto
|
||||
self.setMainButton.isHidden = isMainPhoto
|
||||
}
|
||||
|
|
@ -179,52 +111,116 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|||
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset)
|
||||
|
||||
let width = size.width
|
||||
var panelHeight: CGFloat = 44.0 + bottomInset
|
||||
var panelHeight: CGFloat = 54.0 + bottomInset
|
||||
panelHeight += contentInset
|
||||
|
||||
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
let constrainedSize = CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude)
|
||||
let nameSize = self.nameNode.measure(constrainedSize)
|
||||
let dateSize = self.dateNode.measure(constrainedSize)
|
||||
|
||||
if nameSize.height.isZero {
|
||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height) / 2.0)), size: dateSize)
|
||||
} else {
|
||||
let labelsSpacing: CGFloat = 0.0
|
||||
self.nameNode.frame = CGRect(origin: CGPoint(x: floor((width - nameSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0)), size: nameSize)
|
||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0) + nameSize.height + labelsSpacing), size: dateSize)
|
||||
var buttonPanelInsets = UIEdgeInsets()
|
||||
buttonPanelInsets.left = 8.0
|
||||
buttonPanelInsets.right = 8.0
|
||||
buttonPanelInsets.bottom = bottomInset + 8.0
|
||||
if bottomInset <= 32.0 {
|
||||
buttonPanelInsets.left += 18.0
|
||||
buttonPanelInsets.right += 18.0
|
||||
}
|
||||
|
||||
let constrainedSize = CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude)
|
||||
let controlsY = panelHeight - buttonPanelInsets.bottom - 44.0
|
||||
|
||||
let mainSize = self.mainNode.measure(constrainedSize)
|
||||
self.mainNode.frame = CGRect(origin: CGPoint(x: floor((width - mainSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - mainSize.height) / 2.0)), size: mainSize)
|
||||
|
||||
self.mainNode.frame = CGRect(origin: CGPoint(x: floor((width - mainSize.width) / 2.0), y: controlsY + floor((44.0 - mainSize.height) / 2.0)), size: mainSize)
|
||||
|
||||
let mainButtonSize = self.setMainButton.measure(constrainedSize)
|
||||
self.setMainButton.frame = CGRect(origin: CGPoint(x: floor((width - mainButtonSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - mainButtonSize.height) / 2.0)), size: mainButtonSize)
|
||||
self.setMainButton.frame = CGRect(origin: CGPoint(x: floor((width - mainButtonSize.width) / 2.0), y: controlsY + floor((44.0 - mainButtonSize.height) / 2.0)), size: mainButtonSize)
|
||||
|
||||
var leftControlItems: [GlassControlGroupComponent.Item] = []
|
||||
var rightControlItems: [GlassControlGroupComponent.Item] = []
|
||||
|
||||
if self.displayActionButton {
|
||||
leftControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("forward"),
|
||||
content: .icon("Chat/Input/Accessory Panels/MessageSelectionForward"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.actionButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
if self.delete != nil {
|
||||
rightControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("delete"),
|
||||
content: .icon("Chat/Input/Accessory Panels/MessageSelectionTrash"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.deleteButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
if leftControlItems.isEmpty && rightControlItems.isEmpty {
|
||||
self.buttonPanel.view?.removeFromSuperview()
|
||||
} else {
|
||||
let buttonPanelSize = self.buttonPanel.update(
|
||||
transition: ComponentTransition(transition),
|
||||
component: AnyComponent(GlassControlPanelComponent(
|
||||
theme: defaultDarkColorPresentationTheme,
|
||||
leftItem: leftControlItems.isEmpty ? nil : GlassControlPanelComponent.Item(
|
||||
items: leftControlItems,
|
||||
background: .panel
|
||||
),
|
||||
centralItem: nil,
|
||||
rightItem: rightControlItems.isEmpty ? nil : GlassControlPanelComponent.Item(
|
||||
items: rightControlItems,
|
||||
background: .panel
|
||||
),
|
||||
centerAlignmentIfPossible: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0)
|
||||
)
|
||||
let buttonPanelFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: panelHeight - buttonPanelInsets.bottom - buttonPanelSize.height), size: buttonPanelSize)
|
||||
if let buttonPanelView = self.buttonPanel.view {
|
||||
if buttonPanelView.superview == nil {
|
||||
self.view.addSubview(buttonPanelView)
|
||||
}
|
||||
ComponentTransition(transition).setFrame(view: buttonPanelView, frame: buttonPanelFrame)
|
||||
}
|
||||
}
|
||||
|
||||
return LayoutInfo(height: panelHeight, needsShadow: false)
|
||||
}
|
||||
|
||||
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {
|
||||
self.deleteButton.alpha = 1.0
|
||||
self.actionButton.alpha = 1.0
|
||||
self.nameNode.alpha = 1.0
|
||||
self.dateNode.alpha = 1.0
|
||||
if let buttonPanelView = self.buttonPanel.view {
|
||||
buttonPanelView.alpha = 1.0
|
||||
}
|
||||
self.mainNode.alpha = 1.0
|
||||
self.setMainButton.alpha = 1.0
|
||||
}
|
||||
|
||||
override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
self.deleteButton.alpha = 0.0
|
||||
self.actionButton.alpha = 0.0
|
||||
self.nameNode.alpha = 0.0
|
||||
self.dateNode.alpha = 0.0
|
||||
if let buttonPanelView = self.buttonPanel.view {
|
||||
buttonPanelView.alpha = 0.0
|
||||
}
|
||||
self.mainNode.alpha = 0.0
|
||||
self.setMainButton.alpha = 0.0
|
||||
completion()
|
||||
}
|
||||
|
||||
@objc private func deleteButtonPressed() {
|
||||
self.delete?()
|
||||
let sourceView: UIView
|
||||
if let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View, let itemView = buttonPanelView.rightItemView?.itemView(id: AnyHashable("delete")) {
|
||||
sourceView = itemView
|
||||
} else if let buttonPanelView = self.buttonPanel.view {
|
||||
sourceView = buttonPanelView
|
||||
} else {
|
||||
sourceView = self.view
|
||||
}
|
||||
self.delete?(sourceView)
|
||||
}
|
||||
|
||||
@objc private func actionButtonPressed() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import AsyncDisplayKit
|
|||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import RadialStatusNode
|
||||
import PhotoResources
|
||||
|
|
@ -50,11 +51,11 @@ class PeerAvatarImageGalleryItem: GalleryItem {
|
|||
let presentationData: PresentationData
|
||||
let entry: AvatarGalleryEntry
|
||||
let sourceCorners: AvatarGalleryController.SourceCorners
|
||||
let delete: (() -> Void)?
|
||||
let delete: ((UIView) -> Void)?
|
||||
let setMain: (() -> Void)?
|
||||
let edit: (() -> Void)?
|
||||
let edit: ((UIView, ContextGesture?) -> Void)?
|
||||
|
||||
init(context: AccountContext, peer: EnginePeer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) {
|
||||
init(context: AccountContext, peer: EnginePeer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: ((UIView) -> Void)?, setMain: (() -> Void)?, edit: ((UIView, ContextGesture?) -> Void)?) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.presentationData = presentationData
|
||||
|
|
@ -68,10 +69,6 @@ class PeerAvatarImageGalleryItem: GalleryItem {
|
|||
func node(synchronous: Bool) -> GalleryItemNode {
|
||||
let node = PeerAvatarImageGalleryItemNode(context: self.context, presentationData: self.presentationData, peer: self.peer, sourceCorners: self.sourceCorners)
|
||||
|
||||
if let indexData = self.entry.indexData {
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string))
|
||||
}
|
||||
|
||||
node.setEntry(self.entry, synchronous: synchronous)
|
||||
node.footerContentNode.delete = self.delete
|
||||
node.footerContentNode.setMain = self.setMain
|
||||
|
|
@ -82,9 +79,6 @@ class PeerAvatarImageGalleryItem: GalleryItem {
|
|||
|
||||
func updateNode(node: GalleryItemNode, synchronous: Bool) {
|
||||
if let node = node as? PeerAvatarImageGalleryItemNode {
|
||||
if let indexData = self.entry.indexData {
|
||||
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string))
|
||||
}
|
||||
let previousContentAnimations = node.imageNode.contentAnimations
|
||||
if synchronous {
|
||||
node.imageNode.contentAnimations = []
|
||||
|
|
@ -124,6 +118,63 @@ private class PeerAvatarImageGalleryContentNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
private final class AvatarGalleryEditButtonNode: HighlightableButtonNode {
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
|
||||
private let textNode: ASTextNode
|
||||
|
||||
var contextAction: ((UIView, ContextGesture?) -> Void)?
|
||||
|
||||
init(title: String) {
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: .white)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.addSubnode(self.textNode)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] _ in
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
return self.contextAction != nil
|
||||
}
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.contextAction?(self.referenceNode.view, gesture)
|
||||
}
|
||||
|
||||
self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let textSize = self.textNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
|
||||
let size = CGSize(width: ceil(textSize.width) + 20.0, height: 44.0)
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.containerNode.frame = bounds
|
||||
self.referenceNode.frame = bounds
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.contextAction?(self.referenceNode.view, nil)
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
|
|
@ -140,6 +191,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
fileprivate let _ready = Promise<Void>()
|
||||
fileprivate let _title = Promise<String>()
|
||||
fileprivate let _titleContent = Promise<GalleryTitleView.Content?>(nil)
|
||||
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
|
||||
|
||||
private let statusNodeContainer: HighlightableButtonNode
|
||||
|
|
@ -151,7 +203,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private var status: EngineMediaResource.FetchStatus?
|
||||
private let playbackStatusDisposable = MetaDisposable()
|
||||
|
||||
fileprivate var edit: (() -> Void)?
|
||||
fileprivate var edit: ((UIView, ContextGesture?) -> Void)?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, peer: EnginePeer, sourceCorners: AvatarGalleryController.SourceCorners) {
|
||||
self.context = context
|
||||
|
|
@ -162,15 +214,17 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self.contentNode = PeerAvatarImageGalleryContentNode()
|
||||
self.imageNode = TransformImageNode()
|
||||
self.footerContentNode = AvatarGalleryItemFooterContentNode(context: context, presentationData: presentationData)
|
||||
|
||||
|
||||
self.statusNodeContainer = HighlightableButtonNode()
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
self.statusNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
self._title.set(.single(""))
|
||||
|
||||
self.contentNode.addSubnode(self.imageNode)
|
||||
|
||||
|
||||
self.imageNode.contentAnimations = .subsequentUpdates
|
||||
self.imageNode.view.contentMode = .scaleAspectFill
|
||||
self.imageNode.clipsToBounds = true
|
||||
|
|
@ -227,7 +281,36 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
transition.updateFrame(node: self.statusNodeContainer, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: floor((layout.size.height - statusSize.height) / 2.0)), size: statusSize))
|
||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize))
|
||||
}
|
||||
|
||||
|
||||
private func makeTitleContent(entry: AvatarGalleryEntry) -> GalleryTitleView.Content? {
|
||||
var counterText: String?
|
||||
if let indexData = entry.indexData {
|
||||
counterText = self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string
|
||||
}
|
||||
|
||||
var authorTitle: String?
|
||||
var dateTitle: String?
|
||||
switch entry {
|
||||
case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, isFallback, _):
|
||||
if date != 0 || isFallback {
|
||||
authorTitle = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
|
||||
}
|
||||
if let date = date, date != 0 {
|
||||
dateTitle = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: date).string
|
||||
} else if isFallback {
|
||||
dateTitle = !videoRepresentations.isEmpty ? self.presentationData.strings.ProfilePhoto_PublicVideo : self.presentationData.strings.ProfilePhoto_PublicPhoto
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if counterText == nil && authorTitle == nil && dateTitle == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return GalleryTitleView.Content(authorTitle: authorTitle, dateTitle: dateTitle, title: counterText, action: nil)
|
||||
}
|
||||
|
||||
fileprivate func setEntry(_ entry: AvatarGalleryEntry, synchronous: Bool) {
|
||||
let previousRepresentations = self.entry?.representations
|
||||
let previousVideoRepresentations = self.entry?.videoRepresentations
|
||||
|
|
@ -237,10 +320,17 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
var barButtonItems: [UIBarButtonItem] = []
|
||||
let footerContent: AvatarGalleryItemFooterContent = .info
|
||||
if self.peer.id == self.context.account.peerId {
|
||||
let rightBarButtonItem = UIBarButtonItem(title: entry.videoRepresentations.isEmpty ? self.presentationData.strings.Settings_EditPhoto : self.presentationData.strings.Settings_EditVideo, style: .plain, target: self, action: #selector(self.editPressed))
|
||||
let editTitle = entry.videoRepresentations.isEmpty ? self.presentationData.strings.Settings_EditPhoto : self.presentationData.strings.Settings_EditVideo
|
||||
let editButtonNode = AvatarGalleryEditButtonNode(title: editTitle)
|
||||
editButtonNode.contextAction = { [weak self] sourceView, gesture in
|
||||
self?.edit?(sourceView, gesture)
|
||||
}
|
||||
let rightBarButtonItem = UIBarButtonItem(customDisplayNode: editButtonNode)!
|
||||
rightBarButtonItem.accessibilityLabel = editTitle
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
}
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
self._titleContent.set(.single(self.makeTitleContent(entry: entry)))
|
||||
|
||||
self.footerContentNode.setEntry(entry, content: footerContent)
|
||||
|
||||
|
|
@ -590,6 +680,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
override func title() -> Signal<String, NoError> {
|
||||
return self._title.get()
|
||||
}
|
||||
|
||||
override func titleContent() -> Signal<GalleryTitleView.Content?, NoError> {
|
||||
return self._titleContent.get()
|
||||
}
|
||||
|
||||
override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {
|
||||
return self._rightBarButtonItems.get()
|
||||
|
|
@ -618,10 +712,6 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func editPressed() {
|
||||
self.edit?()
|
||||
}
|
||||
|
||||
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
|
||||
return .single((self.footerContentNode, nil))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,10 @@ public final class SecretChatKeyController: ViewController {
|
|||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
|
||||
|
||||
self.navigationPresentation = .modal
|
||||
self._hasGlassStyle = true
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
self.title = self.presentationData.strings.EncryptionKey_Title
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
|||
}
|
||||
|
||||
@objc private func deleteButtonPressed() {
|
||||
self.interfaceInteraction?.deleteSelectedMessages()
|
||||
self.interfaceInteraction?.deleteSelectedMessages(self.deleteButton)
|
||||
}
|
||||
|
||||
@objc private func reportButtonPressed() {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
|||
}, setupEditMessage: { _, _ in
|
||||
}, beginMessageSelection: { _, _ in
|
||||
}, cancelMessageSelection: { _ in
|
||||
}, deleteSelectedMessages: {
|
||||
}, deleteSelectedMessages: { _ in
|
||||
}, reportSelectedMessages: {
|
||||
}, reportMessages: { _, _ in
|
||||
}, blockMessageAuthor: { _, _ in
|
||||
|
|
|
|||
|
|
@ -407,7 +407,7 @@ public final class ChatTextInputPanelComponent: Component {
|
|||
},
|
||||
cancelMessageSelection: { _ in
|
||||
},
|
||||
deleteSelectedMessages: {
|
||||
deleteSelectedMessages: { _ in
|
||||
},
|
||||
reportSelectedMessages: {
|
||||
},
|
||||
|
|
|
|||
|
|
@ -98,9 +98,9 @@ private final class PeerInfoScreenDisclosureEncryptionKeyItemNode: PeerInfoScree
|
|||
|
||||
let arrowInset: CGFloat = 18.0
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 12.0), size: textSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 16.0), size: textSize)
|
||||
|
||||
let height = textSize.height + 24.0
|
||||
let height = textSize.height + 32.0
|
||||
|
||||
if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) {
|
||||
self.arrowNode.image = arrowImage
|
||||
|
|
@ -118,7 +118,7 @@ private final class PeerInfoScreenDisclosureEncryptionKeyItemNode: PeerInfoScree
|
|||
let hasTopCorners = hasCorners && topItem == nil
|
||||
let hasBottomCorners = hasCorners && bottomItem == nil
|
||||
|
||||
self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil
|
||||
self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height))
|
||||
self.bottomSeparatorNode.isHidden = hasBottomCorners
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||
}, setupEditMessage: { _, _ in
|
||||
}, beginMessageSelection: { _, _ in
|
||||
}, cancelMessageSelection: { _ in
|
||||
}, deleteSelectedMessages: {
|
||||
}, deleteSelectedMessages: { _ in
|
||||
deleteMessages()
|
||||
}, reportSelectedMessages: {
|
||||
reportMessages()
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
}, setupEditMessage: { _, _ in
|
||||
}, beginMessageSelection: { _, _ in
|
||||
}, cancelMessageSelection: { _ in
|
||||
}, deleteSelectedMessages: {
|
||||
}, deleteSelectedMessages: { _ in
|
||||
}, reportSelectedMessages: {
|
||||
}, reportMessages: { _, _ in
|
||||
}, blockMessageAuthor: { _, _ in
|
||||
|
|
|
|||
|
|
@ -1842,7 +1842,7 @@ extension ChatControllerImpl {
|
|||
return
|
||||
}
|
||||
self.updateChatPresentationInterfaceState(transition: transition, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
}, deleteSelectedMessages: { [weak self] in
|
||||
}, deleteSelectedMessages: { [weak self] sourceView in
|
||||
if let strongSelf = self {
|
||||
if let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||
strongSelf.messageContextDisposable.set((strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, keepUpdated: false)
|
||||
|
|
@ -1856,7 +1856,7 @@ extension ChatControllerImpl {
|
|||
if actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty {
|
||||
strongSelf.presentClearCacheSuggestion()
|
||||
} else {
|
||||
strongSelf.presentDeleteMessageOptions(messageIds: messageIds, options: actions.options, contextController: nil, completion: { _ in })
|
||||
strongSelf.presentDeleteMessageOptions(messageIds: messageIds, options: actions.options, contextController: nil, sourceView: sourceView, completion: { _ in })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -687,15 +687,17 @@ final class ChatControllerContextReferenceContentSource: ContextReferenceContent
|
|||
let sourceView: UIView
|
||||
let insets: UIEdgeInsets
|
||||
let contentInsets: UIEdgeInsets
|
||||
let actionsOnTop: Bool
|
||||
|
||||
init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) {
|
||||
init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets(), actionsOnTop: Bool = false) {
|
||||
self.controller = controller
|
||||
self.sourceView = sourceView
|
||||
self.insets = insets
|
||||
self.contentInsets = contentInsets
|
||||
self.actionsOnTop = actionsOnTop
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets)
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets, actionsPosition: self.actionsOnTop ? .top : .bottom)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
|
|
@ -439,7 +440,7 @@ extension ChatControllerImpl {
|
|||
}), in: .current)
|
||||
}
|
||||
|
||||
func presentDeleteMessageOptions(messageIds: Set<EngineMessage.Id>, options: ChatAvailableMessageActionOptions, contextController: ContextControllerProtocol?, completion: @escaping (ContextMenuActionResult) -> Void) {
|
||||
func presentDeleteMessageOptions(messageIds: Set<EngineMessage.Id>, options: ChatAvailableMessageActionOptions, contextController: ContextControllerProtocol?, sourceView: UIView? = nil, completion: @escaping (ContextMenuActionResult) -> Void) {
|
||||
let _ = (self.context.engine.data.get(
|
||||
EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
|
||||
)
|
||||
|
|
@ -564,7 +565,17 @@ extension ChatControllerImpl {
|
|||
isChannel = true
|
||||
}
|
||||
|
||||
var contextItems: [ContextMenuItem] = []
|
||||
var canDisplayContextMenu = true
|
||||
|
||||
if options.contains(.cancelSending) {
|
||||
contextItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCancelSending, textColor: .destructive, icon: { _ in nil }, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
strongSelf.beginDeleteMessagesWithUndo(messageIds: messageIds, type: .forEveryone)
|
||||
}
|
||||
})))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ContextMenuCancelSending, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
|
|
@ -574,9 +585,6 @@ extension ChatControllerImpl {
|
|||
}))
|
||||
}
|
||||
|
||||
var contextItems: [ContextMenuItem] = []
|
||||
var canDisplayContextMenu = true
|
||||
|
||||
var unsendPersonalMessages = false
|
||||
if options.contains(.unsendPersonal) {
|
||||
canDisplayContextMenu = false
|
||||
|
|
@ -705,6 +713,16 @@ extension ChatControllerImpl {
|
|||
|
||||
if canDisplayContextMenu, let contextController = contextController {
|
||||
contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true)
|
||||
} else if canDisplayContextMenu, let sourceView = sourceView {
|
||||
let contextController = makeContextController(
|
||||
presentationData: self.presentationData,
|
||||
source: .reference(ChatControllerContextReferenceContentSource(controller: self, sourceView: sourceView, insets: .zero, actionsOnTop: true)),
|
||||
items: .single(ContextController.Items(content: .list(contextItems))),
|
||||
gesture: nil
|
||||
)
|
||||
self.chatDisplayNode.dismissInput()
|
||||
self.presentInGlobalOverlay(contextController)
|
||||
completion(.default)
|
||||
} else {
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue