Merge commit 'aed14676171da506a0429e92b7eb9c4ebe2f62b0'

This commit is contained in:
Ali 2021-01-21 20:06:19 +04:00
commit f8bf225170
36 changed files with 4941 additions and 4640 deletions

View File

@ -5825,6 +5825,7 @@ Sorry for the inconvenience.";
"InviteLink.Title" = "Invite Links"; "InviteLink.Title" = "Invite Links";
"InviteLink.PermanentLink" = "Permanent Link"; "InviteLink.PermanentLink" = "Permanent Link";
"InviteLink.PublicLink" = "Public Link";
"InviteLink.Share" = "Share Link"; "InviteLink.Share" = "Share Link";
"InviteLink.PeopleJoinedNone" = "no one joined yet"; "InviteLink.PeopleJoinedNone" = "no one joined yet";
"InviteLink.PeopleJoined_1" = "%@ people joined"; "InviteLink.PeopleJoined_1" = "%@ people joined";
@ -5895,6 +5896,8 @@ Sorry for the inconvenience.";
"InviteLink.ExpiresIn" = "expires in %@"; "InviteLink.ExpiresIn" = "expires in %@";
"InviteLink.InviteLinkCopiedText" = "Invite link copied to clipboard";
"Conversation.ChecksTooltip.Delivered" = "Delivered"; "Conversation.ChecksTooltip.Delivered" = "Delivered";
"Conversation.ChecksTooltip.Read" = "Read"; "Conversation.ChecksTooltip.Read" = "Read";
@ -5902,5 +5905,12 @@ Sorry for the inconvenience.";
"Common.Save" = "Save"; "Common.Save" = "Save";
"UserInfo.FakeUserWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
"UserInfo.FakeBotWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
"GroupInfo.FakeGroupWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
"ChannelInfo.FakeChannelWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
"ReportPeer.ReasonFake" = "Fake Account";
"ChatList.HeaderImportIntoAnExistingGroup" = "OR IMPORT INTO AN EXISTING GROUP"; "ChatList.HeaderImportIntoAnExistingGroup" = "OR IMPORT INTO AN EXISTING GROUP";
"Conversation.ImportedMessageHint" = "The messages was imported from another app. We can't guarantee it's real."; "Conversation.ImportedMessageHint" = "The messages was imported from another app. We can't guarantee it's real.";

View File

@ -383,11 +383,13 @@ public struct ContactListAdditionalOption: Equatable {
public let title: String public let title: String
public let icon: ContactListActionItemIcon public let icon: ContactListActionItemIcon
public let action: () -> Void public let action: () -> Void
public let clearHighlightAutomatically: Bool
public init(title: String, icon: ContactListActionItemIcon, action: @escaping () -> Void) { public init(title: String, icon: ContactListActionItemIcon, action: @escaping () -> Void, clearHighlightAutomatically: Bool = false) {
self.title = title self.title = title
self.icon = icon self.icon = icon
self.action = action self.action = action
self.clearHighlightAutomatically = clearHighlightAutomatically
} }
public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool { public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool {

View File

@ -1258,6 +1258,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if peer.isScam { if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0 credibilityIconOffset = 2.0
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0 credibilityIconOffset = 3.0
@ -1270,6 +1273,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if peer.isScam { if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0 credibilityIconOffset = 2.0
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0 credibilityIconOffset = 3.0

View File

@ -175,7 +175,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
interaction.authorize() interaction.authorize()
}) })
case let .option(_, option, header, _, _): case let .option(_, option, header, _, _):
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: false, header: header, action: option.action) return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action)
case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, enabled): case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, enabled):
var status: ContactsPeerItemStatus var status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer let itemPeer: ContactsPeerItemPeer

View File

@ -56,7 +56,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
case usageHeader(PresentationTheme, String) case usageHeader(PresentationTheme, String)
case usagePicker(PresentationTheme, InviteLinkUsageLimit) case usagePicker(PresentationTheme, InviteLinkUsageLimit)
case usageCustomPicker(PresentationTheme, Int32?, Bool) case usageCustomPicker(PresentationTheme, Int32?, Bool, Bool)
case usageInfo(PresentationTheme, String) case usageInfo(PresentationTheme, String)
case revoke(PresentationTheme, String) case revoke(PresentationTheme, String)
@ -141,8 +141,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused): case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused, lhsCustomValue):
if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused { if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused, rhsCustomValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused, lhsCustomValue == rhsCustomValue {
return true return true
} else { } else {
return false return false
@ -221,9 +221,14 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
return updatedState return updatedState
}) })
}) })
case let .usageCustomPicker(theme, value, focused): case let .usageCustomPicker(theme, value, focused, customValue):
let text = value.flatMap { String($0) } ?? (focused ? "" : presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsersUnlimited) let text: String
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, selectAllOnFocus: true, tag: nil, sectionId: self.section, textUpdated: { updatedText in if let value = value, value != 0 {
text = String(value)
} else {
text = focused ? "" : presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsersUnlimited
}
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, selectAllOnFocus: true, secondaryStyle: !customValue, tag: nil, sectionId: self.section, textUpdated: { updatedText in
guard !updatedText.isEmpty else { guard !updatedText.isEmpty else {
return return
} }
@ -284,7 +289,12 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased())) entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased()))
entries.append(.usagePicker(presentationData.theme, state.usage)) entries.append(.usagePicker(presentationData.theme, state.usage))
entries.append(.usageCustomPicker(presentationData.theme, state.usage.value, state.pickingUsageLimit))
var customValue = false
if case .custom = state.usage {
customValue = true
}
entries.append(.usageCustomPicker(presentationData.theme, state.usage.value, state.pickingUsageLimit, customValue))
entries.append(.usageInfo(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimitInfo)) entries.append(.usageInfo(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimitInfo))
@ -401,7 +411,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
expireDate = currentTime + value expireDate = currentTime + value
} else { } else {
expireDate = nil expireDate = 0
} }
let usageLimit = state.usage.value let usageLimit = state.usage.value

View File

