Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
overtake 2021-01-19 18:16:03 +03:00
commit d66a80670a
15 changed files with 212 additions and 137 deletions

View File

@ -55,7 +55,7 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
case header(PresentationTheme, String, String) case header(PresentationTheme, String, String)
case mainLink(PresentationTheme, ExportedInvitation) case mainLink(PresentationTheme, ExportedInvitation)
case links(Int32, PresentationTheme, [ExportedInvitation]) case links(Int32, PresentationTheme, [ExportedInvitation])
case manage(PresentationTheme, String) case manage(PresentationTheme, String, Bool)
var stableId: InviteLinkInviteEntryId { var stableId: InviteLinkInviteEntryId {
switch self { switch self {
@ -90,8 +90,8 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .manage(lhsTheme, lhsText): case let .manage(lhsTheme, lhsText, lhsStandalone):
if case let .manage(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone {
return true return true
} else { } else {
return false return false
@ -148,13 +148,13 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
}, viewAction: { }, viewAction: {
}) })
case let .links(_, _, invites): case let .links(_, _, invites):
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, share: true, sectionId: 0, style: .plain, tapAction: { invite in return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, share: true, sectionId: 1, style: .plain, tapAction: { invite in
interaction.copyLink(invite) interaction.copyLink(invite)
}, contextAction: { invite, _ in }, contextAction: { invite, _ in
interaction.shareLink(invite) interaction.shareLink(invite)
}) })
case let .manage(theme, text): case let .manage(theme, text, standalone):
return InviteLinkInviteManageItem(theme: theme, text: text, action: { return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: {
interaction.manageLinks() interaction.manageLinks()
}) })
} }
@ -257,6 +257,7 @@ public final class InviteLinkInviteController: ViewController {
private let context: AccountContext private let context: AccountContext
private let peerId: PeerId private let peerId: PeerId
private let invitesContext: PeerExportedInvitationsContext
private var interaction: InviteLinkInviteInteraction? private var interaction: InviteLinkInviteInteraction?
@ -290,6 +291,8 @@ public final class InviteLinkInviteController: ViewController {
self.presentationDataPromise = Promise(self.presentationData) self.presentationDataPromise = Promise(self.presentationData)
self.controller = controller self.controller = controller
self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
self.dimNode = ASDisplayNode() self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
@ -386,8 +389,9 @@ public final class InviteLinkInviteController: ViewController {
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController) self?.controller?.presentInGlobalOverlay(contextController)
}, copyLink: { [weak self] invite in }, copyLink: { [weak self] invite in
let shareController = ShareController(context: context, subject: .url(invite.link)) UIPasteboard.general.string = invite.link
self?.controller?.present(shareController, in: .window(.root)) let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, 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))
@ -400,20 +404,38 @@ public final class InviteLinkInviteController: ViewController {
let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil) let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
let peerView = context.account.postbox.peerView(id: peerId) let peerView = context.account.postbox.peerView(id: peerId)
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView) self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, self.invitesContext.state)
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view in |> deliverOnMainQueue).start(next: { [weak self] presentationData, view, invites in
if let strongSelf = self { if let strongSelf = self {
var entries: [InviteLinkInviteEntry] = [] var entries: [InviteLinkInviteEntry] = []
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, presentationData.strings.InviteLink_CreatePrivateLinkHelp)) entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
let mainInvite: ExportedInvitation?
if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation { if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation {
entries.append(.mainLink(presentationData.theme, invite)) mainInvite = invite
} else if let cachedData = view.cachedData as? CachedChannelData, let invite = cachedData.exportedInvitation { } else if let cachedData = view.cachedData as? CachedChannelData, let invite = cachedData.exportedInvitation {
entries.append(.mainLink(presentationData.theme, invite)) mainInvite = invite
} else {
mainInvite = nil
}
if let mainInvite = mainInvite {
entries.append(.mainLink(presentationData.theme, mainInvite))
} }
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage)) let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
var index: Int32 = 0
for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
var invitesPair: [ExportedInvitation] = []
invitesPair.append(additionalInvites[i])
if i + 1 < additionalInvites.count {
invitesPair.append(additionalInvites[i + 1])
}
entries.append(.links(index, presentationData.theme, invitesPair))
index += 1
}
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, additionalInvites.isEmpty))
let previousEntries = previousEntries.swap(entries) let previousEntries = previousEntries.swap(entries)

View File

