Update next channel navigation UI

This commit is contained in:
Ali 2021-08-03 16:27:10 +02:00
parent 321f4002ab
commit 0a0517122c
9 changed files with 120 additions and 30 deletions

View File

@ -289,9 +289,10 @@ public final class NavigateToChatControllerParams {
public let animated: Bool
public let options: NavigationAnimationOptions
public let parentGroupId: PeerGroupId?
public let chatListFilter: ChatListFilterData?
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, 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, 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, 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: ChatListFilterData? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
self.navigationController = navigationController
self.chatController = chatController
self.chatLocationContextHolder = chatLocationContextHolder
@ -312,6 +313,7 @@ public final class NavigateToChatControllerParams {
self.animated = animated
self.options = options
self.parentGroupId = parentGroupId
self.chatListFilter = chatListFilter
self.completion = completion
}
}

View File

@ -611,8 +611,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
scrollToEndIfExists = true
}
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), activateInput: activateInput && !peer.isDeleted, scrollToEndIfExists: scrollToEndIfExists, animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] controller in
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), activateInput: activateInput && !peer.isDeleted, scrollToEndIfExists: scrollToEndIfExists, animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, chatListFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.data, completion: { [weak self] controller in
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
if let promoInfo = promoInfo {
switch promoInfo {

View File

@ -28,7 +28,7 @@ struct ChatListNodeViewUpdate {
let scrollPosition: ChatListNodeViewScrollPosition?
}
func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredicate {
public func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredicate {
var includePeers = Set(filter.includePeers.peers)
var excludePeers = Set(filter.excludePeers)

View File

@ -495,16 +495,19 @@ public extension TelegramEngine {
return _internal_updatePeerDescription(account: self.account, peerId: peerId, description: description)
}
public func getNextUnreadChannel(peerId: PeerId) -> Signal<EnginePeer?, NoError> {
public func getNextUnreadChannel(peerId: PeerId, filter: ChatListFilterPredicate?) -> Signal<EnginePeer?, NoError> {
return self.account.postbox.transaction { transaction -> EnginePeer? in
var peers: [RenderedPeer] = []
peers.append(contentsOf: transaction.getTopChatListEntries(groupId: .root, count: 100))
peers.append(contentsOf: transaction.getTopChatListEntries(groupId: Namespaces.PeerGroup.archive, count: 100))
var results: [(EnginePeer, Int32)] = []
for peer in peers {
guard let channel = peer.chatMainPeer as? TelegramChannel, case .broadcast = channel.info else {
var peerIds: [PeerId] = []
peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: .root, filterPredicate: filter))
peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: Namespaces.PeerGroup.archive, filterPredicate: filter))
for listId in peerIds {
guard let peer = transaction.getPeer(listId) else {
continue
}
guard let channel = peer as? TelegramChannel, case .broadcast = channel.info else {
continue
}
if channel.id == peerId {
@ -516,6 +519,7 @@ public extension TelegramEngine {
guard let topMessageIndex = transaction.getTopPeerMessageIndex(peerId: channel.id) else {
continue
}
results.append((EnginePeer(channel), topMessageIndex.timestamp))
}

View File

@ -137,6 +137,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case callsTabTip = 18
case chatFolderTips = 19
case locationProximityAlertTip = 20
case nextChatSuggestionTip = 21
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -268,6 +269,10 @@ private struct ApplicationSpecificNoticeKeys {
static func locationProximityAlertTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.locationProximityAlertTip.key)
}
static func nextChatSuggestionTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.nextChatSuggestionTip.key)
}
}
public struct ApplicationSpecificNotice {
@ -735,6 +740,28 @@ public struct ApplicationSpecificNotice {
transaction.setNotice(ApplicationSpecificNoticeKeys.chatMessageOptionsTip(), ApplicationSpecificCounterNotice(value: currentValue))
}
}
public static func getNextChatSuggestionTip(accountManager: AccountManager) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.nextChatSuggestionTip()) as? ApplicationSpecificCounterNotice {
return value.value
} else {
return 0
}
}
}
public static func incrementNextChatSuggestionTip(accountManager: AccountManager, count: Int32 = 1) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.nextChatSuggestionTip()) as? ApplicationSpecificCounterNotice {
currentValue = value.value
}
currentValue += count
transaction.setNotice(ApplicationSpecificNoticeKeys.nextChatSuggestionTip(), ApplicationSpecificCounterNotice(value: currentValue))
}
}
public static func reset(accountManager: AccountManager) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in

