UI improvements

This commit is contained in:
Ali 2022-04-12 22:07:58 +04:00
parent 98ba4e7d77
commit c7c6f98795
10 changed files with 360 additions and 130 deletions

View File

@ -351,6 +351,7 @@ public final class NavigateToChatControllerParams {
public let activateInput: Bool public let activateInput: Bool
public let keepStack: NavigateToChatKeepStack public let keepStack: NavigateToChatKeepStack
public let useExisting: Bool public let useExisting: Bool
public let useBackAnimation: Bool
public let purposefulAction: (() -> Void)? public let purposefulAction: (() -> Void)?
public let scrollToEndIfExists: Bool public let scrollToEndIfExists: Bool
public let activateMessageSearch: (ChatSearchDomain, String)? public let activateMessageSearch: (ChatSearchDomain, String)?
@ -366,7 +367,7 @@ public final class NavigateToChatControllerParams {
public let setupController: (ChatController) -> Void public let setupController: (ChatController) -> Void
public let completion: (ChatController) -> Void public let completion: (ChatController) -> Void
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, completion: @escaping (ChatController) -> Void = { _ in }) { public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, completion: @escaping (ChatController) -> Void = { _ in }) {
self.navigationController = navigationController self.navigationController = navigationController
self.chatController = chatController self.chatController = chatController
self.chatLocationContextHolder = chatLocationContextHolder self.chatLocationContextHolder = chatLocationContextHolder
@ -379,6 +380,7 @@ public final class NavigateToChatControllerParams {
self.activateInput = activateInput self.activateInput = activateInput
self.keepStack = keepStack self.keepStack = keepStack
self.useExisting = useExisting self.useExisting = useExisting
self.useBackAnimation = useBackAnimation
self.purposefulAction = purposefulAction self.purposefulAction = purposefulAction
self.scrollToEndIfExists = scrollToEndIfExists self.scrollToEndIfExists = scrollToEndIfExists
self.activateMessageSearch = activateMessageSearch self.activateMessageSearch = activateMessageSearch

View File

@ -160,7 +160,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
var imageApply: (() -> Void)? var imageApply: (() -> Void)?
var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if let photo = item.invoice.photo, let dimensions = photo.dimensions { if let photo = item.invoice.photo, let dimensions = photo.dimensions {
let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.cgSize.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) let arguments = TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: dimensions.cgSize.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
imageApply = makeImageLayout(arguments) imageApply = makeImageLayout(arguments)
maxTextWidth = max(1.0, maxTextWidth - imageSize.width - imageTextSpacing) maxTextWidth = max(1.0, maxTextWidth - imageSize.width - imageTextSpacing)
if imageUpdated { if imageUpdated {

View File

@ -352,7 +352,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|> take(1) |> take(1)
self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterPromise.get(), self.searchQuery.get(), accountPeer) self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterPromise.get(), self.searchQuery.get(), accountPeer)
|> mapToSignal { peers, dates, selectedFilter, searchQuery, accountPeer -> Signal<([EnginePeer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?, String?, EnginePeer?), NoError> in |> mapToSignal { peers, dates, selectedFilter, searchQuery, accountPeer -> Signal<([EnginePeer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?, String?, EnginePeer?), NoError> in
if searchQuery?.isEmpty ?? true { if searchQuery?.isEmpty ?? true {

View File

@ -275,6 +275,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case recentlyDownloaded case recentlyDownloaded
} }
case recentlySearchedPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType) case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType) case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int, isFirstInList: Bool)?, MessageSection, Bool) case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int, isFirstInList: Bool)?, MessageSection, Bool)
@ -282,126 +283,219 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
public var stableId: ChatListSearchEntryStableId { public var stableId: ChatListSearchEntryStableId {
switch self { switch self {
case let .localPeer(peer, _, _, _, _, _, _, _, _): case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _):
return .localPeerId(peer.id) return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _): case let .localPeer(peer, _, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id) return .localPeerId(peer.id)
case let .message(message, _, _, _, _, _, _, _, _, section, _): case let .globalPeer(peer, _, _, _, _, _, _, _):
return .messageId(message.id, section) return .globalPeerId(peer.peer.id)
case .addContact: case let .message(message, _, _, _, _, _, _, _, _, section, _):
return .addContact return .messageId(message.id, section)
case .addContact:
return .addContact
} }
} }
public static func ==(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool { public static func ==(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
switch lhs { switch lhs {
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType): case let .recentlySearchedPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType { if case let .recentlySearchedPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 {
return true return true
} else { } else {
return false
}
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
return false
}
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
return false
}
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused) = rhs {
if lhsMessage.id != rhsMessage.id {
return false return false
} }
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType): if lhsMessage.stableVersion != rhsMessage.stableVersion {
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
return false return false
} }
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused): if lhsPeer != rhsPeer {
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
if lhsMessage.stableVersion != rhsMessage.stableVersion {
return false
}
if lhsPeer != rhsPeer {
return false
}
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
return false
}
if lhsTotalCount != rhsTotalCount {
return false
}
if lhsSelected != rhsSelected {
return false
}
if lhsDisplayCustomHeader != rhsDisplayCustomHeader {
return false
}
if lhsKey != rhsKey {
return false
}
if lhsResourceId?.0 != rhsResourceId?.0 {
return false
}
if lhsResourceId?.1 != rhsResourceId?.1 {
return false
}
if lhsSection != rhsSection {
return false
}
if lhsAllPaused != rhsAllPaused {
return false
}
return true
} else {
return false return false
} }
case let .addContact(lhsPhoneNumber, lhsTheme, lhsStrings): if lhsPresentationData !== rhsPresentationData {
if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs {
if lhsPhoneNumber != rhsPhoneNumber {
return false
}
if lhsTheme !== rhsTheme {
return false
}
if lhsStrings !== rhsStrings {
return false
}
return true
} else {
return false return false
} }
if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
return false
}
if lhsTotalCount != rhsTotalCount {
return false
}
if lhsSelected != rhsSelected {
return false
}
if lhsDisplayCustomHeader != rhsDisplayCustomHeader {
return false
}
if lhsKey != rhsKey {
return false
}
if lhsResourceId?.0 != rhsResourceId?.0 {
return false
}
if lhsResourceId?.1 != rhsResourceId?.1 {
return false
}
if lhsSection != rhsSection {
return false
}
if lhsAllPaused != rhsAllPaused {
return false
}
return true
} else {
return false
}
case let .addContact(lhsPhoneNumber, lhsTheme, lhsStrings):
if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs {
if lhsPhoneNumber != rhsPhoneNumber {
return false
}
if lhsTheme !== rhsTheme {
return false
}
if lhsStrings !== rhsStrings {
return false
}
return true
} else {
return false
}
} }
} }
public static func <(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool { public static func <(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
switch lhs { switch lhs {
case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _): case let .recentlySearchedPeer(_, _, _, lhsIndex, _, _, _, _):
if case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _) = rhs { if case let .recentlySearchedPeer(_, _, _, rhsIndex, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex return lhsIndex <= rhsIndex
} else { } else {
return true return true
} }
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _): case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _):
switch rhs { switch rhs {
case .localPeer: case .recentlySearchedPeer:
return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .message, .addContact:
return true
}
case let .message(_, _, _, _, _, _, _, lhsKey, _, _, _):
if case let .message(_, _, _, _, _, _, _, rhsKey, _, _, _) = rhs {
return lhsKey < rhsKey
} else if case .addContact = rhs {
return true
} else {
return false
}
case .addContact:
return false return false
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .globalPeer, .message, .addContact:
return true
}
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _):
switch rhs {
case .recentlySearchedPeer, .localPeer:
return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .message, .addContact:
return true
}
case let .message(_, _, _, _, _, _, _, lhsKey, _, _, _):
if case let .message(_, _, _, _, _, _, _, rhsKey, _, _, _) = rhs {
return lhsKey < rhsKey
} else if case .addContact = rhs {
return true
} else {
return false
}
case .addContact:
return false
} }
} }
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem { public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem {
switch self { switch self {
case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder):
let primaryPeer: EnginePeer
var chatPeer: EnginePeer?
if let associatedPeer = associatedPeer {
primaryPeer = associatedPeer
chatPeer = peer
} else {
primaryPeer = peer
chatPeer = peer
}
var enabled = true
if filter.contains(.onlyWriteable) {
if let peer = chatPeer {
enabled = canSendMessagesToPeer(peer._asPeer())
} else {
enabled = false
}
}
if filter.contains(.onlyPrivateChats) {
if let peer = chatPeer {
switch peer {
case .user, .secretChat:
break
default:
enabled = false
}
} else {
enabled = false
}
}
if filter.contains(.onlyGroups) {
if let peer = chatPeer {
if case .legacyGroup = peer {
} else if case let .channel(peer) = peer, case .group = peer.info {
} else {
enabled = false
}
} else {
enabled = false
}
}
var badge: ContactsPeerItemBadge?
if let unreadBadge = unreadBadge {
badge = ContactsPeerItemBadge(count: unreadBadge.0, type: unreadBadge.1 ? .inactive : .active)
}
let header: ChatListSearchItemHeader?
if filter.contains(.removeSearchHeader) {
header = nil
} else {
let headerType: ChatListSearchItemHeaderType
if filter.contains(.onlyGroups) {
headerType = .chats
} else {
headerType = .recentPeers
}
header = ChatListSearchItemHeader(type: headerType, theme: theme, strings: strings, actionTitle: nil, action: nil)
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer {
interaction.peerSelected(chatPeer, peer, nil)
} else {
interaction.peerSelected(peer, nil, nil)
}
}, contextAction: peerContextAction.flatMap { peerContextAction in
return { node, gesture in
if let chatPeer = chatPeer, chatPeer.id.namespace != Namespaces.Peer.SecretChat {
peerContextAction(chatPeer, .search(nil), node, gesture)
} else {
gesture?.cancel()
}
}
}, arrowAction: nil)
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: EnginePeer let primaryPeer: EnginePeer
var chatPeer: EnginePeer? var chatPeer: EnginePeer?
@ -1063,27 +1157,53 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1)
let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)]), NoError> let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>), NoError>
if let query = query { if let query = query {
foundLocalPeers = context.engine.contacts.searchLocalPeers(query: query.lowercased()) foundLocalPeers = combineLatest(
|> mapToSignal { local -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: Int], [EngineRenderedPeer]), NoError> in context.engine.contacts.searchLocalPeers(query: query.lowercased()),
context.engine.peers.recentlySearchedPeers()
)
|> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: Int], [EngineRenderedPeer], Set<EnginePeer.Id>), NoError> in
let recentlySearched = allRecentlySearched.filter { peer in
guard let peer = peer.peer.peer else {
return false
}
return peer.indexName.matchesByTokens(query)
}
var peerIds = Set<EnginePeer.Id>()
var peers: [EngineRenderedPeer] = []
for peer in recentlySearched {
if !peerIds.contains(peer.peer.peerId) {
peerIds.insert(peer.peer.peerId)
peers.append(EngineRenderedPeer(peer.peer))
}
}
for peer in local {
if !peerIds.contains(peer.peerId) {
peerIds.insert(peer.peerId)
peers.append(peer)
}
}
return context.engine.data.subscribe( return context.engine.data.subscribe(
EngineDataMap( EngineDataMap(
local.map { peer -> TelegramEngine.EngineData.Item.Peer.NotificationSettings in peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.NotificationSettings in
return TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.peerId) return TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId)
} }
), ),
EngineDataMap( EngineDataMap(
local.map { peer -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in
return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peer.peerId) return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId)
} }
) )
) )
|> map { notificationSettings, unreadCounts in |> map { notificationSettings, unreadCounts in
return (notificationSettings, unreadCounts, local) return (notificationSettings, unreadCounts, peers, Set(recentlySearched.map(\.peer.peerId)))
} }
} }
|> map { notificationSettings, unreadCounts, peers -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)]) in |> map { notificationSettings, unreadCounts, peers, recentlySearchedPeerIds -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>) in
var unread: [EnginePeer.Id: (Int32, Bool)] = [:] var unread: [EnginePeer.Id: (Int32, Bool)] = [:]
for peer in peers { for peer in peers {
var isMuted: Bool = false var isMuted: Bool = false
@ -1101,10 +1221,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
unread[peer.peerId] = (Int32(unreadCount), isMuted) unread[peer.peerId] = (Int32(unreadCount), isMuted)
} }
} }
return (peers: peers, unread: unread) return (peers: peers, unread: unread, recentlySearchedPeerIds: recentlySearchedPeerIds)
} }
} else { } else {
foundLocalPeers = .single((peers: [], unread: [:])) foundLocalPeers = .single((peers: [], unread: [:], recentlySearchedPeerIds: Set()))
} }
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError>
@ -1293,10 +1413,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
} }
var numberOfLocalPeers = 0
for renderedPeer in foundLocalPeers.peers { for renderedPeer in foundLocalPeers.peers {
if case .expand = localExpandType, numberOfLocalPeers >= 3 { if !foundLocalPeers.recentlySearchedPeerIds.contains(renderedPeer.peerId) {
break continue
} }
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, EnginePeer(accountPeer)) { if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, EnginePeer(accountPeer)) {
@ -1306,6 +1425,31 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case let .secretChat(secretChat) = peer, let associatedPeerId = secretChat.associatedPeerId { if case let .secretChat(secretChat) = peer, let associatedPeerId = secretChat.associatedPeerId {
associatedPeer = renderedPeer.peers[associatedPeerId] associatedPeer = renderedPeer.peers[associatedPeerId]
} }
entries.append(.recentlySearchedPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
index += 1
}
}
}
var numberOfLocalPeers = 0
for renderedPeer in foundLocalPeers.peers {
if case .expand = localExpandType, numberOfLocalPeers >= 3 {
break
}
if foundLocalPeers.recentlySearchedPeerIds.contains(renderedPeer.peerId) {
continue
}
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, EnginePeer(accountPeer)) {
if !existingPeerIds.contains(peer.id) {
existingPeerIds.insert(peer.id)
var associatedPeer: EnginePeer?
if case let .secretChat(secretChat) = peer, let associatedPeerId = secretChat.associatedPeerId {
associatedPeer = renderedPeer.peers[associatedPeerId]
}
entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
index += 1 index += 1
numberOfLocalPeers += 1 numberOfLocalPeers += 1

View File

@ -1274,6 +1274,12 @@ open class NavigationController: UINavigationController, ContainableController,
completion() completion()
} }
public func replaceControllers(controllers: [UIViewController], animated: Bool, options: NavigationAnimationOptions = [], ready: ValuePromise<Bool>? = nil, completion: @escaping () -> Void = {}) {
ready?.set(true)
self.setViewControllers(controllers, animated: animated)
completion()
}
public func replaceAllButRootController(_ controller: ViewController, animated: Bool, animationOptions: NavigationAnimationOptions = [], ready: ValuePromise<Bool>? = nil, completion: @escaping () -> Void = {}) { public func replaceAllButRootController(_ controller: ViewController, animated: Bool, animationOptions: NavigationAnimationOptions = [], ready: ValuePromise<Bool>? = nil, completion: @escaping () -> Void = {}) {
ready?.set(true) ready?.set(true)
var controllers = self.viewControllers var controllers = self.viewControllers

View File

@ -110,9 +110,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
self.contentContainer.addSubnode(self.scrollNode) self.contentContainer.addSubnode(self.scrollNode)
self.contentContainerMask = UIImageView() self.contentContainerMask = UIImageView()
let maskGradientWidth: CGFloat = 10.0 self.contentContainerMask.image = generateImage(CGSize(width: 52.0, height: 52.0), rotatedContext: { size, context in
self.contentContainerMask.image = generateImage(CGSize(width: maskGradientWidth * 2.0 + 1.0, height: 8.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: 1.1)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
let shadowColor = UIColor.black let shadowColor = UIColor.black
@ -122,17 +124,21 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
for i in 0 ... stepCount { for i in 0 ... stepCount {
let t = CGFloat(i) / CGFloat(stepCount) let t = CGFloat(i) / CGFloat(stepCount)
colors.append(shadowColor.withAlphaComponent(t * t).cgColor) colors.append(shadowColor.withAlphaComponent(t).cgColor)
locations.append(t) locations.append(t)
} }
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colors as CFArray, locations: &locations)! let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: maskGradientWidth, y: 0.0), options: CGGradientDrawingOptions())
context.drawLinearGradient(gradient, start: CGPoint(x: size.width, y: 0.0), end: CGPoint(x: maskGradientWidth + 1.0, y: 0.0), options: CGGradientDrawingOptions()) let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
let gradientWidth = 6.0
context.drawRadialGradient(gradient, startCenter: center, startRadius: size.width / 2.0, endCenter: center, endRadius: size.width / 2.0 - gradientWidth, options: [])
context.setFillColor(shadowColor.cgColor) context.setFillColor(shadowColor.cgColor)
context.fill(CGRect(origin: CGPoint(x: maskGradientWidth, y: 0.0), size: CGSize(width: 1.0, height: size.height))) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: gradientWidth - 1.0, dy: gradientWidth - 1.0))
})?.stretchableImage(withLeftCapWidth: Int(maskGradientWidth), topCapHeight: 0) })?.stretchableImage(withLeftCapWidth: Int(52.0 / 2.0), topCapHeight: Int(52.0 / 2.0))
self.contentContainer.view.mask = self.contentContainerMask self.contentContainer.view.mask = self.contentContainerMask
//self.contentContainer.view.addSubview(self.contentContainerMask)
super.init() super.init()