@ -14,12 +14,13 @@ class InviteLinkInviteManageItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let text: String let text: String
let standalone: Bool
let action: () -> Void let action: () -> Void
init(theme: PresentationTheme, text: String, action: @escaping () -> Void) { init(theme: PresentationTheme, text: String, standalone: Bool, action: @escaping () -> Void) {
self.theme = theme self.theme = theme
self.text = text self.text = text
self.standalone = standalone
self.action = action self.action = action
} }
@ -94,8 +95,12 @@ class InviteLinkInviteManageItemNode: ListViewItemNode {
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
strongSelf.backgroundNode.backgroundColor = item.standalone ? .clear : item.theme.list.blocksBackgroundColor
strongSelf.buttonNode.setTitle(item.text, with: Font.regular(17.0), with: item.theme.actionSheet.controlAccentColor, for: .normal) strongSelf.buttonNode.setTitle(item.text, with: Font.regular(17.0), with: item.theme.actionSheet.controlAccentColor, for: .normal)
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: 1000.0))
let size = strongSelf.buttonNode.measure(layout.contentSize) let size = strongSelf.buttonNode.measure(layout.contentSize)
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.contentSize.width - size.width) / 2.0), y: floorToScreenPixels((layout.contentSize.height - size.height) / 2.0)), size: size) strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.contentSize.width - size.width) / 2.0), y: floorToScreenPixels((layout.contentSize.height - size.height) / 2.0)), size: size)
} }

View File

@ -316,7 +316,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
let shareController = ShareController(context: context, subject: .url(invite.link)) let shareController = ShareController(context: context, subject: .url(invite.link))
presentControllerImpl?(shareController, nil) presentControllerImpl?(shareController, nil)
}, openMainLink: { invite in }, openMainLink: { invite in
let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: nil, importersContext: nil) let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: nil, revokedInvitationsContext: revokedInvitesContext, importersContext: nil)
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, copyLink: { invite in }, copyLink: { invite in
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
@ -409,7 +409,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, openLink: { invite in }, openLink: { invite in
if let invite = invite { if let invite = invite {
let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: invitesContext, importersContext: nil) let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: invitesContext, revokedInvitationsContext: revokedInvitesContext, importersContext: nil)
pushControllerImpl?(controller) pushControllerImpl?(controller)
} }
}, linkContextAction: { invite, node, gesture in }, linkContextAction: { invite, node, gesture in
@ -449,7 +449,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
if let invite = invite { if let invite = invite {
if invite.isRevoked { if invite.isRevoked {
invitesContext.remove(invite) invitesContext.remove(invite)
revokedInvitesContext.add(invite) revokedInvitesContext.add(invite.withUpdated(isRevoked: true))
} else { } else {
invitesContext.update(invite) invitesContext.update(invite)
} }
@ -508,7 +508,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
})) }))
invitesContext.remove(invite) invitesContext.remove(invite)
revokedInvitesContext.add(invite) revokedInvitesContext.add(invite.withUpdated(isRevoked: true))
}) })
]), ]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])

View File