View File

@ -64,6 +64,7 @@ import TelegramPermissionsUI
import Speak
import UniversalMediaPlayer
import WallpaperBackgroundNode
import ChatListUI
#if DEBUG
import os.signpost
@ -212,6 +213,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var validLayout: ContainerViewLayout?
public weak var parentController: ViewController?
private let currentChatListFilter: ChatListFilterData?
public var peekActions: ChatControllerPeekActions = .standard
private var didSetup3dTouch: Bool = false
@ -282,6 +285,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var preloadHistoryPeerId: PeerId?
private let preloadHistoryPeerIdDisposable = MetaDisposable()
private var preloadNextChatPeerId: PeerId?
private let preloadNextChatPeerIdDisposable = MetaDisposable()
private let botCallbackAlertMessage = Promise<String?>(nil)
private var botCallbackAlertMessageDisposable: Disposable?
@ -459,7 +465,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var nextChannelToReadDisposable: Disposable?
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil) {
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: ChatListFilterData? = nil) {
let _ = ChatControllerCount.modify { value in
return value + 1
}
@ -470,6 +476,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.subject = subject
self.botStart = botStart
self.peekData = peekData
self.currentChatListFilter = chatListFilter
var useSharedAnimationPhase = false
switch mode {
@ -3306,16 +3313,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
if strongSelf.nextChannelToReadDisposable == nil {
strongSelf.nextChannelToReadDisposable = (strongSelf.context.engine.peers.getNextUnreadChannel(peerId: channel.id)
|> deliverOnMainQueue
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
strongSelf.context.engine.peers.getNextUnreadChannel(peerId: channel.id, filter: strongSelf.currentChatListFilter.flatMap(chatListFilterPredicate)),
ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager)
)
|> then(.complete() |> delay(1.0, queue: .mainQueue()))
|> restart).start(next: { nextPeer in
|> restart).start(next: { nextPeer, nextChatSuggestionTip in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.historyNode.offerNextChannelToRead = true
strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextPeer
strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
let nextPeerId = nextPeer?.id
if strongSelf.preloadNextChatPeerId != nextPeerId {
strongSelf.preloadNextChatPeerId = nextPeerId
if let nextPeerId = nextPeerId {
let combinedDisposable = DisposableSet()
strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable)
combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: nextPeerId).start())
combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId))
} else {
strongSelf.preloadNextChatPeerIdDisposable.set(nil)
}
}
})
}
}
@ -3878,6 +3902,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.shareStatusDisposable?.dispose()
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeTarget(self)
self.preloadHistoryPeerIdDisposable.dispose()
self.preloadNextChatPeerIdDisposable.dispose()
self.reportIrrelvantGeoDisposable?.dispose()
self.reminderActivity?.invalidate()
self.updateSlowmodeStatusDisposable.dispose()
@ -7053,11 +7078,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
if let navigationController = strongSelf.effectiveNavigationController {
ApplicationSpecificNotice.incrementNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager).start()
let snapshotState = strongSelf.chatDisplayNode.prepareSnapshotState(
titleViewSnapshotState: strongSelf.chatTitleView?.prepareSnapshotState(),
avatarSnapshotState: (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.prepareSnapshotState()
)
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), animated: false, completion: { nextController in
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), animated: false, chatListFilter: strongSelf.currentChatListFilter, completion: { nextController in
(nextController as! ChatControllerImpl).animateFromPreviousController(snapshotState: snapshotState)
}))
}

View File

