mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
f0336cd98a
commit
03122c136d
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -96,7 +96,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
@ -23,6 +23,8 @@ swift_library(
|
||||
"//submodules/PeerInfoUI:PeerInfoUI",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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)
|
||||
|
@ -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?()
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 = [
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -267,7 +267,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openActiveSessions: {
|
||||
}, performActiveSessionAction: { _ in
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
Loading…
x
Reference in New Issue
Block a user