@ -218,15 +218,17 @@ public final class InviteLinkViewController: ViewController {
private let peerId: PeerId private let peerId: PeerId
private let invite: ExportedInvitation private let invite: ExportedInvitation
private let invitationsContext: PeerExportedInvitationsContext? private let invitationsContext: PeerExportedInvitationsContext?
private let revokedInvitationsContext: PeerExportedInvitationsContext?
private let importersContext: PeerInvitationImportersContext? private let importersContext: PeerInvitationImportersContext?
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
public init(context: AccountContext, peerId: PeerId, invite: ExportedInvitation, invitationsContext: PeerExportedInvitationsContext?, importersContext: PeerInvitationImportersContext?) { public init(context: AccountContext, peerId: PeerId, invite: ExportedInvitation, invitationsContext: PeerExportedInvitationsContext?, revokedInvitationsContext: PeerExportedInvitationsContext?, importersContext: PeerInvitationImportersContext?) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.invite = invite self.invite = invite
self.invitationsContext = invitationsContext self.invitationsContext = invitationsContext
self.revokedInvitationsContext = revokedInvitationsContext
self.importersContext = importersContext self.importersContext = importersContext
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -299,12 +301,16 @@ public final class InviteLinkViewController: ViewController {
private let peerId: PeerId private let peerId: PeerId
private let invite: ExportedInvitation private let invite: ExportedInvitation
private let importersContext: PeerInvitationImportersContext
private var interaction: InviteLinkViewInteraction? private var interaction: InviteLinkViewInteraction?
private var presentationData: PresentationData private var presentationData: PresentationData
private let presentationDataPromise: Promise<PresentationData> private let presentationDataPromise: Promise<PresentationData>
private var presentationDataDisposable: Disposable?
private var disposable: Disposable? private var disposable: Disposable?
private let actionDisposable = MetaDisposable()
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
private let contentNode: ASDisplayNode private let contentNode: ASDisplayNode
@ -323,10 +329,6 @@ public final class InviteLinkViewController: ViewController {
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
private var presentationDataDisposable: Disposable?
private let importersContext: PeerInvitationImportersContext
init(context: AccountContext, peerId: PeerId, invite: ExportedInvitation, importersContext: PeerInvitationImportersContext?, controller: InviteLinkViewController) { init(context: AccountContext, peerId: PeerId, invite: ExportedInvitation, importersContext: PeerInvitationImportersContext?, controller: InviteLinkViewController) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
@ -420,7 +422,24 @@ public final class InviteLinkViewController: ViewController {
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = InviteLinkQRCodeController(context: context, invite: invite) let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
dismissAction()
self?.actionDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
}))
self?.controller?.revokedInvitationsContext?.remove(invite)
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self?.controller?.present(controller, in: .window(.root)) self?.controller?.present(controller, in: .window(.root))
}))) })))
} else { } else {

View File

@ -63,11 +63,36 @@ private enum ItemBackgroundColor: Equatable {
case .red: case .red:
return (UIColor(rgb: 0xf2656a), UIColor(rgb: 0xf25f65), UIColor(rgb: 0xffd3de)) return (UIColor(rgb: 0xf2656a), UIColor(rgb: 0xf25f65), UIColor(rgb: 0xffd3de))
case .gray: case .gray:
return (UIColor(rgb: 0xd4d8db), UIColor(rgb: 0xced2d5), UIColor(rgb: 0xf8f9f9)) return (UIColor(rgb: 0xa8b2bb), UIColor(rgb: 0xa2abb4), UIColor(rgb: 0xe3e6e8))
} }
} }
} }
private let moreIcon = generateImage(CGSize(width: 26.0, height: 26.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 4.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 11.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 18.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
})
private let shareIcon = generateImage(CGSize(width: 26.0, height: 26.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
if let maskImage = UIImage(bundleImageName: "Chat/Links/Share") {
context.clip(to: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - maskImage.size.width) / 2.0), y: floorToScreenPixels((size.height - maskImage.size.height) / 2.0)), size: maskImage.size), mask: maskImage.cgImage!)
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
}
})
private class ItemNode: ASDisplayNode { private class ItemNode: ASDisplayNode {
private let selectionNode: HighlightTrackingButtonNode private let selectionNode: HighlightTrackingButtonNode
private let wrapperNode: ASDisplayNode private let wrapperNode: ASDisplayNode
@ -114,17 +139,6 @@ private class ItemNode: ASDisplayNode {
self.buttonIconNode = ASImageNode() self.buttonIconNode = ASImageNode()
self.buttonIconNode.displaysAsynchronously = false self.buttonIconNode.displaysAsynchronously = false
self.buttonIconNode.displayWithoutProcessing = true self.buttonIconNode.displayWithoutProcessing = true
self.buttonIconNode.image = generateImage(CGSize(width: 26.0, height: 26.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 4.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 11.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 18.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
})
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 2 self.titleNode.maximumNumberOfLines = 2
@ -264,6 +278,8 @@ private class ItemNode: ASDisplayNode {
} }
self.titleNode.attributedText = title self.titleNode.attributedText = title
self.buttonIconNode.image = share ? shareIcon : moreIcon
var subtitleText: String = "" var subtitleText: String = ""
if let count = invite.count { if let count = invite.count {
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count) subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
@ -282,7 +298,11 @@ private class ItemNode: ASDisplayNode {
if !subtitleText.isEmpty { if !subtitleText.isEmpty {
subtitleText += "" subtitleText += ""
} }
if share {
subtitleText = presentationData.strings.InviteLink_Expired
} else {
subtitleText += presentationData.strings.InviteLink_Expired subtitleText += presentationData.strings.InviteLink_Expired
}
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white) self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode() self.timerNode?.removeFromSupernode()
self.timerNode = nil self.timerNode = nil
@ -290,7 +310,11 @@ private class ItemNode: ASDisplayNode {
if !subtitleText.isEmpty { if !subtitleText.isEmpty {
subtitleText += "" subtitleText += ""
} }
if share {
subtitleText = presentationData.strings.InviteLink_UsageLimitReached
} else {
subtitleText += presentationData.strings.InviteLink_UsageLimitReached subtitleText += presentationData.strings.InviteLink_UsageLimitReached
}
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white) self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode() self.timerNode?.removeFromSupernode()
self.timerNode = nil self.timerNode = nil
@ -306,10 +330,16 @@ private class ItemNode: ASDisplayNode {
self.addSubnode(timerNode) self.addSubnode(timerNode)
} }
timerNode.update(color: UIColor.white, creationTimestamp: invite.date, deadlineTimestamp: expireDate) timerNode.update(color: UIColor.white, creationTimestamp: invite.date, deadlineTimestamp: expireDate)
if share {
subtitleText = presentationData.strings.InviteLink_TapToCopy
}
} else { } else {
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Link"), color: .white) self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Link"), color: .white)
self.timerNode?.removeFromSupernode() self.timerNode?.removeFromSupernode()
self.timerNode = nil self.timerNode = nil
if share {
subtitleText = presentationData.strings.InviteLink_TapToCopy
}
} }
self.iconNode.frame = CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0) self.iconNode.frame = CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)

