From 03122c136ddfb5fe2207ca9f651d520d73aa58ca Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 1 Sep 2023 22:07:30 +0400 Subject: [PATCH] Stories --- submodules/ChatListUI/BUILD | 1 + .../Sources/ChatListControllerNode.swift | 2 +- .../Sources/ChatListSearchListPaneNode.swift | 4 +- .../Sources/Node/ChatListNode.swift | 43 ++-- .../Sources/Node/ChatListNodeEntries.swift | 2 +- .../Node/ChatListStorageInfoItem.swift | 24 ++- .../Source/Components/HStack.swift | 2 + .../Display/Source/ListViewItemNode.swift | 2 +- .../Sources/HashtagSearchController.swift | 2 +- submodules/JoinLinkPreviewUI/BUILD | 2 + .../Sources/JoinLinkPreviewController.swift | 2 +- .../JoinLinkPreviewControllerNode.swift | 4 +- .../JoinLinkPreviewPeerContentNode.swift | 120 ++++++++++- .../Postbox/Sources/TimeBasedCleanup.swift | 14 +- .../TextSizeSelectionController.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 2 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- .../Sources/Account/Account.swift | 46 +++- .../State/AccountStateManagementUtils.swift | 13 +- .../SyncCore/SyncCore_Namespaces.swift | 1 + .../SyncCore/SyncCore_NewSessionReview.swift | 104 +++++++++ .../SyncCore_ReactionsMessageAttribute.swift | 2 +- .../TelegramEngine/Messages/AdMessages.swift | 2 +- .../TelegramEngine/Messages/Stories.swift | 83 +++++++- .../Messages/TelegramEngineMessages.swift | 30 +-- .../TelegramEngine/Peers/JoinLink.swift | 5 +- .../Sources/ButtonComponent.swift | 8 +- .../Settings/NewSessionInfoScreen/BUILD | 2 + .../NewSessionInfoContentComponent.swift | 200 +++++------------- .../Sources/NewSessionInfoScreen.swift | 101 +++++---- .../Sources/StoryItemOverlaysView.swift | 18 +- ...StoryItemSetContainerViewSendMessage.swift | 2 - .../ChatSearchResultsContollerNode.swift | 2 +- 33 files changed, 561 insertions(+), 288 deletions(-) create mode 100644 submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index a8c8cf1028..1e8e8a45b9 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -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", diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index d69303cd59..c9bf9c3d75 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -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 diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 12001f3652..df56415297 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -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 }) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 9cc5fa5f33..dc45ee810b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -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 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 in + |> mapToSignal { suggestions, configuration, newSessionReviews -> Signal 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) } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index c90df507bb..fccc9ab744 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -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 { diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift index ebe5e03003..2c3d428b03 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift @@ -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) } diff --git a/submodules/ComponentFlow/Source/Components/HStack.swift b/submodules/ComponentFlow/Source/Components/HStack.swift index f86e2c9148..81b4e628cc 100644 --- a/submodules/ComponentFlow/Source/Components/HStack.swift +++ b/submodules/ComponentFlow/Source/Components/HStack.swift @@ -46,6 +46,8 @@ public final class HStack: 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 diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index 8183be537f..7386ec3f17 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -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() diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index ea08245cfa..2e3bf3d894 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -96,7 +96,7 @@ public final class HashtagSearchController: TelegramBaseController { }, openPasswordSetup: { }, openPremiumIntro: { }, openActiveSessions: { - }, performActiveSessionAction: { _ in + }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: { }, hideChatFolderUpdates: { }, openStories: { _, _ in diff --git a/submodules/JoinLinkPreviewUI/BUILD b/submodules/JoinLinkPreviewUI/BUILD index 2d87887404..480a72f6fd 100644 --- a/submodules/JoinLinkPreviewUI/BUILD +++ b/submodules/JoinLinkPreviewUI/BUILD @@ -23,6 +23,8 @@ swift_library( "//submodules/PeerInfoUI:PeerInfoUI", "//submodules/UndoUI:UndoUI", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/EmojiStatusComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift index 9b5f5b7917..5bbf773208 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift @@ -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) diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift index c2fde45fa9..ab465684b6 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift @@ -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?() } diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift index c4d827f983..8e8ede5680 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift @@ -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? 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 + var avatarIconTransition = Transition(transition) + if let current = self.avatarIcon { + avatarIcon = current + } else { + avatarIconTransition = avatarIconTransition.withAnimation(.none) + avatarIcon = ComponentView() + 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)) diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index 6579b10911..e1a9c01485 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -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()) } diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 3fd9892890..7c06b37f1a 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -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 }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 492bb27fab..6b5749db51 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -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 }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index cb1c317edd..a79f4d6c36 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -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 }) diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 4b6a33219c..b3b50d44e6 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -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] = [ - 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] = extractedExpr1 let importantBackgroundOperations: [Signal] = extractedExpr let importantBackgroundOperationsRunning = combineLatest(queue: Queue(), importantBackgroundOperations) |> map { values -> AccountRunningImportantTasks in diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index fde413f909..cf3096fab3 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -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)) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 13cf0c2274..c1b0f32c6b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift new file mode 100644 index 0000000000..e999641dfc --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift @@ -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 { + 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 { + return postbox.transaction { transaction -> Void in + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.NewSessionReviews, items: []) + } + |> ignoreValues +} + +public func removeNewSessionReviews(postbox: Postbox, ids: [Int64]) -> Signal { + return postbox.transaction { transaction -> Void in + for id in ids { + transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, itemId: NewSessionReview.Id(id: id).rawValue) + } + } + |> ignoreValues +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift index dc323b533c..9368b3f577 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index 22bdb93a47..f89b245cdf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index f8c3e09b9e..99466c7361 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -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 { 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, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index edc2ac5dd9..4700dda967 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift index 2fc7134648..be5a1648a5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift @@ -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 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, 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 } diff --git a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD index 51b983ac39..06ad7ca3aa 100644 --- a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD @@ -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 = [ diff --git a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoContentComponent.swift b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoContentComponent.swift index 49f0ec5d2c..faf0f02f94 100644 --- a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoContentComponent.swift +++ b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoContentComponent.swift @@ -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() - let title = ComponentView() - let text = ComponentView() - - init() { - } - } - public final class View: UIView { private let scrollView: UIScrollView private let iconBackground: UIImageView private let iconForeground: UIImageView + private let notice = ComponentView() + private let noticeBackground: SimpleLayer + private let title = ComponentView() private let mainText = ComponentView() - 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)) diff --git a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoScreen.swift b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoScreen.swift index 049875fdbc..9acf92ccfa 100644 --- a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoScreen.swift +++ b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/Sources/NewSessionInfoScreen.swift @@ -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() private let button = ComponentView() + 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, 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] = [] + 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 diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift index a5205b6939..4530d3fd94 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift @@ -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, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 5c9b2279e6..b0ba62c4c2 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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() } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 783a4bd083..44c423aa20 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -267,7 +267,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe }, openPasswordSetup: { }, openPremiumIntro: { }, openActiveSessions: { - }, performActiveSessionAction: { _ in + }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: { }, hideChatFolderUpdates: { }, openStories: { _, _ in