Invite Links Improvements

This commit is contained in:
Ilya Laktyushin 2021-01-21 16:28:51 +03:00
parent ebb613fb9d
commit d6777d68eb
30 changed files with 4595 additions and 4330 deletions

View File

@ -5821,6 +5821,7 @@ Sorry for the inconvenience.";
"InviteLink.Title" = "Invite Links";
"InviteLink.PermanentLink" = "Permanent Link";
"InviteLink.PublicLink" = "Public Link";
"InviteLink.Share" = "Share Link";
"InviteLink.PeopleJoinedNone" = "no one joined yet";
"InviteLink.PeopleJoined_1" = "%@ people joined";
@ -5891,9 +5892,18 @@ Sorry for the inconvenience.";
"InviteLink.ExpiresIn" = "expires in %@";
"InviteLink.InviteLinkCopiedText" = "Invite link copied to clipboard";
"Conversation.ChecksTooltip.Delivered" = "Delivered";
"Conversation.ChecksTooltip.Read" = "Read";
"DialogList.MultipleTypingPair" = "%@ and %@ are typing";
"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";

View File

@ -383,11 +383,13 @@ public struct ContactListAdditionalOption: Equatable {
public let title: String
public let icon: ContactListActionItemIcon
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.icon = icon
self.action = action
self.clearHighlightAutomatically = clearHighlightAutomatically
}
public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool {

View File

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

View File

@ -175,7 +175,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
interaction.authorize()
})
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):
var status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer

View File

@ -56,7 +56,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
case usageHeader(PresentationTheme, String)
case usagePicker(PresentationTheme, InviteLinkUsageLimit)
case usageCustomPicker(PresentationTheme, Int32?, Bool)
case usageCustomPicker(PresentationTheme, Int32?, Bool, Bool)
case usageInfo(PresentationTheme, String)
case revoke(PresentationTheme, String)
@ -141,8 +141,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
} else {
return false
}
case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused):
if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused {
case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused, lhsCustomValue):
if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused, rhsCustomValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused, lhsCustomValue == rhsCustomValue {
return true
} else {
return false
@ -221,9 +221,14 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
return updatedState
})
})
case let .usageCustomPicker(theme, value, focused):
let text = value.flatMap { String($0) } ?? (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, tag: nil, sectionId: self.section, textUpdated: { updatedText in
case let .usageCustomPicker(theme, value, focused, customValue):
let text: String
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 {
return
}
@ -284,7 +289,12 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased()))
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))
@ -401,7 +411,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
expireDate = currentTime + value
} else {
expireDate = nil
expireDate = 0
}
let usageLimit = state.usage.value

View File