View File

@ -130,14 +130,21 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
let leftInset = 16.0 + params.leftInset let leftInset = 16.0 + params.leftInset
let topInset: CGFloat
if case .plain = item.style, case .otherSection = neighbors.top {
topInset = 16.0
} else {
topInset = 4.0
}
var height: CGFloat var height: CGFloat
let count = item.invites?.count ?? 0 let count = item.invites?.count ?? 0
if count > 0 { if count > 0 {
if count % 2 == 0 { if count % 2 == 0 {
height = 4.0 + 122.0 + 6.0 height = topInset + 122.0 + 6.0
} else { } else {
height = 4.0 + 102.0 + 6.0 height = topInset + 102.0 + 6.0
} }
} else { } else {
height = 0.001 height = 0.001
@ -145,9 +152,9 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
switch item.style { switch item.style {
case .plain: case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor itemBackgroundColor = item.presentationData.theme.list.blocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor itemSeparatorColor = item.presentationData.theme.list.blocksBackgroundColor
insets = itemListNeighborsPlainInsets(neighbors) insets = UIEdgeInsets()
case .blocks: case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
@ -155,7 +162,7 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
} }
if case .sameSection(false) = neighbors.bottom { if case .sameSection(false) = neighbors.bottom {
} else { } else {
height += 6.0 height += 10.0
} }
contentSize = CGSize(width: params.width, height: height) contentSize = CGSize(width: params.width, height: height)
@ -170,7 +177,7 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
} }
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites ?? [], share: item.share, presentationData: item.presentationData, transition: .immediate) let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites ?? [], share: item.share, presentationData: item.presentationData, transition: .immediate)
strongSelf.gridNode.frame = CGRect(origin: CGPoint(), size: gridSize) strongSelf.gridNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset - 4.0), size: gridSize)
strongSelf.gridNode.action = { invite in strongSelf.gridNode.action = { invite in
item.tapAction?(invite) item.tapAction?(invite)
} }
@ -180,18 +187,19 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
switch item.style { switch item.style {
case .plain: case .plain:
if strongSelf.backgroundNode.supernode != nil { if strongSelf.backgroundNode.supernode == nil {
strongSelf.backgroundNode.removeFromSupernode() strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
} }
if strongSelf.topStripeNode.supernode != nil { if strongSelf.topStripeNode.supernode != nil {
strongSelf.topStripeNode.removeFromSupernode() strongSelf.topStripeNode.removeFromSupernode()
} }
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode != nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) strongSelf.bottomStripeNode.removeFromSupernode()
} }
if strongSelf.maskNode.supernode != nil { if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode() strongSelf.maskNode.removeFromSupernode()
} }
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: contentSize.height))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks: case .blocks:
if strongSelf.backgroundNode.supernode == nil { if strongSelf.backgroundNode.supernode == nil {

View File

@ -169,6 +169,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
self.containerNode = ContextControllerSourceNode() self.containerNode = ContextControllerSourceNode()
self.containerNode.isGestureEnabled = false self.containerNode.isGestureEnabled = false
self.addressButtonIconNode = ASImageNode() self.addressButtonIconNode = ASImageNode()
self.addressButtonIconNode.contentMode = .center
self.addressButtonIconNode.displaysAsynchronously = false self.addressButtonIconNode.displaysAsynchronously = false
self.addressButtonIconNode.displayWithoutProcessing = true self.addressButtonIconNode.displayWithoutProcessing = true
@ -423,7 +424,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
strongSelf.addressNode.frame = CGRect(origin: CGPoint(x: fieldFrame.minX + floorToScreenPixels((fieldFrame.width - addressLayout.size.width) / 2.0), y: fieldFrame.minY + floorToScreenPixels((fieldFrame.height - addressLayout.size.height) / 2.0) + 1.0), size: addressLayout.size) strongSelf.addressNode.frame = CGRect(origin: CGPoint(x: fieldFrame.minX + floorToScreenPixels((fieldFrame.width - addressLayout.size.width) / 2.0), y: fieldFrame.minY + floorToScreenPixels((fieldFrame.height - addressLayout.size.height) / 2.0) + 1.0), size: addressLayout.size)
strongSelf.addressButtonNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - 38.0, y: verticalInset + 14.0), size: CGSize(width: 24.0, height: 24.0)) strongSelf.addressButtonNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - 38.0 - 14.0, y: verticalInset), size: CGSize(width: 52.0, height: 52.0))
strongSelf.extractedContainerNode.frame = strongSelf.addressButtonNode.bounds strongSelf.extractedContainerNode.frame = strongSelf.addressButtonNode.bounds
strongSelf.extractedContainerNode.contentRect = strongSelf.addressButtonNode.bounds strongSelf.extractedContainerNode.contentRect = strongSelf.addressButtonNode.bounds
strongSelf.addressButtonIconNode.frame = strongSelf.addressButtonNode.bounds strongSelf.addressButtonIconNode.frame = strongSelf.addressButtonNode.bounds

