This commit is contained in:
Ali 2023-09-01 22:07:30 +04:00
parent f0336cd98a
commit 03122c136d
33 changed files with 561 additions and 288 deletions

View File

@ -99,6 +99,7 @@ swift_library(
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen",
"//submodules/TelegramUI/Components/Settings/ArchiveInfoScreen",
"//submodules/TelegramUI/Components/Settings/NewSessionInfoScreen",
],
visibility = [
"//visibility:public",

View File

@ -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: {}, openActiveSessions: {}, performActiveSessionAction: { _ in }, 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

View File

@ -2217,7 +2217,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openPasswordSetup: {
}, openPremiumIntro: {
}, openActiveSessions: {
}, performActiveSessionAction: { _ in
}, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {
}, hideChatFolderUpdates: {
}, openStories: { [weak self] subject, sourceNode in
@ -3536,7 +3536,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
}, 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: {}, openActiveSessions: {
}, performActiveSessionAction: { _ in
}, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
})

View File

@ -21,6 +21,9 @@ import ChatFolderLinkPreviewScreen
import StoryContainerScreen
import ChatListHeaderComponent
import UndoUI
import NewSessionInfoScreen
private var debugDidAddNewSessionReview = false
public enum ChatListNodeMode {
case chatList(appendContacts: Bool)
@ -100,7 +103,7 @@ public final class ChatListNodeInteraction {
let openPasswordSetup: () -> Void
let openPremiumIntro: () -> Void
let openActiveSessions: () -> Void
let performActiveSessionAction: (Bool) -> Void
let performActiveSessionAction: (NewSessionReview, Bool) -> Void
let openChatFolderUpdates: () -> Void
let hideChatFolderUpdates: () -> Void
let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
@ -150,7 +153,7 @@ public final class ChatListNodeInteraction {
openPasswordSetup: @escaping () -> Void,
openPremiumIntro: @escaping () -> Void,
openActiveSessions: @escaping () -> Void,
performActiveSessionAction: @escaping (Bool) -> Void,
performActiveSessionAction: @escaping (NewSessionReview, Bool) -> Void,
openChatFolderUpdates: @escaping () -> Void,
hideChatFolderUpdates: @escaping () -> Void,
openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
@ -717,8 +720,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
}
case let .buttonChoice(isPositive):
switch notice {
case .reviewLogin:
nodeInteraction?.performActiveSessionAction(isPositive)
case let .reviewLogin(newSessionReview):
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
@ -1037,8 +1040,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
}
case let .buttonChoice(isPositive):
switch notice {
case .reviewLogin:
nodeInteraction?.performActiveSessionAction(isPositive)
case let .reviewLogin(newSessionReview):
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
@ -1608,7 +1611,7 @@ public final class ChatListNode: ListView {
let recentSessionsController = self.context.sharedContext.makeRecentSessionsController(context: self.context, activeSessionsContext: activeSessionsContext)
self.push?(recentSessionsController)
})
}, performActiveSessionAction: { [weak self] isPositive in
}, performActiveSessionAction: { [weak self] newSessionReview, isPositive in
guard let self else {
return
}
@ -1633,8 +1636,12 @@ public final class ChatListNode: ListView {
return true
}))
} else {
let _ = removeNewSessionReviews(postbox: self.context.account.postbox, ids: [newSessionReview.id]).start()
} else {
self.push?(NewSessionInfoScreen(context: self.context, newSessionReview: newSessionReview))
let _ = removeNewSessionReviews(postbox: self.context.account.postbox, ids: [newSessionReview.id]).start()
}
}, openChatFolderUpdates: { [weak self] in
guard let self else {
@ -1736,13 +1743,24 @@ public final class ChatListNode: ListView {
let suggestedChatListNotice: Signal<ChatListNotice?, NoError>
if case .chatList(groupId: .root) = location, chatListFilter == nil {
#if DEBUG
if !debugDidAddNewSessionReview {
debugDidAddNewSessionReview = true
let _ = addNewSessionReview(postbox: context.account.postbox, item: NewSessionReview(id: 1, device: "iPhone 14 Pro", location: "Dubai, UAE")).start()
}
#endif
suggestedChatListNotice = .single(nil)
|> then (
combineLatest(
getServerProvidedSuggestions(account: context.account),
context.engine.auth.twoStepVerificationConfiguration()
context.engine.auth.twoStepVerificationConfiguration(),
newSessionReviews(postbox: context.account.postbox)
)
|> mapToSignal { suggestions, configuration -> Signal<ChatListNotice?, NoError> in
|> mapToSignal { suggestions, configuration, newSessionReviews -> Signal<ChatListNotice?, NoError> in
if let newSessionReview = newSessionReviews.first {
return .single(.reviewLogin(newSessionReview: newSessionReview))
}
if suggestions.contains(.setupPassword) {
var notSet = false
switch configuration {
@ -1790,11 +1808,6 @@ public final class ChatListNode: ListView {
}
}
} else {
#if DEBUG
if "".isEmpty {
return .single(.reviewLogin(device: "Macbook M2", location: "Stockholm, Sweden"))
}
#endif
return .single(nil)
}
}

View File

@ -85,7 +85,7 @@ enum ChatListNotice: Equatable {
case premiumUpgrade(discount: Int32)
case premiumAnnualDiscount(discount: Int32)
case premiumRestore(discount: Int32)
case reviewLogin(device: String, location: String)
case reviewLogin(newSessionReview: NewSessionReview)
}
enum ChatListNodeEntry: Comparable, Identifiable {

View File

@ -92,6 +92,13 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
private var item: ChatListStorageInfoItem?
override var apparentHeight: CGFloat {
didSet {
self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.bounds.width, height: self.apparentHeight))
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.contentContainer.bounds.height - UIScreenPixel), size: CGSize(width: self.contentContainer.bounds.width, height: UIScreenPixel))
}
}
required init() {
self.contentContainer = ASDisplayNode()
@ -102,6 +109,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.contentContainer.clipsToBounds = true
self.clipsToBounds = true
self.addSubnode(self.separatorNode)
@ -195,7 +203,7 @@ 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):
case let .reviewLogin(newSessionReview):
spacing = 2.0
alignment = .center
@ -203,7 +211,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
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)
textString = NSAttributedString(string: "We detected a new login to your account from \(newSessionReview.device), \(newSessionReview.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)))
@ -225,13 +233,11 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
strongSelf.item = item
if themeUpdated {
strongSelf.backgroundColor = item.theme.chatList.pinnedItemBackgroundColor
strongSelf.contentContainer.backgroundColor = item.theme.chatList.pinnedItemBackgroundColor
strongSelf.separatorNode.backgroundColor = item.theme.chatList.itemSeparatorColor
strongSelf.arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.theme)
}
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()
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)
@ -260,7 +266,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
} else {
okButton = HighlightableButtonNode()
strongSelf.okButton = okButton
strongSelf.addSubnode(okButton)
strongSelf.contentContainer.addSubnode(okButton)
okButton.addTarget(strongSelf, action: #selector(strongSelf.okButtonPressed), forControlEvents: .touchUpInside)
}
@ -270,7 +276,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
} else {
cancelButton = HighlightableButtonNode()
strongSelf.cancelButton = cancelButton
strongSelf.addSubnode(cancelButton)
strongSelf.contentContainer.addSubnode(cancelButton)
cancelButton.addTarget(strongSelf, action: #selector(strongSelf.cancelButtonPressed), forControlEvents: .touchUpInside)
}
@ -326,7 +332,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
//strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
switch item.notice {
default:
@ -348,7 +354,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
//self.transitionOffset = self.bounds.size.height * 1.6
//self.transitionOffset = self.bounds.size.height
//self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp)
}

View File

@ -46,6 +46,8 @@ public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
for child in updatedChildren {
context.add(child
.position(child.size.centered(in: CGRect(origin: CGPoint(x: nextX, y: floor((size.height - child.size.height) * 0.5)), size: child.size)).center)
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
nextX += child.size.width
nextX += context.component.spacing

View File

@ -237,7 +237,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
self.isLayerBacked = layerBacked
}
var apparentHeight: CGFloat = 0.0
open var apparentHeight: CGFloat = 0.0
public private(set) var apparentHeightTransition: (CGFloat, CGFloat)?
private var _bounds: CGRect = CGRect()
private var _position: CGPoint = CGPoint()

View File

@ -96,7 +96,7 @@ public final class HashtagSearchController: TelegramBaseController {
}, openPasswordSetup: {
}, openPremiumIntro: {
}, openActiveSessions: {
}, performActiveSessionAction: { _ in
}, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {
}, hideChatFolderUpdates: {
}, openStories: { _, _ in

View File

@ -23,6 +23,8 @@ swift_library(
"//submodules/PeerInfoUI:PeerInfoUI",
"//submodules/UndoUI:UndoUI",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View File

@ -82,7 +82,7 @@ public final class JoinLinkPreviewController: ViewController {
if invite.flags.requestNeeded {
strongSelf.isRequest = true
strongSelf.isGroup = !invite.flags.isBroadcast
strongSelf.controllerNode.setRequestPeer(image: invite.photoRepresentation, title: invite.title, about: invite.about, memberCount: invite.participantsCount, isGroup: !invite.flags.isBroadcast)
strongSelf.controllerNode.setRequestPeer(image: invite.photoRepresentation, title: invite.title, about: invite.about, memberCount: invite.participantsCount, isGroup: !invite.flags.isBroadcast, isVerified: invite.flags.isVerified, isFake: invite.flags.isFake, isScam: invite.flags.isScam)
} else {
let data = JoinLinkPreviewData(isGroup: !invite.flags.isBroadcast, isJoined: false)
strongSelf.controllerNode.setInvitePeer(image: invite.photoRepresentation, title: invite.title, memberCount: invite.participantsCount, members: invite.participants?.map({ $0 }) ?? [], data: data)

View File

@ -400,8 +400,8 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
self.transitionToContentNode(contentNode)
}
func setRequestPeer(image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32, isGroup: Bool) {
let contentNode = JoinLinkPreviewPeerContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, content: .request(isGroup: isGroup, image: image, title: title, about: about, memberCount: memberCount))
func setRequestPeer(image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32, isGroup: Bool, isVerified: Bool, isFake: Bool, isScam: Bool) {
let contentNode = JoinLinkPreviewPeerContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, content: .request(isGroup: isGroup, image: image, title: title, about: about, memberCount: memberCount, isVerified: isVerified, isFake: isFake, isScam: isScam))
contentNode.join = { [weak self] in
self?.join?()
}

View File

@ -10,6 +10,8 @@ import SelectablePeerNode
import ShareController
import SolidRoundedButtonNode
import ActivityIndicator
import ComponentFlow
import EmojiStatusComponent
private let avatarFont = avatarPlaceholderFont(size: 42.0)
@ -31,41 +33,69 @@ private final class MoreNode: ASDisplayNode {
final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainerNode {
enum Content {
case invite(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, memberCount: Int32, members: [EnginePeer])
case request(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32)
case request(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32, isVerified: Bool, isFake: Bool, isScam: Bool)
var isGroup: Bool {
switch self {
case let .invite(isGroup, _, _, _, _), let .request(isGroup, _, _, _, _):
case let .invite(isGroup, _, _, _, _), let .request(isGroup, _, _, _, _, _, _, _):
return isGroup
}
}
var image: TelegramMediaImageRepresentation? {
switch self {
case let .invite(_, image, _, _, _), let .request(_, image, _, _, _):
case let .invite(_, image, _, _, _), let .request(_, image, _, _, _, _, _, _):
return image
}
}
var title: String {
switch self {
case let .invite(_, _, title, _, _), let .request(_, _, title, _, _):
case let .invite(_, _, title, _, _), let .request(_, _, title, _, _, _, _, _):
return title
}
}
var memberCount: Int32 {
switch self {
case let .invite(_, _, _, memberCount, _), let .request(_, _, _, _, memberCount):
case let .invite(_, _, _, memberCount, _), let .request(_, _, _, _, memberCount, _, _, _):
return memberCount
}
}
var isVerified: Bool {
switch self {
case .invite:
return false
case let .request(_, _, _, _, _, isVerified, _, _):
return isVerified
}
}
var isFake: Bool {
switch self {
case .invite:
return false
case let .request(_, _, _, _, _, _, isFake, _):
return isFake
}
}
var isScam: Bool {
switch self {
case .invite:
return false
case let .request(_, _, _, _, _, _, _, isScam):
return isScam
}
}
}
private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
private let avatarNode: AvatarNode
private let titleNode: ASTextNode
private let titleNode: ImmediateTextNode
private var avatarIcon: ComponentView<Empty>?
private let countNode: ASTextNode
private let aboutNode: ASTextNode
private let descriptionNode: ASTextNode
@ -76,11 +106,22 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
private let actionButtonNode: SolidRoundedButtonNode
private let context: AccountContext
private let content: Content
private let theme: PresentationTheme
private let strings: PresentationStrings
var join: (() -> Void)?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: JoinLinkPreviewPeerContentNode.Content) {
self.context = context
self.content = content
self.theme = theme
self.strings = strings
self.avatarNode = AvatarNode(font: avatarFont)
self.titleNode = ASTextNode()
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 2
self.titleNode.textAlignment = .center
self.countNode = ASTextNode()
self.aboutNode = ASTextNode()
@ -153,7 +194,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
}
self.moreNode.flatMap(self.peersScrollNode.addSubnode)
if case let .request(isGroup, _, _, about, _) = content {
if case let .request(isGroup, _, _, about, _, _, _, _) = content {
if let about = about, !about.isEmpty {
self.aboutNode.attributedText = NSAttributedString(string: about, font: Font.regular(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
self.addSubnode(self.aboutNode)
@ -219,7 +260,22 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
}
let constrainSize = CGSize(width: size.width - 32.0, height: size.height)
let titleSize = self.titleNode.measure(constrainSize)
var statusIcon: EmojiStatusComponent.Content?
var constrainedTextSize = constrainSize
if self.content.isFake {
statusIcon = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased())
constrainedTextSize.width -= 32.0
} else if self.content.isScam {
statusIcon = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased())
constrainedTextSize.width -= 32.0
} else if self.content.isVerified {
statusIcon = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)
constrainedTextSize.width -= 24.0
}
let titleInfo = self.titleNode.updateLayoutFullInfo(constrainedTextSize)
let titleSize = titleInfo.size
nodeHeight += titleSize.height
let verticalOrigin = size.height - nodeHeight
@ -228,7 +284,51 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floor((size.width - avatarSize) / 2.0), y: verticalOrigin + 32.0), size: CGSize(width: avatarSize, height: avatarSize)))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: verticalOrigin + 27.0 + avatarSize + 15.0), size: titleSize))
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: verticalOrigin + 27.0 + avatarSize + 15.0), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
if let statusIcon, let lastLine = titleInfo.linesRects().last {
let animationCache = self.context.animationCache
let animationRenderer = self.context.animationRenderer
let avatarIcon: ComponentView<Empty>
var avatarIconTransition = Transition(transition)
if let current = self.avatarIcon {
avatarIcon = current
} else {
avatarIconTransition = avatarIconTransition.withAnimation(.none)
avatarIcon = ComponentView<Empty>()
self.avatarIcon = avatarIcon
}
let avatarIconComponent = EmojiStatusComponent(
context: self.context,
animationCache: animationCache,
animationRenderer: animationRenderer,
content: statusIcon,
isVisibleForAnimations: true,
action: nil,
emojiFileUpdated: nil
)
let iconSize = avatarIcon.update(
transition: avatarIconTransition,
component: AnyComponent(avatarIconComponent),
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
if let avatarIconView = avatarIcon.view {
if avatarIconView.superview == nil {
avatarIconView.isUserInteractionEnabled = false
self.view.addSubview(avatarIconView)
}
avatarIconTransition.setFrame(view: avatarIconView, frame: CGRect(origin: CGPoint(x: titleFrame.minX + floor((titleSize.width - lastLine.width) * 0.5) + lastLine.width + 5.0, y: 8.0 + titleFrame.minY + floorToScreenPixels(lastLine.midY - iconSize.height / 2.0) - lastLine.height), size: iconSize))
}
} else if let avatarIcon = self.avatarIcon {
self.avatarIcon = nil
avatarIcon.view?.removeFromSuperview()
}
let countSize = self.countNode.measure(constrainSize)
transition.updateFrame(node: self.countNode, frame: CGRect(origin: CGPoint(x: floor((size.width - countSize.width) / 2.0), y: verticalOrigin + 27.0 + avatarSize + 15.0 + titleSize.height + 3.0), size: countSize))

View File

@ -337,6 +337,13 @@ private final class TimeBasedCleanupImpl {
}
private func resetScan(general: Int32, shortLived: Int32, gigabytesLimit: Int32) {
let shortLived = gigabytesLimit == Int32.max ? Int32.max : shortLived
if general == Int32.max && shortLived == Int32.max && gigabytesLimit == Int32.max {
self.scheduledScanDisposable.set(nil)
return
}
let generalPaths = self.generalPaths
let totalSizeBasedPath = self.totalSizeBasedPath
let shortLivedPaths = self.shortLivedPaths
@ -478,13 +485,8 @@ private final class TimeBasedCleanupImpl {
}
let scanFirstTime = scanOnce
|> delay(10.0, queue: Queue.concurrentDefaultQueue())
let scanRepeatedly = (
scanOnce
|> suspendAwareDelay(60.0 * 60.0, granularity: 10.0, queue: Queue.concurrentDefaultQueue())
)
|> restart
let scan = scanFirstTime
|> then(scanRepeatedly)
self.scheduledScanDisposable.set((scan
|> deliverOn(self.queue)).start())
}

View File

@ -223,7 +223,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
}, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {
}, performActiveSessionAction: { _ in
}, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
})