View File

@ -567,6 +567,8 @@ public final class MediaStreamComponent: CombinedComponent {
private var scheduledDismissUITimer: SwiftSignalKit.Timer? private var scheduledDismissUITimer: SwiftSignalKit.Timer?
let deactivatePictureInPictureIfVisible = StoredActionSlot(Void.self)
init(call: PresentationGroupCallImpl) { init(call: PresentationGroupCallImpl) {
self.call = call self.call = call
@ -639,8 +641,6 @@ public final class MediaStreamComponent: CombinedComponent {
} }
}) })
//let _ = call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: call.peerId, revokePreviousCredentials: false).start()
self.isVisibleInHierarchyDisposable = (call.accountContext.sharedContext.applicationBindings.applicationInForeground self.isVisibleInHierarchyDisposable = (call.accountContext.sharedContext.applicationBindings.applicationInForeground
|> deliverOnMainQueue).start(next: { [weak self] inForeground in |> deliverOnMainQueue).start(next: { [weak self] inForeground in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -649,6 +649,16 @@ public final class MediaStreamComponent: CombinedComponent {
if strongSelf.isVisibleInHierarchy != inForeground { if strongSelf.isVisibleInHierarchy != inForeground {
strongSelf.isVisibleInHierarchy = inForeground strongSelf.isVisibleInHierarchy = inForeground
strongSelf.updated(transition: .immediate) strongSelf.updated(transition: .immediate)
if inForeground {
Queue.mainQueue().after(0.5, {
guard let strongSelf = self, strongSelf.isVisibleInHierarchy else {
return
}
strongSelf.deactivatePictureInPictureIfVisible.invoke(Void())
})
}
} }
}) })
} }
@ -705,6 +715,7 @@ public final class MediaStreamComponent: CombinedComponent {
let toolbar = Child(ToolbarComponent.self) let toolbar = Child(ToolbarComponent.self)
let activatePictureInPicture = StoredActionSlot(Action<Void>.self) let activatePictureInPicture = StoredActionSlot(Action<Void>.self)
let deactivatePictureInPicture = StoredActionSlot(Void.self)
let moreButtonTag = GenericComponentViewTag() let moreButtonTag = GenericComponentViewTag()
let moreAnimationTag = GenericComponentViewTag() let moreAnimationTag = GenericComponentViewTag()
@ -725,6 +736,16 @@ public final class MediaStreamComponent: CombinedComponent {
let state = context.state let state = context.state
let controller = environment.controller let controller = environment.controller
context.state.deactivatePictureInPictureIfVisible.connect {
guard let controller = controller() else {
return
}
if controller.view.window == nil {
return
}
deactivatePictureInPicture.invoke(Void())
}
let video = video.update( let video = video.update(
component: MediaStreamVideoComponent( component: MediaStreamVideoComponent(
call: context.component.call, call: context.component.call,
@ -733,6 +754,7 @@ public final class MediaStreamComponent: CombinedComponent {
isAdmin: context.state.canManageCall, isAdmin: context.state.canManageCall,
peerTitle: context.state.peerTitle, peerTitle: context.state.peerTitle,
activatePictureInPicture: activatePictureInPicture, activatePictureInPicture: activatePictureInPicture,
deactivatePictureInPicture: deactivatePictureInPicture,
bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in
guard let call = call else { guard let call = call else {
completed() completed()
@ -742,6 +764,9 @@ public final class MediaStreamComponent: CombinedComponent {
call.accountContext.sharedContext.mainWindow?.inCallNavigate?() call.accountContext.sharedContext.mainWindow?.inCallNavigate?()
completed() completed()
},
pictureInPictureClosed: { [weak call] in
let _ = call?.leave(terminateIfPossible: false)
} }
), ),
availableSize: context.availableSize, availableSize: context.availableSize,
@ -1095,8 +1120,13 @@ public final class MediaStreamComponent: CombinedComponent {
state.updateDismissOffset(value: offset.y, interactive: true) state.updateDismissOffset(value: offset.y, interactive: true)
case let .ended(velocity): case let .ended(velocity):
if abs(velocity.y) > 200.0 { if abs(velocity.y) > 200.0 {
state.updateDismissOffset(value: velocity.y < 0 ? -height : height, interactive: false) activatePictureInPicture.invoke(Action { [weak state] in
(controller() as? MediaStreamComponentController)?.dismiss(closing: false, manual: true) guard let state = state, let controller = controller() as? MediaStreamComponentController else {
return
}
state.updateDismissOffset(value: velocity.y < 0 ? -height : height, interactive: false)
controller.dismiss(closing: false, manual: true)
})
} else { } else {
state.updateDismissOffset(value: 0.0, interactive: false) state.updateDismissOffset(value: 0.0, interactive: false)
} }

View File

@ -14,16 +14,30 @@ final class MediaStreamVideoComponent: Component {
let isAdmin: Bool let isAdmin: Bool
let peerTitle: String let peerTitle: String
let activatePictureInPicture: ActionSlot<Action<Void>> let activatePictureInPicture: ActionSlot<Action<Void>>
let deactivatePictureInPicture: ActionSlot<Void>
let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
let pictureInPictureClosed: () -> Void
init(call: PresentationGroupCallImpl, hasVideo: Bool, isVisible: Bool, isAdmin: Bool, peerTitle: String, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) { init(
call: PresentationGroupCallImpl,
hasVideo: Bool,
isVisible: Bool,
isAdmin: Bool,
peerTitle: String,
activatePictureInPicture: ActionSlot<Action<Void>>,
deactivatePictureInPicture: ActionSlot<Void>,
bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void,
pictureInPictureClosed: @escaping () -> Void
) {
self.call = call self.call = call
self.hasVideo = hasVideo self.hasVideo = hasVideo
self.isVisible = isVisible self.isVisible = isVisible
self.isAdmin = isAdmin self.isAdmin = isAdmin
self.peerTitle = peerTitle self.peerTitle = peerTitle
self.activatePictureInPicture = activatePictureInPicture self.activatePictureInPicture = activatePictureInPicture
self.deactivatePictureInPicture = deactivatePictureInPicture
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
self.pictureInPictureClosed = pictureInPictureClosed
} }
public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool { public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool {
@ -72,6 +86,8 @@ final class MediaStreamVideoComponent: Component {
private var component: MediaStreamVideoComponent? private var component: MediaStreamVideoComponent?
private var hadVideo: Bool = false private var hadVideo: Bool = false
private var requestedExpansion: Bool = false
private var noSignalTimer: Timer? private var noSignalTimer: Timer?
private var noSignalTimeout: Bool = false private var noSignalTimeout: Bool = false
@ -101,7 +117,10 @@ final class MediaStreamVideoComponent: Component {
} }
func expandFromPictureInPicture() { func expandFromPictureInPicture() {
self.pictureInPictureController?.stopPictureInPicture() if let pictureInPictureController = self.pictureInPictureController, pictureInPictureController.isPictureInPictureActive {
self.requestedExpansion = true
self.pictureInPictureController?.stopPictureInPicture()
}
} }
func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize { func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
@ -290,6 +309,14 @@ final class MediaStreamVideoComponent: Component {
completion(Void()) completion(Void())
} }
component.deactivatePictureInPicture.connect { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.expandFromPictureInPicture()
}
return availableSize return availableSize
} }
@ -308,6 +335,14 @@ final class MediaStreamVideoComponent: Component {
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
} }
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
if self.requestedExpansion {
self.requestedExpansion = false
} else {
self.component?.pictureInPictureClosed()
}
}
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
} }

View File

@ -8848,7 +8848,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
updatedChatNavigationStack.removeSubrange(0 ..< (index + 1)) updatedChatNavigationStack.removeSubrange(0 ..< (index + 1))
} }
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), animated: true, chatListFilter: nextFolderId, chatNavigationStack: updatedChatNavigationStack, completion: { nextController in strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), useBackAnimation: true, animated: true, chatListFilter: nextFolderId, chatNavigationStack: updatedChatNavigationStack, completion: { nextController in
let _ = nextController let _ = nextController
})) }))
}))) })))

View File

@ -122,9 +122,16 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
params.completion(controller) params.completion(controller)
}) })
} else { } else {
params.navigationController.replaceControllersAndPush(controllers: viewControllers, controller: controller, animated: params.animated, options: params.options, completion: { if params.useBackAnimation {
params.completion(controller) params.navigationController.viewControllers = [controller] + params.navigationController.viewControllers
}) params.navigationController.replaceControllers(controllers: viewControllers + [controller], animated: params.animated, options: params.options, completion: {
params.completion(controller)
})
} else {
params.navigationController.replaceControllersAndPush(controllers: viewControllers, controller: controller, animated: params.animated, options: params.options, completion: {
params.completion(controller)
})
}
} }
} }
if params.activateInput { if params.activateInput {