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.Title" = "Invite Links";
|
||||||
"InviteLink.PermanentLink" = "Permanent Link";
|
"InviteLink.PermanentLink" = "Permanent Link";
|
||||||
|
"InviteLink.PublicLink" = "Public Link";
|
||||||
"InviteLink.Share" = "Share Link";
|
"InviteLink.Share" = "Share Link";
|
||||||
"InviteLink.PeopleJoinedNone" = "no one joined yet";
|
"InviteLink.PeopleJoinedNone" = "no one joined yet";
|
||||||
"InviteLink.PeopleJoined_1" = "%@ people joined";
|
"InviteLink.PeopleJoined_1" = "%@ people joined";
|
||||||
@ -5891,9 +5892,18 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"InviteLink.ExpiresIn" = "expires in %@";
|
"InviteLink.ExpiresIn" = "expires in %@";
|
||||||
|
|
||||||
|
"InviteLink.InviteLinkCopiedText" = "Invite link copied to clipboard";
|
||||||
|
|
||||||
"Conversation.ChecksTooltip.Delivered" = "Delivered";
|
"Conversation.ChecksTooltip.Delivered" = "Delivered";
|
||||||
"Conversation.ChecksTooltip.Read" = "Read";
|
"Conversation.ChecksTooltip.Read" = "Read";
|
||||||
|
|
||||||
"DialogList.MultipleTypingPair" = "%@ and %@ are typing";
|
"DialogList.MultipleTypingPair" = "%@ and %@ are typing";
|
||||||
|
|
||||||
"Common.Save" = "Save";
|
"Common.Save" = "Save";
|
||||||
|
|
||||||
|
"UserInfo.FakeUserWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
|
||||||
|
"UserInfo.FakeBotWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
|
||||||
|
"GroupInfo.FakeGroupWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
|
||||||
|
"ChannelInfo.FakeChannelWarning" = "⚠️ Warning: Many users reported that this account impersonates a famous person or organization.";
|
||||||
|
|
||||||
|
"ReportPeer.ReasonFake" = "Fake Account";
|
||||||
|
@ -383,11 +383,13 @@ public struct ContactListAdditionalOption: Equatable {
|
|||||||
public let title: String
|
public let title: String
|
||||||
public let icon: ContactListActionItemIcon
|
public let icon: ContactListActionItemIcon
|
||||||
public let action: () -> Void
|
public let action: () -> Void
|
||||||
|
public let clearHighlightAutomatically: Bool
|
||||||
|
|
||||||
public init(title: String, icon: ContactListActionItemIcon, action: @escaping () -> Void) {
|
public init(title: String, icon: ContactListActionItemIcon, action: @escaping () -> Void, clearHighlightAutomatically: Bool = false) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.action = action
|
self.action = action
|
||||||
|
self.clearHighlightAutomatically = clearHighlightAutomatically
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool {
|
public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool {
|
||||||
|
@ -1258,6 +1258,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if peer.isScam {
|
if peer.isScam {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
||||||
credibilityIconOffset = 2.0
|
credibilityIconOffset = 2.0
|
||||||
|
} else if peer.isFake {
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
|
||||||
|
credibilityIconOffset = 2.0
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
credibilityIconOffset = 3.0
|
credibilityIconOffset = 3.0
|
||||||
@ -1270,6 +1273,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if peer.isScam {
|
if peer.isScam {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
||||||
credibilityIconOffset = 2.0
|
credibilityIconOffset = 2.0
|
||||||
|
} else if peer.isFake {
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
|
||||||
|
credibilityIconOffset = 2.0
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
credibilityIconOffset = 3.0
|
credibilityIconOffset = 3.0
|
||||||
|
@ -175,7 +175,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||||||
interaction.authorize()
|
interaction.authorize()
|
||||||
})
|
})
|
||||||
case let .option(_, option, header, _, _):
|
case let .option(_, option, header, _, _):
|
||||||
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: false, header: header, action: option.action)
|
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action)
|
||||||
case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, enabled):
|
case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, enabled):
|
||||||
var status: ContactsPeerItemStatus
|
var status: ContactsPeerItemStatus
|
||||||
let itemPeer: ContactsPeerItemPeer
|
let itemPeer: ContactsPeerItemPeer
|
||||||
|
@ -56,7 +56,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
case usageHeader(PresentationTheme, String)
|
case usageHeader(PresentationTheme, String)
|
||||||
case usagePicker(PresentationTheme, InviteLinkUsageLimit)
|
case usagePicker(PresentationTheme, InviteLinkUsageLimit)
|
||||||
case usageCustomPicker(PresentationTheme, Int32?, Bool)
|
case usageCustomPicker(PresentationTheme, Int32?, Bool, Bool)
|
||||||
case usageInfo(PresentationTheme, String)
|
case usageInfo(PresentationTheme, String)
|
||||||
|
|
||||||
case revoke(PresentationTheme, String)
|
case revoke(PresentationTheme, String)
|
||||||
@ -141,8 +141,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused):
|
case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused, lhsCustomValue):
|
||||||
if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused {
|
if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused, rhsCustomValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused, lhsCustomValue == rhsCustomValue {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -221,9 +221,14 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
|||||||
return updatedState
|
return updatedState
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
case let .usageCustomPicker(theme, value, focused):
|
case let .usageCustomPicker(theme, value, focused, customValue):
|
||||||
let text = value.flatMap { String($0) } ?? (focused ? "" : presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsersUnlimited)
|
let text: String
|
||||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, selectAllOnFocus: true, tag: nil, sectionId: self.section, textUpdated: { updatedText in
|
if let value = value, value != 0 {
|
||||||
|
text = String(value)
|
||||||
|
} else {
|
||||||
|
text = focused ? "" : presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsersUnlimited
|
||||||
|
}
|
||||||
|
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, selectAllOnFocus: true, secondaryStyle: !customValue, tag: nil, sectionId: self.section, textUpdated: { updatedText in
|
||||||
guard !updatedText.isEmpty else {
|
guard !updatedText.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -284,7 +289,12 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
|
|||||||
|
|
||||||
entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased()))
|
entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased()))
|
||||||
entries.append(.usagePicker(presentationData.theme, state.usage))
|
entries.append(.usagePicker(presentationData.theme, state.usage))
|
||||||
entries.append(.usageCustomPicker(presentationData.theme, state.usage.value, state.pickingUsageLimit))
|
|
||||||
|
var customValue = false
|
||||||
|
if case .custom = state.usage {
|
||||||
|
customValue = true
|
||||||
|
}
|
||||||
|
entries.append(.usageCustomPicker(presentationData.theme, state.usage.value, state.pickingUsageLimit, customValue))
|
||||||
|
|
||||||
entries.append(.usageInfo(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimitInfo))
|
entries.append(.usageInfo(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimitInfo))
|
||||||
|
|
||||||
@ -401,7 +411,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
|
|||||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
expireDate = currentTime + value
|
expireDate = currentTime + value
|
||||||
} else {
|
} else {
|
||||||
expireDate = nil
|
expireDate = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
let usageLimit = state.usage.value
|
let usageLimit = state.usage.value
|
||||||
|
@ -20,6 +20,7 @@ import ShareController
|
|||||||
import OverlayStatusController
|
import OverlayStatusController
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import DirectionalPanGesture
|
import DirectionalPanGesture
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
class InviteLinkInviteInteraction {
|
class InviteLinkInviteInteraction {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -139,7 +140,7 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
case let .header(theme, title, text):
|
case let .header(theme, title, text):
|
||||||
return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text)
|
return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text)
|
||||||
case let .mainLink(_, invite):
|
case let .mainLink(_, invite):
|
||||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
|
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
|
||||||
interaction.copyLink(invite)
|
interaction.copyLink(invite)
|
||||||
}, shareAction: {
|
}, shareAction: {
|
||||||
interaction.shareLink(invite)
|
interaction.shareLink(invite)
|
||||||
@ -345,8 +346,9 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
if let invite = invite {
|
if let invite = invite {
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root))
|
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
@ -390,8 +392,9 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self?.controller?.presentInGlobalOverlay(contextController)
|
self?.controller?.presentInGlobalOverlay(contextController)
|
||||||
}, copyLink: { [weak self] invite in
|
}, copyLink: { [weak self] invite in
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root))
|
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
}, shareLink: { [weak self] invite in
|
}, shareLink: { [weak self] invite in
|
||||||
let shareController = ShareController(context: context, subject: .url(invite.link))
|
let shareController = ShareController(context: context, subject: .url(invite.link))
|
||||||
self?.controller?.present(shareController, in: .window(.root))
|
self?.controller?.present(shareController, in: .window(.root))
|
||||||
@ -625,6 +628,10 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var panGestureArguments: CGFloat?
|
private var panGestureArguments: CGFloat?
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
return gestureRecognizer is DirectionalPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer
|
||||||
|
}
|
||||||
|
|
||||||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||||
let contentOffset = self.listNode.visibleContentOffset()
|
let contentOffset = self.listNode.visibleContentOffset()
|
||||||
@ -633,10 +640,20 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self.panGestureArguments = 0.0
|
self.panGestureArguments = 0.0
|
||||||
case .changed:
|
case .changed:
|
||||||
var translation = recognizer.translation(in: self.contentNode.view).y
|
var translation = recognizer.translation(in: self.contentNode.view).y
|
||||||
if let currentPanOffset = self.panGestureArguments {
|
if let currentOffset = self.panGestureArguments {
|
||||||
if case let .known(value) = contentOffset, value <= 0.5 {
|
if case let .known(value) = contentOffset, value <= 0.5 {
|
||||||
|
if currentOffset > 0.0 {
|
||||||
|
let translation = self.listNode.scroller.panGestureRecognizer.translation(in: self.listNode.scroller)
|
||||||
|
if translation.y > 10.0 {
|
||||||
|
self.listNode.scroller.panGestureRecognizer.isEnabled = false
|
||||||
|
self.listNode.scroller.panGestureRecognizer.isEnabled = true
|
||||||
|
} else {
|
||||||
|
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
translation = currentPanOffset
|
translation = 0.0
|
||||||
|
recognizer.setTranslation(CGPoint(), in: self.contentNode.view)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.panGestureArguments = translation
|
self.panGestureArguments = translation
|
||||||
|
@ -19,6 +19,7 @@ import ContextUI
|
|||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import ItemListPeerActionItem
|
import ItemListPeerActionItem
|
||||||
import ShareController
|
import ShareController
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
private final class InviteLinkListControllerArguments {
|
private final class InviteLinkListControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -55,7 +56,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
case header(PresentationTheme, String)
|
case header(PresentationTheme, String)
|
||||||
|
|
||||||
case mainLinkHeader(PresentationTheme, String)
|
case mainLinkHeader(PresentationTheme, String)
|
||||||
case mainLink(PresentationTheme, ExportedInvitation?, [Peer])
|
case mainLink(PresentationTheme, ExportedInvitation?, [Peer], Int32, Bool)
|
||||||
|
|
||||||
case linksHeader(PresentationTheme, String)
|
case linksHeader(PresentationTheme, String)
|
||||||
case linksCreate(PresentationTheme, String)
|
case linksCreate(PresentationTheme, String)
|
||||||
@ -117,8 +118,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .mainLink(lhsTheme, lhsInvite, lhsPeers):
|
case let .mainLink(lhsTheme, lhsInvite, lhsPeers, lhsImportersCount, lhsIsPublic):
|
||||||
if case let .mainLink(rhsTheme, rhsInvite, rhsPeers) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, arePeerArraysEqual(lhsPeers, rhsPeers) {
|
if case let .mainLink(rhsTheme, rhsInvite, rhsPeers, rhsImportersCount, rhsIsPublic) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, arePeerArraysEqual(lhsPeers, rhsPeers), lhsImportersCount == rhsImportersCount, lhsIsPublic == rhsIsPublic {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -179,8 +180,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
return InviteLinkHeaderItem(theme: theme, text: text, sectionId: self.section)
|
return InviteLinkHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
case let .mainLinkHeader(_, text):
|
case let .mainLinkHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .mainLink(_, invite, peers):
|
case let .mainLink(_, invite, peers, importersCount, isPublic):
|
||||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: peers, displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, displayImporters: !isPublic, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||||
if let invite = invite {
|
if let invite = invite {
|
||||||
arguments.copyLink(invite)
|
arguments.copyLink(invite)
|
||||||
}
|
}
|
||||||
@ -225,14 +226,18 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, mainPeers: [Peer]) -> [InviteLinksListEntry] {
|
private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?) -> [InviteLinksListEntry] {
|
||||||
var entries: [InviteLinksListEntry] = []
|
var entries: [InviteLinksListEntry] = []
|
||||||
|
|
||||||
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
|
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
|
||||||
entries.append(.mainLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased()))
|
|
||||||
|
|
||||||
let mainInvite: ExportedInvitation?
|
let mainInvite: ExportedInvitation?
|
||||||
if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) {
|
var isPublic = false
|
||||||
|
if let peer = peerViewMainPeer(view), let address = peer.addressName, !address.isEmpty {
|
||||||
|
mainInvite = ExportedInvitation(link: "t.me/\(address)", isPermanent: true, isRevoked: false, adminId: PeerId(0), date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil)
|
||||||
|
isPublic = true
|
||||||
|
} else if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) {
|
||||||
mainInvite = invite
|
mainInvite = invite
|
||||||
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation {
|
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation {
|
||||||
mainInvite = invite
|
mainInvite = invite
|
||||||
@ -241,7 +246,19 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
|||||||
} else {
|
} else {
|
||||||
mainInvite = nil
|
mainInvite = nil
|
||||||
}
|
}
|
||||||
entries.append(.mainLink(presentationData.theme, mainInvite, mainPeers))
|
|
||||||
|
entries.append(.mainLinkHeader(presentationData.theme, isPublic ? presentationData.strings.InviteLink_PublicLink.uppercased() : presentationData.strings.InviteLink_PermanentLink.uppercased()))
|
||||||
|
|
||||||
|
let importersCount: Int32
|
||||||
|
if let count = importers?.count {
|
||||||
|
importersCount = count
|
||||||
|
} else if let count = mainInvite?.count {
|
||||||
|
importersCount = count
|
||||||
|
} else {
|
||||||
|
importersCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.mainLink(presentationData.theme, mainInvite, importers?.importers.prefix(3).compactMap { $0.peer.peer } ?? [], importersCount, isPublic))
|
||||||
|
|
||||||
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
|
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
|
||||||
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
|
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
|
||||||
@ -320,10 +337,11 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
pushControllerImpl?(controller)
|
pushControllerImpl?(controller)
|
||||||
}, copyLink: { invite in
|
}, copyLink: { invite in
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil)
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
}, mainLinkContextAction: { invite, node, gesture in
|
}, mainLinkContextAction: { invite, node, gesture in
|
||||||
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else {
|
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?(), let invite = invite else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -334,11 +352,10 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
if let invite = invite {
|
UIPasteboard.general.string = invite.link
|
||||||
UIPasteboard.general.string = invite.link
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil)
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
}
|
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||||
@ -346,56 +363,56 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
if let invite = invite {
|
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
presentControllerImpl?(controller, nil)
|
||||||
presentControllerImpl?(controller, nil)
|
|
||||||
}
|
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
if invite.adminId.toInt64() != 0 {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||||
}, action: { _, f in
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
f(.dismissWithoutContent)
|
}, action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
let controller = ActionSheetController(presentationData: presentationData)
|
|
||||||
let dismissAction: () -> Void = { [weak controller] in
|
let controller = ActionSheetController(presentationData: presentationData)
|
||||||
controller?.dismissAnimated()
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
}
|
controller?.dismissAnimated()
|
||||||
controller.setItemGroups([
|
}
|
||||||
ActionSheetItemGroup(items: [
|
controller.setItemGroups([
|
||||||
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
|
ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
|
||||||
dismissAction()
|
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||||
|
dismissAction()
|
||||||
var revoke = false
|
|
||||||
updateState { state in
|
var revoke = false
|
||||||
if !state.revokingPrivateLink {
|
updateState { state in
|
||||||
revoke = true
|
if !state.revokingPrivateLink {
|
||||||
var updatedState = state
|
revoke = true
|
||||||
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
|
var updatedState = state
|
||||||
updatedState.revokingPrivateLink = false
|
updatedState.revokingPrivateLink = true
|
||||||
return updatedState
|
return updatedState
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
}
|
}
|
||||||
|
}
|
||||||
invitesContext.reload()
|
if revoke {
|
||||||
revokedInvitesContext.reload()
|
revokeLinkDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
|
||||||
}))
|
updateState { state in
|
||||||
}
|
var updatedState = state
|
||||||
})
|
updatedState.revokingPrivateLink = false
|
||||||
]),
|
return updatedState
|
||||||
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)
|
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
presentInGlobalOverlayImpl?(contextController)
|
presentInGlobalOverlayImpl?(contextController)
|
||||||
@ -426,11 +443,21 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil)
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if !invite.isRevoked {
|
if !invite.isRevoked {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextShare, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
let shareController = ShareController(context: context, subject: .url(invite.link))
|
||||||
|
presentControllerImpl?(shareController, nil)
|
||||||
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
@ -564,11 +591,19 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
importersState.set(context.state |> map(Optional.init))
|
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)
|
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
|
let previousRevokedInvites = previousRevokedInvites.swap(invites)
|
||||||
|
|
||||||
|
var crossfade = false
|
||||||
|
if (previousRevokedInvites?.hasLoadedOnce ?? false) != (revokedInvites.hasLoadedOnce) {
|
||||||
|
crossfade = true
|
||||||
|
}
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.invitations, revokedInvites: revokedInvites.invitations, mainPeers: importers?.importers.compactMap { $0.peer.peer } ?? []), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.invitations, revokedInvites: revokedInvites.invitations, importers: importers), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: false)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import ShareController
|
|||||||
import OverlayStatusController
|
import OverlayStatusController
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import DirectionalPanGesture
|
import DirectionalPanGesture
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
class InviteLinkViewInteraction {
|
class InviteLinkViewInteraction {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -171,7 +172,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
case let .link(_, invite):
|
case let .link(_, invite):
|
||||||
let buttonColor = color(for: invite)
|
let buttonColor = color(for: invite)
|
||||||
let availability = invitationAvailability(invite)
|
let availability = invitationAvailability(invite)
|
||||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: !invite.isRevoked && !availability.isZero, displayImporters: false, buttonColor: buttonColor, sectionId: 0, style: .plain, copyAction: {
|
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: !invite.isRevoked && !availability.isZero, displayImporters: false, buttonColor: buttonColor, sectionId: 0, style: .plain, copyAction: {
|
||||||
interaction.copyLink(invite)
|
interaction.copyLink(invite)
|
||||||
}, shareAction: {
|
}, shareAction: {
|
||||||
interaction.shareLink(invite)
|
interaction.shareLink(invite)
|
||||||
@ -393,8 +394,9 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
}
|
}
|
||||||
}, copyLink: { [weak self] invite in
|
}, copyLink: { [weak self] invite in
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root))
|
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
}, shareLink: { [weak self] invite in
|
}, shareLink: { [weak self] invite in
|
||||||
let shareController = ShareController(context: context, subject: .url(invite.link))
|
let shareController = ShareController(context: context, subject: .url(invite.link))
|
||||||
self?.controller?.present(shareController, in: .window(.root))
|
self?.controller?.present(shareController, in: .window(.root))
|
||||||
@ -412,8 +414,9 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root))
|
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if invite.isRevoked {
|
if invite.isRevoked {
|
||||||
@ -452,6 +455,8 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
self?.controller?.present(controller, in: .window(.root))
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
|
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
self?.controller?.presentInGlobalOverlay(contextController)
|
self?.controller?.presentInGlobalOverlay(contextController)
|
||||||
@ -717,12 +722,20 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
self.panGestureArguments = 0.0
|
self.panGestureArguments = 0.0
|
||||||
case .changed:
|
case .changed:
|
||||||
var translation = recognizer.translation(in: self.contentNode.view).y
|
var translation = recognizer.translation(in: self.contentNode.view).y
|
||||||
if let currentPanOffset = self.panGestureArguments {
|
if let currentOffset = self.panGestureArguments {
|
||||||
|
|
||||||
|
|
||||||
if case let .known(value) = contentOffset, value <= 0.5 {
|
if case let .known(value) = contentOffset, value <= 0.5 {
|
||||||
|
if currentOffset > 0.0 {
|
||||||
|
let translation = self.listNode.scroller.panGestureRecognizer.translation(in: self.listNode.scroller)
|
||||||
|
if translation.y > 10.0 {
|
||||||
|
self.listNode.scroller.panGestureRecognizer.isEnabled = false
|
||||||
|
self.listNode.scroller.panGestureRecognizer.isEnabled = true
|
||||||
|
} else {
|
||||||
|
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
translation = currentPanOffset
|
translation = 0.0
|
||||||
|
recognizer.setTranslation(CGPoint(), in: self.contentNode.view)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.panGestureArguments = translation
|
self.panGestureArguments = translation
|
||||||
|
@ -96,7 +96,8 @@ private let shareIcon = generateImage(CGSize(width: 26.0, height: 26.0), context
|
|||||||
private class ItemNode: ASDisplayNode {
|
private class ItemNode: ASDisplayNode {
|
||||||
private let selectionNode: HighlightTrackingButtonNode
|
private let selectionNode: HighlightTrackingButtonNode
|
||||||
private let wrapperNode: ASDisplayNode
|
private let wrapperNode: ASDisplayNode
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let backgroundGradientLayer: CAGradientLayer
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private var timerNode: TimerNode?
|
private var timerNode: TimerNode?
|
||||||
@ -122,11 +123,19 @@ private class ItemNode: ASDisplayNode {
|
|||||||
self.selectionNode = HighlightTrackingButtonNode()
|
self.selectionNode = HighlightTrackingButtonNode()
|
||||||
self.wrapperNode = ASDisplayNode()
|
self.wrapperNode = ASDisplayNode()
|
||||||
|
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.displaysAsynchronously = false
|
self.backgroundNode.clipsToBounds = true
|
||||||
self.backgroundNode.displayWithoutProcessing = true
|
self.backgroundNode.cornerRadius = 15.0
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.backgroundNode.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
self.backgroundNode.isUserInteractionEnabled = false
|
self.backgroundNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.backgroundGradientLayer = CAGradientLayer()
|
||||||
|
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
|
||||||
|
self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
|
||||||
|
self.backgroundNode.layer.addSublayer(self.backgroundGradientLayer)
|
||||||
|
|
||||||
self.iconNode = ASImageNode()
|
self.iconNode = ASImageNode()
|
||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
self.iconNode.displayWithoutProcessing = true
|
self.iconNode.displayWithoutProcessing = true
|
||||||
@ -256,10 +265,10 @@ private class ItemNode: ASDisplayNode {
|
|||||||
snapshotView?.removeFromSuperview()
|
snapshotView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
self.backgroundNode.image = generateBackgroundImage(colors: colors)
|
self.backgroundGradientLayer.colors = colors as? [Any]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.backgroundNode.image = generateBackgroundImage(colors: colors)
|
self.backgroundGradientLayer.colors = colors as? [Any]
|
||||||
}
|
}
|
||||||
|
|
||||||
let secondaryTextColor = color.colors.text
|
let secondaryTextColor = color.colors.text
|
||||||
@ -329,7 +338,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
self.timerNode = timerNode
|
self.timerNode = timerNode
|
||||||
self.addSubnode(timerNode)
|
self.addSubnode(timerNode)
|
||||||
}
|
}
|
||||||
timerNode.update(color: UIColor.white, creationTimestamp: invite.date, deadlineTimestamp: expireDate)
|
timerNode.update(color: UIColor.white, creationTimestamp: invite.startDate ?? invite.date, deadlineTimestamp: expireDate)
|
||||||
if share {
|
if share {
|
||||||
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
||||||
}
|
}
|
||||||
@ -359,6 +368,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: self.wrapperNode, frame: backgroundFrame)
|
transition.updateFrame(node: self.wrapperNode, frame: backgroundFrame)
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
transition.updateFrame(node: self.selectionNode, frame: backgroundFrame)
|
transition.updateFrame(node: self.selectionNode, frame: backgroundFrame)
|
||||||
|
transition.updateFrame(layer: self.backgroundGradientLayer, frame: backgroundFrame)
|
||||||
|
|
||||||
let buttonSize = CGSize(width: 26.0, height: 26.0)
|
let buttonSize = CGSize(width: 26.0, height: 26.0)
|
||||||
let buttonFrame = CGRect(origin: CGPoint(x: itemSize.width - buttonSize.width - 12.0, y: 12.0), size: buttonSize)
|
let buttonFrame = CGRect(origin: CGPoint(x: itemSize.width - buttonSize.width - 12.0, y: 12.0), size: buttonSize)
|
||||||
@ -532,7 +542,7 @@ private final class TimerNode: ASDisplayNode {
|
|||||||
|
|
||||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||||
var fraction = CGFloat(params.deadlineTimestamp - currentTimestamp) / CGFloat(params.deadlineTimestamp - params.creationTimestamp)
|
var fraction = CGFloat(params.deadlineTimestamp - currentTimestamp) / CGFloat(params.deadlineTimestamp - params.creationTimestamp)
|
||||||
fraction = 1.0 - max(0.0, min(1.0, fraction))
|
fraction = max(0.0001, 1.0 - max(0.0, min(1.0, fraction)))
|
||||||
|
|
||||||
let image: UIImage?
|
let image: UIImage?
|
||||||
|
|
||||||
|
@ -32,7 +32,9 @@ enum InviteLinkUsageLimit: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(value: Int32?) {
|
init(value: Int32?) {
|
||||||
if let value = value {
|
if value == 0 {
|
||||||
|
self = .unlimited
|
||||||
|
} else if let value = value {
|
||||||
if value == 1 {
|
if value == 1 {
|
||||||
self = .low
|
self = .low
|
||||||
} else if value == 10 {
|
} else if value == 10 {
|
||||||
@ -56,7 +58,7 @@ enum InviteLinkUsageLimit: Equatable {
|
|||||||
case .high:
|
case .high:
|
||||||
return 100
|
return 100
|
||||||
case .unlimited:
|
case .unlimited:
|
||||||
return nil
|
return 0
|
||||||
case let .custom(value):
|
case let .custom(value):
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let invite: ExportedInvitation?
|
let invite: ExportedInvitation?
|
||||||
|
let count: Int32
|
||||||
let peers: [Peer]
|
let peers: [Peer]
|
||||||
let displayButton: Bool
|
let displayButton: Bool
|
||||||
let displayImporters: Bool
|
let displayImporters: Bool
|
||||||
@ -45,6 +46,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
presentationData: ItemListPresentationData,
|
presentationData: ItemListPresentationData,
|
||||||
invite: ExportedInvitation?,
|
invite: ExportedInvitation?,
|
||||||
|
count: Int32,
|
||||||
peers: [Peer],
|
peers: [Peer],
|
||||||
displayButton: Bool,
|
displayButton: Bool,
|
||||||
displayImporters: Bool,
|
displayImporters: Bool,
|
||||||
@ -60,6 +62,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.invite = invite
|
self.invite = invite
|
||||||
|
self.count = count
|
||||||
self.peers = peers
|
self.peers = peers
|
||||||
self.displayButton = displayButton
|
self.displayButton = displayButton
|
||||||
self.displayImporters = displayImporters
|
self.displayImporters = displayImporters
|
||||||
@ -290,18 +293,13 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
|
|
||||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
|
|
||||||
let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.invite.flatMap({ $0.link.replacingOccurrences(of: "https://", with: "") }) ?? "", font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.invite.flatMap({ $0.link.replacingOccurrences(of: "https://", with: "") }) ?? "", font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 90.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let subtitle: String
|
let subtitle: String
|
||||||
let subtitleColor: UIColor
|
let subtitleColor: UIColor
|
||||||
if let count = item.invite?.count {
|
if item.count > 0 {
|
||||||
if count > 0 {
|
subtitle = item.presentationData.strings.InviteLink_PeopleJoined(item.count)
|
||||||
subtitle = item.presentationData.strings.InviteLink_PeopleJoined(count)
|
subtitleColor = item.presentationData.theme.list.itemAccentColor
|
||||||
subtitleColor = item.presentationData.theme.list.itemAccentColor
|
|
||||||
} else {
|
|
||||||
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
|
|
||||||
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
|
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
|
||||||
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
|
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||||
|
@ -376,6 +376,9 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||||||
if peer.isScam {
|
if peer.isScam {
|
||||||
credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
||||||
credibilityIconOffset = 6.0
|
credibilityIconOffset = 6.0
|
||||||
|
} else if peer.isFake {
|
||||||
|
credibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
|
||||||
|
credibilityIconOffset = 2.0
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
|
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
|||||||
let maxLength: Int
|
let maxLength: Int
|
||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
let selectAllOnFocus: Bool
|
let selectAllOnFocus: Bool
|
||||||
|
let secondaryStyle: Bool
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let textUpdated: (String) -> Void
|
let textUpdated: (String) -> Void
|
||||||
@ -56,7 +57,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
|||||||
let cleared: (() -> Void)?
|
let cleared: (() -> Void)?
|
||||||
public let tag: ItemListItemTag?
|
public let tag: ItemListItemTag?
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
|
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.title = title
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -69,6 +70,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
|||||||
self.maxLength = maxLength
|
self.maxLength = maxLength
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
self.selectAllOnFocus = selectAllOnFocus
|
self.selectAllOnFocus = selectAllOnFocus
|
||||||
|
self.secondaryStyle = secondaryStyle
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.textUpdated = textUpdated
|
self.textUpdated = textUpdated
|
||||||
@ -183,7 +185,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||||||
self.textNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)]
|
self.textNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)]
|
||||||
self.textNode.textField.font = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
self.textNode.textField.font = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
|
|
||||||
self.textNode.textField.textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
self.textNode.textField.textColor = item.secondaryStyle ? item.presentationData.theme.list.itemSecondaryTextColor : item.presentationData.theme.list.itemPrimaryTextColor
|
||||||
self.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
self.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
||||||
self.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor
|
self.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor
|
||||||
self.textNode.textField.accessibilityHint = item.placeholder
|
self.textNode.textField.accessibilityHint = item.placeholder
|
||||||
@ -218,6 +220,11 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||||||
fontUpdated = true
|
fontUpdated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var styleUpdated = false
|
||||||
|
if currentItem?.secondaryStyle != item.secondaryStyle {
|
||||||
|
styleUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
let leftInset: CGFloat = 16.0 + params.leftInset
|
let leftInset: CGFloat = 16.0 + params.leftInset
|
||||||
var rightInset: CGFloat = 16.0 + params.rightInset
|
var rightInset: CGFloat = 16.0 + params.rightInset
|
||||||
|
|
||||||
@ -252,13 +259,17 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||||||
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||||
|
|
||||||
strongSelf.textNode.textField.textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
strongSelf.textNode.textField.textColor = item.secondaryStyle ? item.presentationData.theme.list.itemSecondaryTextColor : item.presentationData.theme.list.itemPrimaryTextColor
|
||||||
strongSelf.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
strongSelf.textNode.textField.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
||||||
strongSelf.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor
|
strongSelf.textNode.textField.tintColor = item.presentationData.theme.list.itemAccentColor
|
||||||
}
|
}
|
||||||
|
|
||||||
if fontUpdated {
|
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()
|
let _ = titleApply()
|
||||||
|
@ -293,7 +293,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||||||
case let .privateLinkHeader(_, title):
|
case let .privateLinkHeader(_, title):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||||
case let .privateLink(_, invite, displayImporters):
|
case let .privateLink(_, invite, displayImporters):
|
||||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||||
if let invite = invite {
|
if let invite = invite {
|
||||||
arguments.copyLink(invite)
|
arguments.copyLink(invite)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ public enum PeerReportSubject {
|
|||||||
|
|
||||||
public enum PeerReportOption {
|
public enum PeerReportOption {
|
||||||
case spam
|
case spam
|
||||||
|
case fake
|
||||||
case violence
|
case violence
|
||||||
case copyright
|
case copyright
|
||||||
case pornography
|
case pornography
|
||||||
@ -37,6 +38,8 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
|||||||
switch option {
|
switch option {
|
||||||
case .spam:
|
case .spam:
|
||||||
title = presentationData.strings.ReportPeer_ReasonSpam
|
title = presentationData.strings.ReportPeer_ReasonSpam
|
||||||
|
case .fake:
|
||||||
|
title = presentationData.strings.ReportPeer_ReasonFake
|
||||||
case .violence:
|
case .violence:
|
||||||
title = presentationData.strings.ReportPeer_ReasonViolence
|
title = presentationData.strings.ReportPeer_ReasonViolence
|
||||||
case .pornography:
|
case .pornography:
|
||||||
@ -57,6 +60,8 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
|||||||
switch option {
|
switch option {
|
||||||
case .spam:
|
case .spam:
|
||||||
reportReason = .spam
|
reportReason = .spam
|
||||||
|
case .fake:
|
||||||
|
reportReason = .fake
|
||||||
case .violence:
|
case .violence:
|
||||||
reportReason = .violence
|
reportReason = .violence
|
||||||
case .pornography:
|
case .pornography:
|
||||||
@ -112,6 +117,8 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
|||||||
switch option {
|
switch option {
|
||||||
case .spam:
|
case .spam:
|
||||||
title = presentationData.strings.ReportPeer_ReasonSpam
|
title = presentationData.strings.ReportPeer_ReasonSpam
|
||||||
|
case .fake:
|
||||||
|
title = presentationData.strings.ReportPeer_ReasonFake
|
||||||
case .violence:
|
case .violence:
|
||||||
title = presentationData.strings.ReportPeer_ReasonViolence
|
title = presentationData.strings.ReportPeer_ReasonViolence
|
||||||
case .pornography:
|
case .pornography:
|
||||||
@ -128,6 +135,8 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
|||||||
switch option {
|
switch option {
|
||||||
case .spam:
|
case .spam:
|
||||||
reportReason = .spam
|
reportReason = .spam
|
||||||
|
case .fake:
|
||||||
|
reportReason = .fake
|
||||||
case .violence:
|
case .violence:
|
||||||
reportReason = .violence
|
reportReason = .violence
|
||||||
case .pornography:
|
case .pornography:
|
||||||
|
@ -652,7 +652,15 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
|
|||||||
} else {
|
} else {
|
||||||
aboutTitle = presentationData.strings.Profile_About
|
aboutTitle = presentationData.strings.Profile_About
|
||||||
}
|
}
|
||||||
if user.isScam {
|
if user.isFake {
|
||||||
|
let aboutValue: String
|
||||||
|
if let _ = user.botInfo {
|
||||||
|
aboutValue = presentationData.strings.UserInfo_FakeBotWarning
|
||||||
|
} else {
|
||||||
|
aboutValue = presentationData.strings.UserInfo_FakeUserWarning
|
||||||
|
}
|
||||||
|
entries.append(UserInfoEntry.about(presentationData.theme, peer, aboutTitle, aboutValue))
|
||||||
|
} else if user.isScam {
|
||||||
let aboutValue: String
|
let aboutValue: String
|
||||||
if let _ = user.botInfo {
|
if let _ = user.botInfo {
|
||||||
aboutValue = presentationData.strings.UserInfo_ScamBotWarning
|
aboutValue = presentationData.strings.UserInfo_ScamBotWarning
|
||||||
|
@ -143,6 +143,7 @@ public struct TelegramChannelFlags: OptionSet {
|
|||||||
public static let hasGeo = TelegramChannelFlags(rawValue: 1 << 3)
|
public static let hasGeo = TelegramChannelFlags(rawValue: 1 << 3)
|
||||||
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
|
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
|
||||||
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
|
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
|
||||||
|
public static let isFake = TelegramChannelFlags(rawValue: 1 << 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TelegramChannel: Peer {
|
public final class TelegramChannel: Peer {
|
||||||
|
@ -14,6 +14,7 @@ public struct UserInfoFlags: OptionSet {
|
|||||||
public static let isVerified = UserInfoFlags(rawValue: (1 << 0))
|
public static let isVerified = UserInfoFlags(rawValue: (1 << 0))
|
||||||
public static let isSupport = UserInfoFlags(rawValue: (1 << 1))
|
public static let isSupport = UserInfoFlags(rawValue: (1 << 1))
|
||||||
public static let isScam = UserInfoFlags(rawValue: (1 << 2))
|
public static let isScam = UserInfoFlags(rawValue: (1 << 2))
|
||||||
|
public static let isFake = UserInfoFlags(rawValue: (1 << 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct BotUserInfoFlags: OptionSet {
|
public struct BotUserInfoFlags: OptionSet {
|
||||||
|
@ -103,6 +103,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
|
|||||||
if (flags & Int32(1 << 24)) != 0 {
|
if (flags & Int32(1 << 24)) != 0 {
|
||||||
channelFlags.insert(.hasActiveVoiceChat)
|
channelFlags.insert(.hasActiveVoiceChat)
|
||||||
}
|
}
|
||||||
|
if (flags & Int32(1 << 25)) != 0 {
|
||||||
|
channelFlags.insert(.isFake)
|
||||||
|
}
|
||||||
|
|
||||||
let restrictionInfo: PeerAccessRestrictionInfo?
|
let restrictionInfo: PeerAccessRestrictionInfo?
|
||||||
if let restrictionReason = restrictionReason {
|
if let restrictionReason = restrictionReason {
|
||||||
|
@ -75,6 +75,21 @@ public extension Message {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isFake: Bool {
|
||||||
|
if let author = self.author, author.isFake {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isFake {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for attribute in self.attributes {
|
||||||
|
if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isFake {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var sourceReference: SourceReferenceMessageAttribute? {
|
var sourceReference: SourceReferenceMessageAttribute? {
|
||||||
for attribute in self.attributes {
|
for attribute in self.attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
|
@ -122,6 +122,17 @@ public extension Peer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isFake: Bool {
|
||||||
|
switch self {
|
||||||
|
case let user as TelegramUser:
|
||||||
|
return user.flags.contains(.isFake)
|
||||||
|
case let channel as TelegramChannel:
|
||||||
|
return channel.flags.contains(.isFake)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var isVerified: Bool {
|
var isVerified: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case let user as TelegramUser:
|
case let user as TelegramUser:
|
||||||
|
@ -78,6 +78,7 @@ public func reportPeer(account: Account, peerId: PeerId) -> Signal<Void, NoError
|
|||||||
|
|
||||||
public enum ReportReason: Equatable {
|
public enum ReportReason: Equatable {
|
||||||
case spam
|
case spam
|
||||||
|
case fake
|
||||||
case violence
|
case violence
|
||||||
case porno
|
case porno
|
||||||
case childAbuse
|
case childAbuse
|
||||||
@ -91,6 +92,8 @@ private extension ReportReason {
|
|||||||
switch self {
|
switch self {
|
||||||
case .spam:
|
case .spam:
|
||||||
return .inputReportReasonSpam
|
return .inputReportReasonSpam
|
||||||
|
case .fake:
|
||||||
|
return .inputReportReasonOther(text: "fake")
|
||||||
case .violence:
|
case .violence:
|
||||||
return .inputReportReasonViolence
|
return .inputReportReasonViolence
|
||||||
case .porno:
|
case .porno:
|
||||||
|
@ -53,6 +53,9 @@ extension TelegramUser {
|
|||||||
if (flags & (1 << 24)) != 0 {
|
if (flags & (1 << 24)) != 0 {
|
||||||
userFlags.insert(.isScam)
|
userFlags.insert(.isScam)
|
||||||
}
|
}
|
||||||
|
if (flags & Int32(1 << 26)) != 0 {
|
||||||
|
userFlags.insert(.isFake)
|
||||||
|
}
|
||||||
|
|
||||||
var botInfo: BotUserInfo?
|
var botInfo: BotUserInfo?
|
||||||
if (flags & (1 << 14)) != 0 {
|
if (flags & (1 << 14)) != 0 {
|
||||||
@ -96,6 +99,9 @@ extension TelegramUser {
|
|||||||
if (flags & (1 << 24)) != 0 {
|
if (flags & (1 << 24)) != 0 {
|
||||||
userFlags.insert(.isScam)
|
userFlags.insert(.isScam)
|
||||||
}
|
}
|
||||||
|
if (flags & Int32(1 << 26)) != 0 {
|
||||||
|
userFlags.insert(.isFake)
|
||||||
|
}
|
||||||
|
|
||||||
var botInfo: BotUserInfo?
|
var botInfo: BotUserInfo?
|
||||||
if (flags & (1 << 14)) != 0 {
|
if (flags & (1 << 14)) != 0 {
|
||||||
@ -156,6 +162,9 @@ extension TelegramUser {
|
|||||||
if rhs.flags.contains(.isScam) {
|
if rhs.flags.contains(.isScam) {
|
||||||
userFlags.insert(.isScam)
|
userFlags.insert(.isScam)
|
||||||
}
|
}
|
||||||
|
if rhs.flags.contains(.isFake) {
|
||||||
|
userFlags.insert(.isFake)
|
||||||
|
}
|
||||||
|
|
||||||
let botInfo: BotUserInfo? = rhs.botInfo
|
let botInfo: BotUserInfo? = rhs.botInfo
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -77,6 +77,9 @@ public enum PresentationResourceKey: Int32 {
|
|||||||
case chatListScamRegularIcon
|
case chatListScamRegularIcon
|
||||||
case chatListScamOutgoingIcon
|
case chatListScamOutgoingIcon
|
||||||
case chatListScamServiceIcon
|
case chatListScamServiceIcon
|
||||||
|
case chatListFakeRegularIcon
|
||||||
|
case chatListFakeOutgoingIcon
|
||||||
|
case chatListFakeServiceIcon
|
||||||
case chatListSecretIcon
|
case chatListSecretIcon
|
||||||
case chatListRecentStatusOnlineIcon
|
case chatListRecentStatusOnlineIcon
|
||||||
case chatListRecentStatusOnlineHighlightedIcon
|
case chatListRecentStatusOnlineHighlightedIcon
|
||||||
|
@ -248,6 +248,42 @@ public struct PresentationResourcesChatList {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func fakeIcon(_ theme: PresentationTheme, type: ScamIconType) -> UIImage? {
|
||||||
|
let key: PresentationResourceKey
|
||||||
|
let color: UIColor
|
||||||
|
switch type {
|
||||||
|
case .regular:
|
||||||
|
key = PresentationResourceKey.chatListFakeRegularIcon
|
||||||
|
color = theme.chat.message.incoming.scamColor
|
||||||
|
case .outgoing:
|
||||||
|
key = PresentationResourceKey.chatListFakeOutgoingIcon
|
||||||
|
color = theme.chat.message.outgoing.scamColor
|
||||||
|
case .service:
|
||||||
|
key = PresentationResourceKey.chatListFakeServiceIcon
|
||||||
|
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
|
||||||
|
}
|
||||||
|
return theme.image(key.rawValue, { theme in
|
||||||
|
return generateImage(CGSize(width: 37.0, height: 16.0), contextGenerator: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
|
||||||
|
context.setFillColor(color.cgColor)
|
||||||
|
context.setStrokeColor(color.cgColor)
|
||||||
|
context.setLineWidth(1.0)
|
||||||
|
|
||||||
|
context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: 2.0).cgPath)
|
||||||
|
context.strokePath()
|
||||||
|
|
||||||
|
let titlePath = CGMutablePath()
|
||||||
|
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
|
||||||
|
let titleString = NSAttributedString(string: "FAKE", font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
|
||||||
|
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
|
||||||
|
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
|
||||||
|
CTFrameDraw(titleFrame, context)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public static func secretIcon(_ theme: PresentationTheme) -> UIImage? {
|
public static func secretIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatListSecretIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatListSecretIcon.rawValue, { theme in
|
||||||
return generateImage(CGSize(width: 9.0, height: 12.0), rotatedContext: { size, context in
|
return generateImage(CGSize(width: 9.0, height: 12.0), rotatedContext: { size, context in
|
||||||
|
Binary file not shown.
@ -1426,9 +1426,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
|
|
||||||
var isScam = effectiveAuthor.isScam
|
var isScam = effectiveAuthor.isScam
|
||||||
if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId {
|
if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId {
|
||||||
isScam = false
|
|
||||||
|
} else if effectiveAuthor.isScam {
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||||
|
} else if effectiveAuthor.isFake {
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||||
}
|
}
|
||||||
currentCredibilityIconImage = isScam ? PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing) : nil
|
|
||||||
}
|
}
|
||||||
if let rawAuthorNameColor = authorNameColor {
|
if let rawAuthorNameColor = authorNameColor {
|
||||||
var dimColors = false
|
var dimColors = false
|
||||||
|
@ -193,7 +193,14 @@ class ChatMessageForwardInfoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.isScam {
|
if peer.isFake {
|
||||||
|
switch type {
|
||||||
|
case let .bubble(incoming):
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||||
|
case .standalone:
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, type: .service)
|
||||||
|
}
|
||||||
|
} else if peer.isScam {
|
||||||
switch type {
|
switch type {
|
||||||
case let .bubble(incoming):
|
case let .bubble(incoming):
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||||
|
@ -937,7 +937,11 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if let cachedData = data.cachedData as? CachedUserData {
|
if let cachedData = data.cachedData as? CachedUserData {
|
||||||
if user.isScam {
|
if user.isFake {
|
||||||
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_FakeBotWarning : presentationData.strings.UserInfo_FakeUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: {
|
||||||
|
interaction.requestLayout()
|
||||||
|
}))
|
||||||
|
} else if user.isScam {
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: {
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: {
|
||||||
interaction.requestLayout()
|
interaction.requestLayout()
|
||||||
}))
|
}))
|
||||||
@ -1025,12 +1029,27 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if let cachedData = data.cachedData as? CachedChannelData {
|
if let cachedData = data.cachedData as? CachedChannelData {
|
||||||
if channel.isScam {
|
let aboutText: String?
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, requestLayout: {
|
if channel.isFake {
|
||||||
interaction.requestLayout()
|
if case .broadcast = channel.info {
|
||||||
}))
|
aboutText = presentationData.strings.ChannelInfo_FakeChannelWarning
|
||||||
|
} else {
|
||||||
|
aboutText = presentationData.strings.GroupInfo_FakeGroupWarning
|
||||||
|
}
|
||||||
|
} else if channel.isScam {
|
||||||
|
if case .broadcast = channel.info {
|
||||||
|
aboutText = presentationData.strings.ChannelInfo_ScamChannelWarning
|
||||||
|
} else {
|
||||||
|
aboutText = presentationData.strings.GroupInfo_ScamGroupWarning
|
||||||
|
}
|
||||||
} else if let about = cachedData.about, !about.isEmpty {
|
} else if let about = cachedData.about, !about.isEmpty {
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
|
aboutText = about
|
||||||
|
} else {
|
||||||
|
aboutText = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let aboutText = aboutText {
|
||||||
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
|
||||||
interaction.requestLayout()
|
interaction.requestLayout()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1061,12 +1080,19 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
}
|
}
|
||||||
} else if let group = data.peer as? TelegramGroup {
|
} else if let group = data.peer as? TelegramGroup {
|
||||||
if let cachedData = data.cachedData as? CachedGroupData {
|
if let cachedData = data.cachedData as? CachedGroupData {
|
||||||
if group.isScam {
|
let aboutText: String?
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, requestLayout: {
|
if group.isFake {
|
||||||
interaction.requestLayout()
|
aboutText = presentationData.strings.GroupInfo_FakeGroupWarning
|
||||||
}))
|
} else if group.isScam {
|
||||||
|
aboutText = presentationData.strings.GroupInfo_ScamGroupWarning
|
||||||
} else if let about = cachedData.about, !about.isEmpty {
|
} else if let about = cachedData.about, !about.isEmpty {
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
|
aboutText = about
|
||||||
|
} else {
|
||||||
|
aboutText = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let aboutText = aboutText {
|
||||||
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
|
||||||
interaction.requestLayout()
|
interaction.requestLayout()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1266,6 +1292,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
interaction.editingOpenPublicLinkSetup()
|
interaction.editingOpenPublicLinkSetup()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.hasPermission(.inviteMembers) {
|
||||||
let invitesText: String
|
let invitesText: String
|
||||||
if let count = data.invitations?.count, count > 0 {
|
if let count = data.invitations?.count, count > 0 {
|
||||||
invitesText = "\(count)"
|
invitesText = "\(count)"
|
||||||
@ -1276,7 +1305,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||||
interaction.editingOpenInviteLinksSetup()
|
interaction.editingOpenInviteLinksSetup()
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cachedData.flags.contains(.canChangeUsername) {
|
||||||
if let linkedDiscussionPeer = data.linkedDiscussionPeer {
|
if let linkedDiscussionPeer = data.linkedDiscussionPeer {
|
||||||
let peerTitle: String
|
let peerTitle: String
|
||||||
if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty {
|
if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty {
|
||||||
@ -4425,7 +4456,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
if canCreateInviteLink {
|
if canCreateInviteLink {
|
||||||
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
|
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
|
||||||
createInviteLinkImpl?()
|
createInviteLinkImpl?()
|
||||||
}))
|
}, clearHighlightAutomatically: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
let contactsController: ViewController
|
let contactsController: ViewController
|
||||||
|
Loading…
x
Reference in New Issue
Block a user