@ -551,6 +551,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private var overscrollView: ComponentHostView<Empty>?
var nextChannelToRead: EnginePeer?
var offerNextChannelToRead: Bool = false
var nextChannelToReadDisplayName: Bool = false
private var currentOverscrollExpandProgress: CGFloat = 0.0
private var feedback: HapticFeedback?
var openNextChannelToRead: ((EnginePeer) -> Void)?
@ -1217,15 +1218,25 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
self.view.addSubview(overscrollView)
}
let expandProgress: CGFloat = min(1.0, max(-offset, 0.0) / 110.0)
let expandDistance = max(-offset - 12.0, 0.0)
let expandProgress: CGFloat = min(1.0, expandDistance / 90.0)
let text: String
if self.nextChannelToRead != nil {
if expandProgress >= 0.99 {
//TODO:localize
text = "Release to go to the next unread channel"
if let nextChannelToRead = nextChannelToRead {
if self.nextChannelToReadDisplayName {
if expandProgress >= 0.99 {
//TODO:localize
text = "Release to go to \(nextChannelToRead.compactDisplayTitle)"
} else {
text = "Swipe up to go to \(nextChannelToRead.compactDisplayTitle)"
}
} else {
text = "Swipe up to go to the next unread channel"
if expandProgress >= 0.99 {
//TODO:localize
text = "Release to go to the next unread channel"
} else {
text = "Swipe up to go to the next unread channel"
}
}
let previousType = self.currentOverscrollExpandProgress >= 0.99
@ -1251,7 +1262,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
foregroundColor: bubbleVariableColor(variableColor: self.currentPresentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: self.currentPresentationData.theme.wallpaper),
peer: self.nextChannelToRead,
context: self.context,
expandProgress: expandProgress
expandDistance: expandDistance
)),
environment: {},
containerSize: CGSize(width: self.bounds.width, height: 200.0)

View File

@ -341,7 +341,7 @@ final class ChatOverscrollControl: CombinedComponent {
let foregroundColor: UIColor
let peer: EnginePeer?
let context: AccountContext
let expandProgress: CGFloat
let expandDistance: CGFloat
init(
text: String,
@ -349,14 +349,14 @@ final class ChatOverscrollControl: CombinedComponent {
foregroundColor: UIColor,
peer: EnginePeer?,
context: AccountContext,
expandProgress: CGFloat
expandDistance: CGFloat
) {
self.text = text
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
self.peer = peer
self.context = context
self.expandProgress = expandProgress
self.expandDistance = expandDistance
}
static func ==(lhs: ChatOverscrollControl, rhs: ChatOverscrollControl) -> Bool {
@ -375,7 +375,7 @@ final class ChatOverscrollControl: CombinedComponent {
if lhs.context !== rhs.context {
return false
}
if lhs.expandProgress != rhs.expandProgress {
if lhs.expandDistance != rhs.expandDistance {
return false
}
return true
@ -431,13 +431,32 @@ final class ChatOverscrollControl: CombinedComponent {
)
})
let progressSize = avatarBackground.size.width - avatarProgressPadding * 2.0
let halfDistance = progressSize
let quarterDistance = halfDistance / 2.0
let clippedDistance = max(0.0, min(halfDistance * 2.0, context.component.expandDistance))
var mappedProgress: CGFloat
if clippedDistance <= quarterDistance {
mappedProgress = acos(1.0 - clippedDistance / quarterDistance) / (CGFloat.pi * 2.0)
} else if clippedDistance <= halfDistance {
let sectionDistance = halfDistance - clippedDistance
mappedProgress = 0.25 + asin(1.0 - sectionDistance / quarterDistance) / (CGFloat.pi * 2.0)
} else {
let restDistance = clippedDistance - halfDistance
mappedProgress = min(1.0, 0.5 + restDistance / 60.0)
}
mappedProgress = max(0.01, mappedProgress)
let avatarExpandProgress = avatarExpandProgress.update(
component: RadialProgressComponent(
color: context.component.foregroundColor,
lineWidth: 2.5,
value: context.component.peer == nil ? 0.0 : context.component.expandProgress
value: context.component.peer == nil ? 0.0 : mappedProgress
),
availableSize: CGSize(width: avatarBackground.size.width - avatarProgressPadding * 2.0, height: avatarBackground.size.height - avatarProgressPadding * 2.0),
availableSize: CGSize(width: progressSize, height: progressSize),
transition: context.transition
)

View File

@ -70,7 +70,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
})
}
} else {
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData)
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter)
}
controller.purposefulAction = params.purposefulAction
if let search = params.activateMessageSearch {