mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branches 'master' and 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
c7292fa83f
@ -5842,6 +5842,7 @@ Sorry for the inconvenience.";
|
|||||||
"InviteLink.Expired" = "expired";
|
"InviteLink.Expired" = "expired";
|
||||||
"InviteLink.UsageLimitReached" = "limit reached";
|
"InviteLink.UsageLimitReached" = "limit reached";
|
||||||
"InviteLink.Revoked" = "revoked";
|
"InviteLink.Revoked" = "revoked";
|
||||||
|
"InviteLink.TapToCopy" = "tap to copy";
|
||||||
|
|
||||||
"InviteLink.AdditionalLinks" = "Additional Links";
|
"InviteLink.AdditionalLinks" = "Additional Links";
|
||||||
"InviteLink.Create" = "Create a New Link";
|
"InviteLink.Create" = "Create a New Link";
|
||||||
@ -5882,9 +5883,11 @@ Sorry for the inconvenience.";
|
|||||||
"InviteLink.InviteLink" = "Invite Link";
|
"InviteLink.InviteLink" = "Invite Link";
|
||||||
"InviteLink.CreatedBy" = "Link Created By";
|
"InviteLink.CreatedBy" = "Link Created By";
|
||||||
|
|
||||||
"InviteLink.DeleteAllRevokedLinksAlert.Text" = "This will delete all revoked links";
|
"InviteLink.DeleteAllRevokedLinksAlert.Text" = "This will delete all revoked links.";
|
||||||
"InviteLink.DeleteAllRevokedLinksAlert.Action" = "Delete";
|
"InviteLink.DeleteAllRevokedLinksAlert.Action" = "Delete";
|
||||||
|
|
||||||
|
"InviteLink.ExpiresIn" = "expires in %@";
|
||||||
|
|
||||||
"Conversation.ChecksTooltip.Delivered" = "Delivered";
|
"Conversation.ChecksTooltip.Delivered" = "Delivered";
|
||||||
"Conversation.ChecksTooltip.Read" = "Read";
|
"Conversation.ChecksTooltip.Read" = "Read";
|
||||||
|
|
||||||
|
@ -45,25 +45,39 @@ private struct InviteLinkInviteTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum InviteLinkInviteEntryId: Hashable {
|
private enum InviteLinkInviteEntryId: Hashable {
|
||||||
|
case header
|
||||||
case mainLink
|
case mainLink
|
||||||
case links(Int32)
|
case links(Int32)
|
||||||
|
case manage
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||||
|
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)
|
||||||
|
|
||||||
var stableId: InviteLinkInviteEntryId {
|
var stableId: InviteLinkInviteEntryId {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .header:
|
||||||
|
return .header
|
||||||
case .mainLink:
|
case .mainLink:
|
||||||
return .mainLink
|
return .mainLink
|
||||||
case let .links(index, _, _):
|
case let .links(index, _, _):
|
||||||
return .links(index)
|
return .links(index)
|
||||||
|
case .manage:
|
||||||
|
return .manage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: InviteLinkInviteEntry, rhs: InviteLinkInviteEntry) -> Bool {
|
static func ==(lhs: InviteLinkInviteEntry, rhs: InviteLinkInviteEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
|
case let .header(lhsTheme, lhsTitle, lhsText):
|
||||||
|
if case let .header(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .mainLink(lhsTheme, lhsInvitation):
|
case let .mainLink(lhsTheme, lhsInvitation):
|
||||||
if case let .mainLink(rhsTheme, rhsInvitation) = rhs, lhsTheme === rhsTheme, lhsInvitation == rhsInvitation {
|
if case let .mainLink(rhsTheme, rhsInvitation) = rhs, lhsTheme === rhsTheme, lhsInvitation == rhsInvitation {
|
||||||
return true
|
return true
|
||||||
@ -76,43 +90,71 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .manage(lhsTheme, lhsText):
|
||||||
|
if case let .manage(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func <(lhs: InviteLinkInviteEntry, rhs: InviteLinkInviteEntry) -> Bool {
|
static func <(lhs: InviteLinkInviteEntry, rhs: InviteLinkInviteEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
|
case .header:
|
||||||
|
switch rhs {
|
||||||
|
case .header:
|
||||||
|
return false
|
||||||
|
case .mainLink, .links, .manage:
|
||||||
|
return true
|
||||||
|
}
|
||||||
case .mainLink:
|
case .mainLink:
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .mainLink:
|
case .header, .mainLink:
|
||||||
return false
|
return false
|
||||||
case .links:
|
case .links, .manage:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .links(lhsIndex, _, _):
|
case let .links(lhsIndex, _, _):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .mainLink:
|
case .header, .mainLink:
|
||||||
return false
|
return false
|
||||||
case let .links(rhsIndex, _, _):
|
case let .links(rhsIndex, _, _):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
|
case .manage:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .manage:
|
||||||
|
switch rhs {
|
||||||
|
case .header, .mainLink, .links:
|
||||||
|
return false
|
||||||
|
case .manage:
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(account: Account, presentationData: PresentationData, interaction: InviteLinkInviteInteraction) -> ListViewItem {
|
func item(account: Account, presentationData: PresentationData, interaction: InviteLinkInviteInteraction) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
|
case let .header(theme, title, 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: [], buttonColor: nil, sectionId: 0, style: .plain, shareAction: {
|
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, shareAction: {
|
||||||
interaction.shareLink(invite)
|
interaction.shareLink(invite)
|
||||||
}, contextAction: { node in
|
}, contextAction: { node in
|
||||||
interaction.mainLinkContextAction(invite, node, nil)
|
interaction.mainLinkContextAction(invite, node, nil)
|
||||||
}, viewAction: {
|
}, viewAction: {
|
||||||
})
|
})
|
||||||
case let .links(_, _, invites):
|
case let .links(_, _, invites):
|
||||||
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, sectionId: 0, style: .plain, tapAction: { invite in
|
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, share: true, sectionId: 0, 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):
|
||||||
|
return InviteLinkInviteManageItem(theme: theme, text: text, action: {
|
||||||
|
interaction.manageLinks()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,13 +178,14 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peerId: PeerId
|
private let peerId: PeerId
|
||||||
|
private weak var parentNavigationController: NavigationController?
|
||||||
|
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
public init(context: AccountContext, peerId: PeerId) {
|
public init(context: AccountContext, peerId: PeerId, parentNavigationController: NavigationController?) {
|
||||||
fatalError()
|
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
|
self.parentNavigationController = parentNavigationController
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
@ -196,7 +239,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
self.controllerNode.animateOut(completion: { [weak self] in
|
self.controllerNode.animateOut(completion: { [weak self] in
|
||||||
completion?()
|
completion?()
|
||||||
self?.dismiss(animated: false)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -348,7 +391,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self?.controller?.present(shareController, in: .window(.root))
|
self?.controller?.present(shareController, in: .window(.root))
|
||||||
}, manageLinks: { [weak self] in
|
}, manageLinks: { [weak self] in
|
||||||
let controller = inviteLinkListController(context: context, peerId: peerId)
|
let controller = inviteLinkListController(context: context, peerId: peerId)
|
||||||
self?.controller?.push(controller)
|
self?.controller?.parentNavigationController?.pushViewController(controller)
|
||||||
self?.controller?.dismiss()
|
self?.controller?.dismiss()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -360,12 +403,15 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
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))
|
||||||
|
|
||||||
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))
|
entries.append(.mainLink(presentationData.theme, 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))
|
entries.append(.mainLink(presentationData.theme, invite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||||
|
|
||||||
let previousEntries = previousEntries.swap(entries)
|
let previousEntries = previousEntries.swap(entries)
|
||||||
|
|
||||||
@ -507,14 +553,14 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
insets.bottom = layout.intrinsicInsets.bottom
|
insets.bottom = layout.intrinsicInsets.bottom
|
||||||
|
|
||||||
let headerHeight: CGFloat = 54.0
|
let headerHeight: CGFloat = 54.0
|
||||||
let visibleItemsHeight: CGFloat = 147.0 + floor(52.0 * 3.5)
|
let visibleItemsHeight: CGFloat = 409.0
|
||||||
|
|
||||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
|
|
||||||
let listTopInset = layoutTopInset + headerHeight
|
let listTopInset = layoutTopInset + headerHeight
|
||||||
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset)
|
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset)
|
||||||
|
|
||||||
insets.top = max(0.0, listNodeSize.height - visibleItemsHeight)
|
insets.top = max(0.0, listNodeSize.height - visibleItemsHeight - insets.bottom)
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, duration: duration, curve: curve)
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, duration: duration, curve: curve)
|
||||||
|
@ -13,10 +13,12 @@ class InviteLinkInviteHeaderItem: ListViewItem, ItemListItem {
|
|||||||
var sectionId: ItemListSectionId = 0
|
var sectionId: ItemListSectionId = 0
|
||||||
|
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let title: String
|
||||||
let text: String
|
let text: String
|
||||||
|
|
||||||
init(theme: PresentationTheme, text: String) {
|
init(theme: PresentationTheme, title: String, text: String) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,58 +59,86 @@ class InviteLinkInviteHeaderItem: ListViewItem, ItemListItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let titleFont = Font.regular(13.0)
|
private let titleFont = Font.medium(23.0)
|
||||||
|
private let textFont = Font.regular(13.0)
|
||||||
|
|
||||||
class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private var animationNode: AnimatedStickerNode
|
private let textNode: TextNode
|
||||||
|
private let iconBackgroundNode: ASImageNode
|
||||||
|
private let iconNode: ASImageNode
|
||||||
|
|
||||||
private var item: InviteLinkInviteHeaderItem?
|
private var item: InviteLinkInviteHeaderItem?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
self.titleNode.contentMode = .left
|
|
||||||
self.titleNode.contentsScale = UIScreen.main.scale
|
|
||||||
|
|
||||||
self.animationNode = AnimatedStickerNode()
|
self.textNode = TextNode()
|
||||||
if let path = getAppBundle().path(forResource: "Invite", ofType: "tgs") {
|
self.textNode.isUserInteractionEnabled = false
|
||||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
|
||||||
self.animationNode.visibility = true
|
self.iconBackgroundNode = ASImageNode()
|
||||||
}
|
self.iconBackgroundNode.displaysAsynchronously = false
|
||||||
|
self.iconBackgroundNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
|
self.iconNode = ASImageNode()
|
||||||
|
self.iconNode.contentMode = .center
|
||||||
|
self.iconNode.displaysAsynchronously = false
|
||||||
|
self.iconNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.animationNode)
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.iconBackgroundNode)
|
||||||
|
self.addSubnode(self.iconNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: InviteLinkInviteHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: InviteLinkInviteHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
|
let currentItem = self.item
|
||||||
|
|
||||||
return { item, params, neighbors in
|
return { item, params, neighbors in
|
||||||
let leftInset: CGFloat = 32.0 + params.leftInset
|
let leftInset: CGFloat = 40.0 + params.leftInset
|
||||||
let topInset: CGFloat = 92.0
|
let topInset: CGFloat = 98.0
|
||||||
|
let spacing: CGFloat = 8.0
|
||||||
|
let bottomInset: CGFloat = 24.0
|
||||||
|
|
||||||
let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor)
|
var updatedTheme: PresentationTheme?
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
if currentItem?.theme !== item.theme {
|
||||||
|
updatedTheme = item.theme
|
||||||
|
}
|
||||||
|
|
||||||
let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height)
|
let titleAttributedText = NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let attributedText = NSAttributedString(string: item.text, font: textFont, textColor: item.theme.list.freeTextColor)
|
||||||
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height + spacing + textLayout.size.height + bottomInset)
|
||||||
|
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
|
||||||
|
|
||||||
return (layout, { [weak self] in
|
return (layout, { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
strongSelf.accessibilityLabel = attributedText.string
|
strongSelf.accessibilityLabel = attributedText.string
|
||||||
|
|
||||||
|
if let _ = updatedTheme {
|
||||||
|
strongSelf.iconBackgroundNode.image = generateFilledCircleImage(diameter: 92.0, color: item.theme.actionSheet.controlAccentColor)
|
||||||
|
strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/LargeLink"), color: item.theme.list.itemCheckColors.foregroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
let iconSize = CGSize(width: 96.0, height: 96.0)
|
let iconSize = CGSize(width: 92.0, height: 92.0)
|
||||||
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
|
strongSelf.iconBackgroundNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
|
||||||
strongSelf.animationNode.updateLayout(size: iconSize)
|
strongSelf.iconNode.frame = strongSelf.iconBackgroundNode.frame
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 8.0), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 8.0), size: titleLayout.size)
|
||||||
|
|
||||||
|
let _ = textApply()
|
||||||
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: topInset + 8.0 + titleLayout.size.height + spacing), size: textLayout.size)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ItemListUI
|
||||||
|
import PresentationDataUtils
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import AppBundle
|
||||||
|
|
||||||
|
class InviteLinkInviteManageItem: ListViewItem, ItemListItem {
|
||||||
|
var sectionId: ItemListSectionId = 0
|
||||||
|
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, text: String, action: @escaping () -> Void) {
|
||||||
|
self.theme = theme
|
||||||
|
self.text = text
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
|
async {
|
||||||
|
let node = InviteLinkInviteManageItemNode()
|
||||||
|
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
|
||||||
|
node.contentSize = layout.contentSize
|
||||||
|
node.insets = layout.insets
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(node, {
|
||||||
|
return (nil, { _ in apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let nodeValue = node() as? InviteLinkInviteManageItemNode else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let makeLayout = nodeValue.asyncLayout()
|
||||||
|
|
||||||
|
async {
|
||||||
|
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(layout, { _ in
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let titleFont = Font.medium(23.0)
|
||||||
|
private let textFont = Font.regular(13.0)
|
||||||
|
|
||||||
|
class InviteLinkInviteManageItemNode: ListViewItemNode {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let buttonNode: HighlightableButtonNode
|
||||||
|
|
||||||
|
private var item: InviteLinkInviteManageItem?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.buttonNode = HighlightableButtonNode()
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.item?.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncLayout() -> (_ item: InviteLinkInviteManageItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
return { item, params, neighbors in
|
||||||
|
let contentSize = CGSize(width: params.width, height: 70.0)
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
|
||||||
|
|
||||||
|
return (layout, { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
|
||||||
|
strongSelf.buttonNode.setTitle(item.text, with: Font.regular(17.0), with: item.theme.actionSheet.controlAccentColor, for: .normal)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
@ -178,7 +178,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
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):
|
||||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: peers, buttonColor: nil, sectionId: self.section, style: .blocks, shareAction: {
|
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: peers, displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, shareAction: {
|
||||||
arguments.shareMainLink(invite)
|
arguments.shareMainLink(invite)
|
||||||
}, contextAction: { node in
|
}, contextAction: { node in
|
||||||
arguments.mainLinkContextAction(invite, node, nil)
|
arguments.mainLinkContextAction(invite, node, nil)
|
||||||
@ -194,7 +194,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
arguments.createLink()
|
arguments.createLink()
|
||||||
})
|
})
|
||||||
case let .links(_, _, invites):
|
case let .links(_, _, invites):
|
||||||
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, sectionId: self.section, style: .blocks, tapAction: { invite in
|
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||||
arguments.openLink(invite)
|
arguments.openLink(invite)
|
||||||
}, contextAction: { invite, node in
|
}, contextAction: { invite, node in
|
||||||
arguments.linkContextAction(invite, node, nil)
|
arguments.linkContextAction(invite, node, nil)
|
||||||
@ -208,7 +208,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
arguments.deleteAllRevokedLinks()
|
arguments.deleteAllRevokedLinks()
|
||||||
})
|
})
|
||||||
case let .revokedLinks(_, _, invites):
|
case let .revokedLinks(_, _, invites):
|
||||||
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, sectionId: self.section, style: .blocks, tapAction: { invite in
|
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||||
arguments.openLink(invite)
|
arguments.openLink(invite)
|
||||||
}, contextAction: { invite, node in
|
}, contextAction: { invite, node in
|
||||||
arguments.linkContextAction(invite, node, nil)
|
arguments.linkContextAction(invite, node, nil)
|
||||||
@ -468,8 +468,8 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
|
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
|
||||||
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||||
dismissAction()
|
dismissAction()
|
||||||
|
|
||||||
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
revokeLinkDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
||||||
|
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
@ -209,7 +209,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
self.contentContainerNode.addSubnode(self.qrIconNode)
|
self.contentContainerNode.addSubnode(self.qrIconNode)
|
||||||
self.contentContainerNode.addSubnode(self.qrButtonNode)
|
self.contentContainerNode.addSubnode(self.qrButtonNode)
|
||||||
|
|
||||||
let textFont = Font.regular(16.0)
|
let textFont = Font.regular(13.0)
|
||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_QRCode_Info, font: textFont, textColor: secondaryTextColor)
|
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_QRCode_Info, font: textFont, textColor: secondaryTextColor)
|
||||||
self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share
|
self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share
|
||||||
@ -352,7 +352,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
|
|
||||||
let _ = imageApply()
|
let _ = imageApply()
|
||||||
|
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: insets.top + 24.0), size: imageSize)
|
let imageFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: insets.top + 16.0), size: imageSize)
|
||||||
transition.updateFrame(node: self.qrImageNode, frame: imageFrame)
|
transition.updateFrame(node: self.qrImageNode, frame: imageFrame)
|
||||||
transition.updateFrame(node: self.qrButtonNode, frame: imageFrame)
|
transition.updateFrame(node: self.qrButtonNode, frame: imageFrame)
|
||||||
|
|
||||||
@ -365,7 +365,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
|
|
||||||
let inset: CGFloat = 22.0
|
let inset: CGFloat = 22.0
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: imageFrame.maxX + 20.0), size: textSize)
|
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: imageFrame.maxY + 20.0), size: textSize)
|
||||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||||
|
|
||||||
let buttonSideInset: CGFloat = 16.0
|
let buttonSideInset: CGFloat = 16.0
|
||||||
@ -379,7 +379,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
|
|
||||||
|
|
||||||
let titleHeight: CGFloat = 54.0
|
let titleHeight: CGFloat = 54.0
|
||||||
let contentHeight = titleHeight + textSize.height + imageSize.height + bottomInset + 52.0 + 77.0
|
let contentHeight = titleHeight + textSize.height + imageSize.height + bottomInset + 121.0
|
||||||
|
|
||||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
||||||
|
|
||||||
@ -401,7 +401,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||||
|
|
||||||
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight))
|
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight))
|
||||||
let cancelFrame = CGRect(origin: CGPoint(x: width - cancelSize.width - 16.0, y: 16.0), size: cancelSize)
|
let cancelFrame = CGRect(origin: CGPoint(x: width - cancelSize.width - 16.0, y: 18.0), size: cancelSize)
|
||||||
transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
|
transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
|
||||||
|
|
||||||
let buttonInset: CGFloat = 16.0
|
let buttonInset: CGFloat = 16.0
|
||||||
|
@ -72,7 +72,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
case creatorHeader(PresentationTheme, String)
|
case creatorHeader(PresentationTheme, String)
|
||||||
case creator(PresentationTheme, PresentationDateTimeFormat, Peer, Int32)
|
case creator(PresentationTheme, PresentationDateTimeFormat, Peer, Int32)
|
||||||
case importerHeader(PresentationTheme, String)
|
case importerHeader(PresentationTheme, String)
|
||||||
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, Peer, Int32)
|
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, Peer, Int32, Bool)
|
||||||
|
|
||||||
var stableId: InviteLinkViewEntryId {
|
var stableId: InviteLinkViewEntryId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -84,7 +84,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
return .creator
|
return .creator
|
||||||
case .importerHeader:
|
case .importerHeader:
|
||||||
return .importerHeader
|
return .importerHeader
|
||||||
case let .importer(_, _, _, peer, _):
|
case let .importer(_, _, _, peer, _, _):
|
||||||
return .importer(peer.id)
|
return .importer(peer.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,8 +115,8 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate):
|
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsLoading):
|
||||||
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate {
|
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -154,11 +154,11 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
case .creator, .importer:
|
case .creator, .importer:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .importer(lhsIndex, _, _, _, _):
|
case let .importer(lhsIndex, _, _, _, _, _):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .link, .creatorHeader, .creator, .importerHeader:
|
case .link, .creatorHeader, .creator, .importerHeader:
|
||||||
return false
|
return false
|
||||||
case let .importer(rhsIndex, _, _, _, _):
|
case let .importer(rhsIndex, _, _, _, _, _):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,7 +168,8 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .link(_, invite):
|
case let .link(_, invite):
|
||||||
let buttonColor = color(for: invite)
|
let buttonColor = color(for: invite)
|
||||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], buttonColor: buttonColor, sectionId: 0, style: .plain, shareAction: {
|
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, shareAction: {
|
||||||
interaction.shareLink(invite)
|
interaction.shareLink(invite)
|
||||||
}, contextAction: { node in
|
}, contextAction: { node in
|
||||||
interaction.contextAction(invite, node, nil)
|
interaction.contextAction(invite, node, nil)
|
||||||
@ -183,11 +184,11 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil)
|
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil)
|
||||||
case let .importerHeader(_, title):
|
case let .importerHeader(_, title):
|
||||||
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
|
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
|
||||||
case let .importer(_, _, dateTimeFormat, peer, date):
|
case let .importer(_, _, dateTimeFormat, peer, date, loading):
|
||||||
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: {
|
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: {
|
||||||
interaction.openPeer(peer.id)
|
interaction.openPeer(peer.id)
|
||||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil)
|
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil, shimmering: ItemListPeerItemShimmering(alternationIndex: 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,7 +404,16 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root))
|
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root))
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if !invite.isRevoked {
|
if invite.isRevoked {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextDelete, textColor: .destructive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
|
}, action: { [weak self] _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||||
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
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: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
@ -430,16 +440,23 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
entries.append(.creatorHeader(presentationData.theme, presentationData.strings.InviteLink_CreatedBy.uppercased()))
|
entries.append(.creatorHeader(presentationData.theme, presentationData.strings.InviteLink_CreatedBy.uppercased()))
|
||||||
entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, creatorPeer, invite.date))
|
entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, creatorPeer, invite.date))
|
||||||
|
|
||||||
if !state.importers.isEmpty {
|
if !state.importers.isEmpty || (state.isLoadingMore && state.count > 0) {
|
||||||
entries.append(.importerHeader(presentationData.theme, presentationData.strings.InviteLink_PeopleJoined(Int32(state.count)).uppercased()))
|
entries.append(.importerHeader(presentationData.theme, presentationData.strings.InviteLink_PeopleJoined(Int32(state.count)).uppercased()))
|
||||||
}
|
}
|
||||||
|
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for importer in state.importers {
|
if state.importers.isEmpty && state.isLoadingMore {
|
||||||
if let peer = importer.peer.peer {
|
let fakeUser = TelegramUser(id: PeerId(namespace: -1, id: 0), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
|
||||||
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, peer, importer.date))
|
for i in 0 ..< min(4, state.count) {
|
||||||
|
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, fakeUser, 0, true))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for importer in state.importers {
|
||||||
|
if let peer = importer.peer.peer {
|
||||||
|
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, peer, importer.date, false))
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
}
|
}
|
||||||
index += 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let previousEntries = previousEntries.swap(entries)
|
let previousEntries = previousEntries.swap(entries)
|
||||||
|
@ -42,10 +42,35 @@ func invitationAvailability(_ invite: ExportedInvitation) -> CGFloat {
|
|||||||
let fraction = 1.0 - (CGFloat(count) / CGFloat(usageLimit))
|
let fraction = 1.0 - (CGFloat(count) / CGFloat(usageLimit))
|
||||||
availability = min(fraction, availability)
|
availability = min(fraction, availability)
|
||||||
}
|
}
|
||||||
return availability
|
return max(0.0, min(1.0, availability))
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ItemBackgroundColor: Equatable {
|
||||||
|
case blue
|
||||||
|
case green
|
||||||
|
case yellow
|
||||||
|
case red
|
||||||
|
case gray
|
||||||
|
|
||||||
|
var colors: (top: UIColor, bottom: UIColor, text: UIColor) {
|
||||||
|
switch self {
|
||||||
|
case .blue:
|
||||||
|
return (UIColor(rgb: 0x00b5f7), UIColor(rgb: 0x00b2f6), UIColor(rgb: 0xa7f4ff))
|
||||||
|
case .green:
|
||||||
|
return (UIColor(rgb: 0x4aca62), UIColor(rgb: 0x43c85c), UIColor(rgb: 0xc5ffe6))
|
||||||
|
case .yellow:
|
||||||
|
return (UIColor(rgb: 0xf8a953), UIColor(rgb: 0xf7a64e), UIColor(rgb: 0xfeffd7))
|
||||||
|
case .red:
|
||||||
|
return (UIColor(rgb: 0xf2656a), UIColor(rgb: 0xf25f65), UIColor(rgb: 0xffd3de))
|
||||||
|
case .gray:
|
||||||
|
return (UIColor(rgb: 0xd4d8db), UIColor(rgb: 0xced2d5), UIColor(rgb: 0xf8f9f9))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ItemNode: ASDisplayNode {
|
private class ItemNode: ASDisplayNode {
|
||||||
|
private let selectionNode: HighlightTrackingButtonNode
|
||||||
|
private let wrapperNode: ASDisplayNode
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
@ -59,19 +84,28 @@ private class ItemNode: ASDisplayNode {
|
|||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let subtitleNode: ImmediateTextNode
|
private let subtitleNode: ImmediateTextNode
|
||||||
|
|
||||||
private var params: (size: CGSize, wide: Bool, invite: ExportedInvitation, presentationData: ItemListPresentationData)?
|
private var updateTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
private var params: (size: CGSize, wide: Bool, invite: ExportedInvitation, color: ItemBackgroundColor, presentationData: ItemListPresentationData)?
|
||||||
|
|
||||||
var action: (() -> Void)?
|
var action: (() -> Void)?
|
||||||
var contextAction: ((ASDisplayNode) -> Void)?
|
var contextAction: ((ASDisplayNode) -> Void)?
|
||||||
|
|
||||||
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
|
self.selectionNode = HighlightTrackingButtonNode()
|
||||||
|
self.wrapperNode = ASDisplayNode()
|
||||||
|
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
self.backgroundNode.displaysAsynchronously = false
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
self.backgroundNode.displayWithoutProcessing = true
|
self.backgroundNode.displayWithoutProcessing = true
|
||||||
|
self.backgroundNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.iconNode = ASImageNode()
|
self.iconNode = ASImageNode()
|
||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
self.iconNode.displayWithoutProcessing = true
|
self.iconNode.displayWithoutProcessing = true
|
||||||
|
self.iconNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
||||||
@ -94,23 +128,41 @@ private class ItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
self.titleNode.maximumNumberOfLines = 2
|
self.titleNode.maximumNumberOfLines = 2
|
||||||
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.subtitleNode = ImmediateTextNode()
|
self.subtitleNode = ImmediateTextNode()
|
||||||
self.subtitleNode.maximumNumberOfLines = 1
|
self.subtitleNode.maximumNumberOfLines = 1
|
||||||
|
self.subtitleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.wrapperNode)
|
||||||
self.addSubnode(self.iconNode)
|
self.wrapperNode.addSubnode(self.backgroundNode)
|
||||||
|
self.wrapperNode.addSubnode(self.iconNode)
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.extractedContainerNode)
|
self.containerNode.addSubnode(self.extractedContainerNode)
|
||||||
self.extractedContainerNode.contentNode.addSubnode(self.buttonIconNode)
|
self.extractedContainerNode.contentNode.addSubnode(self.buttonIconNode)
|
||||||
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
||||||
self.buttonNode.addSubnode(self.containerNode)
|
self.buttonNode.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.buttonNode)
|
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.wrapperNode.addSubnode(self.selectionNode)
|
||||||
self.addSubnode(self.subtitleNode)
|
self.wrapperNode.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
|
self.wrapperNode.addSubnode(self.titleNode)
|
||||||
|
self.wrapperNode.addSubnode(self.subtitleNode)
|
||||||
|
|
||||||
|
self.selectionNode.addTarget(self, action: #selector(self.tapped), forControlEvents: .touchUpInside)
|
||||||
|
self.selectionNode.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .linear)
|
||||||
|
transition.updateSublayerTransformScale(node: strongSelf, scale: 0.95)
|
||||||
|
} else {
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .linear)
|
||||||
|
transition.updateSublayerTransformScale(node: strongSelf, scale: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||||
@ -126,11 +178,12 @@ private class ItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
deinit {
|
||||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
self.updateTimer?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
|
@objc private func tapped() {
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
self.action?()
|
self.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,35 +191,66 @@ private class ItemNode: ASDisplayNode {
|
|||||||
self.contextAction?(self.extractedContainerNode)
|
self.contextAction?(self.extractedContainerNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, wide: Bool, invite: ExportedInvitation, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
func update(size: CGSize, wide: Bool, share: Bool, invite: ExportedInvitation, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
let updated = self.params?.size != size || self.params?.wide != wide || self.params?.invite != invite
|
|
||||||
self.params = (size, wide, invite, presentationData)
|
|
||||||
|
|
||||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
|
||||||
let availability = invitationAvailability(invite)
|
let availability = invitationAvailability(invite)
|
||||||
|
let color: ItemBackgroundColor
|
||||||
var isExpired = false
|
|
||||||
let secondaryTextColor: UIColor
|
|
||||||
if invite.isRevoked {
|
if invite.isRevoked {
|
||||||
self.backgroundNode.image = generateBackgroundImage(colors: [UIColor(rgb: 0xd4d8db).cgColor, UIColor(rgb: 0xced2d5).cgColor])
|
color = .gray
|
||||||
secondaryTextColor = UIColor(rgb: 0xf8f9f9)
|
|
||||||
} else if invite.expireDate == nil && invite.usageLimit == nil {
|
} else if invite.expireDate == nil && invite.usageLimit == nil {
|
||||||
self.backgroundNode.image = generateBackgroundImage(colors: [UIColor(rgb: 0x00b5f7).cgColor, UIColor(rgb: 0x00b2f6).cgColor])
|
color = .blue
|
||||||
secondaryTextColor = UIColor(rgb: 0xa7f4ff)
|
|
||||||
} else if availability >= 0.5 {
|
} else if availability >= 0.5 {
|
||||||
self.backgroundNode.image = generateBackgroundImage(colors: [UIColor(rgb: 0x4aca62).cgColor, UIColor(rgb: 0x43c85c).cgColor])
|
color = .green
|
||||||
secondaryTextColor = UIColor(rgb: 0xc5ffe6)
|
|
||||||
} else if availability > 0.0 {
|
} else if availability > 0.0 {
|
||||||
self.backgroundNode.image = generateBackgroundImage(colors: [UIColor(rgb: 0xf8a953).cgColor, UIColor(rgb: 0xf7a64e).cgColor])
|
color = .yellow
|
||||||
secondaryTextColor = UIColor(rgb: 0xfeffd7)
|
|
||||||
} else {
|
} else {
|
||||||
self.backgroundNode.image = generateBackgroundImage(colors: [UIColor(rgb: 0xf2656a).cgColor, UIColor(rgb: 0xf25f65).cgColor])
|
color = .red
|
||||||
secondaryTextColor = UIColor(rgb: 0xffd3de)
|
|
||||||
isExpired = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemWidth = wide ? size.width : floor((size.width - itemSpacing) / 2.0)
|
let previousParams = self.params
|
||||||
|
self.params = (size, wide, invite, color, presentationData)
|
||||||
|
|
||||||
|
let previousExpireDate = previousParams?.invite.expireDate
|
||||||
|
if previousExpireDate != invite.expireDate {
|
||||||
|
self.updateTimer?.invalidate()
|
||||||
|
self.updateTimer = nil
|
||||||
|
|
||||||
|
if let _ = invite.expireDate, availability > 0.0 {
|
||||||
|
let updateTimer = SwiftSignalKit.Timer(timeout: 5.0, repeat: true, completion: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if let (size, wide, invite, _, presentationData) = strongSelf.params {
|
||||||
|
let _ = strongSelf.update(size: size, wide: wide, share: share, invite: invite, presentationData: presentationData, transition: .animated(duration: 0.3, curve: .linear))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.updateTimer = updateTimer
|
||||||
|
updateTimer.start()
|
||||||
|
}
|
||||||
|
} else if availability.isZero {
|
||||||
|
self.updateTimer?.invalidate()
|
||||||
|
self.updateTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let colors: NSArray = [color.colors.top.cgColor, color.colors.bottom.cgColor]
|
||||||
|
if let (_, _, previousInvite, previousColor, _) = previousParams, previousInvite == invite {
|
||||||
|
if previousColor != color {
|
||||||
|
if let snapshotView = self.wrapperNode.view.snapshotContentTree() {
|
||||||
|
snapshotView.frame = self.wrapperNode.bounds
|
||||||
|
self.wrapperNode.view.addSubview(snapshotView)
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
self.backgroundNode.image = generateBackgroundImage(colors: colors)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.backgroundNode.image = generateBackgroundImage(colors: colors)
|
||||||
|
}
|
||||||
|
|
||||||
|
let secondaryTextColor = color.colors.text
|
||||||
|
|
||||||
|
let itemWidth = wide ? size.width : floor((size.width - itemSpacing) / 2.0)
|
||||||
var inviteLink = invite.link.replacingOccurrences(of: "https://", with: "")
|
var inviteLink = invite.link.replacingOccurrences(of: "https://", with: "")
|
||||||
if !wide {
|
if !wide {
|
||||||
inviteLink = inviteLink.replacingOccurrences(of: "joinchat/", with: "joinchat/\n")
|
inviteLink = inviteLink.replacingOccurrences(of: "joinchat/", with: "joinchat/\n")
|
||||||
@ -184,7 +268,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
if let count = invite.count {
|
if let count = invite.count {
|
||||||
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
|
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
|
||||||
} else {
|
} else {
|
||||||
subtitleText = isExpired || invite.isRevoked ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
|
subtitleText = [.red, .gray].contains(color) ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
|
||||||
}
|
}
|
||||||
if invite.isRevoked {
|
if invite.isRevoked {
|
||||||
if !subtitleText.isEmpty {
|
if !subtitleText.isEmpty {
|
||||||
@ -194,7 +278,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
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
|
||||||
} else if let expireDate = invite.expireDate, currentTime > expireDate {
|
} else if let expireDate = invite.expireDate, currentTime >= expireDate {
|
||||||
if !subtitleText.isEmpty {
|
if !subtitleText.isEmpty {
|
||||||
subtitleText += " • "
|
subtitleText += " • "
|
||||||
}
|
}
|
||||||
@ -202,6 +286,14 @@ private class ItemNode: ASDisplayNode {
|
|||||||
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
|
||||||
|
} else if let usageLimit = invite.usageLimit, let count = invite.count, count >= usageLimit {
|
||||||
|
if !subtitleText.isEmpty {
|
||||||
|
subtitleText += " • "
|
||||||
|
}
|
||||||
|
subtitleText += presentationData.strings.InviteLink_UsageLimitReached
|
||||||
|
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
||||||
|
self.timerNode?.removeFromSupernode()
|
||||||
|
self.timerNode = nil
|
||||||
} else if let expireDate = invite.expireDate {
|
} else if let expireDate = invite.expireDate {
|
||||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Flame"), color: .white)
|
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Flame"), color: .white)
|
||||||
let timerNode: TimerNode
|
let timerNode: TimerNode
|
||||||
@ -209,6 +301,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
timerNode = current
|
timerNode = current
|
||||||
} else {
|
} else {
|
||||||
timerNode = TimerNode()
|
timerNode = TimerNode()
|
||||||
|
timerNode.isUserInteractionEnabled = false
|
||||||
self.timerNode = timerNode
|
self.timerNode = timerNode
|
||||||
self.addSubnode(timerNode)
|
self.addSubnode(timerNode)
|
||||||
}
|
}
|
||||||
@ -222,8 +315,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
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)
|
||||||
self.timerNode?.frame = CGRect(x: 8.0, y: 8.0, width: 34.0, height: 34.0)
|
self.timerNode?.frame = CGRect(x: 8.0, y: 8.0, width: 34.0, height: 34.0)
|
||||||
|
|
||||||
let subtitle: NSMutableAttributedString = NSMutableAttributedString(string: subtitleText, font: subtitleFont, textColor: secondaryTextColor)
|
self.subtitleNode.attributedText = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: secondaryTextColor)
|
||||||
self.subtitleNode.attributedText = subtitle
|
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: itemWidth - 24.0, height: 100.0))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: itemWidth - 24.0, height: 100.0))
|
||||||
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: itemWidth - 24.0, height: 100.0))
|
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: itemWidth - 24.0, height: 100.0))
|
||||||
@ -234,7 +326,9 @@ private class ItemNode: ASDisplayNode {
|
|||||||
let itemSize = CGSize(width: itemWidth, height: wide ? 102.0 : 122.0)
|
let itemSize = CGSize(width: itemWidth, height: wide ? 102.0 : 122.0)
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(), size: itemSize)
|
let backgroundFrame = CGRect(origin: CGPoint(), size: itemSize)
|
||||||
|
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)
|
||||||
|
|
||||||
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)
|
||||||
@ -260,7 +354,7 @@ class InviteLinksGridNode: ASDisplayNode {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, safeInset: CGFloat, items: [ExportedInvitation], presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
func update(size: CGSize, safeInset: CGFloat, items: [ExportedInvitation], share: Bool, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
self.items = items
|
self.items = items
|
||||||
|
|
||||||
var contentSize: CGSize = size
|
var contentSize: CGSize = size
|
||||||
@ -288,7 +382,7 @@ class InviteLinksGridNode: ASDisplayNode {
|
|||||||
let col = CGFloat(i % 2)
|
let col = CGFloat(i % 2)
|
||||||
let row = floor(CGFloat(i) / 2.0)
|
let row = floor(CGFloat(i) / 2.0)
|
||||||
let wide = (i == self.items.count - 1 && (self.items.count % 2) != 0)
|
let wide = (i == self.items.count - 1 && (self.items.count % 2) != 0)
|
||||||
let itemSize = itemNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: size.height), wide: wide, invite: invite, presentationData: presentationData, transition: transition)
|
let itemSize = itemNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: size.height), wide: wide, share: share, invite: invite, presentationData: presentationData, transition: transition)
|
||||||
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: 4.0 + row * (122.0 + itemSpacing)), size: itemSize)
|
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: 4.0 + row * (122.0 + itemSpacing)), size: itemSize)
|
||||||
if !wide && col > 0 {
|
if !wide && col > 0 {
|
||||||
itemFrame.origin.x += itemSpacing + itemSize.width
|
itemFrame.origin.x += itemSpacing + itemSize.width
|
||||||
@ -408,7 +502,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(0.94, fraction))
|
fraction = 1.0 - max(0.0, min(1.0, fraction))
|
||||||
|
|
||||||
let image: UIImage?
|
let image: UIImage?
|
||||||
|
|
||||||
@ -424,44 +518,47 @@ private final class TimerNode: ASDisplayNode {
|
|||||||
let startAngle: CGFloat = -CGFloat.pi / 2.0
|
let startAngle: CGFloat = -CGFloat.pi / 2.0
|
||||||
let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction
|
let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction
|
||||||
|
|
||||||
let v = CGPoint(x: sin(endAngle), y: -cos(endAngle))
|
let sparks = fraction > 0.1 && fraction != 1.0
|
||||||
let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y)
|
if sparks {
|
||||||
|
let v = CGPoint(x: sin(endAngle), y: -cos(endAngle))
|
||||||
let dt: CGFloat = 1.0 / 60.0
|
let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y)
|
||||||
var removeIndices: [Int] = []
|
|
||||||
for i in 0 ..< self.particles.count {
|
let dt: CGFloat = 1.0 / 60.0
|
||||||
let currentTime = timestamp - self.particles[i].beginTime
|
var removeIndices: [Int] = []
|
||||||
if currentTime > self.particles[i].lifetime {
|
for i in 0 ..< self.particles.count {
|
||||||
removeIndices.append(i)
|
let currentTime = timestamp - self.particles[i].beginTime
|
||||||
} else {
|
if currentTime > self.particles[i].lifetime {
|
||||||
let input: CGFloat = CGFloat(currentTime / self.particles[i].lifetime)
|
removeIndices.append(i)
|
||||||
let decelerated: CGFloat = (1.0 - (1.0 - input) * (1.0 - input))
|
} else {
|
||||||
self.particles[i].alpha = 1.0 - decelerated
|
let input: CGFloat = CGFloat(currentTime / self.particles[i].lifetime)
|
||||||
|
let decelerated: CGFloat = (1.0 - (1.0 - input) * (1.0 - input))
|
||||||
var p = self.particles[i].position
|
self.particles[i].alpha = 1.0 - decelerated
|
||||||
let d = self.particles[i].direction
|
|
||||||
let v = self.particles[i].velocity
|
var p = self.particles[i].position
|
||||||
p = CGPoint(x: p.x + d.x * v * dt, y: p.y + d.y * v * dt)
|
let d = self.particles[i].direction
|
||||||
self.particles[i].position = p
|
let v = self.particles[i].velocity
|
||||||
|
p = CGPoint(x: p.x + d.x * v * dt, y: p.y + d.y * v * dt)
|
||||||
|
self.particles[i].position = p
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for i in removeIndices.reversed() {
|
|
||||||
self.particles.remove(at: i)
|
|
||||||
}
|
|
||||||
|
|
||||||
let newParticleCount = 1
|
|
||||||
for _ in 0 ..< newParticleCount {
|
|
||||||
let degrees: CGFloat = CGFloat(arc4random_uniform(140)) - 40.0
|
|
||||||
let angle: CGFloat = degrees * CGFloat.pi / 180.0
|
|
||||||
|
|
||||||
let direction = CGPoint(x: v.x * cos(angle) - v.y * sin(angle), y: v.x * sin(angle) + v.y * cos(angle))
|
for i in removeIndices.reversed() {
|
||||||
let velocity = (20.0 + (CGFloat(arc4random()) / CGFloat(UINT32_MAX)) * 4.0) * 0.3
|
self.particles.remove(at: i)
|
||||||
|
}
|
||||||
|
|
||||||
let lifetime = Double(0.4 + CGFloat(arc4random_uniform(100)) * 0.01)
|
let newParticleCount = 1
|
||||||
|
for _ in 0 ..< newParticleCount {
|
||||||
let particle = ContentParticle(position: c, direction: direction, velocity: velocity, alpha: 1.0, lifetime: lifetime, beginTime: timestamp)
|
let degrees: CGFloat = CGFloat(arc4random_uniform(140)) - 40.0
|
||||||
self.particles.append(particle)
|
let angle: CGFloat = degrees * CGFloat.pi / 180.0
|
||||||
|
|
||||||
|
let direction = CGPoint(x: v.x * cos(angle) - v.y * sin(angle), y: v.x * sin(angle) + v.y * cos(angle))
|
||||||
|
let velocity = (20.0 + (CGFloat(arc4random()) / CGFloat(UINT32_MAX)) * 4.0) * 0.3
|
||||||
|
|
||||||
|
let lifetime = Double(0.4 + CGFloat(arc4random_uniform(100)) * 0.01)
|
||||||
|
|
||||||
|
let particle = ContentParticle(position: c, direction: direction, velocity: velocity, alpha: 1.0, lifetime: lifetime, beginTime: timestamp)
|
||||||
|
self.particles.append(particle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image = generateImage(CGSize(width: diameter + inset, height: diameter + inset), rotatedContext: { size, context in
|
image = generateImage(CGSize(width: diameter + inset, height: diameter + inset), rotatedContext: { size, context in
|
||||||
@ -476,10 +573,12 @@ private final class TimerNode: ASDisplayNode {
|
|||||||
context.addPath(path)
|
context.addPath(path)
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
|
|
||||||
for particle in self.particles {
|
if sparks {
|
||||||
let size: CGFloat = 2.0
|
for particle in self.particles {
|
||||||
context.setAlpha(particle.alpha)
|
let size: CGFloat = 2.0
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: particle.position.x - size / 2.0, y: particle.position.y - size / 2.0), size: CGSize(width: size, height: size)))
|
context.setAlpha(particle.alpha)
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: particle.position.x - size / 2.0, y: particle.position.y - size / 2.0), size: CGSize(width: size, height: size)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import ItemListUI
|
|||||||
public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let invites: [ExportedInvitation]?
|
let invites: [ExportedInvitation]?
|
||||||
|
let share: Bool
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let tapAction: ((ExportedInvitation) -> Void)?
|
let tapAction: ((ExportedInvitation) -> Void)?
|
||||||
@ -19,6 +20,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
|||||||
public init(
|
public init(
|
||||||
presentationData: ItemListPresentationData,
|
presentationData: ItemListPresentationData,
|
||||||
invites: [ExportedInvitation]?,
|
invites: [ExportedInvitation]?,
|
||||||
|
share: Bool,
|
||||||
sectionId: ItemListSectionId,
|
sectionId: ItemListSectionId,
|
||||||
style: ItemListStyle,
|
style: ItemListStyle,
|
||||||
tapAction: ((ExportedInvitation) -> Void)?,
|
tapAction: ((ExportedInvitation) -> Void)?,
|
||||||
@ -27,6 +29,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
|||||||
) {
|
) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.invites = invites
|
self.invites = invites
|
||||||
|
self.share = share
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
self.tapAction = tapAction
|
self.tapAction = tapAction
|
||||||
@ -167,7 +170,7 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
|
|||||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites ?? [], 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(), size: gridSize)
|
||||||
strongSelf.gridNode.action = { invite in
|
strongSelf.gridNode.action = { invite in
|
||||||
item.tapAction?(invite)
|
item.tapAction?(invite)
|
||||||
|
@ -30,6 +30,8 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let invite: ExportedInvitation?
|
let invite: ExportedInvitation?
|
||||||
let peers: [Peer]
|
let peers: [Peer]
|
||||||
|
let displayButton: Bool
|
||||||
|
let displayImporters: Bool
|
||||||
let buttonColor: UIColor?
|
let buttonColor: UIColor?
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
@ -43,6 +45,8 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
presentationData: ItemListPresentationData,
|
presentationData: ItemListPresentationData,
|
||||||
invite: ExportedInvitation?,
|
invite: ExportedInvitation?,
|
||||||
peers: [Peer],
|
peers: [Peer],
|
||||||
|
displayButton: Bool,
|
||||||
|
displayImporters: Bool,
|
||||||
buttonColor: UIColor?,
|
buttonColor: UIColor?,
|
||||||
sectionId: ItemListSectionId,
|
sectionId: ItemListSectionId,
|
||||||
style: ItemListStyle,
|
style: ItemListStyle,
|
||||||
@ -55,6 +59,8 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.invite = invite
|
self.invite = invite
|
||||||
self.peers = peers
|
self.peers = peers
|
||||||
|
self.displayButton = displayButton
|
||||||
|
self.displayImporters = displayImporters
|
||||||
self.buttonColor = buttonColor
|
self.buttonColor = buttonColor
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
@ -287,7 +293,6 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
|
|
||||||
switch item.style {
|
switch item.style {
|
||||||
case .plain:
|
case .plain:
|
||||||
height -= 57.0
|
|
||||||
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||||
itemSeparatorColor = .clear
|
itemSeparatorColor = .clear
|
||||||
insets = UIEdgeInsets()
|
insets = UIEdgeInsets()
|
||||||
@ -296,6 +301,14 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !item.displayImporters {
|
||||||
|
height -= 57.0
|
||||||
|
}
|
||||||
|
if !item.displayButton {
|
||||||
|
height -= 63.0
|
||||||
|
}
|
||||||
|
|
||||||
contentSize = CGSize(width: params.width, height: height)
|
contentSize = CGSize(width: params.width, height: height)
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
@ -432,6 +445,11 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
|
|
||||||
strongSelf.avatarsButtonNode.frame = CGRect(x: floorToScreenPixels((params.width - totalWidth) / 2.0), y: fieldFrame.maxY + 87.0, width: totalWidth, height: 32.0)
|
strongSelf.avatarsButtonNode.frame = CGRect(x: floorToScreenPixels((params.width - totalWidth) / 2.0), y: fieldFrame.maxY + 87.0, width: totalWidth, height: 32.0)
|
||||||
strongSelf.avatarsButtonNode.isUserInteractionEnabled = !item.peers.isEmpty
|
strongSelf.avatarsButtonNode.isUserInteractionEnabled = !item.peers.isEmpty
|
||||||
|
|
||||||
|
strongSelf.shareButtonNode?.isHidden = !item.displayButton
|
||||||
|
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
|
||||||
|
strongSelf.avatarsNode.isHidden = !item.displayImporters
|
||||||
|
strongSelf.invitedPeersNode.isHidden = !item.displayImporters
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -368,17 +368,17 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||||
let arguments = arguments as! ChannelAdminControllerArguments
|
let arguments = arguments as! ChannelAdminControllerArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .info(theme, strings, dateTimeFormat, peer, presence):
|
case let .info(_, strings, dateTimeFormat, peer, presence):
|
||||||
return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, cachedData: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in
|
return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, cachedData: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in
|
||||||
}, avatarTapped: {
|
}, avatarTapped: {
|
||||||
})
|
})
|
||||||
case let .rankTitle(theme, text, count, limit):
|
case let .rankTitle(_, text, count, limit):
|
||||||
var accessoryText: ItemListSectionHeaderAccessoryText?
|
var accessoryText: ItemListSectionHeaderAccessoryText?
|
||||||
if let count = count {
|
if let count = count {
|
||||||
accessoryText = ItemListSectionHeaderAccessoryText(value: "\(limit - count)", color: count > limit ? .destructive : .generic)
|
accessoryText = ItemListSectionHeaderAccessoryText(value: "\(limit - count)", color: count > limit ? .destructive : .generic)
|
||||||
}
|
}
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: accessoryText, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: accessoryText, sectionId: self.section)
|
||||||
case let .rank(theme, strings, placeholder, text, enabled):
|
case let .rank(_, _, placeholder, text, enabled):
|
||||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in
|
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in
|
||||||
arguments.updateRank(text, updatedText)
|
arguments.updateRank(text, updatedText)
|
||||||
}, shouldUpdateText: { text in
|
}, shouldUpdateText: { text in
|
||||||
@ -392,23 +392,23 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
}, action: {
|
}, action: {
|
||||||
arguments.dismissInput()
|
arguments.dismissInput()
|
||||||
})
|
})
|
||||||
case let .rankInfo(theme, text):
|
case let .rankInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .rightsTitle(theme, text):
|
case let .rightsTitle(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .rightItem(theme, _, text, right, flags, value, enabled):
|
case let .rightItem(_, _, text, right, flags, value, enabled):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { _ in
|
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { _ in
|
||||||
arguments.toggleRight(right, flags)
|
arguments.toggleRight(right, flags)
|
||||||
}, activatedWhileDisabled: {
|
}, activatedWhileDisabled: {
|
||||||
arguments.toggleRightWhileDisabled(right, flags)
|
arguments.toggleRightWhileDisabled(right, flags)
|
||||||
})
|
})
|
||||||
case let .addAdminsInfo(theme, text):
|
case let .addAdminsInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .transfer(theme, text):
|
case let .transfer(_, text):
|
||||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.transferOwnership()
|
arguments.transferOwnership()
|
||||||
}, tag: nil)
|
}, tag: nil)
|
||||||
case let .dismiss(theme, text):
|
case let .dismiss(_, text):
|
||||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.dismissAdmin()
|
arguments.dismissAdmin()
|
||||||
}, tag: nil)
|
}, tag: nil)
|
||||||
@ -1004,12 +1004,12 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
|||||||
var currentRank: String?
|
var currentRank: String?
|
||||||
var currentFlags: TelegramChatAdminRightsFlags?
|
var currentFlags: TelegramChatAdminRightsFlags?
|
||||||
switch initialParticipant {
|
switch initialParticipant {
|
||||||
case let .creator(creator):
|
case let .creator(_, adminInfo, rank):
|
||||||
currentRank = creator.rank
|
currentRank = rank
|
||||||
currentFlags = maskRightsFlags
|
currentFlags = adminInfo?.rights.flags ?? maskRightsFlags.subtracting(.canBeAnonymous)
|
||||||
case let .member(member):
|
case let .member(_, _, adminInfo, _, rank):
|
||||||
if updateFlags == nil {
|
if updateFlags == nil {
|
||||||
if member.adminInfo?.rights == nil {
|
if adminInfo?.rights == nil {
|
||||||
if channel.flags.contains(.isCreator) {
|
if channel.flags.contains(.isCreator) {
|
||||||
updateFlags = maskRightsFlags.subtracting([.canAddAdmins, .canBeAnonymous])
|
updateFlags = maskRightsFlags.subtracting([.canAddAdmins, .canBeAnonymous])
|
||||||
} else if let adminRights = channel.adminRights {
|
} else if let adminRights = channel.adminRights {
|
||||||
@ -1019,8 +1019,8 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentRank = member.rank
|
currentRank = rank
|
||||||
currentFlags = member.adminInfo?.rights.flags
|
currentFlags = adminInfo?.rights.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
let effectiveRank = updateRank ?? currentRank
|
let effectiveRank = updateRank ?? currentRank
|
||||||
|
@ -342,6 +342,8 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
|||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var dismissInputImpl: (() -> Void)?
|
var dismissInputImpl: (() -> Void)?
|
||||||
|
|
||||||
|
var getControllerImpl: (() -> ViewController?)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
let addMembersDisposable = MetaDisposable()
|
let addMembersDisposable = MetaDisposable()
|
||||||
@ -462,7 +464,9 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
|||||||
pushControllerImpl?(controller)
|
pushControllerImpl?(controller)
|
||||||
}
|
}
|
||||||
}, inviteViaLink: {
|
}, inviteViaLink: {
|
||||||
pushControllerImpl?(InviteLinkInviteController(context: context, peerId: peerId))
|
if let controller = getControllerImpl?() {
|
||||||
|
presentControllerImpl?(InviteLinkInviteController(context: context, peerId: peerId, parentNavigationController: controller.navigationController as? NavigationController), nil)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let peerView = context.account.viewTracker.peerView(peerId)
|
let peerView = context.account.viewTracker.peerView(peerId)
|
||||||
@ -551,6 +555,9 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
|||||||
dismissInputImpl = { [weak controller] in
|
dismissInputImpl = { [weak controller] in
|
||||||
controller?.view.endEditing(true)
|
controller?.view.endEditing(true)
|
||||||
}
|
}
|
||||||
|
getControllerImpl = { [weak controller] in
|
||||||
|
return controller
|
||||||
|
}
|
||||||
controller.visibleBottomContentOffsetChanged = { offset in
|
controller.visibleBottomContentOffsetChanged = { offset in
|
||||||
if let loadMoreControl = loadMoreControl, case let .known(value) = offset, value < 40.0 {
|
if let loadMoreControl = loadMoreControl, case let .known(value) = offset, value < 40.0 {
|
||||||
context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl)
|
context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl)
|
||||||
|
@ -291,7 +291,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):
|
case let .privateLink(_, invite):
|
||||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: [], buttonColor: nil, sectionId: self.section, style: .blocks, shareAction: {
|
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: [], displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, shareAction: {
|
||||||
arguments.shareLink()
|
arguments.shareLink()
|
||||||
}, contextAction: { node in
|
}, contextAction: { node in
|
||||||
arguments.linkContextAction(node)
|
arguments.linkContextAction(node)
|
||||||
|
@ -251,6 +251,205 @@ public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: Pe
|
|||||||
|> switchToLatest
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let cachedPeerExportedInvitationsCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
||||||
|
|
||||||
|
public struct PeerExportedInvitationsState: Equatable {
|
||||||
|
public var invitations: [ExportedInvitation]
|
||||||
|
public var isLoadingMore: Bool
|
||||||
|
public var hasLoadedOnce: Bool
|
||||||
|
public var canLoadMore: Bool
|
||||||
|
public var count: Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CachedPeerExportedInvitations: PostboxCoding {
|
||||||
|
let invitations: [ExportedInvitation]
|
||||||
|
let canLoadMore: Bool
|
||||||
|
let count: Int32
|
||||||
|
|
||||||
|
public static func key(peerId: PeerId) -> ValueBoxKey {
|
||||||
|
let key = ValueBoxKey(length: 8 + 4)
|
||||||
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
init(invitations: [ExportedInvitation], canLoadMore: Bool, count: Int32) {
|
||||||
|
self.invitations = invitations
|
||||||
|
self.canLoadMore = canLoadMore
|
||||||
|
self.count = count
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.invitations = decoder.decodeObjectArrayForKey("invitations")
|
||||||
|
self.canLoadMore = decoder.decodeBoolForKey("canLoadMore", orElse: false)
|
||||||
|
self.count = decoder.decodeInt32ForKey("count", orElse: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeObjectArray(self.invitations, forKey: "invitations")
|
||||||
|
encoder.encodeBool(self.canLoadMore, forKey: "canLoadMore")
|
||||||
|
encoder.encodeInt32(self.count, forKey: "count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PeerExportedInvitationsContextImpl {
|
||||||
|
private let queue: Queue
|
||||||
|
private let account: Account
|
||||||
|
private let peerId: PeerId
|
||||||
|
private let disposable = MetaDisposable()
|
||||||
|
private var isLoadingMore: Bool = false
|
||||||
|
private var hasLoadedOnce: Bool = false
|
||||||
|
private var canLoadMore: Bool = true
|
||||||
|
private var results: [ExportedInvitation] = []
|
||||||
|
private var count: Int32
|
||||||
|
private var populateCache: Bool = true
|
||||||
|
|
||||||
|
let state = Promise<PeerExportedInvitationsState>()
|
||||||
|
|
||||||
|
init(queue: Queue, account: Account, peerId: PeerId) {
|
||||||
|
self.queue = queue
|
||||||
|
self.account = account
|
||||||
|
self.peerId = peerId
|
||||||
|
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
self.isLoadingMore = true
|
||||||
|
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
|
||||||
|
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerExportedInvitations.key(peerId: peerId))) as? CachedPeerExportedInvitations
|
||||||
|
}
|
||||||
|
|> deliverOn(self.queue)).start(next: { [weak self] cachedResult in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.isLoadingMore = false
|
||||||
|
if let cachedResult = cachedResult {
|
||||||
|
strongSelf.results = cachedResult.invitations
|
||||||
|
strongSelf.count = cachedResult.count
|
||||||
|
strongSelf.hasLoadedOnce = true
|
||||||
|
strongSelf.canLoadMore = cachedResult.canLoadMore
|
||||||
|
}
|
||||||
|
strongSelf.loadMore()
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.loadMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMore() {
|
||||||
|
if self.isLoadingMore {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.isLoadingMore = true
|
||||||
|
let account = self.account
|
||||||
|
let peerId = self.peerId
|
||||||
|
let lastResult = self.results.last
|
||||||
|
let populateCache = self.populateCache
|
||||||
|
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
|
}
|
||||||
|
|> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in
|
||||||
|
if let inputPeer = inputPeer {
|
||||||
|
let offsetLink = lastResult?.link
|
||||||
|
|
||||||
|
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: 0, peer: inputPeer, adminId: nil, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<([ExportedInvitation], Int32), NoError> in
|
||||||
|
return account.postbox.transaction { transaction -> ([ExportedInvitation], Int32) in
|
||||||
|
guard let result = result else {
|
||||||
|
return ([], 0)
|
||||||
|
}
|
||||||
|
switch result {
|
||||||
|
case let .exportedChatInvites(count, invites, users):
|
||||||
|
var peers: [Peer] = []
|
||||||
|
for apiUser in users {
|
||||||
|
peers.append(TelegramUser(user: apiUser))
|
||||||
|
}
|
||||||
|
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
let invitations: [ExportedInvitation] = invites.compactMap { ExportedInvitation(apiExportedInvite: $0) }
|
||||||
|
if populateCache {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerExportedInvitations.key(peerId: peerId)), entry: CachedPeerExportedInvitations(invitations: invitations, canLoadMore: count >= 50, count: count), collectionSpec: cachedPeerExportedInvitationsCollectionSpec)
|
||||||
|
}
|
||||||
|
return (invitations, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return signal
|
||||||
|
} else {
|
||||||
|
return .single(([], 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOn(self.queue)).start(next: { [weak self] invitations, updatedCount in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.populateCache {
|
||||||
|
strongSelf.populateCache = false
|
||||||
|
strongSelf.results.removeAll()
|
||||||
|
}
|
||||||
|
var existingLinks = Set(strongSelf.results.map { $0.link })
|
||||||
|
for invitation in invitations {
|
||||||
|
if !existingLinks.contains(invitation.link) {
|
||||||
|
strongSelf.results.append(invitation)
|
||||||
|
existingLinks.insert(invitation.link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.isLoadingMore = false
|
||||||
|
strongSelf.hasLoadedOnce = true
|
||||||
|
strongSelf.canLoadMore = !invitations.isEmpty
|
||||||
|
if strongSelf.canLoadMore {
|
||||||
|
strongSelf.count = max(updatedCount, Int32(strongSelf.results.count))
|
||||||
|
} else {
|
||||||
|
strongSelf.count = Int32(strongSelf.results.count)
|
||||||
|
}
|
||||||
|
strongSelf.updateState()
|
||||||
|
}))
|
||||||
|
self.updateState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateState() {
|
||||||
|
self.state.set(.single(PeerExportedInvitationsState(invitations: self.results, isLoadingMore: self.isLoadingMore, hasLoadedOnce: self.hasLoadedOnce, canLoadMore: self.canLoadMore, count: self.count)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class PeerExportedInvitationsContext {
|
||||||
|
private let queue: Queue = Queue()
|
||||||
|
private let impl: QueueLocalObject<PeerExportedInvitationsContextImpl>
|
||||||
|
|
||||||
|
public var state: Signal<PeerExportedInvitationsState, NoError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.impl.with { impl in
|
||||||
|
disposable.set(impl.state.get().start(next: { value in
|
||||||
|
subscriber.putNext(value)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(account: Account, peerId: PeerId, invite: ExportedInvitation) {
|
||||||
|
let queue = self.queue
|
||||||
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
|
return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadMore() {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private let cachedPeerInvitationImportersCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
private let cachedPeerInvitationImportersCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
||||||
|
|
||||||
public struct PeerInvitationImportersState: Equatable {
|
public struct PeerInvitationImportersState: Equatable {
|
||||||
@ -262,7 +461,7 @@ public struct PeerInvitationImportersState: Equatable {
|
|||||||
public var isLoadingMore: Bool
|
public var isLoadingMore: Bool
|
||||||
public var hasLoadedOnce: Bool
|
public var hasLoadedOnce: Bool
|
||||||
public var canLoadMore: Bool
|
public var canLoadMore: Bool
|
||||||
public var count: Int
|
public var count: Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
final class CachedPeerInvitationImporters: PostboxCoding {
|
final class CachedPeerInvitationImporters: PostboxCoding {
|
||||||
@ -331,7 +530,7 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
private var hasLoadedOnce: Bool = false
|
private var hasLoadedOnce: Bool = false
|
||||||
private var canLoadMore: Bool = true
|
private var canLoadMore: Bool = true
|
||||||
private var results: [PeerInvitationImportersState.Importer] = []
|
private var results: [PeerInvitationImportersState.Importer] = []
|
||||||
private var count: Int
|
private var count: Int32
|
||||||
private var populateCache: Bool = true
|
private var populateCache: Bool = true
|
||||||
|
|
||||||
let state = Promise<PeerInvitationImportersState>()
|
let state = Promise<PeerInvitationImportersState>()
|
||||||
@ -342,7 +541,7 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.link = invite.link
|
self.link = invite.link
|
||||||
|
|
||||||
let count = invite.count.flatMap { Int($0) } ?? 0
|
let count = invite.count ?? 0
|
||||||
self.count = count
|
self.count = count
|
||||||
|
|
||||||
self.isLoadingMore = true
|
self.isLoadingMore = true
|
||||||
@ -395,7 +594,7 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
}
|
}
|
||||||
|> mapToSignal { inputPeer -> Signal<([PeerInvitationImportersState.Importer], Int), NoError> in
|
|> mapToSignal { inputPeer -> Signal<([PeerInvitationImportersState.Importer], Int32), NoError> in
|
||||||
if let inputPeer = inputPeer {
|
if let inputPeer = inputPeer {
|
||||||
let offsetUser = lastResult?.peer.peer.flatMap { apiInputUser($0) } ?? .inputUserEmpty
|
let offsetUser = lastResult?.peer.peer.flatMap { apiInputUser($0) } ?? .inputUserEmpty
|
||||||
let offsetDate = populateCache ? 0 : lastResult?.date ?? 0
|
let offsetDate = populateCache ? 0 : lastResult?.date ?? 0
|
||||||
@ -404,8 +603,8 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
|> `catch` { _ -> Signal<Api.messages.ChatInviteImporters?, NoError> in
|
|> `catch` { _ -> Signal<Api.messages.ChatInviteImporters?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<([PeerInvitationImportersState.Importer], Int), NoError> in
|
|> mapToSignal { result -> Signal<([PeerInvitationImportersState.Importer], Int32), NoError> in
|
||||||
return account.postbox.transaction { transaction -> ([PeerInvitationImportersState.Importer], Int) in
|
return account.postbox.transaction { transaction -> ([PeerInvitationImportersState.Importer], Int32) in
|
||||||
guard let result = result else {
|
guard let result = result else {
|
||||||
return ([], 0)
|
return ([], 0)
|
||||||
}
|
}
|
||||||
@ -434,7 +633,7 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
if populateCache {
|
if populateCache {
|
||||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerInvitationImporters.key(peerId: peerId, link: link)), entry: CachedPeerInvitationImporters(importers: resultImporters, count: count), collectionSpec: cachedPeerInvitationImportersCollectionSpec)
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerInvitationImporters.key(peerId: peerId, link: link)), entry: CachedPeerInvitationImporters(importers: resultImporters, count: count), collectionSpec: cachedPeerInvitationImportersCollectionSpec)
|
||||||
}
|
}
|
||||||
return (resultImporters, Int(count))
|
return (resultImporters, count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,9 +661,9 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
strongSelf.hasLoadedOnce = true
|
strongSelf.hasLoadedOnce = true
|
||||||
strongSelf.canLoadMore = !importers.isEmpty
|
strongSelf.canLoadMore = !importers.isEmpty
|
||||||
if strongSelf.canLoadMore {
|
if strongSelf.canLoadMore {
|
||||||
strongSelf.count = max(updatedCount, strongSelf.results.count)
|
strongSelf.count = max(updatedCount, Int32(strongSelf.results.count))
|
||||||
} else {
|
} else {
|
||||||
strongSelf.count = strongSelf.results.count
|
strongSelf.count = Int32(strongSelf.results.count)
|
||||||
}
|
}
|
||||||
strongSelf.updateState()
|
strongSelf.updateState()
|
||||||
}))
|
}))
|
||||||
|
File diff suppressed because it is too large
Load Diff
12
submodules/TelegramUI/Images.xcassets/Chat/Links/LargeLink.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Links/LargeLink.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_biglink.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Links/LargeLink.imageset/ic_biglink.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Links/LargeLink.imageset/ic_biglink.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -1255,11 +1255,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
interaction.editingOpenPublicLinkSetup()
|
interaction.editingOpenPublicLinkSetup()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if !isPublic {
|
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(""), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||||
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(""), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
interaction.editingOpenInviteLinksSetup()
|
||||||
interaction.editingOpenInviteLinksSetup()
|
}))
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let linkedDiscussionPeer = data.linkedDiscussionPeer {
|
if let linkedDiscussionPeer = data.linkedDiscussionPeer {
|
||||||
let peerTitle: String
|
let peerTitle: String
|
||||||
@ -1318,9 +1316,10 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
}
|
}
|
||||||
} else if let group = data.peer as? TelegramGroup {
|
} else if let group = data.peer as? TelegramGroup {
|
||||||
let ItemUsername = 1
|
let ItemUsername = 1
|
||||||
let ItemPreHistory = 2
|
let ItemInviteLinks = 2
|
||||||
let ItemPermissions = 3
|
let ItemPreHistory = 3
|
||||||
let ItemAdmins = 4
|
let ItemPermissions = 4
|
||||||
|
let ItemAdmins = 5
|
||||||
|
|
||||||
if case .creator = group.role {
|
if case .creator = group.role {
|
||||||
if let cachedData = data.cachedData as? CachedGroupData {
|
if let cachedData = data.cachedData as? CachedGroupData {
|
||||||
@ -1330,6 +1329,11 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(""), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||||
|
interaction.editingOpenInviteLinksSetup()
|
||||||
|
}))
|
||||||
|
|
||||||
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistory, action: {
|
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistory, action: {
|
||||||
interaction.editingOpenPreHistorySetup()
|
interaction.editingOpenPreHistorySetup()
|
||||||
}))
|
}))
|
||||||
@ -4547,7 +4551,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
|
|
||||||
contactsController?.push(visibilityController)
|
contactsController?.push(visibilityController)
|
||||||
} else {
|
} else {
|
||||||
contactsController?.push(InviteLinkInviteController(context: context, peerId: groupPeer.id))
|
contactsController?.present(InviteLinkInviteController(context: context, peerId: groupPeer.id, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,9 +70,10 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
self.addSubnode(self.imageNode)
|
self.addSubnode(self.imageNode)
|
||||||
self.addSubnode(self.actionArea)
|
self.addSubnode(self.actionArea)
|
||||||
|
|
||||||
self.messageDisposable.set((context.account.postbox.messageAtId(messageId)
|
self.messageDisposable.set((context.account.postbox.messageView(messageId)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] message in
|
|> deliverOnMainQueue).start(next: { [weak self] messageView in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
let message = messageView.message
|
||||||
var authorName = ""
|
var authorName = ""
|
||||||
var text = ""
|
var text = ""
|
||||||
if let author = message?.effectiveAuthor {
|
if let author = message?.effectiveAuthor {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user