View File

@ -125,18 +125,18 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return 8 return 8
case .privateLinkInfo: case .privateLinkInfo:
return 9 return 9
case .privateLinkManage:
return 10
case .privateLinkManageInfo:
return 11
case .publicLinkStatus: case .publicLinkStatus:
return 12 return 10
case .publicLinkInfo: case .publicLinkInfo:
return 13 return 11
case .existingLinksInfo: case .existingLinksInfo:
return 14 return 12
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _): case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
return 15 + index return 13 + index
case .privateLinkManage:
return 1000
case .privateLinkManageInfo:
return 1001
} }
} }
@ -598,6 +598,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
} else { } else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp)) entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
} }
switch mode {
case .initialSetup:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
} }
case .privateChannel: case .privateChannel:
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation

View File

@ -66,4 +66,8 @@ public struct ExportedInvitation: PostboxCoding, Equatable {
public static func ==(lhs: ExportedInvitation, rhs: ExportedInvitation) -> Bool { public static func ==(lhs: ExportedInvitation, rhs: ExportedInvitation) -> Bool {
return lhs.link == rhs.link && lhs.isPermanent == rhs.isPermanent && lhs.isRevoked == rhs.isRevoked && lhs.adminId == rhs.adminId && lhs.date == rhs.date && lhs.startDate == rhs.startDate && lhs.expireDate == rhs.expireDate && lhs.usageLimit == rhs.usageLimit && lhs.count == rhs.count return lhs.link == rhs.link && lhs.isPermanent == rhs.isPermanent && lhs.isRevoked == rhs.isRevoked && lhs.adminId == rhs.adminId && lhs.date == rhs.date && lhs.startDate == rhs.startDate && lhs.expireDate == rhs.expireDate && lhs.usageLimit == rhs.usageLimit && lhs.count == rhs.count
} }
public func withUpdated(isRevoked: Bool) -> ExportedInvitation {
return ExportedInvitation(link: self.link, isPermanent: self.isPermanent, isRevoked: isRevoked, adminId: self.adminId, date: self.date, startDate: self.startDate, expireDate: self.expireDate, usageLimit: self.usageLimit, count: self.count)
}
} }

View File