View File

@ -857,7 +857,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
gesture?.cancel()
}, present: { _ in
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {
}, performActiveSessionAction: { _ in
}, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
})

View File

@ -371,7 +371,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
gesture?.cancel()
}, present: { _ in
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {
}, performActiveSessionAction: { _ in
}, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
})

View File

@ -1161,21 +1161,47 @@ public class Account {
self.managedOperationsDisposable.add(managedSynchronizePeerStoriesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
let extractedExpr: [Signal<AccountRunningImportantTasks, NoError>] = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
self.pendingMessageManager.hasPendingMessages |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] },
(self.pendingStoryManager?.hasPending ?? .single(false)) |> map {
hasPending in hasPending ? AccountRunningImportantTasks.pendingMessages : []
let extractedExpr1 = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { inputStates in
if inputStates {
print("inputStates: true")
}
return inputStates ? AccountRunningImportantTasks.other : []
},
self.pendingUpdateMessageManager.updatingMessageMedia |> map {
!$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
self.pendingMessageManager.hasPendingMessages |> map { hasPendingMessages in
if !hasPendingMessages.isEmpty {
print("hasPendingMessages: true")
}
return !hasPendingMessages.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
},
self.pendingPeerMediaUploadManager.uploadingPeerMedia |> map {
!$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
(self.pendingStoryManager?.hasPending ?? .single(false)) |> map { hasPending in
if hasPending {
print("hasPending: true")
}
return hasPending ? AccountRunningImportantTasks.pendingMessages : []
},
self.pendingUpdateMessageManager.updatingMessageMedia |> map { updatingMessageMedia in
if !updatingMessageMedia.isEmpty {
print("updatingMessageMedia: true")
}
return !updatingMessageMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
},
self.pendingPeerMediaUploadManager.uploadingPeerMedia |> map { uploadingPeerMedia in
if !uploadingPeerMedia.isEmpty {
print("updatingMessageMedia: true")
}
return !uploadingPeerMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
},
self.accountPresenceManager.isPerformingUpdate() |> map { presenceUpdate in
if presenceUpdate {
print("updatingMessageMedia: true")
return []
}
return presenceUpdate ? AccountRunningImportantTasks.other : []
},
self.accountPresenceManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] },
//self.notificationAutolockReportManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] }
]
let extractedExpr: [Signal<AccountRunningImportantTasks, NoError>] = extractedExpr1
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = extractedExpr
let importantBackgroundOperationsRunning = combineLatest(queue: Queue(), importantBackgroundOperations)
|> map { values -> AccountRunningImportantTasks in

View File

@ -4566,6 +4566,8 @@ func replayFinalState(
return item.id == id
}) {
if let value = updatedPeerEntries[index].value.get(Stories.StoredItem.self), case let .item(item) = value {
let updatedReaction = MessageReaction.Reaction(apiReaction: reaction)
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
@ -4574,7 +4576,7 @@ func replayFinalState(
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views,
views: _internal_updateStoryViewsForMyReaction(views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
@ -4584,7 +4586,7 @@ func replayFinalState(
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
myReaction: MessageReaction.Reaction(apiReaction: reaction)
myReaction: updatedReaction
))
if let entry = CodableEntry(updatedItem) {
updatedPeerEntries[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: item.expirationTimestamp, isCloseFriends: item.isCloseFriends)
@ -4592,8 +4594,10 @@ func replayFinalState(
}
}
transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries)
if let value = transaction.getStory(id: StoryId(peerId: peerId, id: id))?.get(Stories.StoredItem.self), case let .item(item) = value {
let updatedReaction = MessageReaction.Reaction(apiReaction: reaction)
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
@ -4602,7 +4606,7 @@ func replayFinalState(
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views,
views: _internal_updateStoryViewsForMyReaction(views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
@ -4616,7 +4620,6 @@ func replayFinalState(
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: peerId, id: id), value: entry)
storyUpdates.append(InternalStoryUpdate.added(peerId: peerId, item: updatedItem))
}
}

View File

@ -79,6 +79,7 @@ public struct Namespaces {
public static let CloudEmojiStatusCategories: Int32 = 22
public static let CloudFeaturedProfilePhotoEmoji: Int32 = 23
public static let CloudFeaturedGroupPhotoEmoji: Int32 = 24
public static let NewSessionReviews: Int32 = 25
}
public struct CachedItemCollection {

View File

@ -0,0 +1,104 @@
import Foundation
import Postbox
import SwiftSignalKit
public final class NewSessionReview: Codable, Equatable {
struct Id {
var rawValue: MemoryBuffer
init(id: Int64) {
let buffer = WriteBuffer()
var id = id
buffer.write(&id, length: 8)
self.rawValue = buffer.makeReadBufferAndReset()
}
}
public let id: Int64
public let device: String
public let location: String
public init(id: Int64, device: String, location: String) {
self.id = id
self.device = device
self.location = location
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.id = try container.decode(Int64.self, forKey: "id")
self.device = try container.decode(String.self, forKey: "device")
self.location = try container.decode(String.self, forKey: "location")
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.id, forKey: "id")
try container.encode(self.device, forKey: "device")
try container.encode(self.location, forKey: "location")
}
public static func ==(lhs: NewSessionReview, rhs: NewSessionReview) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.device != rhs.device {
return false
}
if lhs.location != rhs.location {
return false
}
return true
}
}
public func newSessionReviews(postbox: Postbox) -> Signal<[NewSessionReview], NoError> {
let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.NewSessionReviews)
return postbox.combinedView(keys: [viewKey])
|> mapToSignal { views -> Signal<[NewSessionReview], NoError> in
guard let view = views.views[viewKey] as? OrderedItemListView else {
return .single([])
}
var result: [NewSessionReview] = []
for item in view.items {
guard let item = item.contents.get(NewSessionReview.self) else {
continue
}
result.append(item)
}
return .single(result)
}
}
public func addNewSessionReview(postbox: Postbox, item: NewSessionReview) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
guard let entry = CodableEntry(item) else {
return
}
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, item: OrderedItemListEntry(id: NewSessionReview.Id(id: item.id).rawValue, contents: entry), removeTailIfCountExceeds: 200)
}
|> ignoreValues
}
public func clearNewSessionReviews(postbox: Postbox) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.NewSessionReviews, items: [])
}
|> ignoreValues
}
public func removeNewSessionReviews(postbox: Postbox, ids: [Int64]) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
for id in ids {
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, itemId: NewSessionReview.Id(id: id).rawValue)
}
}
|> ignoreValues
}

