mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Invite Links Improvements
This commit is contained in:
parent
ebb613fb9d
commit
d6777d68eb
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
Binary file not shown.
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user