@ -20,6 +20,7 @@ import ShareController
import OverlayStatusController import OverlayStatusController
import PresentationDataUtils import PresentationDataUtils
import DirectionalPanGesture import DirectionalPanGesture
import UndoUI
class InviteLinkInviteInteraction { class InviteLinkInviteInteraction {
let context: AccountContext let context: AccountContext
@ -139,7 +140,7 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
case let .header(theme, title, text): case let .header(theme, title, text):
return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text) return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text)
case let .mainLink(_, invite): case let .mainLink(_, invite):
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: { return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
interaction.copyLink(invite) interaction.copyLink(invite)
}, shareAction: { }, shareAction: {
interaction.shareLink(invite) interaction.shareLink(invite)
@ -345,8 +346,9 @@ public final class InviteLinkInviteController: ViewController {
if let invite = invite { if let invite = invite {
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root)) self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
} }
}))) })))
@ -390,8 +392,9 @@ public final class InviteLinkInviteController: ViewController {
self?.controller?.presentInGlobalOverlay(contextController) self?.controller?.presentInGlobalOverlay(contextController)
}, copyLink: { [weak self] invite in }, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root)) self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}, shareLink: { [weak self] invite in }, shareLink: { [weak self] invite in
let shareController = ShareController(context: context, subject: .url(invite.link)) let shareController = ShareController(context: context, subject: .url(invite.link))
self?.controller?.present(shareController, in: .window(.root)) self?.controller?.present(shareController, in: .window(.root))
@ -626,6 +629,10 @@ public final class InviteLinkInviteController: ViewController {
private var panGestureArguments: CGFloat? private var panGestureArguments: CGFloat?
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer is DirectionalPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) { @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
let contentOffset = self.listNode.visibleContentOffset() let contentOffset = self.listNode.visibleContentOffset()
switch recognizer.state { switch recognizer.state {
@ -633,10 +640,20 @@ public final class InviteLinkInviteController: ViewController {
self.panGestureArguments = 0.0 self.panGestureArguments = 0.0
case .changed: case .changed:
var translation = recognizer.translation(in: self.contentNode.view).y var translation = recognizer.translation(in: self.contentNode.view).y
if let currentPanOffset = self.panGestureArguments { if let currentOffset = self.panGestureArguments {
if case let .known(value) = contentOffset, value <= 0.5 { if case let .known(value) = contentOffset, value <= 0.5 {
if currentOffset > 0.0 {
let translation = self.listNode.scroller.panGestureRecognizer.translation(in: self.listNode.scroller)
if translation.y > 10.0 {
self.listNode.scroller.panGestureRecognizer.isEnabled = false
self.listNode.scroller.panGestureRecognizer.isEnabled = true
} else { } else {
translation = currentPanOffset self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
} else {
translation = 0.0
recognizer.setTranslation(CGPoint(), in: self.contentNode.view)
} }
self.panGestureArguments = translation self.panGestureArguments = translation

View File

@ -19,6 +19,7 @@ import ContextUI
import TelegramStringFormatting import TelegramStringFormatting
import ItemListPeerActionItem import ItemListPeerActionItem
import ShareController import ShareController
import UndoUI
private final class InviteLinkListControllerArguments { private final class InviteLinkListControllerArguments {
let context: AccountContext let context: AccountContext
@ -55,7 +56,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case header(PresentationTheme, String) case header(PresentationTheme, String)
case mainLinkHeader(PresentationTheme, String) case mainLinkHeader(PresentationTheme, String)
case mainLink(PresentationTheme, ExportedInvitation?, [Peer]) case mainLink(PresentationTheme, ExportedInvitation?, [Peer], Int32, Bool)
case linksHeader(PresentationTheme, String) case linksHeader(PresentationTheme, String)
case linksCreate(PresentationTheme, String) case linksCreate(PresentationTheme, String)
@ -117,8 +118,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .mainLink(lhsTheme, lhsInvite, lhsPeers): case let .mainLink(lhsTheme, lhsInvite, lhsPeers, lhsImportersCount, lhsIsPublic):
if case let .mainLink(rhsTheme, rhsInvite, rhsPeers) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, arePeerArraysEqual(lhsPeers, rhsPeers) { if case let .mainLink(rhsTheme, rhsInvite, rhsPeers, rhsImportersCount, rhsIsPublic) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, arePeerArraysEqual(lhsPeers, rhsPeers), lhsImportersCount == rhsImportersCount, lhsIsPublic == rhsIsPublic {
return true return true
} else { } else {
return false return false
@ -179,8 +180,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return InviteLinkHeaderItem(theme: theme, text: text, sectionId: self.section) return InviteLinkHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .mainLinkHeader(_, text): case let .mainLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .mainLink(_, invite, peers): case let .mainLink(_, invite, peers, importersCount, isPublic):
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: peers, displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, displayImporters: !isPublic, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
if let invite = invite { if let invite = invite {
arguments.copyLink(invite) arguments.copyLink(invite)
} }
@ -225,14 +226,18 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} }
} }
private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, mainPeers: [Peer]) -> [InviteLinksListEntry] { private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?) -> [InviteLinksListEntry] {
var entries: [InviteLinksListEntry] = [] var entries: [InviteLinksListEntry] = []
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp)) entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
entries.append(.mainLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased()))
let mainInvite: ExportedInvitation? let mainInvite: ExportedInvitation?
if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) { var isPublic = false
if let peer = peerViewMainPeer(view), let address = peer.addressName, !address.isEmpty {
mainInvite = ExportedInvitation(link: "t.me/\(address)", isPermanent: true, isRevoked: false, adminId: PeerId(0), date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil)
isPublic = true
} else if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) {
mainInvite = invite mainInvite = invite
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation { } else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation {
mainInvite = invite mainInvite = invite
@ -241,7 +246,19 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
} else { } else {
mainInvite = nil mainInvite = nil
} }
entries.append(.mainLink(presentationData.theme, mainInvite, mainPeers))
entries.append(.mainLinkHeader(presentationData.theme, isPublic ? presentationData.strings.InviteLink_PublicLink.uppercased() : presentationData.strings.InviteLink_PermanentLink.uppercased()))
let importersCount: Int32
if let count = importers?.count {
importersCount = count
} else if let count = mainInvite?.count {
importersCount = count
} else {
importersCount = 0
}
entries.append(.mainLink(presentationData.theme, mainInvite, importers?.importers.prefix(3).compactMap { $0.peer.peer } ?? [], importersCount, isPublic))
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased())) entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create)) entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
@ -320,10 +337,11 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, copyLink: { invite in }, copyLink: { invite in
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}, mainLinkContextAction: { invite, node, gesture in }, mainLinkContextAction: { invite, node, gesture in
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else { guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?(), let invite = invite else {
return return
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -334,11 +352,10 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
if let invite = invite {
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
}))) })))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
@ -346,12 +363,11 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
if let invite = invite {
let controller = InviteLinkQRCodeController(context: context, invite: invite) let controller = InviteLinkQRCodeController(context: context, invite: invite)
presentControllerImpl?(controller, nil) presentControllerImpl?(controller, nil)
}
}))) })))
if invite.adminId.toInt64() != 0 {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in }, action: { _, f in
@ -396,6 +412,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
]) ])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}))) })))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController) presentInGlobalOverlayImpl?(contextController)
@ -426,11 +443,21 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
f(.dismissWithoutContent) f(.dismissWithoutContent)
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}))) })))
if !invite.isRevoked { if !invite.isRevoked {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextShare, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
let shareController = ShareController(context: context, subject: .url(invite.link))
presentControllerImpl?(shareController, nil)
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in
@ -546,29 +573,41 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|> deliverOnMainQueue |> deliverOnMainQueue
let importersState = Promise<PeerInvitationImportersState?>(nil) let importersState = Promise<PeerInvitationImportersState?>(nil)
let importersContext: Signal<PeerInvitationImportersContext, NoError> = peerView let importersContext: Signal<PeerInvitationImportersContext?, NoError> = peerView
|> mapToSignal { view -> Signal<ExportedInvitation, NoError> in |> mapToSignal { view -> Signal<ExportedInvitation?, NoError> in
if let cachedData = view.cachedData as? CachedGroupData, let exportedInvitation = cachedData.exportedInvitation { if let cachedData = view.cachedData as? CachedGroupData, let exportedInvitation = cachedData.exportedInvitation {
return .single(exportedInvitation) return .single(exportedInvitation)
} else if let cachedData = view.cachedData as? CachedChannelData, let exportedInvitation = cachedData.exportedInvitation { } else if let cachedData = view.cachedData as? CachedChannelData, let exportedInvitation = cachedData.exportedInvitation {
return .single(exportedInvitation) return .single(exportedInvitation)
} else { } else {
return .complete() return .single(nil)
} }
} }
|> distinctUntilChanged |> distinctUntilChanged
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { invite -> PeerInvitationImportersContext in |> map { invite -> PeerInvitationImportersContext? in
return PeerInvitationImportersContext(account: context.account, peerId: peerId, invite: invite) return invite.flatMap { PeerInvitationImportersContext(account: context.account, peerId: peerId, invite: $0) }
} |> afterNext { context in } |> afterNext { context in
if let context = context {
importersState.set(context.state |> map(Optional.init)) importersState.set(context.state |> map(Optional.init))
} else {
importersState.set(.single(nil))
}
} }
let previousRevokedInvites = Atomic<PeerExportedInvitationsState?>(value: nil)
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state) let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state)
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previousRevokedInvites = previousRevokedInvites.swap(invites)
var crossfade = false
if (previousRevokedInvites?.hasLoadedOnce ?? false) != (revokedInvites.hasLoadedOnce) {
crossfade = true
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.invitations, revokedInvites: revokedInvites.invitations, mainPeers: importers?.importers.compactMap { $0.peer.peer } ?? []), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.invitations, revokedInvites: revokedInvites.invitations, importers: importers), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -20,6 +20,7 @@ import ShareController
import OverlayStatusController import OverlayStatusController
import PresentationDataUtils import PresentationDataUtils
import DirectionalPanGesture import DirectionalPanGesture
import UndoUI
class InviteLinkViewInteraction { class InviteLinkViewInteraction {
let context: AccountContext let context: AccountContext
@ -171,7 +172,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
case let .link(_, invite): case let .link(_, invite):
let buttonColor = color(for: invite) let buttonColor = color(for: invite)
let availability = invitationAvailability(invite) let availability = invitationAvailability(invite)
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: !invite.isRevoked && !availability.isZero, displayImporters: false, buttonColor: buttonColor, sectionId: 0, style: .plain, copyAction: { return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: !invite.isRevoked && !availability.isZero, displayImporters: false, buttonColor: buttonColor, sectionId: 0, style: .plain, copyAction: {
interaction.copyLink(invite) interaction.copyLink(invite)
}, shareAction: { }, shareAction: {
interaction.shareLink(invite) interaction.shareLink(invite)
@ -393,8 +394,9 @@ public final class InviteLinkViewController: ViewController {
} }
}, copyLink: { [weak self] invite in }, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root)) self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}, shareLink: { [weak self] invite in }, shareLink: { [weak self] invite in
let shareController = ShareController(context: context, subject: .url(invite.link)) let shareController = ShareController(context: context, subject: .url(invite.link))
self?.controller?.present(shareController, in: .window(.root)) self?.controller?.present(shareController, in: .window(.root))
@ -412,8 +414,9 @@ public final class InviteLinkViewController: ViewController {
f(.dismissWithoutContent) f(.dismissWithoutContent)
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root)) self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}))) })))
if invite.isRevoked { if invite.isRevoked {
@ -453,6 +456,8 @@ public final class InviteLinkViewController: ViewController {
}))) })))
} }
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController) self?.controller?.presentInGlobalOverlay(contextController)
}) })
@ -717,12 +722,20 @@ public final class InviteLinkViewController: ViewController {
self.panGestureArguments = 0.0 self.panGestureArguments = 0.0
case .changed: case .changed:
var translation = recognizer.translation(in: self.contentNode.view).y var translation = recognizer.translation(in: self.contentNode.view).y
if let currentPanOffset = self.panGestureArguments { if let currentOffset = self.panGestureArguments {
if case let .known(value) = contentOffset, value <= 0.5 { if case let .known(value) = contentOffset, value <= 0.5 {
if currentOffset > 0.0 {
let translation = self.listNode.scroller.panGestureRecognizer.translation(in: self.listNode.scroller)
if translation.y > 10.0 {
self.listNode.scroller.panGestureRecognizer.isEnabled = false
self.listNode.scroller.panGestureRecognizer.isEnabled = true
} else { } else {
translation = currentPanOffset self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
} else {
translation = 0.0
recognizer.setTranslation(CGPoint(), in: self.contentNode.view)
} }
self.panGestureArguments = translation self.panGestureArguments = translation

View File

@ -96,7 +96,8 @@ private let shareIcon = generateImage(CGSize(width: 26.0, height: 26.0), context
private class ItemNode: ASDisplayNode { private class ItemNode: ASDisplayNode {
private let selectionNode: HighlightTrackingButtonNode private let selectionNode: HighlightTrackingButtonNode
private let wrapperNode: ASDisplayNode private let wrapperNode: ASDisplayNode
private let backgroundNode: ASImageNode private let backgroundNode: ASDisplayNode
private let backgroundGradientLayer: CAGradientLayer
private let iconNode: ASImageNode private let iconNode: ASImageNode
private var timerNode: TimerNode? private var timerNode: TimerNode?
@ -122,11 +123,19 @@ private class ItemNode: ASDisplayNode {
self.selectionNode = HighlightTrackingButtonNode() self.selectionNode = HighlightTrackingButtonNode()
self.wrapperNode = ASDisplayNode() self.wrapperNode = ASDisplayNode()
self.backgroundNode = ASImageNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.displaysAsynchronously = false self.backgroundNode.clipsToBounds = true
self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.cornerRadius = 15.0
if #available(iOS 13.0, *) {
self.backgroundNode.layer.cornerCurve = .continuous
}
self.backgroundNode.isUserInteractionEnabled = false self.backgroundNode.isUserInteractionEnabled = false
self.backgroundGradientLayer = CAGradientLayer()
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
self.backgroundNode.layer.addSublayer(self.backgroundGradientLayer)
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true self.iconNode.displayWithoutProcessing = true
@ -256,10 +265,10 @@ private class ItemNode: ASDisplayNode {
snapshotView?.removeFromSuperview() snapshotView?.removeFromSuperview()
}) })
} }
self.backgroundNode.image = generateBackgroundImage(colors: colors) self.backgroundGradientLayer.colors = colors as? [Any]
} }
} else { } else {
self.backgroundNode.image = generateBackgroundImage(colors: colors) self.backgroundGradientLayer.colors = colors as? [Any]
} }
let secondaryTextColor = color.colors.text let secondaryTextColor = color.colors.text
@ -329,7 +338,7 @@ private class ItemNode: ASDisplayNode {
self.timerNode = timerNode self.timerNode = timerNode
self.addSubnode(timerNode) self.addSubnode(timerNode)
} }
timerNode.update(color: UIColor.white, creationTimestamp: invite.date, deadlineTimestamp: expireDate) timerNode.update(color: UIColor.white, creationTimestamp: invite.startDate ?? invite.date, deadlineTimestamp: expireDate)
if share { if share {
subtitleText = presentationData.strings.InviteLink_TapToCopy subtitleText = presentationData.strings.InviteLink_TapToCopy
} }
@ -359,6 +368,7 @@ private class ItemNode: ASDisplayNode {
transition.updateFrame(node: self.wrapperNode, frame: backgroundFrame) transition.updateFrame(node: self.wrapperNode, frame: backgroundFrame)
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
transition.updateFrame(node: self.selectionNode, frame: backgroundFrame) transition.updateFrame(node: self.selectionNode, frame: backgroundFrame)
transition.updateFrame(layer: self.backgroundGradientLayer, frame: backgroundFrame)
let buttonSize = CGSize(width: 26.0, height: 26.0) let buttonSize = CGSize(width: 26.0, height: 26.0)
let buttonFrame = CGRect(origin: CGPoint(x: itemSize.width - buttonSize.width - 12.0, y: 12.0), size: buttonSize) let buttonFrame = CGRect(origin: CGPoint(x: itemSize.width - buttonSize.width - 12.0, y: 12.0), size: buttonSize)
@ -532,7 +542,7 @@ private final class TimerNode: ASDisplayNode {
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var fraction = CGFloat(params.deadlineTimestamp - currentTimestamp) / CGFloat(params.deadlineTimestamp - params.creationTimestamp) var fraction = CGFloat(params.deadlineTimestamp - currentTimestamp) / CGFloat(params.deadlineTimestamp - params.creationTimestamp)
fraction = 1.0 - max(0.0, min(1.0, fraction)) fraction = max(0.0001, 1.0 - max(0.0, min(1.0, fraction)))
let image: UIImage? let image: UIImage?

View File

@ -32,7 +32,9 @@ enum InviteLinkUsageLimit: Equatable {
} }
init(value: Int32?) { init(value: Int32?) {
if let value = value { if value == 0 {
self = .unlimited
} else if let value = value {
if value == 1 { if value == 1 {
self = .low self = .low
} else if value == 10 { } else if value == 10 {
@ -56,7 +58,7 @@ enum InviteLinkUsageLimit: Equatable {
case .high: case .high:
return 100 return 100
case .unlimited: case .unlimited:
return nil return 0
case let .custom(value): case let .custom(value):
return value return value
} }

View File

@ -29,6 +29,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
let context: AccountContext let context: AccountContext
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let invite: ExportedInvitation? let invite: ExportedInvitation?
let count: Int32
let peers: [Peer] let peers: [Peer]
let displayButton: Bool let displayButton: Bool
let displayImporters: Bool let displayImporters: Bool
@ -45,6 +46,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
context: AccountContext, context: AccountContext,
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
invite: ExportedInvitation?, invite: ExportedInvitation?,
count: Int32,
peers: [Peer], peers: [Peer],
displayButton: Bool, displayButton: Bool,
displayImporters: Bool, displayImporters: Bool,
@ -60,6 +62,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.invite = invite self.invite = invite
self.count = count
self.peers = peers self.peers = peers
self.displayButton = displayButton self.displayButton = displayButton
self.displayImporters = displayImporters self.displayImporters = displayImporters
@ -290,22 +293,17 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.invite.flatMap({ $0.link.replacingOccurrences(of: "https://", with: "") }) ?? "", font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.invite.flatMap({ $0.link.replacingOccurrences(of: "https://", with: "") }) ?? "", font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 90.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let subtitle: String let subtitle: String
let subtitleColor: UIColor let subtitleColor: UIColor
if let count = item.invite?.count { if item.count > 0 {
if count > 0 { subtitle = item.presentationData.strings.InviteLink_PeopleJoined(item.count)
subtitle = item.presentationData.strings.InviteLink_PeopleJoined(count)
subtitleColor = item.presentationData.theme.list.itemAccentColor subtitleColor = item.presentationData.theme.list.itemAccentColor
} else { } else {
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
} }
} else {
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
}
let (invitedPeersLayout, invitedPeersApply) = makeInvitedPeersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (invitedPeersLayout, invitedPeersApply) = makeInvitedPeersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))

View File

@ -376,6 +376,9 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
if peer.isScam { if peer.isScam {
credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular) credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 6.0 credibilityIconOffset = 6.0
} else if peer.isFake {
credibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme) credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
} }

View File

@ -47,6 +47,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let maxLength: Int let maxLength: Int
let enabled: Bool let enabled: Bool
let selectAllOnFocus: Bool let selectAllOnFocus: Bool
let secondaryStyle: Bool
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
let action: () -> Void let action: () -> Void
let textUpdated: (String) -> Void let textUpdated: (String) -> Void
@ -56,7 +57,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let cleared: (() -> Void)? let cleared: (() -> Void)?
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) { public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.title = title self.title = title
self.text = text self.text = text
@ -69,6 +70,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
self.maxLength = maxLength self.maxLength = maxLength
self.enabled = enabled self.enabled = enabled
self.selectAllOnFocus = selectAllOnFocus self.selectAllOnFocus = selectAllOnFocus
self.secondaryStyle = secondaryStyle
self.tag = tag self.tag = tag
self.sectionId = sectionId self.sectionId = sectionId
self.textUpdated = textUpdated self.textUpdated = textUpdated
@ -183,7 +185,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
self.textNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)] self.textNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)]
self.textNode.textField.font = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) self.textNode.textField.font = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
self.textNode.textField.textColor = item.presentationData.theme.list.itemPrimaryTextColor self.textNode.textField.textColor = item.secondaryStyle ? item.presentationData.theme.list.itemSecondaryTextColor : item.presentationData.theme.list.itemPrimaryTextColor
self.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance self.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
self.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor self.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor
self.textNode.textField.accessibilityHint = item.placeholder self.textNode.textField.accessibilityHint = item.placeholder
@ -218,6 +220,11 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
fontUpdated = true fontUpdated = true
} }
var styleUpdated = false
if currentItem?.secondaryStyle != item.secondaryStyle {
styleUpdated = true
}
let leftInset: CGFloat = 16.0 + params.leftInset let leftInset: CGFloat = 16.0 + params.leftInset
var rightInset: CGFloat = 16.0 + params.rightInset var rightInset: CGFloat = 16.0 + params.rightInset
@ -252,7 +259,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
strongSelf.textNode.textField.textColor = item.presentationData.theme.list.itemPrimaryTextColor strongSelf.textNode.textField.textColor = item.secondaryStyle ? item.presentationData.theme.list.itemSecondaryTextColor : item.presentationData.theme.list.itemPrimaryTextColor
strongSelf.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance strongSelf.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
strongSelf.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor strongSelf.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor
} }
@ -261,6 +268,10 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
strongSelf.textNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)] strongSelf.textNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)]
} }
if styleUpdated {
strongSelf.textNode.textField.textColor = item.secondaryStyle ? item.presentationData.theme.list.itemSecondaryTextColor : item.presentationData.theme.list.itemPrimaryTextColor
}
let _ = titleApply() let _ = titleApply()
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)

