mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
[WIP] Sessions notification
This commit is contained in:
parent
eb78d075d7
commit
6856b41b37
@ -192,7 +192,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in })
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {}, performActiveSessionAction: { _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in })
|
||||
interaction.isInlineMode = isInlineMode
|
||||
|
||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||
|
@ -2216,6 +2216,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
}, openStories: { [weak self] subject, sourceNode in
|
||||
@ -3533,7 +3535,9 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
})
|
||||
var isInlineMode = false
|
||||
|
@ -20,6 +20,7 @@ import Postbox
|
||||
import ChatFolderLinkPreviewScreen
|
||||
import StoryContainerScreen
|
||||
import ChatListHeaderComponent
|
||||
import UndoUI
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList(appendContacts: Bool)
|
||||
@ -98,6 +99,8 @@ public final class ChatListNodeInteraction {
|
||||
let openStorageManagement: () -> Void
|
||||
let openPasswordSetup: () -> Void
|
||||
let openPremiumIntro: () -> Void
|
||||
let openActiveSessions: () -> Void
|
||||
let performActiveSessionAction: (Bool) -> Void
|
||||
let openChatFolderUpdates: () -> Void
|
||||
let hideChatFolderUpdates: () -> Void
|
||||
let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
|
||||
@ -146,6 +149,8 @@ public final class ChatListNodeInteraction {
|
||||
openStorageManagement: @escaping () -> Void,
|
||||
openPasswordSetup: @escaping () -> Void,
|
||||
openPremiumIntro: @escaping () -> Void,
|
||||
openActiveSessions: @escaping () -> Void,
|
||||
performActiveSessionAction: @escaping (Bool) -> Void,
|
||||
openChatFolderUpdates: @escaping () -> Void,
|
||||
hideChatFolderUpdates: @escaping () -> Void,
|
||||
openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
|
||||
@ -181,6 +186,8 @@ public final class ChatListNodeInteraction {
|
||||
self.openStorageManagement = openStorageManagement
|
||||
self.openPasswordSetup = openPasswordSetup
|
||||
self.openPremiumIntro = openPremiumIntro
|
||||
self.openActiveSessions = openActiveSessions
|
||||
self.performActiveSessionAction = performActiveSessionAction
|
||||
self.openChatFolderUpdates = openChatFolderUpdates
|
||||
self.hideChatFolderUpdates = hideChatFolderUpdates
|
||||
self.openStories = openStories
|
||||
@ -700,12 +707,21 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
nodeInteraction?.openPasswordSetup()
|
||||
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
|
||||
nodeInteraction?.openPremiumIntro()
|
||||
case .reviewLogin:
|
||||
nodeInteraction?.openActiveSessions()
|
||||
}
|
||||
case .hide:
|
||||
switch notice {
|
||||
default:
|
||||
break
|
||||
}
|
||||
case let .buttonChoice(isPositive):
|
||||
switch notice {
|
||||
case .reviewLogin:
|
||||
nodeInteraction?.performActiveSessionAction(isPositive)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
}
|
||||
@ -1011,12 +1027,21 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
nodeInteraction?.openPasswordSetup()
|
||||
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
|
||||
nodeInteraction?.openPremiumIntro()
|
||||
case .reviewLogin:
|
||||
nodeInteraction?.openActiveSessions()
|
||||
}
|
||||
case .hide:
|
||||
switch notice {
|
||||
default:
|
||||
break
|
||||
}
|
||||
case let .buttonChoice(isPositive):
|
||||
switch notice {
|
||||
case .reviewLogin:
|
||||
nodeInteraction?.performActiveSessionAction(isPositive)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
case .HeaderEntry:
|
||||
@ -1564,6 +1589,53 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads, forceDark: false, dismissed: nil)
|
||||
self.push?(controller)
|
||||
}, openActiveSessions: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let activeSessionsContext = self.context.engine.privacy.activeSessions()
|
||||
let _ = (activeSessionsContext.state
|
||||
|> filter { state in
|
||||
return !state.sessions.isEmpty
|
||||
}
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let recentSessionsController = self.context.sharedContext.makeRecentSessionsController(context: self.context, activeSessionsContext: activeSessionsContext)
|
||||
self.push?(recentSessionsController)
|
||||
})
|
||||
}, performActiveSessionAction: { [weak self] isPositive in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if isPositive {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let animationBackgroundColor: UIColor
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
|
||||
} else {
|
||||
animationBackgroundColor = UIColor(rgb: 0x474747)
|
||||
}
|
||||
//TODO:localize
|
||||
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: "New Login Allowed", text: "You can check the list of your active logins in [Settings > Devices]().", customUndoText: nil, timeout: 5), elevatedLayout: true, action: { [weak self] action in
|
||||
switch action {
|
||||
case .info:
|
||||
self?.interaction?.openActiveSessions()
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return true
|
||||
}))
|
||||
} else {
|
||||
|
||||
}
|
||||
}, openChatFolderUpdates: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
@ -1718,6 +1790,11 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if DEBUG
|
||||
if "".isEmpty {
|
||||
return .single(.reviewLogin(device: "Macbook M2", location: "Stockholm, Sweden"))
|
||||
}
|
||||
#endif
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ enum ChatListNotice: Equatable {
|
||||
case premiumUpgrade(discount: Int32)
|
||||
case premiumAnnualDiscount(discount: Int32)
|
||||
case premiumRestore(discount: Int32)
|
||||
case reviewLogin(device: String, location: String)
|
||||
}
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
|
@ -12,6 +12,7 @@ class ChatListStorageInfoItem: ListViewItem {
|
||||
enum Action {
|
||||
case activate
|
||||
case hide
|
||||
case buttonChoice(isPositive: Bool)
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
@ -84,6 +85,11 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||
private let arrowNode: ASImageNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private var okButtonText: TextNode?
|
||||
private var cancelButtonText: TextNode?
|
||||
private var okButton: HighlightableButtonNode?
|
||||
private var cancelButton: HighlightableButtonNode?
|
||||
|
||||
private var item: ChatListStorageInfoItem?
|
||||
|
||||
required init() {
|
||||
@ -124,20 +130,27 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
|
||||
let makeOkButtonTextLayout = TextNode.asyncLayout(self.okButtonText)
|
||||
let makeCancelButtonTextLayout = TextNode.asyncLayout(self.cancelButtonText)
|
||||
|
||||
return { item, params, last in
|
||||
let baseWidth = params.width - params.leftInset - params.rightInset
|
||||
let _ = baseWidth
|
||||
|
||||
let sideInset: CGFloat = params.leftInset + 16.0
|
||||
let rightInset: CGFloat = sideInset + 24.0
|
||||
let verticalInset: CGFloat = 8.0
|
||||
let spacing: CGFloat = 0.0
|
||||
let verticalInset: CGFloat = 9.0
|
||||
var spacing: CGFloat = 0.0
|
||||
|
||||
let themeUpdated = item.theme !== previousItem?.theme
|
||||
|
||||
let titleString: NSAttributedString
|
||||
let textString: NSAttributedString
|
||||
|
||||
var okButtonLayout: (TextNodeLayout, () -> TextNode)?
|
||||
var cancelButtonLayout: (TextNodeLayout, () -> TextNode)?
|
||||
var alignment: NSTextAlignment = .left
|
||||
|
||||
switch item.notice {
|
||||
case let .clearStorage(sizeFraction):
|
||||
let sizeString = dataSizeString(Int64(sizeFraction), formatting: DataSizeStringFormatting(strings: item.strings, decimalSeparator: "."))
|
||||
@ -182,13 +195,30 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||
titleString = titleStringValue
|
||||
|
||||
textString = NSAttributedString(string: item.strings.ChatList_PremiumRestoreDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
case let .reviewLogin(device, location):
|
||||
spacing = 2.0
|
||||
alignment = .center
|
||||
|
||||
//TODO:localize
|
||||
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: "Someone just got acess to your messages!", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||
titleString = titleStringValue
|
||||
|
||||
textString = NSAttributedString(string: "We detected a new login to your account from \(device), \(location). Is it you?", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
|
||||
okButtonLayout = makeOkButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Yes, it's me", font: titleFont, textColor: item.theme.list.itemAccentColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
cancelButtonLayout = makeCancelButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "No, it's not me!", font: titleFont, textColor: item.theme.list.itemDestructiveColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
}
|
||||
|
||||
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18))
|
||||
|
||||
let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, maximumNumberOfLines: 10, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18))
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.0.size.height + textLayout.0.size.height), insets: UIEdgeInsets())
|
||||
var contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.0.size.height + textLayout.0.size.height)
|
||||
if let okButtonLayout {
|
||||
contentSize.height += okButtonLayout.0.size.height + 20.0
|
||||
}
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
@ -203,15 +233,94 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))
|
||||
|
||||
let _ = titleLayout.1()
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: titleLayout.0.size)
|
||||
if case .center = alignment {
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.0.size.width) * 0.5), y: verticalInset), size: titleLayout.0.size)
|
||||
} else {
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: titleLayout.0.size)
|
||||
}
|
||||
|
||||
let _ = textLayout.1()
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: strongSelf.titleNode.frame.maxY + spacing), size: textLayout.0.size)
|
||||
|
||||
if case .center = alignment {
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.0.size.width) * 0.5), y: strongSelf.titleNode.frame.maxY + spacing), size: textLayout.0.size)
|
||||
} else {
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: strongSelf.titleNode.frame.maxY + spacing), size: textLayout.0.size)
|
||||
}
|
||||
|
||||
if let image = strongSelf.arrowNode.image {
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
if let okButtonLayout, let cancelButtonLayout {
|
||||
strongSelf.arrowNode.isHidden = true
|
||||
|
||||
let okButton: HighlightableButtonNode
|
||||
if let current = strongSelf.okButton {
|
||||
okButton = current
|
||||
} else {
|
||||
okButton = HighlightableButtonNode()
|
||||
strongSelf.okButton = okButton
|
||||
strongSelf.addSubnode(okButton)
|
||||
okButton.addTarget(strongSelf, action: #selector(strongSelf.okButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
let cancelButton: HighlightableButtonNode
|
||||
if let current = strongSelf.cancelButton {
|
||||
cancelButton = current
|
||||
} else {
|
||||
cancelButton = HighlightableButtonNode()
|
||||
strongSelf.cancelButton = cancelButton
|
||||
strongSelf.addSubnode(cancelButton)
|
||||
cancelButton.addTarget(strongSelf, action: #selector(strongSelf.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
let okButtonText = okButtonLayout.1()
|
||||
if okButtonText !== strongSelf.okButtonText {
|
||||
strongSelf.okButtonText?.removeFromSupernode()
|
||||
strongSelf.okButtonText = okButtonText
|
||||
okButton.addSubnode(okButtonText)
|
||||
}
|
||||
|
||||
let cancelButtonText = cancelButtonLayout.1()
|
||||
if cancelButtonText !== strongSelf.okButtonText {
|
||||
strongSelf.cancelButtonText?.removeFromSupernode()
|
||||
strongSelf.cancelButtonText = cancelButtonText
|
||||
cancelButton.addSubnode(cancelButtonText)
|
||||
}
|
||||
|
||||
let buttonsWidth: CGFloat = max(min(300.0, params.width), okButtonLayout.0.size.width + cancelButtonLayout.0.size.width + 32.0)
|
||||
let buttonWidth: CGFloat = floor(buttonsWidth * 0.5)
|
||||
let buttonHeight: CGFloat = 32.0
|
||||
|
||||
let okButtonFrame = CGRect(origin: CGPoint(x: floor((params.width - buttonsWidth) * 0.5), y: strongSelf.textNode.frame.maxY + 6.0), size: CGSize(width: buttonWidth, height: buttonHeight))
|
||||
let cancelButtonFrame = CGRect(origin: CGPoint(x: okButtonFrame.maxX, y: strongSelf.textNode.frame.maxY + 6.0), size: CGSize(width: buttonWidth, height: buttonHeight))
|
||||
|
||||
okButton.frame = okButtonFrame
|
||||
cancelButton.frame = cancelButtonFrame
|
||||
|
||||
okButtonText.frame = CGRect(origin: CGPoint(x: floor((okButtonFrame.width - okButtonLayout.0.size.width) * 0.5), y: floor((okButtonFrame.height - okButtonLayout.0.size.height) * 0.5)), size: okButtonLayout.0.size)
|
||||
cancelButtonText.frame = CGRect(origin: CGPoint(x: floor((cancelButtonFrame.width - cancelButtonLayout.0.size.width) * 0.5), y: floor((cancelButtonFrame.height - cancelButtonLayout.0.size.height) * 0.5)), size: cancelButtonLayout.0.size)
|
||||
} else {
|
||||
strongSelf.arrowNode.isHidden = false
|
||||
|
||||
if let okButton = strongSelf.okButton {
|
||||
strongSelf.okButton = nil
|
||||
okButton.removeFromSupernode()
|
||||
}
|
||||
if let cancelButton = strongSelf.cancelButton {
|
||||
strongSelf.cancelButton = nil
|
||||
cancelButton.removeFromSupernode()
|
||||
}
|
||||
if let okButtonText = strongSelf.okButtonText {
|
||||
strongSelf.okButtonText = nil
|
||||
okButtonText.removeFromSupernode()
|
||||
}
|
||||
if let cancelButtonText = strongSelf.cancelButtonText {
|
||||
strongSelf.cancelButtonText = nil
|
||||
cancelButtonText.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.contentSize = layout.contentSize
|
||||
strongSelf.insets = layout.insets
|
||||
|
||||
@ -228,6 +337,14 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func okButtonPressed() {
|
||||
self.item?.action(.buttonChoice(isPositive: true))
|
||||
}
|
||||
|
||||
@objc private func cancelButtonPressed() {
|
||||
self.item?.action(.buttonChoice(isPositive: false))
|
||||
}
|
||||
|
||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
super.animateInsertion(currentTimestamp, duration: duration, short: short)
|
||||
|
||||
|
@ -95,6 +95,8 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
@ -222,7 +222,9 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
})
|
||||
|
||||
|
@ -856,7 +856,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
})
|
||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
|
@ -370,7 +370,9 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
})
|
||||
|
||||
|
@ -0,0 +1,35 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "NewSessionInfoScreen",
|
||||
module_name = "NewSessionInfoScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/Components/SheetComponent",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Markdown",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,332 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
import AppBundle
|
||||
import BundleIconComponent
|
||||
import Markdown
|
||||
import TelegramCore
|
||||
|
||||
public final class NewSessionInfoContentComponent: Component {
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let settings: GlobalPrivacySettings
|
||||
public let openSettings: () -> Void
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
settings: GlobalPrivacySettings,
|
||||
openSettings: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.settings = settings
|
||||
self.openSettings = openSettings
|
||||
}
|
||||
|
||||
public static func ==(lhs: NewSessionInfoContentComponent, rhs: NewSessionInfoContentComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.settings != rhs.settings {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class Item {
|
||||
let icon = ComponentView<Empty>()
|
||||
let title = ComponentView<Empty>()
|
||||
let text = ComponentView<Empty>()
|
||||
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let scrollView: UIScrollView
|
||||
private let iconBackground: UIImageView
|
||||
private let iconForeground: UIImageView
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let mainText = ComponentView<Empty>()
|
||||
|
||||
private var chevronImage: UIImage?
|
||||
|
||||
private var items: [Item] = []
|
||||
|
||||
private var component: NewSessionInfoContentComponent?
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
self.scrollView = UIScrollView()
|
||||
|
||||
self.iconBackground = UIImageView()
|
||||
self.iconForeground = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.clipsToBounds = false
|
||||
|
||||
self.scrollView.addSubview(self.iconBackground)
|
||||
self.scrollView.addSubview(self.iconForeground)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = super.hitTest(point, with: event) {
|
||||
return result
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: NewSessionInfoContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let sideIconInset: CGFloat = 40.0
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
let iconSize: CGFloat = 90.0
|
||||
if self.iconBackground.image == nil {
|
||||
let backgroundColors = component.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors
|
||||
let colors: NSArray = [backgroundColors.1.cgColor, backgroundColors.0.cgColor]
|
||||
self.iconBackground.image = generateGradientFilledCircleImage(diameter: iconSize, colors: colors)
|
||||
}
|
||||
let iconBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize) * 0.5), y: contentHeight), size: CGSize(width: iconSize, height: iconSize))
|
||||
transition.setFrame(view: self.iconBackground, frame: iconBackgroundFrame)
|
||||
|
||||
if self.iconForeground.image == nil {
|
||||
self.iconForeground.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/ArchiveIconLarge"), color: .white)
|
||||
}
|
||||
if let image = self.iconForeground.image {
|
||||
transition.setFrame(view: self.iconForeground, frame: CGRect(origin: CGPoint(x: iconBackgroundFrame.minX + floor((iconBackgroundFrame.width - image.size.width) * 0.5), y: iconBackgroundFrame.minY + floor((iconBackgroundFrame.height - image.size.height) * 0.5)), size: image.size))
|
||||
}
|
||||
|
||||
contentHeight += iconSize
|
||||
contentHeight += 15.0
|
||||
|
||||
let titleString = NSMutableAttributedString()
|
||||
titleString.append(NSAttributedString(string: component.strings.NewSessionInfo_Title, font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor))
|
||||
let imageAttachment = NSTextAttachment()
|
||||
imageAttachment.image = self.iconBackground.image
|
||||
titleString.append(NSAttributedString(attachment: imageAttachment))
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(titleString),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.scrollView.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize))
|
||||
}
|
||||
contentHeight += titleSize.height
|
||||
contentHeight += 16.0
|
||||
|
||||
let text: String
|
||||
if component.settings.keepArchivedUnmuted {
|
||||
text = component.strings.NewSessionInfo_TextKeepArchivedUnmuted
|
||||
} else {
|
||||
text = component.strings.NewSessionInfo_TextKeepArchivedDefault
|
||||
}
|
||||
|
||||
let mainText = NSMutableAttributedString()
|
||||
mainText.append(parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(
|
||||
font: Font.regular(15.0),
|
||||
textColor: component.theme.list.itemSecondaryTextColor
|
||||
),
|
||||
bold: MarkdownAttributeSet(
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.theme.list.itemSecondaryTextColor
|
||||
),
|
||||
link: MarkdownAttributeSet(
|
||||
font: Font.regular(15.0),
|
||||
textColor: component.theme.list.itemAccentColor,
|
||||
additionalAttributes: [:]
|
||||
),
|
||||
linkAttribute: { attributes in
|
||||
return ("URL", "")
|
||||
}
|
||||
)))
|
||||
if self.chevronImage == nil {
|
||||
self.chevronImage = UIImage(bundleImageName: "Settings/TextArrowRight")
|
||||
}
|
||||
if let range = mainText.string.range(of: ">"), let chevronImage = self.chevronImage {
|
||||
mainText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mainText.string))
|
||||
}
|
||||
|
||||
let mainTextSize = self.mainText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(mainText),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||
return NSAttributedString.Key(rawValue: "URL")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak self] _, _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.openSettings()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
if let mainTextView = self.mainText.view {
|
||||
if mainTextView.superview == nil {
|
||||
self.scrollView.addSubview(mainTextView)
|
||||
}
|
||||
transition.setFrame(view: mainTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - mainTextSize.width) * 0.5), y: contentHeight), size: mainTextSize))
|
||||
}
|
||||
contentHeight += mainTextSize.height
|
||||
|
||||
contentHeight += 24.0
|
||||
|
||||
struct ItemDesc {
|
||||
var icon: String
|
||||
var title: String
|
||||
var text: String
|
||||
}
|
||||
let itemDescs: [ItemDesc] = [
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconArchived",
|
||||
title: component.strings.NewSessionInfo_ChatsTitle,
|
||||
text: component.strings.NewSessionInfo_ChatsText
|
||||
),
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconHide",
|
||||
title: component.strings.NewSessionInfo_HideTitle,
|
||||
text: component.strings.NewSessionInfo_HideText
|
||||
),
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconStories",
|
||||
title: component.strings.NewSessionInfo_StoriesTitle,
|
||||
text: component.strings.NewSessionInfo_StoriesText
|
||||
)
|
||||
]
|
||||
for i in 0 ..< itemDescs.count {
|
||||
if i != 0 {
|
||||
contentHeight += 24.0
|
||||
}
|
||||
|
||||
let item: Item
|
||||
if self.items.count > i {
|
||||
item = self.items[i]
|
||||
} else {
|
||||
item = Item()
|
||||
self.items.append(item)
|
||||
}
|
||||
|
||||
let itemDesc = itemDescs[i]
|
||||
|
||||
let iconSize = item.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: itemDesc.icon,
|
||||
tintColor: component.theme.list.itemAccentColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let titleSize = item.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: itemDesc.title, font: Font.semibold(15.0), textColor: component.theme.list.itemPrimaryTextColor)),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - sideIconInset, height: 1000.0)
|
||||
)
|
||||
let textSize = item.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: itemDesc.text, font: Font.regular(15.0), textColor: component.theme.list.itemSecondaryTextColor)),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.18
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - sideIconInset, height: 1000.0)
|
||||
)
|
||||
|
||||
if let iconView = item.icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.scrollView.addSubview(iconView)
|
||||
}
|
||||
transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 4.0), size: iconSize))
|
||||
}
|
||||
|
||||
if let titleView = item.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.scrollView.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: sideInset + sideIconInset, y: contentHeight), size: titleSize))
|
||||
}
|
||||
contentHeight += titleSize.height
|
||||
contentHeight += 2.0
|
||||
|
||||
if let textView = item.text.view {
|
||||
if textView.superview == nil {
|
||||
self.scrollView.addSubview(textView)
|
||||
}
|
||||
transition.setFrame(view: textView, frame: CGRect(origin: CGPoint(x: sideInset + sideIconInset, y: contentHeight), size: textSize))
|
||||
}
|
||||
contentHeight += textSize.height
|
||||
}
|
||||
|
||||
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
||||
let size = CGSize(width: availableSize.width, height: min(availableSize.height, contentSize.height))
|
||||
if self.scrollView.bounds.size != size || self.scrollView.contentSize != contentSize {
|
||||
self.scrollView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -0,0 +1,287 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import ViewControllerComponent
|
||||
import AccountContext
|
||||
import SheetComponent
|
||||
import ButtonComponent
|
||||
import TelegramCore
|
||||
|
||||
private final class NewSessionInfoSheetContentComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let settings: GlobalPrivacySettings
|
||||
let openSettings: () -> Void
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
settings: GlobalPrivacySettings,
|
||||
openSettings: @escaping () -> Void,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.settings = settings
|
||||
self.openSettings = openSettings
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
static func ==(lhs: NewSessionInfoSheetContentComponent, rhs: NewSessionInfoSheetContentComponent) -> Bool {
|
||||
if lhs.settings != rhs.settings {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let content = ComponentView<Empty>()
|
||||
private let button = ComponentView<Empty>()
|
||||
|
||||
private var component: NewSessionInfoSheetContentComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: NewSessionInfoSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let environment = environment[EnvironmentType.self].value
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
contentHeight += 30.0
|
||||
|
||||
let contentSize = self.content.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(NewSessionInfoContentComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
settings: component.settings,
|
||||
openSettings: component.openSettings
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
if let contentView = self.content.view {
|
||||
if contentView.superview == nil {
|
||||
self.addSubview(contentView)
|
||||
}
|
||||
transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: contentSize))
|
||||
}
|
||||
contentHeight += contentSize.height
|
||||
contentHeight += 30.0
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: environment.theme.list.itemCheckColors.fillColor,
|
||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
|
||||
Text(text: environment.strings.NewSessionInfo_CloseAction, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
|
||||
)),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.dismiss()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize)
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
transition.setFrame(view: buttonView, frame: buttonFrame)
|
||||
}
|
||||
contentHeight += buttonSize.height
|
||||
|
||||
if environment.safeInsets.bottom.isZero {
|
||||
contentHeight += 16.0
|
||||
} else {
|
||||
contentHeight += environment.safeInsets.bottom + 14.0
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class NewSessionInfoScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let settings: GlobalPrivacySettings
|
||||
let buttonAction: (() -> Void)?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
settings: GlobalPrivacySettings,
|
||||
buttonAction: (() -> Void)?
|
||||
) {
|
||||
self.context = context
|
||||
self.settings = settings
|
||||
self.buttonAction = buttonAction
|
||||
}
|
||||
|
||||
static func ==(lhs: NewSessionInfoScreenComponent, rhs: NewSessionInfoScreenComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.settings != rhs.settings {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>()
|
||||
private let sheetAnimateOut = ActionSlot<Action<Void>>()
|
||||
|
||||
private var component: NewSessionInfoScreenComponent?
|
||||
private var environment: EnvironmentType?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: NewSessionInfoScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
self.environment = environment
|
||||
|
||||
let sheetEnvironment = SheetComponentEnvironment(
|
||||
isDisplaying: environment.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
||||
dismiss: { [weak self] _ in
|
||||
guard let self, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
self.sheetAnimateOut.invoke(Action { _ in
|
||||
if let controller = environment.controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
let _ = self.sheet.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(SheetComponent(
|
||||
content: AnyComponent(NewSessionInfoSheetContentComponent(
|
||||
settings: component.settings,
|
||||
openSettings: { [weak self] in
|
||||
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
let context = component.context
|
||||
self.sheetAnimateOut.invoke(Action { [weak context, weak controller] _ in
|
||||
if let controller, let context {
|
||||
if let navigationController = controller.navigationController as? NavigationController {
|
||||
navigationController.pushViewController(context.sharedContext.makeArchiveSettingsController(context: context))
|
||||
}
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
},
|
||||
dismiss: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.sheetAnimateOut.invoke(Action { [weak self] _ in
|
||||
if let controller = environment.controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.component?.buttonAction?()
|
||||
})
|
||||
}
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.list.plainBackgroundColor),
|
||||
animateOut: self.sheetAnimateOut
|
||||
)),
|
||||
environment: {
|
||||
environment
|
||||
sheetEnvironment
|
||||
},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let sheetView = self.sheet.view {
|
||||
if sheetView.superview == nil {
|
||||
self.addSubview(sheetView)
|
||||
}
|
||||
transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public class NewSessionInfoScreen: ViewControllerComponentContainer {
|
||||
public init(context: AccountContext, settings: GlobalPrivacySettings, buttonAction: (() -> Void)? = nil) {
|
||||
super.init(context: context, component: NewSessionInfoScreenComponent(
|
||||
context: context,
|
||||
settings: settings,
|
||||
buttonAction: buttonAction
|
||||
), navigationBarAppearance: .none)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.navigationPresentation = .flatModal
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
}
|
||||
}
|
@ -461,8 +461,11 @@ public final class StoryPeerListComponent: Component {
|
||||
}
|
||||
|
||||
public func ensureItemVisible(peerId: EnginePeer.Id) {
|
||||
if let visibleItem = self.visibleItems[peerId], let itemView = visibleItem.view.view as? StoryPeerListItemComponent.View {
|
||||
let itemFrame = itemView.frame.offsetBy(dx: self.scrollView.bounds.minX, dy: 0.0)
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
if let index = self.sortedItems.firstIndex(where: { $0.peer.id == peerId }) {
|
||||
let itemFrame = itemLayout.frame(at: index)
|
||||
if !self.scrollView.bounds.contains(itemFrame.insetBy(dx: 20.0, dy: 0.0)) {
|
||||
self.scrollView.scrollRectToVisible(itemFrame.insetBy(dx: -40.0, dy: 0.0), animated: false)
|
||||
}
|
||||
|
@ -266,6 +266,8 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
Loading…
x
Reference in New Issue
Block a user