@ -20,6 +20,7 @@ import ShareController
import OverlayStatusController
import PresentationDataUtils
import DirectionalPanGesture
import UndoUI
class InviteLinkInviteInteraction {
let context: AccountContext
@ -139,7 +140,7 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
case let .header(theme, title, text):
return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text)
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)
}, shareAction: {
interaction.shareLink(invite)
@ -345,8 +346,9 @@ public final class InviteLinkInviteController: ViewController {
if let invite = invite {
UIPasteboard.general.string = invite.link
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)
}, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link
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
let shareController = ShareController(context: context, subject: .url(invite.link))
self?.controller?.present(shareController, in: .window(.root))
@ -626,6 +629,10 @@ public final class InviteLinkInviteController: ViewController {
private var panGestureArguments: CGFloat?
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer is DirectionalPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
let contentOffset = self.listNode.visibleContentOffset()
switch recognizer.state {
@ -633,10 +640,20 @@ public final class InviteLinkInviteController: ViewController {
self.panGestureArguments = 0.0
case .changed:
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 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 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
} else {
translation = currentPanOffset
translation = 0.0
recognizer.setTranslation(CGPoint(), in: self.contentNode.view)
}
self.panGestureArguments = translation

View File

@ -19,6 +19,7 @@ import ContextUI
import TelegramStringFormatting
import ItemListPeerActionItem
import ShareController
import UndoUI
private final class InviteLinkListControllerArguments {
let context: AccountContext
@ -55,7 +56,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case header(PresentationTheme, String)
case mainLinkHeader(PresentationTheme, String)
case mainLink(PresentationTheme, ExportedInvitation?, [Peer])
case mainLink(PresentationTheme, ExportedInvitation?, [Peer], Int32, Bool)
case linksHeader(PresentationTheme, String)
case linksCreate(PresentationTheme, String)
@ -117,8 +118,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else {
return false
}
case let .mainLink(lhsTheme, lhsInvite, lhsPeers):
if case let .mainLink(rhsTheme, rhsInvite, rhsPeers) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, arePeerArraysEqual(lhsPeers, rhsPeers) {
case let .mainLink(lhsTheme, lhsInvite, lhsPeers, lhsImportersCount, lhsIsPublic):
if case let .mainLink(rhsTheme, rhsInvite, rhsPeers, rhsImportersCount, rhsIsPublic) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, arePeerArraysEqual(lhsPeers, rhsPeers), lhsImportersCount == rhsImportersCount, lhsIsPublic == rhsIsPublic {
return true
} else {
return false
@ -179,8 +180,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return InviteLinkHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .mainLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .mainLink(_, invite, peers):
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: peers, displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
case let .mainLink(_, invite, peers, importersCount, isPublic):
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 {
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] = []
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
entries.append(.mainLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased()))
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
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation {
mainInvite = invite
@ -241,7 +246,19 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
} else {
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(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
@ -320,10 +337,11 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
pushControllerImpl?(controller)
}, copyLink: { invite in
UIPasteboard.general.string = invite.link
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
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else {
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?(), let invite = invite else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -334,11 +352,10 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
}, action: { _, f in
f(.dismissWithoutContent)
if let invite = invite {
UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil)
}
UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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
@ -346,56 +363,56 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
}, action: { _, f in
f(.dismissWithoutContent)
if let invite = invite {
let controller = InviteLinkQRCodeController(context: context, invite: invite)
presentControllerImpl?(controller, nil)
}
let controller = InviteLinkQRCodeController(context: context, invite: invite)
presentControllerImpl?(controller, nil)
})))
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)
}, action: { _, f in
f(.dismissWithoutContent)
if invite.adminId.toInt64() != 0 {
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)
}, action: { _, f in
f(.dismissWithoutContent)
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
var revoke = false
updateState { state in
if !state.revokingPrivateLink {
revoke = true
var updatedState = state
updatedState.revokingPrivateLink = true
return updatedState
} else {
return state
}
}
if revoke {
revokeLinkDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
updateState { state in
var revoke = false
updateState { state in
if !state.revokingPrivateLink {
revoke = true
var updatedState = state
updatedState.revokingPrivateLink = false
updatedState.revokingPrivateLink = true
return updatedState
} else {
return state
}
}
if revoke {
revokeLinkDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
updateState { state in
var updatedState = state
updatedState.revokingPrivateLink = false
return updatedState
}
invitesContext.reload()
revokedInvitesContext.reload()
}))
}
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})))
invitesContext.reload()
revokedInvitesContext.reload()
}))
}
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
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)
presentInGlobalOverlayImpl?(contextController)
@ -426,11 +443,21 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
f(.dismissWithoutContent)
UIPasteboard.general.string = invite.link
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 {
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
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
@ -564,11 +591,19 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
importersState.set(context.state |> map(Optional.init))
}
let previousRevokedInvites = Atomic<PeerExportedInvitationsState?>(value: nil)
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state)
|> deliverOnMainQueue
|> 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 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))
}

View File