View File

@ -293,7 +293,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case let .privateLinkHeader(_, title): case let .privateLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .privateLink(_, invite, displayImporters): case let .privateLink(_, invite, displayImporters):
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
if let invite = invite { if let invite = invite {
arguments.copyLink(invite) arguments.copyLink(invite)
} }

View File

@ -20,6 +20,7 @@ public enum PeerReportSubject {
public enum PeerReportOption { public enum PeerReportOption {
case spam case spam
case fake
case violence case violence
case copyright case copyright
case pornography case pornography
@ -37,6 +38,8 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
switch option { switch option {
case .spam: case .spam:
title = presentationData.strings.ReportPeer_ReasonSpam title = presentationData.strings.ReportPeer_ReasonSpam
case .fake:
title = presentationData.strings.ReportPeer_ReasonFake
case .violence: case .violence:
title = presentationData.strings.ReportPeer_ReasonViolence title = presentationData.strings.ReportPeer_ReasonViolence
case .pornography: case .pornography:
@ -57,6 +60,8 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
switch option { switch option {
case .spam: case .spam:
reportReason = .spam reportReason = .spam
case .fake:
reportReason = .fake
case .violence: case .violence:
reportReason = .violence reportReason = .violence
case .pornography: case .pornography:
@ -112,6 +117,8 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
switch option { switch option {
case .spam: case .spam:
title = presentationData.strings.ReportPeer_ReasonSpam title = presentationData.strings.ReportPeer_ReasonSpam
case .fake:
title = presentationData.strings.ReportPeer_ReasonFake
case .violence: case .violence:
title = presentationData.strings.ReportPeer_ReasonViolence title = presentationData.strings.ReportPeer_ReasonViolence
case .pornography: case .pornography:
@ -128,6 +135,8 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
switch option { switch option {
case .spam: case .spam:
reportReason = .spam reportReason = .spam
case .fake:
reportReason = .fake
case .violence: case .violence:
reportReason = .violence reportReason = .violence
case .pornography: case .pornography:

View File

@ -652,7 +652,15 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
} else { } else {
aboutTitle = presentationData.strings.Profile_About aboutTitle = presentationData.strings.Profile_About
} }
if user.isScam { if user.isFake {
let aboutValue: String
if let _ = user.botInfo {
aboutValue = presentationData.strings.UserInfo_FakeBotWarning
} else {
aboutValue = presentationData.strings.UserInfo_FakeUserWarning
}
entries.append(UserInfoEntry.about(presentationData.theme, peer, aboutTitle, aboutValue))
} else if user.isScam {
let aboutValue: String let aboutValue: String
if let _ = user.botInfo { if let _ = user.botInfo {
aboutValue = presentationData.strings.UserInfo_ScamBotWarning aboutValue = presentationData.strings.UserInfo_ScamBotWarning

View File

@ -143,6 +143,7 @@ public struct TelegramChannelFlags: OptionSet {
public static let hasGeo = TelegramChannelFlags(rawValue: 1 << 3) public static let hasGeo = TelegramChannelFlags(rawValue: 1 << 3)
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4) public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5) public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
public static let isFake = TelegramChannelFlags(rawValue: 1 << 6)
} }
public final class TelegramChannel: Peer { public final class TelegramChannel: Peer {

View File

@ -14,6 +14,7 @@ public struct UserInfoFlags: OptionSet {
public static let isVerified = UserInfoFlags(rawValue: (1 << 0)) public static let isVerified = UserInfoFlags(rawValue: (1 << 0))
public static let isSupport = UserInfoFlags(rawValue: (1 << 1)) public static let isSupport = UserInfoFlags(rawValue: (1 << 1))
public static let isScam = UserInfoFlags(rawValue: (1 << 2)) public static let isScam = UserInfoFlags(rawValue: (1 << 2))
public static let isFake = UserInfoFlags(rawValue: (1 << 3))
} }
public struct BotUserInfoFlags: OptionSet { public struct BotUserInfoFlags: OptionSet {

View File

@ -11,8 +11,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) } dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) } dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
dict[2055070967] = { return Api.ChatFull.parse_channelFull($0) }
dict[-213431562] = { return Api.ChatFull.parse_chatFull($0) } dict[-213431562] = { return Api.ChatFull.parse_chatFull($0) }
dict[2055070967] = { return Api.ChatFull.parse_channelFull($0) }
dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) } dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) }
dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) } dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) }
dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) } dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) }
@ -474,6 +474,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1685456582] = { return Api.ReportReason.parse_inputReportReasonCopyright($0) } dict[-1685456582] = { return Api.ReportReason.parse_inputReportReasonCopyright($0) }
dict[-1376497949] = { return Api.ReportReason.parse_inputReportReasonChildAbuse($0) } dict[-1376497949] = { return Api.ReportReason.parse_inputReportReasonChildAbuse($0) }
dict[-606798099] = { return Api.ReportReason.parse_inputReportReasonGeoIrrelevant($0) } dict[-606798099] = { return Api.ReportReason.parse_inputReportReasonGeoIrrelevant($0) }
dict[-170010905] = { return Api.ReportReason.parse_inputReportReasonFake($0) }
dict[-247351839] = { return Api.InputEncryptedChat.parse_inputEncryptedChat($0) } dict[-247351839] = { return Api.InputEncryptedChat.parse_inputEncryptedChat($0) }
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) } dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) } dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
@ -659,7 +660,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) } dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) } dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) } dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) }
dict[-1966921727] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsGooglePay($0) } dict[-905587442] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsAndroidPay($0) }
dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) } dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) } dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) } dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }

View File

@ -2278,11 +2278,31 @@ public extension Api {
} }
public enum ChatFull: TypeConstructorDescription { public enum ChatFull: TypeConstructorDescription {
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?)
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?) case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?)
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
if boxed {
buffer.appendInt32(-213431562)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false)
participants.serialize(buffer, true)
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true)
if Int(flags) & Int(1 << 13) != 0 {exportedInvite!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(botInfo!.count))
for item in botInfo! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)}
break
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call): case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call):
if boxed { if boxed {
buffer.appendInt32(2055070967) buffer.appendInt32(2055070967)
@ -2320,38 +2340,71 @@ public extension Api {
serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 21) != 0 {call!.serialize(buffer, true)} if Int(flags) & Int(1 << 21) != 0 {call!.serialize(buffer, true)}
break break
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
if boxed {
buffer.appendInt32(-213431562)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false)
participants.serialize(buffer, true)
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true)
if Int(flags) & Int(1 << 13) != 0 {exportedInvite!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(botInfo!.count))
for item in botInfo! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)}
break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call):
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call)])
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call): case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call)]) return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call)])
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call):
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call)])
} }
} }
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Api.ChatParticipants?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
}
var _5: Api.Photo?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _6: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _7: Api.ExportedChatInvite?
if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
} }
var _8: [Api.BotInfo]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
} }
var _9: Int32?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
var _10: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
var _11: Api.InputGroupCall?
if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11)
}
else {
return nil
}
}
public static func parse_channelFull(_ reader: BufferReader) -> ChatFull? { public static func parse_channelFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()
@ -2458,59 +2511,6 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Api.ChatParticipants?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
}
var _5: Api.Photo?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _6: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _7: Api.ExportedChatInvite?
if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
} }
var _8: [Api.BotInfo]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
} }
var _9: Int32?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
var _10: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
var _11: Api.InputGroupCall?
if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11)
}
else {
return nil
}
}
} }
public enum PollResults: TypeConstructorDescription { public enum PollResults: TypeConstructorDescription {
@ -13931,6 +13931,7 @@ public extension Api {
case inputReportReasonCopyright case inputReportReasonCopyright
case inputReportReasonChildAbuse case inputReportReasonChildAbuse
case inputReportReasonGeoIrrelevant case inputReportReasonGeoIrrelevant
case inputReportReasonFake
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -13975,6 +13976,12 @@ public extension Api {
buffer.appendInt32(-606798099) buffer.appendInt32(-606798099)
} }
break
case .inputReportReasonFake:
if boxed {
buffer.appendInt32(-170010905)
}
break break
} }
} }
@ -13995,6 +14002,8 @@ public extension Api {
return ("inputReportReasonChildAbuse", []) return ("inputReportReasonChildAbuse", [])
case .inputReportReasonGeoIrrelevant: case .inputReportReasonGeoIrrelevant:
return ("inputReportReasonGeoIrrelevant", []) return ("inputReportReasonGeoIrrelevant", [])
case .inputReportReasonFake:
return ("inputReportReasonFake", [])
} }
} }
@ -14027,6 +14036,9 @@ public extension Api {
public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? { public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonGeoIrrelevant return Api.ReportReason.inputReportReasonGeoIrrelevant
} }
public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonFake
}
} }
public enum InputEncryptedChat: TypeConstructorDescription { public enum InputEncryptedChat: TypeConstructorDescription {
@ -19205,7 +19217,7 @@ public extension Api {
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer) case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON) case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON) case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON) case inputPaymentCredentialsAndroidPay(paymentToken: Api.DataJSON, googleTransactionId: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -19229,11 +19241,12 @@ public extension Api {
} }
paymentData.serialize(buffer, true) paymentData.serialize(buffer, true)
break break
case .inputPaymentCredentialsGooglePay(let paymentToken): case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
if boxed { if boxed {
buffer.appendInt32(-1966921727) buffer.appendInt32(-905587442)
} }
paymentToken.serialize(buffer, true) paymentToken.serialize(buffer, true)
serializeString(googleTransactionId, buffer: buffer, boxed: false)
break break
} }
} }
@ -19246,8 +19259,8 @@ public extension Api {
return ("inputPaymentCredentials", [("flags", flags), ("data", data)]) return ("inputPaymentCredentials", [("flags", flags), ("data", data)])
case .inputPaymentCredentialsApplePay(let paymentData): case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData)]) return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData)])
case .inputPaymentCredentialsGooglePay(let paymentToken): case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken)]) return ("inputPaymentCredentialsAndroidPay", [("paymentToken", paymentToken), ("googleTransactionId", googleTransactionId)])
} }
} }
@ -19294,14 +19307,17 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? { public static func parse_inputPaymentCredentialsAndroidPay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON? var _1: Api.DataJSON?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON _1 = Api.parse(reader, signature: signature) as? Api.DataJSON
} }
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil let _c1 = _1 != nil
if _c1 { let _c2 = _2 != nil
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!) if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentialsAndroidPay(paymentToken: _1!, googleTransactionId: _2!)
} }
else { else {
return nil return nil

View File

@ -2012,21 +2012,6 @@ public extension Api {
}) })
} }
public static func deleteChatUser(chatId: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-530505962)
serializeInt32(chatId, buffer: buffer, boxed: false)
userId.serialize(buffer, true)
return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("chatId", chatId), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func createChat(users: [Api.InputUser], title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { public static func createChat(users: [Api.InputUser], title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(164303470) buffer.appendInt32(164303470)
@ -3889,25 +3874,6 @@ public extension Api {
}) })
} }
public static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser?, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ExportedChatInvites>) {
let buffer = Buffer()
buffer.appendInt32(1785900140)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {adminId!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", flags), ("peer", peer), ("adminId", adminId), ("offsetDate", offsetDate), ("offsetLink", offsetLink), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in
let reader = BufferReader(buffer)
var result: Api.messages.ExportedChatInvites?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites
}
return result
})
}
public static func exportChatInvite(flags: Int32, peer: Api.InputPeer, expireDate: Int32?, usageLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.ExportedChatInvite>) { public static func exportChatInvite(flags: Int32, peer: Api.InputPeer, expireDate: Int32?, usageLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.ExportedChatInvite>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(347716823) buffer.appendInt32(347716823)
@ -3943,6 +3909,43 @@ public extension Api {
}) })
} }
public static func getChatInviteImporters(peer: Api.InputPeer, link: String, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ChatInviteImporters>) {
let buffer = Buffer()
buffer.appendInt32(654013065)
peer.serialize(buffer, true)
serializeString(link, buffer: buffer, boxed: false)
serializeInt32(offsetDate, buffer: buffer, boxed: false)
offsetUser.serialize(buffer, true)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("peer", peer), ("link", link), ("offsetDate", offsetDate), ("offsetUser", offsetUser), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in
let reader = BufferReader(buffer)
var result: Api.messages.ChatInviteImporters?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters
}
return result
})
}
public static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser?, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ExportedChatInvites>) {
let buffer = Buffer()
buffer.appendInt32(1785900140)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {adminId!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", flags), ("peer", peer), ("adminId", adminId), ("offsetDate", offsetDate), ("offsetLink", offsetLink), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in
let reader = BufferReader(buffer)
var result: Api.messages.ExportedChatInvites?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites
}
return result
})
}
public static func deleteRevokedExportedChatInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { public static func deleteRevokedExportedChatInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1375999075) buffer.appendInt32(1375999075)
@ -3972,24 +3975,6 @@ public extension Api {
}) })
} }
public static func getChatInviteImporters(peer: Api.InputPeer, link: String, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ChatInviteImporters>) {
let buffer = Buffer()
buffer.appendInt32(654013065)
peer.serialize(buffer, true)
serializeString(link, buffer: buffer, boxed: false)
serializeInt32(offsetDate, buffer: buffer, boxed: false)
offsetUser.serialize(buffer, true)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("peer", peer), ("link", link), ("offsetDate", offsetDate), ("offsetUser", offsetUser), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in
let reader = BufferReader(buffer)
var result: Api.messages.ChatInviteImporters?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters
}
return result
})
}
public static func discardEncryption(flags: Int32, chatId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { public static func discardEncryption(flags: Int32, chatId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-208425312) buffer.appendInt32(-208425312)
@ -4019,6 +4004,22 @@ public extension Api {
}) })
} }
public static func deleteChatUser(flags: Int32, chatId: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-986430054)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(chatId, buffer: buffer, boxed: false)
userId.serialize(buffer, true)
return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("flags", flags), ("chatId", chatId), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func deletePhoneCallHistory(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) { public static func deletePhoneCallHistory(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1828657989) buffer.appendInt32(1828657989)
@ -4033,6 +4034,20 @@ public extension Api {
}) })
} }
public static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HistoryImportParsed>) {
let buffer = Buffer()
buffer.appendInt32(1140726259)
serializeString(importHead, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", importHead)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in
let reader = BufferReader(buffer)
var result: Api.messages.HistoryImportParsed?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed
}
return result
})
}
public static func initHistoryImport(peer: Api.InputPeer, file: Api.InputFile, mediaCount: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HistoryImport>) { public static func initHistoryImport(peer: Api.InputPeer, file: Api.InputFile, mediaCount: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HistoryImport>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(873008187) buffer.appendInt32(873008187)

View File

@ -103,6 +103,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
if (flags & Int32(1 << 24)) != 0 { if (flags & Int32(1 << 24)) != 0 {
channelFlags.insert(.hasActiveVoiceChat) channelFlags.insert(.hasActiveVoiceChat)
} }
if (flags & Int32(1 << 25)) != 0 {
channelFlags.insert(.isFake)
}
let restrictionInfo: PeerAccessRestrictionInfo? let restrictionInfo: PeerAccessRestrictionInfo?
if let restrictionReason = restrictionReason { if let restrictionReason = restrictionReason {

View File

@ -26,7 +26,7 @@ public func leftGroup(account: Account, peerId: PeerId) -> Signal<Void, NoError>
|> take(1) |> take(1)
|> mapToSignal { peer -> Signal<Void, NoError> in |> mapToSignal { peer -> Signal<Void, NoError> in
if let inputUser = apiInputUser(peer) { if let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.messages .deleteChatUser(chatId: peerId.id, userId: inputUser)) return account.network.request(Api.functions.messages.deleteChatUser(flags: 0, chatId: peerId.id, userId: inputUser))
|> retryRequest |> retryRequest
|> mapToSignal { updates -> Signal<Void, NoError> in |> mapToSignal { updates -> Signal<Void, NoError> in
account.stateManager.addUpdates(updates) account.stateManager.addUpdates(updates)

View File

@ -275,7 +275,7 @@ private func removeChat(transaction: Transaction, postbox: Postbox, network: Net
return .complete() return .complete()
} }
} else { } else {
deleteUser = network.request(Api.functions.messages.deleteChatUser(chatId: peer.id.id, userId: Api.InputUser.inputUserSelf)) deleteUser = network.request(Api.functions.messages.deleteChatUser(flags: 0, chatId: peer.id.id, userId: Api.InputUser.inputUserSelf))
|> map { result -> Api.Updates? in |> map { result -> Api.Updates? in
return result return result
} }

View File

@ -75,6 +75,21 @@ public extension Message {
return false return false
} }
var isFake: Bool {
if let author = self.author, author.isFake {
return true
}
if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isFake {
return true
}
for attribute in self.attributes {
if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isFake {
return true
}
}
return false
}
var sourceReference: SourceReferenceMessageAttribute? { var sourceReference: SourceReferenceMessageAttribute? {
for attribute in self.attributes { for attribute in self.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute { if let attribute = attribute as? SourceReferenceMessageAttribute {

View File

@ -122,6 +122,17 @@ public extension Peer {
} }
} }
var isFake: Bool {
switch self {
case let user as TelegramUser:
return user.flags.contains(.isFake)
case let channel as TelegramChannel:
return channel.flags.contains(.isFake)
default:
return false
}
}
var isVerified: Bool { var isVerified: Bool {
switch self { switch self {
case let user as TelegramUser: case let user as TelegramUser:

View File

@ -17,7 +17,7 @@ public func removePeerMember(account: Account, peerId: PeerId, memberId: PeerId)
return account.postbox.transaction { transaction -> Signal<Void, NoError> in return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) { if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
if let group = peer as? TelegramGroup { if let group = peer as? TelegramGroup {
return account.network.request(Api.functions.messages.deleteChatUser(chatId: group.id.id, userId: inputUser)) return account.network.request(Api.functions.messages.deleteChatUser(flags: 0, chatId: group.id.id, userId: inputUser))
|> mapError { error -> Void in |> mapError { error -> Void in
return Void() return Void()
} }

View File

@ -78,6 +78,7 @@ public func reportPeer(account: Account, peerId: PeerId) -> Signal<Void, NoError
public enum ReportReason: Equatable { public enum ReportReason: Equatable {
case spam case spam
case fake
case violence case violence
case porno case porno
case childAbuse case childAbuse
@ -91,6 +92,8 @@ private extension ReportReason {
switch self { switch self {
case .spam: case .spam:
return .inputReportReasonSpam return .inputReportReasonSpam
case .fake:
return .inputReportReasonFake
case .violence: case .violence:
return .inputReportReasonViolence return .inputReportReasonViolence
case .porno: case .porno:

View File

@ -53,6 +53,9 @@ extension TelegramUser {
if (flags & (1 << 24)) != 0 { if (flags & (1 << 24)) != 0 {
userFlags.insert(.isScam) userFlags.insert(.isScam)
} }
if (flags & (1 << 26)) != 0 {
userFlags.insert(.isFake)
}
var botInfo: BotUserInfo? var botInfo: BotUserInfo?
if (flags & (1 << 14)) != 0 { if (flags & (1 << 14)) != 0 {
@ -96,6 +99,9 @@ extension TelegramUser {
if (flags & (1 << 24)) != 0 { if (flags & (1 << 24)) != 0 {
userFlags.insert(.isScam) userFlags.insert(.isScam)
} }
if (flags & Int32(1 << 26)) != 0 {
userFlags.insert(.isFake)
}
var botInfo: BotUserInfo? var botInfo: BotUserInfo?
if (flags & (1 << 14)) != 0 { if (flags & (1 << 14)) != 0 {
@ -156,6 +162,9 @@ extension TelegramUser {
if rhs.flags.contains(.isScam) { if rhs.flags.contains(.isScam) {
userFlags.insert(.isScam) userFlags.insert(.isScam)
} }
if rhs.flags.contains(.isFake) {
userFlags.insert(.isFake)
}
let botInfo: BotUserInfo? = rhs.botInfo let botInfo: BotUserInfo? = rhs.botInfo

View File

@ -77,6 +77,9 @@ public enum PresentationResourceKey: Int32 {
case chatListScamRegularIcon case chatListScamRegularIcon
case chatListScamOutgoingIcon case chatListScamOutgoingIcon
case chatListScamServiceIcon case chatListScamServiceIcon
case chatListFakeRegularIcon
case chatListFakeOutgoingIcon
case chatListFakeServiceIcon
case chatListSecretIcon case chatListSecretIcon
case chatListRecentStatusOnlineIcon case chatListRecentStatusOnlineIcon
case chatListRecentStatusOnlineHighlightedIcon case chatListRecentStatusOnlineHighlightedIcon

View File

@ -248,6 +248,42 @@ public struct PresentationResourcesChatList {
}) })
} }
public static func fakeIcon(_ theme: PresentationTheme, type: ScamIconType) -> UIImage? {
let key: PresentationResourceKey
let color: UIColor
switch type {
case .regular:
key = PresentationResourceKey.chatListFakeRegularIcon
color = theme.chat.message.incoming.scamColor
case .outgoing:
key = PresentationResourceKey.chatListFakeOutgoingIcon
color = theme.chat.message.outgoing.scamColor
case .service:
key = PresentationResourceKey.chatListFakeServiceIcon
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
}
return theme.image(key.rawValue, { theme in
return generateImage(CGSize(width: 37.0, height: 16.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(color.cgColor)
context.setStrokeColor(color.cgColor)
context.setLineWidth(1.0)
context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: 2.0).cgPath)
context.strokePath()
let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
let titleString = NSAttributedString(string: "FAKE", font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
CTFrameDraw(titleFrame, context)
})
})
}
public static func secretIcon(_ theme: PresentationTheme) -> UIImage? { public static func secretIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListSecretIcon.rawValue, { theme in return theme.image(PresentationResourceKey.chatListSecretIcon.rawValue, { theme in
return generateImage(CGSize(width: 9.0, height: 12.0), rotatedContext: { size, context in return generateImage(CGSize(width: 9.0, height: 12.0), rotatedContext: { size, context in

View File

@ -1430,9 +1430,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var isScam = effectiveAuthor.isScam var isScam = effectiveAuthor.isScam
if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId { if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId {
isScam = false
} else if effectiveAuthor.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing)
} else if effectiveAuthor.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing)
} }
currentCredibilityIconImage = isScam ? PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing) : nil
} }
if let rawAuthorNameColor = authorNameColor { if let rawAuthorNameColor = authorNameColor {
var dimColors = false var dimColors = false

View File

@ -193,7 +193,14 @@ class ChatMessageForwardInfoNode: ASDisplayNode {
} }
} }
if peer.isScam { if peer.isFake {
switch type {
case let .bubble(incoming):
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)
case .standalone:
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, type: .service)
}
} else if peer.isScam {
switch type { switch type {
case let .bubble(incoming): case let .bubble(incoming):
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)

View File

@ -937,7 +937,11 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
})) }))
} }
if let cachedData = data.cachedData as? CachedUserData { if let cachedData = data.cachedData as? CachedUserData {
if user.isScam { if user.isFake {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_FakeBotWarning : presentationData.strings.UserInfo_FakeUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: {
interaction.requestLayout()
}))
} else if user.isScam {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: {
interaction.requestLayout() interaction.requestLayout()
})) }))
@ -1025,12 +1029,27 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
})) }))
} }
if let cachedData = data.cachedData as? CachedChannelData { if let cachedData = data.cachedData as? CachedChannelData {
if channel.isScam { let aboutText: String?
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, requestLayout: { if channel.isFake {
interaction.requestLayout() if case .broadcast = channel.info {
})) aboutText = presentationData.strings.ChannelInfo_FakeChannelWarning
} else {
aboutText = presentationData.strings.GroupInfo_FakeGroupWarning
}
} else if channel.isScam {
if case .broadcast = channel.info {
aboutText = presentationData.strings.ChannelInfo_ScamChannelWarning
} else {
aboutText = presentationData.strings.GroupInfo_ScamGroupWarning
}
} else if let about = cachedData.about, !about.isEmpty { } else if let about = cachedData.about, !about.isEmpty {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { aboutText = about
} else {
aboutText = nil
}
if let aboutText = aboutText {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout() interaction.requestLayout()
})) }))
} }
@ -1061,12 +1080,19 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
} }
} else if let group = data.peer as? TelegramGroup { } else if let group = data.peer as? TelegramGroup {
if let cachedData = data.cachedData as? CachedGroupData { if let cachedData = data.cachedData as? CachedGroupData {
if group.isScam { let aboutText: String?
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, requestLayout: { if group.isFake {
interaction.requestLayout() aboutText = presentationData.strings.GroupInfo_FakeGroupWarning
})) } else if group.isScam {
aboutText = presentationData.strings.GroupInfo_ScamGroupWarning
} else if let about = cachedData.about, !about.isEmpty { } else if let about = cachedData.about, !about.isEmpty {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { aboutText = about
} else {
aboutText = nil
}
if let aboutText = aboutText {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout() interaction.requestLayout()
})) }))
} }
@ -1266,6 +1292,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
interaction.editingOpenPublicLinkSetup() interaction.editingOpenPublicLinkSetup()
})) }))
}
if channel.hasPermission(.inviteMembers) {
let invitesText: String let invitesText: String
if let count = data.invitations?.count, count > 0 { if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)" invitesText = "\(count)"
@ -1276,7 +1305,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: { items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
interaction.editingOpenInviteLinksSetup() interaction.editingOpenInviteLinksSetup()
})) }))
}
if cachedData.flags.contains(.canChangeUsername) {
if let linkedDiscussionPeer = data.linkedDiscussionPeer { if let linkedDiscussionPeer = data.linkedDiscussionPeer {
let peerTitle: String let peerTitle: String
if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty { if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty {
@ -4426,7 +4457,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if canCreateInviteLink { if canCreateInviteLink {
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: { options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
createInviteLinkImpl?() createInviteLinkImpl?()
})) }, clearHighlightAutomatically: true))
} }
let contactsController: ViewController let contactsController: ViewController