View File

@ -84,7 +84,7 @@ public struct MessageReaction: Equatable, PostboxCoding, Codable {
self.value = .custom(try container.decode(Int64.self, forKey: "cfid"))
}
self.count = try container.decode(Int32.self, forKey: "c")
if let chosenOrder = try container.decodeIfPresent(Int32.self, forKey: "s") {
if let chosenOrder = try container.decodeIfPresent(Int32.self, forKey: "cord") {
self.chosenOrder = Int(chosenOrder)
} else if let isSelected = try container.decodeIfPresent(Int32.self, forKey: "s"), isSelected != 0 {
self.chosenOrder = 0

View File

@ -512,7 +512,7 @@ private class AdMessagesHistoryContextImpl {
switch chatInvite {
case let .chatInvite(flags, title, _, photo, participantsCount, participants):
let photo = telegramMediaImageFromApiPhoto(photo).flatMap({ smallestImageRepresentation($0.representations) })
let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0)
let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0, isVerified: (flags & (1 << 7)) != 0, isScam: (flags & (1 << 8)) != 0, isFake: (flags & (1 << 9)) != 0)
let _ = photo
let _ = flags

View File

@ -55,6 +55,29 @@ public enum Stories {
public var reactions: [MessageReaction]
public var hasList: Bool
public var isEmpty: Bool {
if self.seenCount != 0 {
return false
}
if self.reactedCount != 0 {
return false
}
if self.forwardCount != 0 {
return false
}
if !self.seenPeerIds.isEmpty {
return false
}
if !self.reactions.isEmpty {
return false
}
if self.hasList {
return false
}
return true
}
public init(seenCount: Int, reactedCount: Int, forwardCount: Int, seenPeerIds: [PeerId], reactions: [MessageReaction], hasList: Bool) {
self.seenCount = seenCount
self.reactedCount = reactedCount
@ -1934,6 +1957,58 @@ public func _internal_setStoryNotificationWasDisplayed(transaction: Transaction,
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.displayedStoryNotifications, key: key), entry: CodableEntry(data: Data()))
}
func _internal_updateStoryViewsForMyReaction(views: Stories.Item.Views?, previousReaction: MessageReaction.Reaction?, reaction: MessageReaction.Reaction?) -> Stories.Item.Views? {
var views = views ?? Stories.Item.Views(seenCount: 0, reactedCount: 0, forwardCount: 0, seenPeerIds: [], reactions: [], hasList: false)
if let reaction {
if previousReaction == nil {
views.reactedCount += 1
}
do {
var reactions = views.reactions
if let previousIndex = reactions.firstIndex(where: { $0.chosenOrder != nil }) {
reactions[previousIndex].chosenOrder = nil
reactions[previousIndex].count = max(0, reactions[previousIndex].count - 1)
}
if let reactionIndex = reactions.firstIndex(where: { $0.value == reaction }) {
reactions[reactionIndex].chosenOrder = 0
reactions[reactionIndex].count += 1
} else {
reactions.append(MessageReaction(
value: reaction,
count: 1,
chosenOrder: 0
))
}
views.reactions = reactions
}
} else {
if previousReaction != nil {
views.reactedCount = max(0, views.reactedCount - 1)
}
do {
var reactions = views.reactions
if let previousIndex = reactions.firstIndex(where: { $0.chosenOrder != nil }) {
reactions[previousIndex].chosenOrder = nil
reactions[previousIndex].count = max(0, reactions[previousIndex].count - 1)
if reactions[previousIndex].count == 0 {
reactions.remove(at: previousIndex)
}
}
views.reactions = reactions
}
}
if views.isEmpty {
return nil
} else {
return views
}
}
func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int32, reaction: MessageReaction.Reaction?) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> (Stories.StoredItem?, Api.InputPeer?) in
guard let peer = transaction.getPeer(peerId) else {
@ -1945,6 +2020,10 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
var updatedItemValue: Stories.StoredItem?
let updateViews: (Stories.Item.Views?, MessageReaction.Reaction?) -> Stories.Item.Views? = { views, previousReaction in
return _internal_updateStoryViewsForMyReaction(views: views, previousReaction: previousReaction, reaction: reaction)
}
var currentItems = transaction.getStoryItems(peerId: peerId)
for i in 0 ..< currentItems.count {
if currentItems[i].id == id {
@ -1957,7 +2036,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views,
views: updateViews(item.views, item.myReaction),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isEdited,
@ -1987,7 +2066,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views,
views: updateViews(item.views, item.myReaction),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isEdited,

View File

@ -868,23 +868,31 @@ public extension TelegramEngine {
}
}
}
var pendingItemCount = 0
var maxPendingTimestamp: Int32?
if let localState {
for item in localState.items {
if case .peer(peerId) = item.target {
pendingItemCount += 1
if let maxPendingTimestampValue = maxPendingTimestamp {
maxPendingTimestamp = max(maxPendingTimestampValue, item.timestamp)
} else {
maxPendingTimestamp = item.timestamp
}
}
}
}
var lastItemTimestamp = lastEntry.timestamp
if let maxPendingTimestamp, maxPendingTimestamp > lastItemTimestamp {
lastItemTimestamp = maxPendingTimestamp
}
let item = EngineStorySubscriptions.Item(
peer: EnginePeer(peer),
hasUnseen: hasUnseen,
hasUnseenCloseFriends: hasUnseenCloseFriends,
hasPending: pendingItemCount != 0,
hasPending: maxPendingTimestamp != nil,
storyCount: itemsView.items.count,
unseenCount: unseenCount,
lastTimestamp: lastEntry.timestamp
lastTimestamp: lastItemTimestamp
)
if peerId == accountPeer.id {
@ -919,15 +927,11 @@ public extension TelegramEngine {
}
items.sort(by: { lhs, rhs in
if lhs.hasPending != rhs.hasPending {
if lhs.hasPending {
return true
} else {
return false
}
}
if lhs.hasUnseen != rhs.hasUnseen {
if lhs.hasUnseen {
let lhsUnseenOrPending = lhs.hasUnseen || lhs.hasPending
let rhsUnseenOrPending = rhs.hasUnseen || rhs.hasPending
if lhsUnseenOrPending != rhsUnseenOrPending {
if lhsUnseenOrPending {
return true
} else {
return false

View File

@ -36,6 +36,9 @@ public enum ExternalJoiningChatState {
public let isPublic: Bool
public let isMegagroup: Bool
public let requestNeeded: Bool
public let isVerified: Bool
public let isScam: Bool
public let isFake: Bool
}
public let flags: Flags
@ -104,7 +107,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
switch result {
case let .chatInvite(flags, title, about, invitePhoto, participantsCount, participants):
let photo = telegramMediaImageFromApiPhoto(invitePhoto).flatMap({ smallestImageRepresentation($0.representations) })
let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0)
let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0, isVerified: (flags & (1 << 7)) != 0, isScam: (flags & (1 << 8)) != 0, isFake: (flags & (1 << 9)) != 0)
return .single(.invite(ExternalJoiningChatState.Invite(flags: flags, title: title, about: about, photoRepresentation: photo, participantsCount: participantsCount, participants: participants?.map({ EnginePeer(TelegramUser(user: $0)) }))))
case let .chatInviteAlready(chat):
if let peer = parseTelegramGroupOrChannel(chat: chat) {

View File

@ -288,6 +288,7 @@ public final class ButtonComponent: Component {
public let background: Background
public let content: AnyComponentWithIdentity<Empty>
public let isEnabled: Bool
public let tintWhenDisabled: Bool
public let allowActionWhenDisabled: Bool
public let displaysProgress: Bool
public let action: () -> Void
@ -296,6 +297,7 @@ public final class ButtonComponent: Component {
background: Background,
content: AnyComponentWithIdentity<Empty>,
isEnabled: Bool,
tintWhenDisabled: Bool = true,
allowActionWhenDisabled: Bool = false,
displaysProgress: Bool,
action: @escaping () -> Void
@ -303,6 +305,7 @@ public final class ButtonComponent: Component {
self.background = background
self.content = content
self.isEnabled = isEnabled
self.tintWhenDisabled = tintWhenDisabled
self.allowActionWhenDisabled = allowActionWhenDisabled
self.displaysProgress = displaysProgress
self.action = action
@ -318,6 +321,9 @@ public final class ButtonComponent: Component {
if lhs.isEnabled != rhs.isEnabled {
return false
}
if lhs.tintWhenDisabled != rhs.tintWhenDisabled {
return false
}
if lhs.allowActionWhenDisabled != rhs.allowActionWhenDisabled {
return false
}
@ -389,7 +395,7 @@ public final class ButtonComponent: Component {
var contentAlpha: CGFloat = 1.0
if component.displaysProgress {
contentAlpha = 0.0
} else if !component.isEnabled {
} else if !component.isEnabled && component.tintWhenDisabled {
contentAlpha = 0.7
}

View File

@ -19,6 +19,7 @@ swift_library(
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
@ -27,6 +28,7 @@ swift_library(
"//submodules/Components/SolidRoundedButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/Markdown",
],
visibility = [

View File

@ -3,6 +3,7 @@ import UIKit
import Display
import ComponentFlow
import MultilineTextComponent
import BalancedTextComponent
import TelegramPresentationData
import AppBundle
import BundleIconComponent
@ -12,19 +13,16 @@ import TelegramCore
public final class NewSessionInfoContentComponent: Component {
public let theme: PresentationTheme
public let strings: PresentationStrings
public let settings: GlobalPrivacySettings
public let openSettings: () -> Void
public let newSessionReview: NewSessionReview
public init(
theme: PresentationTheme,
strings: PresentationStrings,
settings: GlobalPrivacySettings,
openSettings: @escaping () -> Void
newSessionReview: NewSessionReview
) {
self.theme = theme
self.strings = strings
self.settings = settings
self.openSettings = openSettings
self.newSessionReview = newSessionReview
}
public static func ==(lhs: NewSessionInfoContentComponent, rhs: NewSessionInfoContentComponent) -> Bool {
@ -34,38 +32,31 @@ public final class NewSessionInfoContentComponent: Component {
if lhs.strings !== rhs.strings {
return false
}
if lhs.settings != rhs.settings {
if lhs.newSessionReview != rhs.newSessionReview {
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 notice = ComponentView<Empty>()
private let noticeBackground: SimpleLayer
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.noticeBackground = SimpleLayer()
self.noticeBackground.cornerRadius = 10.0
self.iconBackground = UIImageView()
self.iconForeground = UIImageView()
@ -84,6 +75,7 @@ public final class NewSessionInfoContentComponent: Component {
self.scrollView.scrollsToTop = false
self.scrollView.clipsToBounds = false
self.scrollView.layer.addSublayer(self.noticeBackground)
self.scrollView.addSubview(self.iconBackground)
self.scrollView.addSubview(self.iconForeground)
}
@ -104,14 +96,41 @@ public final class NewSessionInfoContentComponent: Component {
self.component = component
let sideInset: CGFloat = 16.0
let sideIconInset: CGFloat = 40.0
let noticeInsetX: CGFloat = 16.0
let noticeInsetY: CGFloat = 12.0
var contentHeight: CGFloat = 0.0
contentHeight += -14.0
//TODO:localize
let noticeSize = self.notice.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: "Never send your login code to anyone or you can lose your Telegram account!", font: Font.semibold(15.0), textColor: component.theme.list.itemDestructiveColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
)),
environment: {},
containerSize: CGSize(width: availableSize.width - noticeInsetX * 2.0, height: 1000.0)
)
let noticeBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: availableSize.width, height: noticeSize.height + noticeInsetY * 2.0))
transition.setFrame(layer: self.noticeBackground, frame: noticeBackgroundFrame)
self.noticeBackground.backgroundColor = component.theme.list.itemDestructiveColor.withMultipliedAlpha(0.1).cgColor
if let noticeView = self.notice.view {
if noticeView.superview == nil {
self.scrollView.addSubview(noticeView)
}
transition.setFrame(view: noticeView, frame: CGRect(origin: CGPoint(x: noticeBackgroundFrame.minX + floor((noticeBackgroundFrame.width - noticeSize.width) * 0.5), y: noticeBackgroundFrame.minY + floor((noticeBackgroundFrame.height - noticeSize.height) * 0.5)), size: noticeSize))
}
contentHeight += noticeBackgroundFrame.height
contentHeight += 24.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]
let colors: NSArray = [component.theme.list.itemAccentColor.cgColor, component.theme.list.itemAccentColor.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))
@ -125,10 +144,11 @@ public final class NewSessionInfoContentComponent: Component {
}
contentHeight += iconSize
contentHeight += 15.0
contentHeight += 16.0
//TODO:localize
let titleString = NSMutableAttributedString()
titleString.append(NSAttributedString(string: component.strings.NewSessionInfo_Title, font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor))
titleString.append(NSAttributedString(string: "New Login Prevented", font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor))
let imageAttachment = NSTextAttachment()
imageAttachment.image = self.iconBackground.image
titleString.append(NSAttributedString(attachment: imageAttachment))
@ -151,22 +171,18 @@ public final class NewSessionInfoContentComponent: Component {
contentHeight += titleSize.height
contentHeight += 16.0
let text: String
if component.settings.keepArchivedUnmuted {
text = component.strings.NewSessionInfo_TextKeepArchivedUnmuted
} else {
text = component.strings.NewSessionInfo_TextKeepArchivedDefault
}
//TODO:localize
let text: String = "We have terminated the login attempt from **\(component.newSessionReview.device), \(component.newSessionReview.location)**"
let mainText = NSMutableAttributedString()
mainText.append(parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(
font: Font.regular(15.0),
textColor: component.theme.list.itemSecondaryTextColor
textColor: component.theme.list.itemPrimaryTextColor
),
bold: MarkdownAttributeSet(
font: Font.semibold(15.0),
textColor: component.theme.list.itemSecondaryTextColor
textColor: component.theme.list.itemPrimaryTextColor
),
link: MarkdownAttributeSet(
font: Font.regular(15.0),
@ -177,34 +193,14 @@ public final class NewSessionInfoContentComponent: Component {
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(
component: AnyComponent(BalancedTextComponent(
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()
}
lineSpacing: 0.2
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
@ -217,99 +213,7 @@ public final class NewSessionInfoContentComponent: Component {
}
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
}
contentHeight += 1.0
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
let size = CGSize(width: availableSize.width, height: min(availableSize.height, contentSize.height))

View File

@ -7,28 +7,23 @@ import AccountContext
import SheetComponent
import ButtonComponent
import TelegramCore
import AnimatedTextComponent
private final class NewSessionInfoSheetContentComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let settings: GlobalPrivacySettings
let openSettings: () -> Void
let newSessionReview: NewSessionReview
let dismiss: () -> Void
init(
settings: GlobalPrivacySettings,
openSettings: @escaping () -> Void,
newSessionReview: NewSessionReview,
dismiss: @escaping () -> Void
) {
self.settings = settings
self.openSettings = openSettings
self.newSessionReview = newSessionReview
self.dismiss = dismiss
}
static func ==(lhs: NewSessionInfoSheetContentComponent, rhs: NewSessionInfoSheetContentComponent) -> Bool {
if lhs.settings != rhs.settings {
return false
}
return true
}
@ -36,9 +31,15 @@ private final class NewSessionInfoSheetContentComponent: Component {
private let content = ComponentView<Empty>()
private let button = ComponentView<Empty>()
private var remainingTimer: Int
private var timer: Foundation.Timer?
private var component: NewSessionInfoSheetContentComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.remainingTimer = 5
super.init(frame: frame)
}
@ -46,8 +47,28 @@ private final class NewSessionInfoSheetContentComponent: Component {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.timer?.invalidate()
}
func update(component: NewSessionInfoSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
if self.timer == nil {
self.timer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] _ in
guard let self else {
return
}
self.remainingTimer = max(0, self.remainingTimer - 1)
if self.remainingTimer == 0 {
self.timer?.invalidate()
}
self.state?.updated(transition: .immediate)
})
}
let previousComponent = self.component
self.component = component
self.state = state
let environment = environment[EnvironmentType.self].value
@ -61,8 +82,7 @@ private final class NewSessionInfoSheetContentComponent: Component {
component: AnyComponent(NewSessionInfoContentComponent(
theme: environment.theme,
strings: environment.strings,
settings: component.settings,
openSettings: component.openSettings
newSessionReview: component.newSessionReview
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
@ -76,8 +96,25 @@ private final class NewSessionInfoSheetContentComponent: Component {
contentHeight += contentSize.height
contentHeight += 30.0
//TODO:localize
var buttonContents: [AnyComponentWithIdentity<Empty>] = []
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
Text(text: "Got it", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
)))
if self.remainingTimer > 0 {
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(
AnimatedTextComponent(font: Font.with(size: 17.0, weight: .semibold, traits: .monospacedNumbers), color: environment.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.5), items: [
AnimatedTextComponent.Item(id: AnyHashable(0 as Int), content: .number(self.remainingTimer, minDigits: 0))
])
)))
}
var buttonTransition = transition
if transition.animation.isImmediate && previousComponent != nil {
buttonTransition = buttonTransition.withAnimation(.curve(duration: 0.2, curve: .easeInOut))
}
let buttonSize = self.button.update(
transition: transition,
transition: buttonTransition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
@ -85,9 +122,10 @@ private final class NewSessionInfoSheetContentComponent: Component {
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)
HStack(buttonContents, spacing: 5.0)
)),
isEnabled: true,
isEnabled: self.remainingTimer == 0,
tintWhenDisabled: false,
displaysProgress: false,
action: { [weak self] in
guard let self, let component = self.component else {
@ -131,24 +169,21 @@ private final class NewSessionInfoScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let settings: GlobalPrivacySettings
let buttonAction: (() -> Void)?
let newSessionReview: NewSessionReview
init(
context: AccountContext,
settings: GlobalPrivacySettings,
buttonAction: (() -> Void)?
newSessionReview: NewSessionReview
) {
self.context = context
self.settings = settings
self.buttonAction = buttonAction
self.newSessionReview = newSessionReview
}
static func ==(lhs: NewSessionInfoScreenComponent, rhs: NewSessionInfoScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.settings != rhs.settings {
if lhs.newSessionReview != rhs.newSessionReview {
return false
}
return true
@ -195,21 +230,7 @@ private final class NewSessionInfoScreenComponent: Component {
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)
}
})
},
newSessionReview: component.newSessionReview,
dismiss: { [weak self] in
guard let self else {
return
@ -222,7 +243,8 @@ private final class NewSessionInfoScreenComponent: Component {
guard let self else {
return
}
self.component?.buttonAction?()
let _ = self
//self.component?.buttonAction?()
})
}
)),
@ -256,11 +278,10 @@ private final class NewSessionInfoScreenComponent: Component {
}
public class NewSessionInfoScreen: ViewControllerComponentContainer {
public init(context: AccountContext, settings: GlobalPrivacySettings, buttonAction: (() -> Void)? = nil) {
public init(context: AccountContext, newSessionReview: NewSessionReview) {
super.init(context: context, component: NewSessionInfoScreenComponent(
context: context,
settings: settings,
buttonAction: buttonAction
newSessionReview: newSessionReview
), navigationBarAppearance: .none)
self.statusBar.statusBarStyle = .Ignore

View File

@ -45,8 +45,6 @@ final class StoryItemOverlaysView: UIView {
var activate: ((UIView, MessageReaction.Reaction) -> Void)?
var requestUpdate: (() -> Void)?
private var demoCounter: Bool = false
private var requestStickerDisposable: Disposable?
private var resolvedFile: TelegramMediaFile?
@ -97,9 +95,6 @@ final class StoryItemOverlaysView: UIView {
return
}
activate(self, reaction)
self.demoCounter = !self.demoCounter
self.requestUpdate?()
}
func update(
@ -112,11 +107,6 @@ final class StoryItemOverlaysView: UIView {
synchronous: Bool,
size: CGSize
) {
var counter = counter
if self.demoCounter {
counter += 1
}
var transition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
if self.reaction == nil {
transition = .immediate
@ -396,11 +386,17 @@ final class StoryItemOverlaysView: UIView {
transition.setPosition(view: itemView, position: targetFrame.center)
transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: targetFrame.size))
transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(coordinates.rotation * (CGFloat.pi / 180.0), 0.0, 0.0, 1.0))
var counter = 0
if let reactionData = story.views?.reactions.first(where: { $0.value == reaction }) {
counter = Int(reactionData.count)
}
itemView.update(
context: context,
reaction: reaction,
flags: flags,
counter: 0,
counter: counter,
availableReactions: availableReactions,
entityFiles: entityFiles,
synchronous: attemptSynchronous,

View File

@ -3318,8 +3318,6 @@ final class StoryItemSetContainerSendMessage {
return
}
if component.slice.peer.id != component.context.account.peerId {
} else if case .channel = component.slice.peer {
} else {
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: reaction).start()
}

View File

@ -267,7 +267,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
}, openPasswordSetup: {
}, openPremiumIntro: {
}, openActiveSessions: {
}, performActiveSessionAction: { _ in
}, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {
}, hideChatFolderUpdates: {
}, openStories: { _, _ in