@ -20,6 +20,7 @@ import ShareController
import OverlayStatusController
import PresentationDataUtils
import DirectionalPanGesture
import UndoUI
class InviteLinkViewInteraction {
let context: AccountContext
@ -171,7 +172,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
case let .link(_, invite):
let buttonColor = color(for: 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)
}, shareAction: {
interaction.shareLink(invite)
@ -393,8 +394,9 @@ public final class InviteLinkViewController: ViewController {
}
}, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link
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
let shareController = ShareController(context: context, subject: .url(invite.link))
self?.controller?.present(shareController, in: .window(.root))
@ -412,8 +414,9 @@ public final class InviteLinkViewController: ViewController {
f(.dismissWithoutContent)
UIPasteboard.general.string = invite.link
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 {
@ -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)
self?.controller?.presentInGlobalOverlay(contextController)
})
@ -717,12 +722,20 @@ public final class InviteLinkViewController: ViewController {
self.panGestureArguments = 0.0
case .changed:
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 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 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
} else {
translation = currentPanOffset
translation = 0.0
recognizer.setTranslation(CGPoint(), in: self.contentNode.view)
}
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 let selectionNode: HighlightTrackingButtonNode
private let wrapperNode: ASDisplayNode
private let backgroundNode: ASImageNode
private let backgroundNode: ASDisplayNode
private let backgroundGradientLayer: CAGradientLayer
private let iconNode: ASImageNode
private var timerNode: TimerNode?
@ -122,11 +123,19 @@ private class ItemNode: ASDisplayNode {
self.selectionNode = HighlightTrackingButtonNode()
self.wrapperNode = ASDisplayNode()
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode = ASDisplayNode()
self.backgroundNode.clipsToBounds = true
self.backgroundNode.cornerRadius = 15.0
if #available(iOS 13.0, *) {
self.backgroundNode.layer.cornerCurve = .continuous
}
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.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
@ -256,10 +265,10 @@ private class ItemNode: ASDisplayNode {
snapshotView?.removeFromSuperview()
})
}
self.backgroundNode.image = generateBackgroundImage(colors: colors)
self.backgroundGradientLayer.colors = colors as? [Any]
}
} else {
self.backgroundNode.image = generateBackgroundImage(colors: colors)
self.backgroundGradientLayer.colors = colors as? [Any]
}
let secondaryTextColor = color.colors.text
@ -329,7 +338,7 @@ private class ItemNode: ASDisplayNode {
self.timerNode = 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 {
subtitleText = presentationData.strings.InviteLink_TapToCopy
}
@ -359,6 +368,7 @@ private class ItemNode: ASDisplayNode {
transition.updateFrame(node: self.wrapperNode, frame: backgroundFrame)
transition.updateFrame(node: self.backgroundNode, 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 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)
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?

View File

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

View File

@ -29,6 +29,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
let context: AccountContext
let presentationData: ItemListPresentationData
let invite: ExportedInvitation?
let count: Int32
let peers: [Peer]
let displayButton: Bool
let displayImporters: Bool
@ -45,6 +46,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
context: AccountContext,
presentationData: ItemListPresentationData,
invite: ExportedInvitation?,
count: Int32,
peers: [Peer],
displayButton: Bool,
displayImporters: Bool,
@ -60,6 +62,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
self.context = context
self.presentationData = presentationData
self.invite = invite
self.count = count
self.peers = peers
self.displayButton = displayButton
self.displayImporters = displayImporters
@ -290,18 +293,13 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
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 subtitleColor: UIColor
if let count = item.invite?.count {
if count > 0 {
subtitle = item.presentationData.strings.InviteLink_PeopleJoined(count)
subtitleColor = item.presentationData.theme.list.itemAccentColor
} else {
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
}
if item.count > 0 {
subtitle = item.presentationData.strings.InviteLink_PeopleJoined(item.count)
subtitleColor = item.presentationData.theme.list.itemAccentColor
} else {
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor

View File

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

View File

@ -47,6 +47,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let maxLength: Int
let enabled: Bool
let selectAllOnFocus: Bool
let secondaryStyle: Bool
public let sectionId: ItemListSectionId
let action: () -> Void
let textUpdated: (String) -> Void
@ -56,7 +57,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let cleared: (() -> Void)?
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.title = title
self.text = text
@ -69,6 +70,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
self.maxLength = maxLength
self.enabled = enabled
self.selectAllOnFocus = selectAllOnFocus
self.secondaryStyle = secondaryStyle
self.tag = tag
self.sectionId = sectionId
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.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.tintColor = item.presentationData.theme.list.itemAccentColor
self.textNode.textField.accessibilityHint = item.placeholder
@ -218,6 +220,11 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
fontUpdated = true
}
var styleUpdated = false
if currentItem?.secondaryStyle != item.secondaryStyle {
styleUpdated = true
}
let leftInset: CGFloat = 16.0 + params.leftInset
var rightInset: CGFloat = 16.0 + params.rightInset
@ -252,13 +259,17 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
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.tintColor = item.presentationData.theme.list.itemAccentColor
}
if fontUpdated {
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()

View File

@ -293,7 +293,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case let .privateLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
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 {
arguments.copyLink(invite)
}

View File

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

View File

@ -652,7 +652,15 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
} else {
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
if let _ = user.botInfo {
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 hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
public static let isFake = TelegramChannelFlags(rawValue: 1 << 6)
}
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 isSupport = UserInfoFlags(rawValue: (1 << 1))
public static let isScam = UserInfoFlags(rawValue: (1 << 2))
public static let isFake = UserInfoFlags(rawValue: (1 << 3))
}
public struct BotUserInfoFlags: OptionSet {

View File

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

View File

@ -75,6 +75,21 @@ public extension Message {
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? {
for attribute in self.attributes {
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 {
switch self {
case let user as TelegramUser:

View File

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

View File

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

View File

@ -77,6 +77,9 @@ public enum PresentationResourceKey: Int32 {
case chatListScamRegularIcon
case chatListScamOutgoingIcon
case chatListScamServiceIcon
case chatListFakeRegularIcon
case chatListFakeOutgoingIcon
case chatListFakeServiceIcon
case chatListSecretIcon
case chatListRecentStatusOnlineIcon
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? {
return theme.image(PresentationResourceKey.chatListSecretIcon.rawValue, { theme in
return generateImage(CGSize(width: 9.0, height: 12.0), rotatedContext: { size, context in

View File

@ -1426,9 +1426,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var isScam = effectiveAuthor.isScam
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 {
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 {
case let .bubble(incoming):
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 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: {
interaction.requestLayout()
}))
@ -1025,12 +1029,27 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}))
}
if let cachedData = data.cachedData as? CachedChannelData {
if channel.isScam {
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: {
interaction.requestLayout()
}))
let aboutText: String?
if channel.isFake {
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 {
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()
}))
}
@ -1061,12 +1080,19 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}
} else if let group = data.peer as? TelegramGroup {
if let cachedData = data.cachedData as? CachedGroupData {
if group.isScam {
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: {
interaction.requestLayout()
}))
let aboutText: String?
if group.isFake {
aboutText = presentationData.strings.GroupInfo_FakeGroupWarning
} else if group.isScam {
aboutText = presentationData.strings.GroupInfo_ScamGroupWarning
} 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()
}))
}
@ -1266,6 +1292,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
interaction.editingOpenPublicLinkSetup()
}))
}
if channel.hasPermission(.inviteMembers) {
let invitesText: String
if let count = data.invitations?.count, count > 0 {
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: {
interaction.editingOpenInviteLinksSetup()
}))
}
if cachedData.flags.contains(.canChangeUsername) {
if let linkedDiscussionPeer = data.linkedDiscussionPeer {
let peerTitle: String
if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty {
@ -4425,7 +4456,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if canCreateInviteLink {
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
createInviteLinkImpl?()
}))
}, clearHighlightAutomatically: true))
}
let contactsController: ViewController