@ -3942,15 +3942,16 @@ public extension Api {
}) })
} }
public static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ExportedChatInvites>) { public static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser?, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ExportedChatInvites>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1838984707) buffer.appendInt32(1785900140)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {adminId!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {adminId!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", flags), ("peer", peer), ("adminId", adminId), ("offsetLink", offsetLink), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", flags), ("peer", peer), ("adminId", adminId), ("offsetDate", offsetDate), ("offsetLink", offsetLink), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.messages.ExportedChatInvites? var result: Api.messages.ExportedChatInvites?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -88,58 +88,6 @@ public func createPeerExportedInvitation(account: Account, peerId: PeerId, expir
|> switchToLatest |> switchToLatest
} }
public struct ExportedInvitations : Equatable {
public let list: [ExportedInvitation]?
public let totalCount: Int32
}
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, offsetLink: String? = nil) -> Signal<ExportedInvitations?, NoError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var flags: Int32 = 0
if let _ = offsetLink {
flags |= (1 << 2)
}
if revoked {
flags |= (1 << 3)
}
return account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetLink: offsetLink, limit: 50))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<ExportedInvitations?, NoError> in
return account.postbox.transaction { transaction -> ExportedInvitations? in
if let result = result, case let .exportedChatInvites(count, apiInvites, users) = result {
var peers: [Peer] = []
var peersMap: [PeerId: Peer] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
peersMap[telegramUser.id] = telegramUser
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
var invites: [ExportedInvitation] = []
for apiInvite in apiInvites {
if let invite = ExportedInvitation(apiExportedInvite: apiInvite) {
invites.append(invite)
}
}
return ExportedInvitations(list: invites, totalCount: count)
} else {
return nil
}
}
}
} else {
return .single(nil)
}
} |> switchToLatest
}
public enum EditPeerExportedInvitationError { public enum EditPeerExportedInvitationError {
case generic case generic
} }
@ -375,6 +323,7 @@ private final class PeerExportedInvitationsContextImpl {
|> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in |> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in
if let inputPeer = inputPeer { if let inputPeer = inputPeer {
let offsetLink = lastResult?.link let offsetLink = lastResult?.link
let offsetDate = lastResult?.date
var flags: Int32 = 0 var flags: Int32 = 0
if let _ = offsetLink { if let _ = offsetLink {
flags |= (1 << 2) flags |= (1 << 2)
@ -382,7 +331,7 @@ private final class PeerExportedInvitationsContextImpl {
if revoked { if revoked {
flags |= (1 << 3) flags |= (1 << 3)
} }
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100)) let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetDate: offsetDate, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in |> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
return .single(nil) return .single(nil)

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_sharelink.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -615,7 +615,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
case .channel: case .channel:
let status = context.account.viewTracker.peerView(peerId, updateData: false) let status = context.account.viewTracker.peerView(peerId, updateData: false)
|> map { peerView -> PeerInfoStatusData? in |> map { peerView -> PeerInfoStatusData? in
guard let channel = peerView.peers[peerId] as? TelegramChannel else { guard let _ = peerView.peers[peerId] as? TelegramChannel else {
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false) return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false)
} }
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount != 0 { if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount != 0 {
@ -654,11 +654,17 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
discussionPeer = peer discussionPeer = peer
} }
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)), currentInvitationsContext == nil { if currentInvitationsContext == nil {
var canManageInvitations = false
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
canManageInvitations = true
}
if canManageInvitations {
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true) let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext)) invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init)) invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
} }
}
return PeerInfoScreenData( return PeerInfoScreenData(
peer: peerView.peers[peerId], peer: peerView.peers[peerId],
@ -805,11 +811,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
} }
} }
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role, currentInvitationsContext == nil { if currentInvitationsContext == nil {
var canManageInvitations = false
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
canManageInvitations = true
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
canManageInvitations = true
}
if canManageInvitations {
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true) let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext)) invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init)) invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
} }
}
return PeerInfoScreenData( return PeerInfoScreenData(
peer: peerView.peers[groupId], peer: peerView.peers[groupId],

View File

@ -1185,6 +1185,17 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
discussionGroupTitle = "..." discussionGroupTitle = "..."
} }
let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
} else {
invitesText = ""
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
interaction.editingOpenInviteLinksSetup()
}))
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, action: { items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, action: {
interaction.editingOpenDiscussionGroupSetup() interaction.editingOpenDiscussionGroupSetup()
})) }))
@ -4559,16 +4570,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return return
} }
strongSelf.view.endEditing(true) strongSelf.view.endEditing(true)
let mode: ChannelVisibilityControllerMode
if groupPeer.addressName != nil {
let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: .generic, upgradedToSupergroup: { _, f in f() }, onDismissRemoveController: contactsController)
//visibilityController.navigationPresentation = .modal
contactsController?.push(visibilityController)
} else {
contactsController?.present(InviteLinkInviteController(context: context, peerId: groupPeer.id, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root)) contactsController?.present(InviteLinkInviteController(context: context, peerId: groupPeer.id, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root))
} }
}
strongSelf.controller?.push(contactsController) strongSelf.controller?.push(contactsController)
let selectAddMemberDisposable = strongSelf.selectAddMemberDisposable let selectAddMemberDisposable = strongSelf.selectAddMemberDisposable