mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
Profile main tab
This commit is contained in:
parent
f828becdb2
commit
ea63abbc8a
2
MODULE.bazel.lock
generated
2
MODULE.bazel.lock
generated
@ -159,7 +159,7 @@
|
||||
"moduleExtensions": {
|
||||
"@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "RjubjYIojbv0PxTpnoknalV9QzT9asbV7elDuN7m2A4=",
|
||||
"bzlTransitiveDigest": "xcBTf2+GaloFpg7YEh/Bv+1yAczRkiCt3DGws4K7kSk=",
|
||||
"usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
|
@ -666,14 +666,15 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
} else {
|
||||
authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
|
||||
}
|
||||
if isSelfGift {
|
||||
title = isStoryEntity ? uniqueGift.title : item.presentationData.strings.Notification_StarGift_Self_Title
|
||||
if isStoryEntity {
|
||||
title = uniqueGift.title
|
||||
} else if isSelfGift {
|
||||
title = item.presentationData.strings.Notification_StarGift_Self_Title
|
||||
} else if item.message.id.peerId.isTelegramNotifications {
|
||||
title = item.presentationData.strings.Notification_StarGift_TitleShort
|
||||
} else {
|
||||
title = isStoryEntity ? uniqueGift.title : item.presentationData.strings.Notification_StarGift_Title(authorName).string
|
||||
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string
|
||||
}
|
||||
|
||||
text = isStoryEntity ? "**\(item.presentationData.strings.Notification_StarGift_Collectible) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: item.presentationData.dateTimeFormat))**" : "**\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: item.presentationData.dateTimeFormat))**"
|
||||
ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift
|
||||
buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View
|
||||
|
@ -392,8 +392,7 @@ final class GiftOptionsScreenComponent: Component {
|
||||
color: .blue
|
||||
)
|
||||
}
|
||||
|
||||
if gift.flags.contains(.requiresPremium) {
|
||||
if !isSoldOut && gift.flags.contains(.requiresPremium) {
|
||||
ribbon = GiftItemComponent.Ribbon(
|
||||
text: environment.strings.Gift_Options_Gift_Premium,
|
||||
color: .orange
|
||||
|
@ -36,14 +36,14 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let gift: ProfileGiftsContext.State.StarGift
|
||||
let gift: StarGift
|
||||
let valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
let animateOut: ActionSlot<Action<()>>
|
||||
let getController: () -> ViewController?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift,
|
||||
gift: StarGift,
|
||||
valueInfo: StarGift.UniqueGift.ValueInfo,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
@ -219,7 +219,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
var giftIconSubject: GiftItemComponent.Subject?
|
||||
var genericGift: StarGift.Gift?
|
||||
|
||||
switch component.gift.gift {
|
||||
switch component.gift {
|
||||
case let .generic(gift):
|
||||
animationFile = gift.file
|
||||
giftIconSubject = .starGift(gift: gift, price: "")
|
||||
@ -676,12 +676,12 @@ final class GiftValueSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let gift: ProfileGiftsContext.State.StarGift
|
||||
let gift: StarGift
|
||||
let valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift,
|
||||
gift: StarGift,
|
||||
valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
) {
|
||||
self.context = context
|
||||
@ -796,12 +796,12 @@ final class GiftValueSheetComponent: CombinedComponent {
|
||||
|
||||
final class GiftValueScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private let gift: ProfileGiftsContext.State.StarGift
|
||||
private let gift: StarGift
|
||||
private let valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift,
|
||||
gift: StarGift,
|
||||
valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
) {
|
||||
self.context = context
|
||||
|
@ -633,7 +633,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
func openValue() {
|
||||
guard let controller = self.getController(), case let .profileGift(_, gift) = self.subject, case let .unique(uniqueGift) = gift.gift else {
|
||||
guard let controller = self.getController(), let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.payments.getUniqueStarGiftValueInfo(slug: uniqueGift.slug)
|
||||
|
@ -23,6 +23,50 @@ public enum PeerInfoPaneKey: Int32 {
|
||||
case groupsInCommon
|
||||
case similarChannels
|
||||
case similarBots
|
||||
|
||||
public init(tab: TelegramProfileTab) {
|
||||
switch tab {
|
||||
case .files:
|
||||
self = .files
|
||||
case .gifs:
|
||||
self = .gifs
|
||||
case .gifts:
|
||||
self = .gifts
|
||||
case .links:
|
||||
self = .links
|
||||
case .media:
|
||||
self = .media
|
||||
case .music:
|
||||
self = .music
|
||||
case .posts:
|
||||
self = .stories
|
||||
case .voice:
|
||||
self = .voice
|
||||
}
|
||||
}
|
||||
|
||||
public var tab: TelegramProfileTab? {
|
||||
switch self {
|
||||
case .stories:
|
||||
return .posts
|
||||
case .gifts:
|
||||
return .gifts
|
||||
case .media:
|
||||
return .media
|
||||
case .files:
|
||||
return .files
|
||||
case .music:
|
||||
return .music
|
||||
case .voice:
|
||||
return .voice
|
||||
case .links:
|
||||
return .links
|
||||
case .gifs:
|
||||
return .gifs
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct PeerInfoStatusData: Equatable {
|
||||
|
@ -161,6 +161,9 @@ swift_library(
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
||||
"//submodules/TelegramUI/Components/MarqueeComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1320,9 +1320,14 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
hasSavedMessageTags = .single(false)
|
||||
}
|
||||
|
||||
let starsRevenueContextAndState = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> mapToSignal { peer -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
|
||||
var canViewStarsRevenue = false
|
||||
let starsRevenueContextAndState = combineLatest(
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> distinctUntilChanged,
|
||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId))
|
||||
|> distinctUntilChanged
|
||||
)
|
||||
|> mapToSignal { peer, canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
|
||||
var canViewStarsRevenue = canViewRevenue
|
||||
if let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal || context.sharedContext.immediateExperimentalUISettings.devRequests {
|
||||
canViewStarsRevenue = true
|
||||
}
|
||||
@ -1399,14 +1404,14 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
var availablePanes = availablePanes
|
||||
if isMyProfile {
|
||||
availablePanes?.insert(.stories, at: 0)
|
||||
if let hasStoryArchive, hasStoryArchive {
|
||||
availablePanes?.insert(.storyArchive, at: 1)
|
||||
}
|
||||
if availablePanes != nil, profileGiftsContext != nil, let cachedData = peerView.cachedData as? CachedUserData {
|
||||
if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 {
|
||||
availablePanes?.insert(.gifts, at: hasStoryArchive == true ? 2 : 1)
|
||||
availablePanes?.insert(.gifts, at: 1)
|
||||
}
|
||||
}
|
||||
if let hasStoryArchive, hasStoryArchive {
|
||||
availablePanes?.append(.storyArchive)
|
||||
}
|
||||
} else if let hasStories {
|
||||
if hasStories, peerView.peers[peerView.peerId] is TelegramUser, peerView.peerId != context.account.peerId {
|
||||
availablePanes?.insert(.stories, at: 0)
|
||||
@ -1454,6 +1459,15 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
availablePanes = nil
|
||||
}
|
||||
|
||||
if var currentAvailablePanes = availablePanes, let cachedData = peerView.cachedData as? CachedUserData, let mainProfileTab = cachedData.mainProfileTab {
|
||||
let mainTabKey = PeerInfoPaneKey(tab: mainProfileTab)
|
||||
if currentAvailablePanes.contains(mainTabKey) && currentAvailablePanes.first != mainTabKey {
|
||||
currentAvailablePanes = currentAvailablePanes.filter { $0 != mainTabKey }
|
||||
currentAvailablePanes.insert(mainTabKey, at: 0)
|
||||
availablePanes = currentAvailablePanes
|
||||
}
|
||||
}
|
||||
|
||||
let peer = peerView.peers[userPeerId]
|
||||
|
||||
var globalSettings: TelegramGlobalSettings?
|
||||
@ -1686,6 +1700,15 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
availablePanes = nil
|
||||
}
|
||||
|
||||
if var currentAvailablePanes = availablePanes, let cachedData = peerView.cachedData as? CachedChannelData, let mainProfileTab = cachedData.mainProfileTab {
|
||||
let mainTabKey = PeerInfoPaneKey(tab: mainProfileTab)
|
||||
if currentAvailablePanes.contains(mainTabKey) && currentAvailablePanes.first != mainTabKey {
|
||||
currentAvailablePanes = currentAvailablePanes.filter { $0 != mainTabKey }
|
||||
currentAvailablePanes.insert(mainTabKey, at: 0)
|
||||
availablePanes = currentAvailablePanes
|
||||
}
|
||||
}
|
||||
|
||||
var discussionPeer: Peer?
|
||||
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
|
||||
discussionPeer = peer
|
||||
|
@ -15,6 +15,11 @@ import PeerInfoChatListPaneNode
|
||||
import PeerInfoChatPaneNode
|
||||
import TextFormat
|
||||
import EmojiTextAttachmentView
|
||||
import ComponentFlow
|
||||
import TabSelectorComponent
|
||||
import MultilineTextComponent
|
||||
import BottomButtonPanelComponent
|
||||
import UndoUI
|
||||
|
||||
final class PeerInfoPaneWrapper {
|
||||
let key: PeerInfoPaneKey
|
||||
@ -38,6 +43,176 @@ final class PeerInfoPaneWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private final class GiftsTabItemComponent: Component {
|
||||
typealias EnvironmentType = TabSelectorComponent.ItemEnvironment
|
||||
|
||||
let context: AccountContext
|
||||
let icons: [ProfileGiftsContext.State.StarGift]
|
||||
let title: String
|
||||
let theme: PresentationTheme
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
icons: [ProfileGiftsContext.State.StarGift],
|
||||
title: String,
|
||||
theme: PresentationTheme
|
||||
) {
|
||||
self.context = context
|
||||
self.icons = icons
|
||||
self.title = title
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftsTabItemComponent, rhs: GiftsTabItemComponent) -> Bool {
|
||||
if lhs.icons != rhs.icons {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let icon = ComponentView<Empty>()
|
||||
private var iconLayers: [AnyHashable: InlineStickerItemLayer] = [:]
|
||||
|
||||
private var component: GiftsTabItemComponent?
|
||||
|
||||
func update(component: GiftsTabItemComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let environment = environment[EnvironmentType.self].value
|
||||
|
||||
let textSpacing: CGFloat = 2.0
|
||||
let iconSpacing: CGFloat = 1.0
|
||||
|
||||
let normalColor = component.theme.list.itemSecondaryTextColor
|
||||
let selectedColor = component.theme.list.itemAccentColor
|
||||
let effectiveColor = normalColor.mixedWith(selectedColor, alpha: environment.selectionFraction)
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: effectiveColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
|
||||
var iconOffset: CGFloat = titleSize.width + textSpacing
|
||||
var iconsWidth: CGFloat = 0.0
|
||||
if !component.icons.isEmpty {
|
||||
iconsWidth += iconSpacing
|
||||
var validIds = Set<AnyHashable>()
|
||||
var index = 0
|
||||
for icon in component.icons {
|
||||
let id: AnyHashable
|
||||
if let reference = icon.reference {
|
||||
id = reference
|
||||
} else {
|
||||
id = index
|
||||
}
|
||||
validIds.insert(id)
|
||||
|
||||
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||
let animationLayer: InlineStickerItemLayer
|
||||
if let current = self.iconLayers[id] {
|
||||
animationLayer = current
|
||||
} else {
|
||||
var file: TelegramMediaFile?
|
||||
switch icon.gift {
|
||||
case let .generic(gift):
|
||||
file = gift.file
|
||||
case let .unique(gift):
|
||||
for attribute in gift.attributes {
|
||||
if case let .model(_, fileValue, _) = attribute {
|
||||
file = fileValue
|
||||
}
|
||||
}
|
||||
}
|
||||
guard let file else {
|
||||
continue
|
||||
}
|
||||
|
||||
let emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||
interactivelySelectedFromPackId: nil,
|
||||
fileId: file.fileId.id,
|
||||
file: file
|
||||
)
|
||||
animationLayer = InlineStickerItemLayer(
|
||||
context: .account(component.context),
|
||||
userLocation: .other,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: emoji,
|
||||
file: file,
|
||||
cache: component.context.animationCache,
|
||||
renderer: component.context.animationRenderer,
|
||||
unique: true,
|
||||
placeholderColor: component.theme.list.mediaPlaceholderColor,
|
||||
pointSize: iconSize,
|
||||
loopCount: 1
|
||||
)
|
||||
animationLayer.isVisibleForAnimations = true
|
||||
self.iconLayers[id] = animationLayer
|
||||
self.layer.addSublayer(animationLayer)
|
||||
|
||||
animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
animationLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
}
|
||||
transition.setFrame(layer: animationLayer, frame: CGRect(origin: CGPoint(x: iconOffset, y: 0.0), size: iconSize))
|
||||
iconOffset += iconSize.width + iconSpacing
|
||||
iconsWidth += iconSize.width + iconSpacing
|
||||
|
||||
index += 1
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, layer) in self.iconLayers {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
layer.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.iconLayers.removeValue(forKey: id)
|
||||
}
|
||||
} else {
|
||||
for (_, layer) in self.iconLayers {
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
self.iconLayers.removeAll()
|
||||
}
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
return CGSize(width: titleSize.width + iconsWidth, height: titleSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
||||
private let pressed: () -> Void
|
||||
|
||||
@ -702,8 +877,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
private let coveringBackgroundNode: NavigationBackgroundNode
|
||||
private let additionalBackgroundNode: ASDisplayNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let tabsContainerNode: PeerInfoPaneTabsContainerNode
|
||||
private let tabsContainer = ComponentView<Empty>()
|
||||
private let tabsSeparatorNode: ASDisplayNode
|
||||
private var didJustReorderTabs = false
|
||||
|
||||
private var actionPanel: ComponentView<Empty>?
|
||||
|
||||
let isReady = Promise<Bool>()
|
||||
var didSetIsReady = false
|
||||
@ -781,51 +959,14 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
self.coveringBackgroundNode = NavigationBackgroundNode(color: .clear)
|
||||
self.coveringBackgroundNode.isUserInteractionEnabled = false
|
||||
|
||||
self.tabsContainerNode = PeerInfoPaneTabsContainerNode(context: context)
|
||||
|
||||
self.tabsSeparatorNode = ASDisplayNode()
|
||||
self.tabsSeparatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
// self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.additionalBackgroundNode)
|
||||
self.addSubnode(self.coveringBackgroundNode)
|
||||
self.addSubnode(self.tabsContainerNode)
|
||||
self.addSubnode(self.tabsSeparatorNode)
|
||||
|
||||
self.tabsContainerNode.requestSelectPane = { [weak self] key in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.currentPaneKey == key {
|
||||
if let requestExpandTabs = strongSelf.requestExpandTabs, requestExpandTabs() {
|
||||
} else {
|
||||
let _ = strongSelf.currentPane?.node.scrollToTop()
|
||||
}
|
||||
return
|
||||
}
|
||||
if strongSelf.currentPanes[key] != nil {
|
||||
strongSelf.currentPaneKey = key
|
||||
|
||||
if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams {
|
||||
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
||||
strongSelf.currentPaneUpdated?(true)
|
||||
|
||||
strongSelf.currentPaneStatusPromise.set(strongSelf.currentPane?.node.status ?? .single(nil))
|
||||
strongSelf.nextPaneStatusPromise.set(.single(nil))
|
||||
strongSelf.paneTransitionPromise.set(nil)
|
||||
}
|
||||
} else if strongSelf.pendingSwitchToPaneKey != key {
|
||||
strongSelf.pendingSwitchToPaneKey = key
|
||||
strongSelf.expandOnSwitch = true
|
||||
|
||||
if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams {
|
||||
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -844,7 +985,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
guard let currentPaneKey = strongSelf.currentPaneKey, let availablePanes = currentParams.data?.availablePanes, let index = availablePanes.firstIndex(of: currentPaneKey) else {
|
||||
return []
|
||||
}
|
||||
if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) {
|
||||
if let tabsContainerView = strongSelf.tabsContainer.view, tabsContainerView.bounds.contains(strongSelf.view.convert(point, to: tabsContainerView)) {
|
||||
return []
|
||||
}
|
||||
if case .savedMessagesChats = currentPaneKey {
|
||||
@ -1017,6 +1158,47 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
}
|
||||
}
|
||||
|
||||
func openTabContextMenu(key: PeerInfoPaneKey, sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
||||
guard let params = self.currentParams, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: "Set as Main Tab", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
|
||||
guard let tab = key.tab else {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().after(0.15) {
|
||||
self.didJustReorderTabs = true
|
||||
let _ = (self.context.engine.peers.setMainProfileTab(peerId: self.peerId, tab: tab)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = UndoOverlayController(presentationData: params.presentationData, content: .actionSucceeded(title: nil, text: "Tab order changed.", cancel: nil, destructive: false), action: { _ in return true })
|
||||
self.parentController?.present(controller, in: .current)
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
||||
let contextController = ContextController(
|
||||
presentationData: params.presentationData,
|
||||
source: .extracted(TabsExtractedContentSource(sourceNode: sourceNode)),
|
||||
items: .single(ContextController.Items(content: .list(items))),
|
||||
recognizer: nil,
|
||||
gesture: gesture
|
||||
)
|
||||
self.parentController?.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, disableTabSwitching: Bool, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let previousAvailablePanes = self.currentAvailablePanes
|
||||
let availablePanes = data?.availablePanes ?? []
|
||||
@ -1241,6 +1423,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
paneDefaultTransition = .immediate
|
||||
}
|
||||
|
||||
if self.didJustReorderTabs && previousAvailablePanes != availablePanes {
|
||||
self.didJustReorderTabs = false
|
||||
paneDefaultTransition = .immediate
|
||||
}
|
||||
|
||||
if let _ = data {
|
||||
if let previousAvailablePanes = previousAvailablePanes, previousAvailablePanes.isEmpty, !availablePanes.isEmpty {
|
||||
self.shouldFadeIn = true
|
||||
@ -1323,8 +1510,6 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
tabsAlpha = 1.0 - tabsOffset / tabsHeight
|
||||
}
|
||||
tabsAlpha *= tabsAlpha
|
||||
transition.updateFrame(node: self.tabsContainerNode, frame: CGRect(origin: CGPoint(x: sideInset, y: -tabsOffset), size: CGSize(width: size.width - sideInset * 2.0, height: tabsHeight)))
|
||||
transition.updateAlpha(node: self.tabsContainerNode, alpha: tabsAlpha)
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel)))
|
||||
@ -1333,48 +1518,136 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
|
||||
transition.updateFrame(node: self.tabsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: tabsHeight - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
|
||||
self.tabsContainerNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: tabsHeight), presentationData: presentationData, paneList: availablePanes.map { key in
|
||||
let title: String
|
||||
var icons: [ProfileGiftsContext.State.StarGift] = []
|
||||
var canManageTabs = false
|
||||
if let peer = data?.peer {
|
||||
if peer.id == self.context.account.peerId {
|
||||
canManageTabs = true
|
||||
} else if let channel = data?.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
canManageTabs = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let items: [TabSelectorComponent.Item] = availablePanes.map { key in
|
||||
let content: TabSelectorComponent.Item.Content
|
||||
var canReorder = false
|
||||
switch key {
|
||||
case .stories:
|
||||
title = presentationData.strings.PeerInfo_PaneStories
|
||||
content = .text(presentationData.strings.PeerInfo_PaneStories)
|
||||
canReorder = true
|
||||
case .storyArchive:
|
||||
title = presentationData.strings.PeerInfo_PaneArchivedStories
|
||||
content = .text(presentationData.strings.PeerInfo_PaneArchivedStories)
|
||||
case .botPreview:
|
||||
title = presentationData.strings.PeerInfo_PaneBotPreviews
|
||||
content = .text(presentationData.strings.PeerInfo_PaneBotPreviews)
|
||||
case .media:
|
||||
title = presentationData.strings.PeerInfo_PaneMedia
|
||||
content = .text(presentationData.strings.PeerInfo_PaneMedia)
|
||||
canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel
|
||||
case .files:
|
||||
title = presentationData.strings.PeerInfo_PaneFiles
|
||||
content = .text(presentationData.strings.PeerInfo_PaneFiles)
|
||||
canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel
|
||||
case .links:
|
||||
title = presentationData.strings.PeerInfo_PaneLinks
|
||||
content = .text(presentationData.strings.PeerInfo_PaneLinks)
|
||||
canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel
|
||||
case .voice:
|
||||
title = presentationData.strings.PeerInfo_PaneVoiceAndVideo
|
||||
content = .text(presentationData.strings.PeerInfo_PaneVoiceAndVideo)
|
||||
canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel
|
||||
case .gifs:
|
||||
title = presentationData.strings.PeerInfo_PaneGifs
|
||||
content = .text(presentationData.strings.PeerInfo_PaneGifs)
|
||||
canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel
|
||||
case .music:
|
||||
title = presentationData.strings.PeerInfo_PaneAudio
|
||||
content = .text(presentationData.strings.PeerInfo_PaneAudio)
|
||||
canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel
|
||||
case .groupsInCommon:
|
||||
title = presentationData.strings.PeerInfo_PaneGroups
|
||||
content = .text(presentationData.strings.PeerInfo_PaneGroups)
|
||||
case .members:
|
||||
title = presentationData.strings.PeerInfo_PaneMembers
|
||||
content = .text(presentationData.strings.PeerInfo_PaneMembers)
|
||||
case .similarChannels:
|
||||
title = presentationData.strings.PeerInfo_PaneRecommended
|
||||
content = .text(presentationData.strings.PeerInfo_PaneRecommended)
|
||||
case .similarBots:
|
||||
title = presentationData.strings.PeerInfo_PaneRecommendedBots
|
||||
content = .text(presentationData.strings.PeerInfo_PaneRecommendedBots)
|
||||
case .savedMessagesChats:
|
||||
title = presentationData.strings.DialogList_TabTitle
|
||||
content = .text(presentationData.strings.DialogList_TabTitle)
|
||||
case .savedMessages:
|
||||
title = presentationData.strings.PeerInfo_SavedMessagesTabTitle
|
||||
content = .text(presentationData.strings.PeerInfo_SavedMessagesTabTitle)
|
||||
case .gifts:
|
||||
title = presentationData.strings.PeerInfo_PaneGifts
|
||||
var icons: [ProfileGiftsContext.State.StarGift] = []
|
||||
if let gifts = data?.profileGiftsContext?.currentState?.gifts.prefix(3) {
|
||||
icons = Array(gifts)
|
||||
}
|
||||
content = .component(AnyComponent(
|
||||
GiftsTabItemComponent(context: self.context, icons: icons, title: presentationData.strings.PeerInfo_PaneGifts, theme: presentationData.theme)
|
||||
))
|
||||
canReorder = true
|
||||
}
|
||||
return TabSelectorComponent.Item(id: key, content: content, isReorderable: false, contextAction: key != availablePanes.first && canManageTabs && canReorder ? { [weak self] node, gesture in
|
||||
self?.openTabContextMenu(key: key, sourceNode: node, gesture: gesture)
|
||||
} : nil)
|
||||
}
|
||||
|
||||
let tabsContainerSize = CGSize(width: size.width - sideInset * 2.0, height: tabsHeight)
|
||||
let tabsContainerEffectiveSize = self.tabsContainer.update(
|
||||
transition: ComponentTransition(transition),
|
||||
component: AnyComponent(TabSelectorComponent(
|
||||
colors: TabSelectorComponent.Colors(
|
||||
foreground: presentationData.theme.list.itemSecondaryTextColor,
|
||||
selection: presentationData.theme.list.itemAccentColor
|
||||
),
|
||||
theme: presentationData.theme,
|
||||
customLayout: TabSelectorComponent.CustomLayout(
|
||||
font: Font.medium(14.0),
|
||||
spacing: 6.0,
|
||||
fillWidth: true,
|
||||
lineSelection: true
|
||||
),
|
||||
items: items,
|
||||
selectedId: self.currentPaneKey,
|
||||
setSelectedId: { [weak self] id in
|
||||
guard let strongSelf = self, let key = id.base as? PeerInfoPaneKey else {
|
||||
return
|
||||
}
|
||||
if strongSelf.currentPaneKey == key {
|
||||
if let requestExpandTabs = strongSelf.requestExpandTabs, requestExpandTabs() {
|
||||
} else {
|
||||
let _ = strongSelf.currentPane?.node.scrollToTop()
|
||||
}
|
||||
return
|
||||
}
|
||||
if strongSelf.currentPanes[key] != nil {
|
||||
strongSelf.currentPaneKey = key
|
||||
|
||||
if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams {
|
||||
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
||||
strongSelf.currentPaneUpdated?(true)
|
||||
|
||||
strongSelf.currentPaneStatusPromise.set(strongSelf.currentPane?.node.status ?? .single(nil))
|
||||
strongSelf.nextPaneStatusPromise.set(.single(nil))
|
||||
strongSelf.paneTransitionPromise.set(nil)
|
||||
}
|
||||
} else if strongSelf.pendingSwitchToPaneKey != key {
|
||||
strongSelf.pendingSwitchToPaneKey = key
|
||||
strongSelf.expandOnSwitch = true
|
||||
|
||||
if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams {
|
||||
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
},
|
||||
transitionFraction: -self.transitionFraction
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: tabsContainerSize
|
||||
)
|
||||
let tabContainerFrameOriginX = floorToScreenPixels((size.width - tabsContainerEffectiveSize.width) / 2.0)
|
||||
let tabContainerFrame = CGRect(origin: CGPoint(x: tabContainerFrameOriginX, y: 10.0 - tabsOffset), size: tabsContainerSize)
|
||||
if let tabsContainerView = self.tabsContainer.view {
|
||||
if tabsContainerView.superview == nil {
|
||||
self.view.insertSubview(tabsContainerView, belowSubview: self.tabsSeparatorNode.view)
|
||||
}
|
||||
transition.updateFrame(view: tabsContainerView, frame: tabContainerFrame)
|
||||
transition.updateAlpha(layer: tabsContainerView.layer, alpha: tabsAlpha)
|
||||
}
|
||||
return PeerInfoPaneSpecifier(key: key, title: title, icons: icons)
|
||||
}, selectedPane: self.currentPaneKey, disableSwitching: disableTabSwitching, transitionFraction: self.transitionFraction, transition: transition)
|
||||
|
||||
for (_, pane) in self.pendingPanes {
|
||||
let paneTransition: ContainedViewLayoutTransition = .immediate
|
||||
@ -1425,3 +1698,23 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class TabsExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let ignoreContentTouches: Bool = false
|
||||
let blurBackground: Bool = true
|
||||
|
||||
private let sourceNode: ContextExtractedContentContainingNode
|
||||
|
||||
init(sourceNode: ContextExtractedContentContainingNode) {
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -53,14 +53,16 @@ public final class TabSelectorComponent: Component {
|
||||
public var font: UIFont
|
||||
public var spacing: CGFloat
|
||||
public var innerSpacing: CGFloat?
|
||||
public var fillWidth: Bool
|
||||
public var lineSelection: Bool
|
||||
public var verticalInset: CGFloat
|
||||
public var allowScroll: Bool
|
||||
|
||||
public init(font: UIFont, spacing: CGFloat, innerSpacing: CGFloat? = nil, lineSelection: Bool = false, verticalInset: CGFloat = 0.0, allowScroll: Bool = true) {
|
||||
public init(font: UIFont, spacing: CGFloat, innerSpacing: CGFloat? = nil, fillWidth: Bool = false, lineSelection: Bool = false, verticalInset: CGFloat = 0.0, allowScroll: Bool = true) {
|
||||
self.font = font
|
||||
self.spacing = spacing
|
||||
self.innerSpacing = innerSpacing
|
||||
self.fillWidth = fillWidth
|
||||
self.lineSelection = lineSelection
|
||||
self.verticalInset = verticalInset
|
||||
self.allowScroll = allowScroll
|
||||
@ -630,7 +632,10 @@ public final class TabSelectorComponent: Component {
|
||||
}
|
||||
|
||||
let estimatedContentWidth = 2.0 * spacing + innerContentWidth + (CGFloat(component.items.count - 1) * (spacing + innerInset))
|
||||
if estimatedContentWidth > availableSize.width && !allowScroll {
|
||||
if component.customLayout?.fillWidth == true && estimatedContentWidth < availableSize.width {
|
||||
spacing = (availableSize.width - innerContentWidth) / CGFloat(component.items.count + 1)
|
||||
innerInset = 0.0
|
||||
} else if estimatedContentWidth > availableSize.width && !allowScroll {
|
||||
spacing = (availableSize.width - innerContentWidth) / CGFloat(component.items.count + 1)
|
||||
innerInset = 0.0
|
||||
}
|
||||
|
@ -849,7 +849,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||
|
||||
let albumArtSize = CGSize(width: 48.0, height: 48.0)
|
||||
let makeAlbumArtLayout = self.albumArtNode.asyncLayout()
|
||||
let applyAlbumArt = makeAlbumArtLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: albumArtSize, boundingSize: albumArtSize, intrinsicInsets: UIEdgeInsets()))
|
||||
let applyAlbumArt = makeAlbumArtLayout(TransformImageArguments(corners: ImageCorners(radius: 10.0), imageSize: albumArtSize, boundingSize: albumArtSize, intrinsicInsets: UIEdgeInsets()))
|
||||
applyAlbumArt()
|
||||
let albumArtFrame = CGRect(origin: CGPoint(x: leftInset + sideInset, y: infoVerticalOrigin - 1.0), size: albumArtSize)
|
||||
let previousAlbumArtNodeFrame = self.albumArtNode.frame
|
||||
|
Loading…
x
Reference in New Issue
Block a user