mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '246ead72ffcc8ba56a45d80bc961c61f096941e4' into experimental-2 [skip ci]
This commit is contained in:
commit
c01467dac0
@ -5902,6 +5902,9 @@ Sorry for the inconvenience.";
|
||||
|
||||
"InviteLink.InviteLinkCopiedText" = "Invite link copied to clipboard";
|
||||
|
||||
"InviteLink.OtherAdminsLinks" = "Invite Links Created By Other Admins";
|
||||
"InviteLink.OtherPermanentLinkInfo" = "**%1$@** can see this link and use it to invite new members to **%2$@**.";
|
||||
|
||||
"Conversation.ChecksTooltip.Delivered" = "Delivered";
|
||||
"Conversation.ChecksTooltip.Read" = "Read";
|
||||
|
||||
|
@ -474,7 +474,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
|
||||
var headerInsets = layout.insets(options: [.input])
|
||||
headerInsets.top += actualNavigationBarHeight
|
||||
|
||||
let countPanelHeight = self.countPanelNode.updateLayout(width: layout.size.width, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
|
||||
let countPanelHeight = self.countPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
|
||||
if self.selectionState.selectedContactIndices.isEmpty {
|
||||
transition.updateFrame(node: self.countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: countPanelHeight)))
|
||||
} else {
|
||||
|
@ -12,15 +12,15 @@ final class InviteContactsCountPanelNode: ASDisplayNode {
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let button: SolidRoundedButtonNode
|
||||
|
||||
private var validLayout: (CGFloat, CGFloat)?
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
var count: Int = 0 {
|
||||
didSet {
|
||||
if self.count != oldValue && self.count > 0 {
|
||||
self.button.title = self.strings.Contacts_InviteContacts(Int32(self.count))
|
||||
|
||||
if let (width, bottomInset) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, bottomInset: bottomInset, transition: .immediate)
|
||||
if let (width, sideInset, bottomInset) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,13 +47,13 @@ final class InviteContactsCountPanelNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = (width, bottomInset)
|
||||
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = (width, sideInset, bottomInset)
|
||||
let topInset: CGFloat = 9.0
|
||||
var bottomInset = bottomInset
|
||||
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
|
||||
|
||||
let buttonInset: CGFloat = 16.0
|
||||
let buttonInset: CGFloat = 16.0 + sideInset
|
||||
let buttonWidth = width - buttonInset * 2.0
|
||||
let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition)
|
||||
transition.updateFrame(node: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight))
|
||||
|
@ -5,9 +5,6 @@ import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
|
||||
private let textFont = Font.regular(13.0)
|
||||
private let selectedTextFont = Font.bold(13.0)
|
||||
|
||||
public final class DatePickerTheme: Equatable {
|
||||
public let backgroundColor: UIColor
|
||||
public let textColor: UIColor
|
||||
@ -15,18 +12,16 @@ public final class DatePickerTheme: Equatable {
|
||||
public let accentColor: UIColor
|
||||
public let disabledColor: UIColor
|
||||
public let selectionColor: UIColor
|
||||
public let selectedCurrentTextColor: UIColor
|
||||
public let secondarySelectionColor: UIColor
|
||||
public let selectionTextColor: UIColor
|
||||
|
||||
public init(backgroundColor: UIColor, textColor: UIColor, secondaryTextColor: UIColor, accentColor: UIColor, disabledColor: UIColor, selectionColor: UIColor, selectedCurrentTextColor: UIColor, secondarySelectionColor: UIColor) {
|
||||
public init(backgroundColor: UIColor, textColor: UIColor, secondaryTextColor: UIColor, accentColor: UIColor, disabledColor: UIColor, selectionColor: UIColor, selectionTextColor: UIColor) {
|
||||
self.backgroundColor = backgroundColor
|
||||
self.textColor = textColor
|
||||
self.secondaryTextColor = secondaryTextColor
|
||||
self.accentColor = accentColor
|
||||
self.disabledColor = disabledColor
|
||||
self.selectionColor = selectionColor
|
||||
self.selectedCurrentTextColor = selectedCurrentTextColor
|
||||
self.secondarySelectionColor = secondarySelectionColor
|
||||
self.selectionTextColor = selectionTextColor
|
||||
}
|
||||
|
||||
public static func ==(lhs: DatePickerTheme, rhs: DatePickerTheme) -> Bool {
|
||||
@ -45,31 +40,26 @@ public final class DatePickerTheme: Equatable {
|
||||
if lhs.selectionColor != rhs.selectionColor {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedCurrentTextColor != rhs.selectedCurrentTextColor {
|
||||
return false
|
||||
}
|
||||
if lhs.secondarySelectionColor != rhs.secondarySelectionColor {
|
||||
if lhs.selectionTextColor != rhs.selectionTextColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
//public extension DatePickerTheme {
|
||||
// convenience init(theme: PresentationTheme) {
|
||||
// self.init(backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor, foregroundColor: theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .black, textColor: theme.rootController.navigationBar.segmentedTextColor, dividerColor: theme.rootController.navigationBar.segmentedDividerColor)
|
||||
// }
|
||||
//}
|
||||
|
||||
private class SegmentedControlItemNode: HighlightTrackingButtonNode {
|
||||
public extension DatePickerTheme {
|
||||
convenience init(theme: PresentationTheme) {
|
||||
self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemDisabledTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0)
|
||||
private let upperLimitDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
|
||||
|
||||
private let controlFont = Font.regular(17.0)
|
||||
private let dayFont = Font.regular(13.0)
|
||||
private let dateFont = Font.with(size: 13.0, design: .regular, traits: .monospacedNumbers)
|
||||
private let selectedDateFont = Font.bold(13.0)
|
||||
private let dateFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers)
|
||||
private let selectedDateFont = Font.with(size: 17.0, design: .regular, traits: [.bold, .monospacedNumbers])
|
||||
|
||||
private let calendar = Calendar(identifier: .gregorian)
|
||||
|
||||
@ -81,12 +71,46 @@ private func monthForDate(_ date: Date) -> Date {
|
||||
return calendar.date(from: components)!
|
||||
}
|
||||
|
||||
public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private func generateSmallArrowImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 7.0, height: 12.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.beginPath()
|
||||
context.move(to: CGPoint(x: 1.0, y: 1.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height / 2.0))
|
||||
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
private func generateNavigationArrowImage(color: UIColor, mirror: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 10.0, height: 17.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.beginPath()
|
||||
if mirror {
|
||||
context.translateBy(x: 5.0, y: 8.5)
|
||||
context.scaleBy(x: -1.0, y: 1.0)
|
||||
context.translateBy(x: -5.0, y: -8.5)
|
||||
}
|
||||
context.move(to: CGPoint(x: 1.0, y: 1.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height / 2.0))
|
||||
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
public final class DatePickerNode: ASDisplayNode {
|
||||
class MonthNode: ASDisplayNode {
|
||||
private let month: Date
|
||||
|
||||
var theme: DatePickerTheme {
|
||||
didSet {
|
||||
self.selectionNode.image = generateStretchableFilledCircleImage(diameter: 44.0, color: self.theme.selectionColor)
|
||||
if let size = self.validSize {
|
||||
self.updateLayout(size: size)
|
||||
}
|
||||
@ -136,6 +160,7 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.selectionNode = ASImageNode()
|
||||
self.selectionNode.displaysAsynchronously = false
|
||||
self.selectionNode.displayWithoutProcessing = true
|
||||
self.selectionNode.image = generateStretchableFilledCircleImage(diameter: 44.0, color: theme.selectionColor)
|
||||
|
||||
self.dateNodes = (0..<42).map { _ in ImmediateTextNode() }
|
||||
|
||||
@ -147,7 +172,7 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.numberOfDays = calendar.range(of: .day, in: .month, for: month)!.count
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
self.addSubnode(self.selectionNode)
|
||||
self.dateNodes.forEach { self.addSubnode($0) }
|
||||
}
|
||||
@ -157,6 +182,10 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var started = false
|
||||
var count = 0
|
||||
|
||||
let sideInset: CGFloat = 12.0
|
||||
let cellSize: CGFloat = floor((size.width - sideInset * 2.0) / 7.0)
|
||||
|
||||
self.selectionNode.isHidden = true
|
||||
for i in 0 ..< 42 {
|
||||
let row: Int = Int(floor(Float(i) / 7.0))
|
||||
let col: Int = i % 7
|
||||
@ -164,49 +193,55 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if !started && weekday == self.startWeekday {
|
||||
started = true
|
||||
}
|
||||
weekday += 1
|
||||
if started {
|
||||
count += 1
|
||||
|
||||
var isAvailableDate = true
|
||||
var components = calendar.dateComponents([.year, .month], from: self.month)
|
||||
components.day = count
|
||||
components.hour = 0
|
||||
components.minute = 0
|
||||
let date = calendar.date(from: components)!
|
||||
|
||||
if let minimumDate = self.minimumDate {
|
||||
var components = calendar.dateComponents([.year, .month], from: self.month)
|
||||
components.day = count
|
||||
components.hour = 0
|
||||
components.minute = 0
|
||||
let date = calendar.date(from: components)!
|
||||
if date < minimumDate {
|
||||
isAvailableDate = false
|
||||
}
|
||||
}
|
||||
if let maximumDate = self.maximumDate {
|
||||
var components = calendar.dateComponents([.year, .month], from: self.month)
|
||||
components.day = count
|
||||
components.hour = 0
|
||||
components.minute = 0
|
||||
let date = calendar.date(from: components)!
|
||||
if date > maximumDate {
|
||||
isAvailableDate = false
|
||||
}
|
||||
}
|
||||
var isSelectedDate = false
|
||||
var isSelectedAndCurrentDate = false
|
||||
|
||||
let isToday = calendar.isDateInToday(date)
|
||||
let isSelected = self.date.flatMap { calendar.isDate(date, equalTo: $0, toGranularity: .day) } ?? false
|
||||
|
||||
let color: UIColor
|
||||
if !isAvailableDate {
|
||||
color = self.theme.disabledColor
|
||||
} else if isSelectedAndCurrentDate {
|
||||
color = .white
|
||||
} else if isSelectedDate {
|
||||
if isSelected {
|
||||
color = self.theme.selectionTextColor
|
||||
} else if isToday {
|
||||
color = self.theme.accentColor
|
||||
} else if !isAvailableDate {
|
||||
color = self.theme.disabledColor
|
||||
} else {
|
||||
color = self.theme.textColor
|
||||
}
|
||||
|
||||
let textNode = self.dateNodes[i]
|
||||
textNode.attributedText = NSAttributedString(string: "\(count)", font: dateFont, textColor: color)
|
||||
|
||||
textNode.attributedText = NSAttributedString(string: "\(count)", font: isSelected ? selectedDateFont : dateFont, textColor: color)
|
||||
|
||||
let textSize = textNode.updateLayout(size)
|
||||
textNode.frame = CGRect(origin: CGPoint(x: CGFloat(col) * 20.0, y: CGFloat(row) * 20.0), size: textSize)
|
||||
|
||||
let cellFrame = CGRect(x: sideInset + CGFloat(col) * cellSize, y: 0.0 + CGFloat(row) * cellSize, width: cellSize, height: cellSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: cellFrame.minX + floor((cellFrame.width - textSize.width) / 2.0), y: cellFrame.minY + floor((cellFrame.height - textSize.height) / 2.0)), size: textSize)
|
||||
textNode.frame = textFrame
|
||||
|
||||
if isSelected {
|
||||
self.selectionNode.isHidden = false
|
||||
let selectionSize = CGSize(width: 44.0, height: 44.0)
|
||||
self.selectionNode.frame = CGRect(origin: CGPoint(x: cellFrame.minX + floor((cellFrame.width - selectionSize.width) / 2.0), y: cellFrame.minY + floor((cellFrame.height - selectionSize.height) / 2.0)), size: selectionSize)
|
||||
}
|
||||
|
||||
if count == self.numberOfDays {
|
||||
break
|
||||
@ -232,40 +267,75 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
private let timeTitleNode: ImmediateTextNode
|
||||
private let timeFieldNode: ASImageNode
|
||||
|
||||
private let dayNodes: [ImmediateTextNode]
|
||||
private var currentIndex = 0
|
||||
private var months: [Date] = []
|
||||
private var monthNodes: [Date: MonthNode] = [:]
|
||||
private let contentNode: ASDisplayNode
|
||||
|
||||
private let pickerBackgroundNode: ASDisplayNode
|
||||
private var pickerNode: MonthPickerNode
|
||||
|
||||
private let monthButtonNode: HighlightTrackingButtonNode
|
||||
private let monthTextNode: ImmediateTextNode
|
||||
private let monthArrowNode: ASImageNode
|
||||
|
||||
private let previousButtonNode: HighlightableButtonNode
|
||||
private let nextButtonNode: HighlightableButtonNode
|
||||
|
||||
private let dayNodes: [ImmediateTextNode]
|
||||
private var previousMonthNode: MonthNode?
|
||||
private var currentMonthNode: MonthNode?
|
||||
private var nextMonthNode: MonthNode?
|
||||
private let scrollNode: ASScrollNode
|
||||
private var transitionFraction: CGFloat = 0.0
|
||||
|
||||
private var gestureRecognizer: UIPanGestureRecognizer?
|
||||
private var gestureSelectedIndex: Int?
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
public var maximumDate: Date? {
|
||||
didSet {
|
||||
|
||||
public var maximumDate: Date {
|
||||
get {
|
||||
return self.state.maxDate
|
||||
}
|
||||
}
|
||||
public var minimumDate: Date = telegramReleaseDate {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
public var date: Date = Date() {
|
||||
didSet {
|
||||
guard self.date != oldValue else {
|
||||
set {
|
||||
guard newValue != self.maximumDate else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedState = State(minDate: self.state.minDate, maxDate: newValue, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
||||
self.updateState(updatedState, animated: false)
|
||||
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
public var minimumDate: Date {
|
||||
get {
|
||||
return self.state.minDate
|
||||
}
|
||||
set {
|
||||
guard newValue != self.minimumDate else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedState = State(minDate: newValue, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
||||
self.updateState(updatedState, animated: false)
|
||||
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
public var date: Date {
|
||||
get {
|
||||
return self.state.date
|
||||
}
|
||||
set {
|
||||
guard newValue != self.date else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: newValue, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
||||
self.updateState(updatedState, animated: false)
|
||||
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
@ -282,33 +352,74 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.timeFieldNode.displaysAsynchronously = false
|
||||
self.timeFieldNode.displayWithoutProcessing = true
|
||||
|
||||
self.dayNodes = (0..<7).map { _ in ImmediateTextNode() }
|
||||
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
self.pickerBackgroundNode = ASDisplayNode()
|
||||
self.pickerBackgroundNode.alpha = 0.0
|
||||
self.pickerBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
self.pickerBackgroundNode.isUserInteractionEnabled = false
|
||||
|
||||
self.pickerNode = MonthPickerNode(theme: theme, strings: strings, date: self.state.date, yearRange: 2013 ..< 2038, valueChanged: { date in
|
||||
|
||||
})
|
||||
|
||||
self.monthButtonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.monthTextNode = ImmediateTextNode()
|
||||
|
||||
self.monthArrowNode = ASImageNode()
|
||||
self.monthArrowNode.displaysAsynchronously = false
|
||||
self.monthArrowNode.displayWithoutProcessing = true
|
||||
|
||||
self.previousButtonNode = HighlightableButtonNode()
|
||||
self.previousButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -10.0, bottom: -6.0, right: -10.0)
|
||||
self.nextButtonNode = HighlightableButtonNode()
|
||||
|
||||
self.dayNodes = (0..<7).map { _ in ImmediateTextNode() }
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.nextButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -10.0, bottom: -6.0, right: -10.0)
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.backgroundColor = theme.backgroundColor
|
||||
|
||||
self.addSubnode(self.contentNode)
|
||||
|
||||
self.dayNodes.forEach { self.addSubnode($0) }
|
||||
|
||||
self.addSubnode(self.previousButtonNode)
|
||||
self.addSubnode(self.nextButtonNode)
|
||||
|
||||
self.addSubnode(self.pickerBackgroundNode)
|
||||
self.pickerBackgroundNode.addSubnode(self.pickerNode)
|
||||
|
||||
self.addSubnode(self.monthTextNode)
|
||||
self.addSubnode(self.monthArrowNode)
|
||||
self.addSubnode(self.monthButtonNode)
|
||||
|
||||
self.addSubnode(self.previousButtonNode)
|
||||
self.addSubnode(self.nextButtonNode)
|
||||
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
|
||||
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
|
||||
self.nextButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: false), for: .normal)
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
self.setupItems()
|
||||
|
||||
self.monthButtonNode.addTarget(self, action: #selector(self.monthButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.monthButtonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.monthTextNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.monthTextNode.alpha = 0.4
|
||||
strongSelf.monthArrowNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.monthArrowNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.monthTextNode.alpha = 1.0
|
||||
strongSelf.monthTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
strongSelf.monthArrowNode.alpha = 1.0
|
||||
strongSelf.monthArrowNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.previousButtonNode.addTarget(self, action: #selector(self.previousButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.nextButtonNode.addTarget(self, action: #selector(self.nextButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
@ -316,15 +427,62 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
self.scrollNode.view.isPagingEnabled = true
|
||||
self.scrollNode.view.delegate = self
|
||||
self.contentNode.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
|
||||
self.contentNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
private func updateState(_ state: State, animated: Bool) {
|
||||
let previousState = self.state
|
||||
self.state = state
|
||||
if let size = self.validLayout {
|
||||
self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
|
||||
|
||||
if previousState.minDate != state.minDate || previousState.maxDate != state.maxDate {
|
||||
self.setupItems()
|
||||
} else if previousState.selectedMonth != state.selectedMonth {
|
||||
for i in 0 ..< self.months.count {
|
||||
if self.months[i].timeIntervalSince1970 > state.selectedMonth.timeIntervalSince1970 {
|
||||
self.currentIndex = max(0, min(self.months.count - 1, i - 1))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupItems() {
|
||||
let startMonth = monthForDate(self.state.minDate)
|
||||
let endMonth = monthForDate(self.state.maxDate)
|
||||
let selectedMonth = monthForDate(self.state.selectedMonth)
|
||||
|
||||
let calendar = Calendar.current
|
||||
|
||||
var currentIndex = 0
|
||||
|
||||
var months: [Date] = [startMonth]
|
||||
var index = 1
|
||||
|
||||
var nextMonth = startMonth
|
||||
while true {
|
||||
if let month = calendar.date(byAdding: .month, value: 1, to: nextMonth) {
|
||||
nextMonth = month
|
||||
if nextMonth == selectedMonth {
|
||||
currentIndex = index
|
||||
}
|
||||
if nextMonth >= endMonth {
|
||||
break
|
||||
} else {
|
||||
months.append(nextMonth)
|
||||
}
|
||||
index += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.months = months
|
||||
self.currentIndex = currentIndex
|
||||
}
|
||||
|
||||
public func updateTheme(_ theme: DatePickerTheme) {
|
||||
@ -334,55 +492,262 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundColor = self.theme.backgroundColor
|
||||
}
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.view.window?.endEditing(true)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if !decelerate {
|
||||
if let size = self.validLayout {
|
||||
self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
|
||||
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
|
||||
self.nextButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: false), for: .normal)
|
||||
|
||||
for (_, monthNode) in self.monthNodes {
|
||||
monthNode.theme = theme
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
if let size = self.validLayout {
|
||||
self.updateLayout(size: size, transition: .immediate)
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
|
||||
}
|
||||
|
||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
self.view.window?.endEditing(true)
|
||||
case .changed:
|
||||
let translation = recognizer.translation(in: self.view)
|
||||
var transitionFraction = translation.x / self.bounds.width
|
||||
if self.currentIndex <= 0 {
|
||||
transitionFraction = min(0.0, transitionFraction)
|
||||
}
|
||||
if self.currentIndex >= self.months.count - 1 {
|
||||
transitionFraction = max(0.0, transitionFraction)
|
||||
}
|
||||
self.transitionFraction = transitionFraction
|
||||
if let size = self.validLayout {
|
||||
let topInset: CGFloat = 78.0
|
||||
let containerSize = CGSize(width: size.width, height: size.height - topInset)
|
||||
self.updateItems(size: containerSize, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
case .cancelled, .ended:
|
||||
let velocity = recognizer.velocity(in: self.view)
|
||||
var directionIsToRight: Bool?
|
||||
if abs(velocity.x) > 10.0 {
|
||||
directionIsToRight = velocity.x < 0.0
|
||||
} else if abs(self.transitionFraction) > 0.5 {
|
||||
directionIsToRight = self.transitionFraction < 0.0
|
||||
}
|
||||
var updatedIndex = self.currentIndex
|
||||
if let directionIsToRight = directionIsToRight {
|
||||
if directionIsToRight {
|
||||
updatedIndex = min(updatedIndex + 1, self.months.count - 1)
|
||||
} else {
|
||||
updatedIndex = max(updatedIndex - 1, 0)
|
||||
}
|
||||
}
|
||||
self.currentIndex = updatedIndex
|
||||
self.transitionFraction = 0.0
|
||||
|
||||
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.months[updatedIndex])
|
||||
self.updateState(updatedState, animated: true)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func updateItems(size: CGSize, update: Bool = false, transition: ContainedViewLayoutTransition) {
|
||||
var validIds: [Date] = []
|
||||
|
||||
if self.currentIndex >= 0 && self.currentIndex < self.months.count {
|
||||
let preloadSpan: Int = 1
|
||||
for i in max(0, self.currentIndex - preloadSpan) ... min(self.currentIndex + preloadSpan, self.months.count - 1) {
|
||||
validIds.append(self.months[i])
|
||||
var itemNode: MonthNode?
|
||||
var wasAdded = false
|
||||
if let current = self.monthNodes[self.months[i]] {
|
||||
itemNode = current
|
||||
current.updateLayout(size: size)
|
||||
} else {
|
||||
wasAdded = true
|
||||
let addedItemNode = MonthNode(theme: self.theme, month: self.months[i], minimumDate: self.minimumDate, maximumDate: self.maximumDate, date: self.date)
|
||||
itemNode = addedItemNode
|
||||
self.monthNodes[self.months[i]] = addedItemNode
|
||||
self.contentNode.addSubnode(addedItemNode)
|
||||
}
|
||||
if let itemNode = itemNode {
|
||||
let indexOffset = CGFloat(i - self.currentIndex)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: indexOffset * size.width + self.transitionFraction * size.width, y: 0.0), size: size)
|
||||
|
||||
if wasAdded {
|
||||
itemNode.frame = itemFrame
|
||||
itemNode.updateLayout(size: size)
|
||||
} else {
|
||||
transition.updateFrame(node: itemNode, frame: itemFrame)
|
||||
itemNode.updateLayout(size: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var removeIds: [Date] = []
|
||||
for (id, _) in self.monthNodes {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
if let itemNode = self.monthNodes.removeValue(forKey: id) {
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = size
|
||||
|
||||
let topInset: CGFloat = 60.0
|
||||
let topInset: CGFloat = 78.0
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
let scrollSize = CGSize(width: size.width, height: size.height - topInset)
|
||||
self.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: scrollSize)
|
||||
self.scrollNode.view.contentSize = CGSize(width: scrollSize.width * 3.0, height: scrollSize.height)
|
||||
self.scrollNode.view.contentOffset = CGPoint(x: scrollSize.width, y: 0.0)
|
||||
let month = monthForDate(self.state.selectedMonth)
|
||||
let components = calendar.dateComponents([.month, .year], from: month)
|
||||
|
||||
self.monthTextNode.attributedText = NSAttributedString(string: stringForMonth(strings: self.strings, month: components.month.flatMap { Int32($0) - 1 } ?? 0, ofYear: components.year.flatMap { Int32($0) - 1900 } ?? 100), font: controlFont, textColor: theme.textColor)
|
||||
let monthSize = self.monthTextNode.updateLayout(size)
|
||||
|
||||
let monthTextFrame = CGRect(x: sideInset, y: 10.0, width: monthSize.width, height: monthSize.height)
|
||||
self.monthTextNode.frame = monthTextFrame
|
||||
self.monthArrowNode.frame = CGRect(x: monthTextFrame.maxX + 10.0, y: monthTextFrame.minY + 4.0, width: 7.0, height: 12.0)
|
||||
self.monthButtonNode.frame = monthTextFrame.inset(by: UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -30.0))
|
||||
|
||||
self.previousButtonNode.frame = CGRect(x: size.width - sideInset - 54.0, y: monthTextFrame.minY + 1.0, width: 10.0, height: 17.0)
|
||||
self.nextButtonNode.frame = CGRect(x: size.width - sideInset - 13.0, y: monthTextFrame.minY + 1.0, width: 10.0, height: 17.0)
|
||||
|
||||
let daysSideInset: CGFloat = 12.0
|
||||
let cellSize: CGFloat = floor((size.width - daysSideInset * 2.0) / 7.0)
|
||||
|
||||
for i in 0 ..< self.dayNodes.count {
|
||||
let dayNode = self.dayNodes[i]
|
||||
dayNode.attributedText = NSAttributedString(string: shortStringForDayOfWeek(strings: self.strings, day: Int32(i)).uppercased(), font: dayFont, textColor: theme.secondaryTextColor)
|
||||
|
||||
let day = Int32(i)
|
||||
dayNode.attributedText = NSAttributedString(string: shortStringForDayOfWeek(strings: self.strings, day: day), font: dayFont, textColor: theme.secondaryTextColor)
|
||||
let size = dayNode.updateLayout(size)
|
||||
dayNode.frame = CGRect(origin: CGPoint(x: CGFloat(i) * 20.0, y: 0.0), size: size)
|
||||
let textSize = dayNode.updateLayout(size)
|
||||
let cellFrame = CGRect(x: daysSideInset + CGFloat(i) * cellSize, y: 40.0, width: cellSize, height: cellSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: cellFrame.minX + floor((cellFrame.width - textSize.width) / 2.0), y: cellFrame.minY + floor((cellFrame.height - textSize.height) / 2.0)), size: textSize)
|
||||
|
||||
dayNode.frame = textFrame
|
||||
}
|
||||
|
||||
let containerSize = CGSize(width: size.width, height: size.height - topInset)
|
||||
self.contentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: containerSize)
|
||||
|
||||
self.updateItems(size: containerSize, transition: transition)
|
||||
|
||||
self.pickerBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.pickerBackgroundNode.isUserInteractionEnabled = self.state.displayingMonthSelection
|
||||
transition.updateAlpha(node: self.pickerBackgroundNode, alpha: self.state.displayingMonthSelection ? 1.0 : 0.0)
|
||||
|
||||
self.pickerNode.frame = CGRect(x: sideInset, y: topInset, width: size.width - sideInset * 2.0, height: 180.0)
|
||||
}
|
||||
|
||||
@objc private func monthButtonPressed() {
|
||||
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: !self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
||||
self.updateState(updatedState, animated: true)
|
||||
}
|
||||
|
||||
@objc private func previousButtonPressed() {
|
||||
guard let month = calendar.date(byAdding: .month, value: -1, to: self.state.selectedMonth), let size = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: month)
|
||||
self.updateState(updatedState, animated: false)
|
||||
|
||||
self.contentNode.layer.animatePosition(from: CGPoint(x: -size.width, y: 0.0), to: CGPoint(), duration: 0.3, additive: true)
|
||||
}
|
||||
|
||||
@objc private func nextButtonPressed() {
|
||||
guard let month = calendar.date(byAdding: .month, value: 1, to: self.state.selectedMonth), let size = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: month)
|
||||
self.updateState(updatedState, animated: false)
|
||||
|
||||
self.contentNode.layer.animatePosition(from: CGPoint(x: size.width, y: 0.0), to: CGPoint(), duration: 0.3, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
private let theme: DatePickerTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private var date: Date
|
||||
private var yearRange: Range<Int> {
|
||||
didSet {
|
||||
self.pickerView.reloadAllComponents()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func monthButtonPressed(_ button: SegmentedControlItemNode) {
|
||||
private let valueChanged: (Date) -> Void
|
||||
private let pickerView: UIPickerView
|
||||
|
||||
init(theme: DatePickerTheme, strings: PresentationStrings, date: Date, yearRange: Range<Int>, valueChanged: @escaping (Date) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.date = date
|
||||
self.yearRange = yearRange
|
||||
|
||||
self.valueChanged = valueChanged
|
||||
|
||||
self.pickerView = UIPickerView()
|
||||
|
||||
super.init()
|
||||
|
||||
self.pickerView.delegate = self
|
||||
self.pickerView.dataSource = self
|
||||
self.view.addSubview(self.pickerView)
|
||||
|
||||
self.pickerView.reloadAllComponents()
|
||||
|
||||
// self.pickerView.selectRow(index, inComponent: 0, animated: false)
|
||||
}
|
||||
|
||||
@objc private func previousButtonPressed(_ button: SegmentedControlItemNode) {
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: constrainedSize.width, height: 180.0)
|
||||
}
|
||||
|
||||
@objc private func nextButtonPressed(_ button: SegmentedControlItemNode) {
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
if component == 1 {
|
||||
return self.yearRange.count
|
||||
} else {
|
||||
return 12
|
||||
}
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
|
||||
return 40.0
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
|
||||
let string: String
|
||||
if component == 1 {
|
||||
string = "\(self.yearRange.startIndex + row)"
|
||||
} else {
|
||||
string = stringForMonth(strings: self.strings, month: Int32(row))
|
||||
}
|
||||
return NSAttributedString(string: string, font: Font.medium(15.0), textColor: self.theme.textColor)
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||
// self.valueChanged(timeoutValues[row])
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.pickerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.bounds.size.width, height: 180.0))
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ public struct Font {
|
||||
}
|
||||
var updatedDescriptor: UIFontDescriptor? = descriptor.withSymbolicTraits(symbolicTraits)
|
||||
if traits.contains(.monospacedNumbers) {
|
||||
updatedDescriptor = descriptor.addingAttributes([
|
||||
updatedDescriptor = updatedDescriptor?.addingAttributes([
|
||||
UIFontDescriptor.AttributeName.featureSettings: [
|
||||
[UIFontDescriptor.FeatureKey.featureIdentifier:
|
||||
kNumberSpacingType,
|
||||
|
@ -32,7 +32,7 @@
|
||||
(int)sourceSampleRate,
|
||||
0,
|
||||
NULL);
|
||||
_ratio = MAX(1, destinationSampleRate / sourceSampleRate) * MAX(1, destinationChannelCount / sourceChannelCount) * 2;
|
||||
_ratio = MAX(1, destinationSampleRate / MAX(sourceSampleRate, 1)) * MAX(1, destinationChannelCount / sourceChannelCount) * 2;
|
||||
swr_init(_context);
|
||||
}
|
||||
return self;
|
||||
|
@ -49,6 +49,7 @@ swift_library(
|
||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||
"//submodules/SectionHeaderItem:SectionHeaderItem",
|
||||
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -193,7 +193,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
arguments.dismissInput()
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.pickingTimeLimit = !state.pickingTimeLimit
|
||||
// updatedState.pickingTimeLimit = !state.pickingTimeLimit
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
|
@ -149,7 +149,7 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
}, viewAction: {
|
||||
})
|
||||
case let .links(_, _, invites):
|
||||
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, share: true, sectionId: 1, style: .plain, tapAction: { invite in
|
||||
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, count: 0, share: true, sectionId: 1, style: .plain, tapAction: { invite in
|
||||
interaction.copyLink(invite)
|
||||
}, contextAction: { invite, _ in
|
||||
interaction.shareLink(invite)
|
||||
@ -218,7 +218,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(context: self.context, peerId: self.peerId, controller: self)
|
||||
}
|
||||
|
||||
|
||||
private var didAppearOnce: Bool = false
|
||||
private var isDismissed: Bool = false
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
@ -288,7 +288,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
self.presentationDataPromise = Promise(self.presentationData)
|
||||
self.controller = controller
|
||||
|
||||
self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
|
||||
self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: false)
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
@ -348,16 +348,16 @@ public final class InviteLinkInviteController: ViewController {
|
||||
}
|
||||
})))
|
||||
|
||||
// items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||
// }, action: { _, f in
|
||||
// f(.dismissWithoutContent)
|
||||
//
|
||||
// if let invite = invite {
|
||||
// let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
// self?.controller?.present(controller, in: .window(.root))
|
||||
// }
|
||||
// })))
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
if let invite = invite {
|
||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
}
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
@ -395,7 +395,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
let shareController = ShareController(context: context, subject: .url(invite.link))
|
||||
self?.controller?.present(shareController, in: .window(.root))
|
||||
}, manageLinks: { [weak self] in
|
||||
let controller = inviteLinkListController(context: context, peerId: peerId)
|
||||
let controller = inviteLinkListController(context: context, peerId: peerId, admin: nil)
|
||||
self?.controller?.parentNavigationController?.pushViewController(controller)
|
||||
self?.controller?.dismiss()
|
||||
})
|
||||
@ -403,8 +403,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
|
||||
|
||||
let peerView = context.account.postbox.peerView(id: peerId)
|
||||
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
|
||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, invites)
|
||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, self.invitesContext.state)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view, invites in
|
||||
if let strongSelf = self {
|
||||
var entries: [InviteLinkInviteEntry] = []
|
||||
@ -423,19 +422,19 @@ public final class InviteLinkInviteController: ViewController {
|
||||
entries.append(.mainLink(presentationData.theme, mainInvite))
|
||||
}
|
||||
|
||||
// let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
|
||||
// var index: Int32 = 0
|
||||
// for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
|
||||
// var invitesPair: [ExportedInvitation] = []
|
||||
// invitesPair.append(additionalInvites[i])
|
||||
// if i + 1 < additionalInvites.count {
|
||||
// invitesPair.append(additionalInvites[i + 1])
|
||||
// }
|
||||
// entries.append(.links(index, presentationData.theme, invitesPair))
|
||||
// index += 1
|
||||
// }
|
||||
let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
|
||||
var index: Int32 = 0
|
||||
for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
|
||||
var invitesPair: [ExportedInvitation] = []
|
||||
invitesPair.append(additionalInvites[i])
|
||||
if i + 1 < additionalInvites.count {
|
||||
invitesPair.append(additionalInvites[i + 1])
|
||||
}
|
||||
entries.append(.links(index, presentationData.theme, invitesPair))
|
||||
index += 1
|
||||
}
|
||||
|
||||
// entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, additionalInvites.isEmpty))
|
||||
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, additionalInvites.isEmpty))
|
||||
|
||||
let previousEntries = previousEntries.swap(entries)
|
||||
|
||||
|
@ -18,6 +18,7 @@ import AppBundle
|
||||
import ContextUI
|
||||
import TelegramStringFormatting
|
||||
import ItemListPeerActionItem
|
||||
import ItemListPeerItem
|
||||
import ShareController
|
||||
import UndoUI
|
||||
|
||||
@ -30,9 +31,10 @@ private final class InviteLinkListControllerArguments {
|
||||
let createLink: () -> Void
|
||||
let openLink: (ExportedInvitation) -> Void
|
||||
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
|
||||
let openAdmin: (ExportedInvitationCreator) -> Void
|
||||
let deleteAllRevokedLinks: () -> Void
|
||||
|
||||
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
|
||||
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, openAdmin: @escaping (ExportedInvitationCreator) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.shareMainLink = shareMainLink
|
||||
self.openMainLink = openMainLink
|
||||
@ -41,6 +43,7 @@ private final class InviteLinkListControllerArguments {
|
||||
self.createLink = createLink
|
||||
self.openLink = openLink
|
||||
self.linkContextAction = linkContextAction
|
||||
self.openAdmin = openAdmin
|
||||
self.deleteAllRevokedLinks = deleteAllRevokedLinks
|
||||
}
|
||||
}
|
||||
@ -49,6 +52,7 @@ private enum InviteLinksListSection: Int32 {
|
||||
case header
|
||||
case mainLink
|
||||
case links
|
||||
case admins
|
||||
case revokedLinks
|
||||
}
|
||||
|
||||
@ -57,11 +61,16 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
|
||||
case mainLinkHeader(PresentationTheme, String)
|
||||
case mainLink(PresentationTheme, ExportedInvitation?, [Peer], Int32, Bool)
|
||||
case mainLinkOtherInfo(PresentationTheme, String)
|
||||
|
||||
case linksHeader(PresentationTheme, String)
|
||||
case linksCreate(PresentationTheme, String)
|
||||
case links(Int32, PresentationTheme, [ExportedInvitation]?)
|
||||
case links(Int32, PresentationTheme, [ExportedInvitation]?, Int)
|
||||
case linksInfo(PresentationTheme, String)
|
||||
|
||||
case adminsHeader(PresentationTheme, String)
|
||||
case admin(Int32, PresentationTheme, ExportedInvitationCreator)
|
||||
|
||||
case revokedLinksHeader(PresentationTheme, String)
|
||||
case revokedLinksDeleteAll(PresentationTheme, String)
|
||||
case revokedLinks(Int32, PresentationTheme, [ExportedInvitation]?)
|
||||
@ -70,10 +79,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case .header:
|
||||
return InviteLinksListSection.header.rawValue
|
||||
case .mainLinkHeader, .mainLink:
|
||||
case .mainLinkHeader, .mainLink, .mainLinkOtherInfo:
|
||||
return InviteLinksListSection.mainLink.rawValue
|
||||
case .linksHeader, .linksCreate, .links, .linksInfo:
|
||||
return InviteLinksListSection.links.rawValue
|
||||
case .adminsHeader, .admin:
|
||||
return InviteLinksListSection.admins.rawValue
|
||||
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLinks:
|
||||
return InviteLinksListSection.revokedLinks.rawValue
|
||||
}
|
||||
@ -87,20 +98,26 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
return 1
|
||||
case .mainLink:
|
||||
return 2
|
||||
case .linksHeader:
|
||||
case .mainLinkOtherInfo:
|
||||
return 3
|
||||
case .linksCreate:
|
||||
case .linksHeader:
|
||||
return 4
|
||||
case let .links(index, _, _):
|
||||
return 5 + index
|
||||
case .linksCreate:
|
||||
return 5
|
||||
case let .links(index, _, _, _):
|
||||
return 6 + index
|
||||
case .linksInfo:
|
||||
return 10000
|
||||
case .revokedLinksHeader:
|
||||
case .adminsHeader:
|
||||
return 10001
|
||||
case let .admin(index, _, _):
|
||||
return 10002 + index
|
||||
case .revokedLinksHeader:
|
||||
return 20001
|
||||
case .revokedLinksDeleteAll:
|
||||
return 10002
|
||||
return 20002
|
||||
case let .revokedLinks(index, _, _):
|
||||
return 10003 + index
|
||||
return 20003 + index
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +141,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .mainLinkOtherInfo(lhsTheme, lhsText):
|
||||
if case let .mainLinkOtherInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .linksHeader(lhsTheme, lhsText):
|
||||
if case let .linksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -136,8 +159,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .links(lhsIndex, lhsTheme, lhsLinks):
|
||||
if case let .links(rhsIndex, rhsTheme, rhsLinks) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLinks == rhsLinks {
|
||||
case let .links(lhsIndex, lhsTheme, lhsLinks, lhsCount):
|
||||
if case let .links(rhsIndex, rhsTheme, rhsLinks, rhsCount) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLinks == rhsLinks, lhsCount == rhsCount {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -148,6 +171,18 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .adminsHeader(lhsTheme, lhsText):
|
||||
if case let .adminsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .admin(lhsIndex, lhsTheme, lhsCreator):
|
||||
if case let .admin(rhsIndex, rhsTheme, rhsCreator) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsCreator == rhsCreator {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .revokedLinksHeader(lhsTheme, lhsText):
|
||||
if case let .revokedLinksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -196,20 +231,28 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
arguments.openLink(invite)
|
||||
}
|
||||
})
|
||||
case let .mainLinkOtherInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: nil, style: .blocks, tag: nil)
|
||||
case let .linksHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .linksCreate(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, hasSeparator: false, sectionId: self.section, editing: false, action: {
|
||||
arguments.createLink()
|
||||
})
|
||||
case let .links(_, _, invites):
|
||||
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||
case let .links(_, _, invites, count):
|
||||
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, count: count, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||
arguments.openLink(invite)
|
||||
}, contextAction: { invite, node in
|
||||
arguments.linkContextAction(invite, node, nil)
|
||||
})
|
||||
case let .linksInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .adminsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .admin(_, _, creator):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: creator.peer.peer!, height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: .disclosure("\(creator.count)"), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openAdmin(creator)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil)
|
||||
case let .revokedLinksHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .revokedLinksDeleteAll(theme, text):
|
||||
@ -217,7 +260,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
arguments.deleteAllRevokedLinks()
|
||||
})
|
||||
case let .revokedLinks(_, _, invites):
|
||||
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, count: 0, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||
arguments.openLink(invite)
|
||||
}, contextAction: { invite, node in
|
||||
arguments.linkContextAction(invite, node, nil)
|
||||
@ -226,22 +269,23 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?) -> [InviteLinksListEntry] {
|
||||
private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?, creators: [ExportedInvitationCreator], admin: ExportedInvitationCreator?) -> [InviteLinksListEntry] {
|
||||
var entries: [InviteLinksListEntry] = []
|
||||
|
||||
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
|
||||
|
||||
|
||||
if admin == nil {
|
||||
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
|
||||
}
|
||||
|
||||
let mainInvite: ExportedInvitation?
|
||||
var isPublic = false
|
||||
if let peer = peerViewMainPeer(view), let address = peer.addressName, !address.isEmpty {
|
||||
if let peer = peerViewMainPeer(view), let address = peer.addressName, !address.isEmpty && admin == nil {
|
||||
mainInvite = ExportedInvitation(link: "t.me/\(address)", isPermanent: true, isRevoked: false, adminId: PeerId(0), date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil)
|
||||
isPublic = true
|
||||
} else if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) {
|
||||
mainInvite = invite
|
||||
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation {
|
||||
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation, admin == nil {
|
||||
mainInvite = invite
|
||||
} else if let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation {
|
||||
} else if let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation, admin == nil {
|
||||
mainInvite = invite
|
||||
} else {
|
||||
mainInvite = nil
|
||||
@ -259,10 +303,15 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
||||
}
|
||||
|
||||
entries.append(.mainLink(presentationData.theme, mainInvite, importers?.importers.prefix(3).compactMap { $0.peer.peer } ?? [], importersCount, isPublic))
|
||||
if let adminPeer = admin?.peer.peer, let peer = peerViewMainPeer(view) {
|
||||
let string = presentationData.strings.InviteLink_OtherPermanentLinkInfo(adminPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))
|
||||
entries.append(.mainLinkOtherInfo(presentationData.theme, string.0))
|
||||
}
|
||||
|
||||
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
|
||||
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
|
||||
|
||||
if admin == nil {
|
||||
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
|
||||
}
|
||||
var additionalInvites: [ExportedInvitation]?
|
||||
if let invites = invites {
|
||||
additionalInvites = invites.filter { $0.link != mainInvite?.link }
|
||||
@ -275,16 +324,34 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
||||
if i + 1 < additionalInvites.count {
|
||||
invitesPair.append(additionalInvites[i + 1])
|
||||
}
|
||||
entries.append(.links(index, presentationData.theme, invitesPair))
|
||||
entries.append(.links(index, presentationData.theme, invitesPair, invitesPair.count))
|
||||
index += 1
|
||||
}
|
||||
} else if let admin = admin {
|
||||
var index: Int32 = 0
|
||||
for _ in stride(from: 0, to: admin.count, by: 2) {
|
||||
entries.append(.links(index, presentationData.theme, nil, 1))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
entries.append(.linksInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
|
||||
if !creators.isEmpty {
|
||||
entries.append(.adminsHeader(presentationData.theme, presentationData.strings.InviteLink_OtherAdminsLinks.uppercased()))
|
||||
var index: Int32 = 0
|
||||
for creator in creators {
|
||||
if let _ = creator.peer.peer {
|
||||
entries.append(.admin(index, presentationData.theme, creator))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let revokedInvites = revokedInvites, !revokedInvites.isEmpty {
|
||||
entries.append(.revokedLinksHeader(presentationData.theme, presentationData.strings.InviteLink_RevokedLinks.uppercased()))
|
||||
entries.append(.revokedLinksDeleteAll(presentationData.theme, presentationData.strings.InviteLink_DeleteAllRevokedLinks))
|
||||
|
||||
if admin == nil {
|
||||
entries.append(.revokedLinksDeleteAll(presentationData.theme, presentationData.strings.InviteLink_DeleteAllRevokedLinks))
|
||||
}
|
||||
var index: Int32 = 0
|
||||
for i in stride(from: 0, to: revokedInvites.endIndex, by: 2) {
|
||||
var invitesPair: [ExportedInvitation] = []
|
||||
@ -305,7 +372,7 @@ private struct InviteLinkListControllerState: Equatable {
|
||||
}
|
||||
|
||||
|
||||
public func inviteLinkListController(context: AccountContext, peerId: PeerId) -> ViewController {
|
||||
public func inviteLinkListController(context: AccountContext, peerId: PeerId, admin: ExportedInvitationCreator?) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
@ -326,8 +393,16 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
||||
|
||||
var getControllerImpl: (() -> ViewController?)?
|
||||
|
||||
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
|
||||
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: true, forceUpdate: true)
|
||||
let adminId = admin?.peer.peer?.id
|
||||
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: false, forceUpdate: false)
|
||||
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true)
|
||||
|
||||
let creators: Signal<[ExportedInvitationCreator], NoError>
|
||||
if adminId == nil {
|
||||
creators = .single([]) |> then(peerExportedInvitationsCreators(account: context.account, peerId: peerId))
|
||||
} else {
|
||||
creators = .single([])
|
||||
}
|
||||
|
||||
let arguments = InviteLinkListControllerArguments(context: context, shareMainLink: { invite in
|
||||
let shareController = ShareController(context: context, subject: .url(invite.link))
|
||||
@ -546,6 +621,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
||||
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
}, openAdmin: { admin in
|
||||
let controller = inviteLinkListController(context: context, peerId: peerId, admin: admin)
|
||||
pushControllerImpl?(controller)
|
||||
}, deleteAllRevokedLinks: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
@ -596,9 +674,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
||||
}
|
||||
|
||||
let previousRevokedInvites = Atomic<PeerExportedInvitationsState?>(value: nil)
|
||||
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state)
|
||||
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state, creators)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { presentationData, view, importersContext, importers, invites, revokedInvites, creators -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let previousRevokedInvites = previousRevokedInvites.swap(invites)
|
||||
|
||||
var crossfade = false
|
||||
@ -606,8 +684,15 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
||||
crossfade = true
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.invitations, revokedInvites: revokedInvites.invitations, importers: importers), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: false)
|
||||
let title: ItemListControllerTitle
|
||||
if let admin = admin, let peer = admin.peer.peer {
|
||||
title = .textWithSubtitle(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), "\(admin.count) invite links")
|
||||
} else {
|
||||
title = .text(presentationData.strings.InviteLink_Title)
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.hasLoadedOnce ? invites.invitations : nil, revokedInvites: revokedInvites.invitations, importers: importers, creators: creators, admin: admin), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ private class ItemNode: ASDisplayNode {
|
||||
|
||||
private var updateTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var params: (size: CGSize, wide: Bool, invite: ExportedInvitation, color: ItemBackgroundColor, presentationData: ItemListPresentationData)?
|
||||
private var params: (size: CGSize, wide: Bool, invite: ExportedInvitation?, color: ItemBackgroundColor, presentationData: ItemListPresentationData)?
|
||||
|
||||
var action: (() -> Void)?
|
||||
var contextAction: ((ASDisplayNode) -> Void)?
|
||||
@ -214,44 +214,50 @@ private class ItemNode: ASDisplayNode {
|
||||
self.contextAction?(self.extractedContainerNode)
|
||||
}
|
||||
|
||||
func update(size: CGSize, wide: Bool, share: Bool, invite: ExportedInvitation, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
func update(size: CGSize, wide: Bool, share: Bool, invite: ExportedInvitation?, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
|
||||
let availability = invitationAvailability(invite)
|
||||
let availability = invite.flatMap { invitationAvailability($0) } ?? 0.0
|
||||
let transitionFraction: CGFloat
|
||||
let color: ItemBackgroundColor
|
||||
let nextColor: ItemBackgroundColor
|
||||
if invite.isRevoked {
|
||||
if let invite = invite {
|
||||
if invite.isRevoked {
|
||||
color = .gray
|
||||
nextColor = .gray
|
||||
transitionFraction = 0.0
|
||||
} else if invite.expireDate == nil && invite.usageLimit == nil {
|
||||
color = .blue
|
||||
nextColor = .blue
|
||||
transitionFraction = 0.0
|
||||
} else if availability >= 0.5 {
|
||||
color = .green
|
||||
nextColor = .yellow
|
||||
transitionFraction = (availability - 0.5) / 0.5
|
||||
} else if availability > 0.0 {
|
||||
color = .yellow
|
||||
nextColor = .red
|
||||
transitionFraction = availability / 0.5
|
||||
} else {
|
||||
color = .red
|
||||
nextColor = .red
|
||||
transitionFraction = 0.0
|
||||
}
|
||||
} else {
|
||||
color = .gray
|
||||
nextColor = .gray
|
||||
transitionFraction = 0.0
|
||||
} else if invite.expireDate == nil && invite.usageLimit == nil {
|
||||
color = .blue
|
||||
nextColor = .blue
|
||||
transitionFraction = 0.0
|
||||
} else if availability >= 0.5 {
|
||||
color = .green
|
||||
nextColor = .yellow
|
||||
transitionFraction = (availability - 0.5) / 0.5
|
||||
} else if availability > 0.0 {
|
||||
color = .yellow
|
||||
nextColor = .red
|
||||
transitionFraction = availability / 0.5
|
||||
} else {
|
||||
color = .red
|
||||
nextColor = .red
|
||||
transitionFraction = 0.0
|
||||
}
|
||||
|
||||
|
||||
let previousParams = self.params
|
||||
self.params = (size, wide, invite, color, presentationData)
|
||||
|
||||
let previousExpireDate = previousParams?.invite.expireDate
|
||||
if previousExpireDate != invite.expireDate {
|
||||
let previousExpireDate = previousParams?.invite?.expireDate
|
||||
if previousExpireDate != invite?.expireDate {
|
||||
self.updateTimer?.invalidate()
|
||||
self.updateTimer = nil
|
||||
|
||||
if let expireDate = invite.expireDate, availability > 0.0 {
|
||||
if let expireDate = invite?.expireDate, availability > 0.0 {
|
||||
let timeout = min(2.0, max(0.001, Double(expireDate - currentTime)))
|
||||
let updateTimer = SwiftSignalKit.Timer(timeout: timeout, repeat: true, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
@ -272,8 +278,13 @@ private class ItemNode: ASDisplayNode {
|
||||
let bottomColor = color.colors.bottom
|
||||
let nextTopColor = nextColor.colors.top
|
||||
let nextBottomColor = nextColor.colors.bottom
|
||||
let colors: NSArray = [nextTopColor.mixedWith(topColor, alpha: transitionFraction).cgColor, nextBottomColor.mixedWith(bottomColor, alpha: transitionFraction).cgColor]
|
||||
|
||||
let colors: NSArray
|
||||
if let invite = invite {
|
||||
colors = [nextTopColor.mixedWith(topColor, alpha: transitionFraction).cgColor, nextBottomColor.mixedWith(bottomColor, alpha: transitionFraction).cgColor]
|
||||
} else {
|
||||
colors = [UIColor(rgb: 0xf2f2f7).cgColor, UIColor(rgb: 0xf2f2f7).cgColor]
|
||||
}
|
||||
|
||||
if let (_, _, previousInvite, previousColor, _) = previousParams, previousInvite == invite {
|
||||
if previousColor != color && color == .red {
|
||||
if let snapshotView = self.wrapperNode.view.snapshotContentTree() {
|
||||
@ -298,7 +309,7 @@ private class ItemNode: ASDisplayNode {
|
||||
let secondaryTextColor = nextColor.colors.text.mixedWith(color.colors.text, alpha: transitionFraction)
|
||||
|
||||
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 {
|
||||
inviteLink = inviteLink.replacingOccurrences(of: "joinchat/", with: "joinchat/\n")
|
||||
inviteLink = inviteLink.replacingOccurrences(of: "join/", with: "join/\n")
|
||||
@ -314,65 +325,72 @@ private class ItemNode: ASDisplayNode {
|
||||
self.buttonIconNode.image = share ? shareIcon : moreIcon
|
||||
|
||||
var subtitleText: String = ""
|
||||
if let count = invite.count {
|
||||
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
|
||||
if let invite = invite {
|
||||
if let count = invite.count {
|
||||
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
|
||||
} else {
|
||||
subtitleText = [.red, .gray].contains(color) ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
|
||||
}
|
||||
if invite.isRevoked {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
}
|
||||
subtitleText += presentationData.strings.InviteLink_Revoked
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
||||
self.timerNode?.removeFromSupernode()
|
||||
self.timerNode = nil
|
||||
} else if let expireDate = invite.expireDate, currentTime >= expireDate {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
}
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_Expired
|
||||
} else {
|
||||
subtitleText += presentationData.strings.InviteLink_Expired
|
||||
}
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
||||
self.timerNode?.removeFromSupernode()
|
||||
self.timerNode = nil
|
||||
} else if let usageLimit = invite.usageLimit, let count = invite.count, count >= usageLimit {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
}
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_UsageLimitReached
|
||||
} else {
|
||||
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 {
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Flame"), color: .white)
|
||||
let timerNode: TimerNode
|
||||
if let current = self.timerNode {
|
||||
timerNode = current
|
||||
} else {
|
||||
timerNode = TimerNode()
|
||||
timerNode.isUserInteractionEnabled = false
|
||||
self.timerNode = timerNode
|
||||
self.addSubnode(timerNode)
|
||||
}
|
||||
timerNode.update(color: UIColor.white, creationTimestamp: invite.startDate ?? invite.date, deadlineTimestamp: expireDate)
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
||||
}
|
||||
} else {
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Link"), color: .white)
|
||||
self.timerNode?.removeFromSupernode()
|
||||
self.timerNode = nil
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
||||
}
|
||||
}
|
||||
self.iconNode.isHidden = false
|
||||
self.buttonIconNode.isHidden = false
|
||||
} else {
|
||||
subtitleText = [.red, .gray].contains(color) ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
|
||||
}
|
||||
if invite.isRevoked {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
}
|
||||
subtitleText += presentationData.strings.InviteLink_Revoked
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
||||
self.timerNode?.removeFromSupernode()
|
||||
self.timerNode = nil
|
||||
} else if let expireDate = invite.expireDate, currentTime >= expireDate {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
}
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_Expired
|
||||
} else {
|
||||
subtitleText += presentationData.strings.InviteLink_Expired
|
||||
}
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
||||
self.timerNode?.removeFromSupernode()
|
||||
self.timerNode = nil
|
||||
} else if let usageLimit = invite.usageLimit, let count = invite.count, count >= usageLimit {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
}
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_UsageLimitReached
|
||||
} else {
|
||||
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 {
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Flame"), color: .white)
|
||||
let timerNode: TimerNode
|
||||
if let current = self.timerNode {
|
||||
timerNode = current
|
||||
} else {
|
||||
timerNode = TimerNode()
|
||||
timerNode.isUserInteractionEnabled = false
|
||||
self.timerNode = timerNode
|
||||
self.addSubnode(timerNode)
|
||||
}
|
||||
timerNode.update(color: UIColor.white, creationTimestamp: invite.startDate ?? invite.date, deadlineTimestamp: expireDate)
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
||||
}
|
||||
} else {
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Link"), color: .white)
|
||||
self.timerNode?.removeFromSupernode()
|
||||
self.timerNode = nil
|
||||
if share {
|
||||
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
||||
}
|
||||
self.iconNode.isHidden = true
|
||||
self.buttonIconNode.isHidden = true
|
||||
}
|
||||
|
||||
self.iconNode.frame = CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)
|
||||
@ -407,7 +425,7 @@ private class ItemNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
class InviteLinksGridNode: ASDisplayNode {
|
||||
private var items: [ExportedInvitation] = []
|
||||
private var items: [ExportedInvitation]?
|
||||
private var itemNodes: [String: ItemNode] = [:]
|
||||
|
||||
var action: ((ExportedInvitation) -> Void)?
|
||||
@ -418,7 +436,7 @@ class InviteLinksGridNode: ASDisplayNode {
|
||||
return result
|
||||
}
|
||||
|
||||
func update(size: CGSize, safeInset: CGFloat, items: [ExportedInvitation], share: Bool, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
func update(size: CGSize, safeInset: CGFloat, items: [ExportedInvitation]?, count: Int, share: Bool, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
self.items = items
|
||||
|
||||
var contentSize: CGSize = size
|
||||
@ -428,24 +446,37 @@ class InviteLinksGridNode: ASDisplayNode {
|
||||
|
||||
var validIds = Set<String>()
|
||||
|
||||
for i in 0 ..< self.items.count {
|
||||
let invite = self.items[i]
|
||||
validIds.insert(invite.link)
|
||||
let count = items?.count ?? count
|
||||
|
||||
for i in 0 ..< count {
|
||||
let invite: ExportedInvitation?
|
||||
let id: String
|
||||
if let items = items, i < items.count {
|
||||
invite = items[i]
|
||||
id = invite!.link
|
||||
} else {
|
||||
invite = nil
|
||||
id = "placeholder_\(i)"
|
||||
}
|
||||
|
||||
validIds.insert(id)
|
||||
|
||||
var itemNode: ItemNode?
|
||||
var wasAdded = false
|
||||
if let current = self.itemNodes[invite.link] {
|
||||
|
||||
if let current = self.itemNodes[id] {
|
||||
itemNode = current
|
||||
} else {
|
||||
wasAdded = true
|
||||
let addedItemNode = ItemNode()
|
||||
itemNode = addedItemNode
|
||||
self.itemNodes[invite.link] = addedItemNode
|
||||
self.itemNodes[id] = addedItemNode
|
||||
self.addSubnode(addedItemNode)
|
||||
}
|
||||
if let itemNode = itemNode {
|
||||
let col = CGFloat(i % 2)
|
||||
let row = floor(CGFloat(i) / 2.0)
|
||||
let wide = (i == self.items.count - 1 && (self.items.count % 2) != 0)
|
||||
let wide = (i == count - 1 && (count % 2) != 0)
|
||||
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)
|
||||
if !wide && col > 0 {
|
||||
@ -460,10 +491,14 @@ class InviteLinksGridNode: ASDisplayNode {
|
||||
transition.updateFrame(node: itemNode, frame: itemFrame)
|
||||
}
|
||||
itemNode.action = { [weak self] in
|
||||
self?.action?(invite)
|
||||
if let invite = invite {
|
||||
self?.action?(invite)
|
||||
}
|
||||
}
|
||||
itemNode.contextAction = { [weak self] node in
|
||||
self?.contextAction?(node, invite)
|
||||
if let invite = invite {
|
||||
self?.contextAction?(node, invite)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import ItemListUI
|
||||
public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
||||
let presentationData: ItemListPresentationData
|
||||
let invites: [ExportedInvitation]?
|
||||
let count: Int
|
||||
let share: Bool
|
||||
public let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
@ -20,6 +21,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
||||
public init(
|
||||
presentationData: ItemListPresentationData,
|
||||
invites: [ExportedInvitation]?,
|
||||
count: Int,
|
||||
share: Bool,
|
||||
sectionId: ItemListSectionId,
|
||||
style: ItemListStyle,
|
||||
@ -29,6 +31,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
||||
) {
|
||||
self.presentationData = presentationData
|
||||
self.invites = invites
|
||||
self.count = count
|
||||
self.share = share
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
@ -133,13 +136,14 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
|
||||
let topInset: CGFloat
|
||||
if case .plain = item.style, case .otherSection = neighbors.top {
|
||||
topInset = 16.0
|
||||
} else if case .blocks = item.style, case .sameSection(true) = neighbors.top {
|
||||
topInset = 16.0
|
||||
} else {
|
||||
topInset = 4.0
|
||||
}
|
||||
|
||||
|
||||
var height: CGFloat
|
||||
let count = item.invites?.count ?? 0
|
||||
let count = item.invites?.count ?? item.count
|
||||
if count > 0 {
|
||||
if count % 2 == 0 {
|
||||
height = topInset + 122.0 + 6.0
|
||||
@ -176,7 +180,7 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
}
|
||||
|
||||
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites ?? [], share: item.share, presentationData: item.presentationData, transition: .immediate)
|
||||
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites, count: item.count, share: item.share, presentationData: item.presentationData, transition: .immediate)
|
||||
strongSelf.gridNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset - 4.0), size: gridSize)
|
||||
strongSelf.gridNode.action = { invite in
|
||||
item.tapAction?(invite)
|
||||
|
@ -10,6 +10,7 @@ import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import SolidRoundedButtonNode
|
||||
import AnimatedAvatarSetNode
|
||||
import ShimmerEffect
|
||||
|
||||
private func actionButtonImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
|
||||
@ -132,6 +133,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
private var avatarsContent: AnimatedAvatarSetContext.Content?
|
||||
private let avatarsNode: AnimatedAvatarSetNode
|
||||
private let invitedPeersNode: TextNode
|
||||
private var peersPlaceholderNode: ShimmerEffectNode?
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
@ -180,7 +183,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
self.avatarsContext = AnimatedAvatarSetContext()
|
||||
self.avatarsNode = AnimatedAvatarSetNode()
|
||||
self.invitedPeersNode = TextNode()
|
||||
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
@ -355,7 +358,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
strongSelf.fieldNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor)
|
||||
strongSelf.addressButtonIconNode.image = actionButtonImage(color: item.presentationData.theme.list.itemInputField.controlColor)
|
||||
}
|
||||
|
||||
|
||||
let _ = addressApply()
|
||||
let _ = invitedPeersApply()
|
||||
|
||||
@ -470,12 +473,45 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
strongSelf.invitedPeersNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: fieldFrame.maxY + 92.0), size: invitedPeersLayout.size)
|
||||
|
||||
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 && item.invite != nil
|
||||
|
||||
strongSelf.addressButtonNode.isUserInteractionEnabled = item.invite != nil
|
||||
strongSelf.fieldButtonNode.isUserInteractionEnabled = item.invite != nil
|
||||
strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0
|
||||
|
||||
strongSelf.shareButtonNode?.isUserInteractionEnabled = item.invite != nil
|
||||
strongSelf.shareButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
|
||||
strongSelf.shareButtonNode?.isHidden = !item.displayButton
|
||||
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
|
||||
strongSelf.avatarsNode.isHidden = !item.displayImporters
|
||||
strongSelf.invitedPeersNode.isHidden = !item.displayImporters
|
||||
strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil
|
||||
strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil
|
||||
|
||||
if item.invite == nil {
|
||||
let shimmerNode: ShimmerEffectNode
|
||||
if let current = strongSelf.peersPlaceholderNode {
|
||||
shimmerNode = current
|
||||
} else {
|
||||
shimmerNode = ShimmerEffectNode()
|
||||
strongSelf.peersPlaceholderNode = shimmerNode
|
||||
strongSelf.insertSubnode(shimmerNode, belowSubnode: strongSelf.fieldNode)
|
||||
}
|
||||
shimmerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
if let (rect, size) = strongSelf.absoluteLocation {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: size)
|
||||
}
|
||||
|
||||
var shapes: [ShimmerEffectNode.Shape] = []
|
||||
|
||||
let lineWidth: CGFloat = 180.0
|
||||
let lineDiameter: CGFloat = 10.0
|
||||
|
||||
let titleFrame = strongSelf.invitedPeersNode.frame
|
||||
shapes.append(.roundedRectLine(startPoint: CGPoint(x: floor(titleFrame.center.x - lineWidth / 2.0), y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: lineWidth, diameter: lineDiameter))
|
||||
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize)
|
||||
} else if let shimmerNode = strongSelf.peersPlaceholderNode {
|
||||
strongSelf.peersPlaceholderNode = nil
|
||||
shimmerNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -492,4 +528,13 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
var rect = rect
|
||||
rect.origin.y += self.insets.top
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
if let shimmerNode = self.peersPlaceholderNode {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1086,7 +1086,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
|
||||
strongSelf.labelNode.frame = labelFrame
|
||||
} else {
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset - rightInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
|
||||
}
|
||||
|
||||
|
@ -293,7 +293,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
case let .privateLinkHeader(_, title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .privateLink(_, invite, displayImporters):
|
||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||
if let invite = invite {
|
||||
arguments.copyLink(invite)
|
||||
}
|
||||
@ -598,13 +598,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
} else {
|
||||
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
|
||||
}
|
||||
// switch mode {
|
||||
// case .initialSetup:
|
||||
// break
|
||||
// case .generic, .privateLink:
|
||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
// }
|
||||
switch mode {
|
||||
case .initialSetup:
|
||||
break
|
||||
case .generic, .privateLink:
|
||||
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
}
|
||||
}
|
||||
case .privateChannel:
|
||||
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
|
||||
@ -615,13 +615,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
} else {
|
||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp))
|
||||
}
|
||||
// switch mode {
|
||||
// case .initialSetup:
|
||||
// break
|
||||
// case .generic, .privateLink:
|
||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
// }
|
||||
switch mode {
|
||||
case .initialSetup:
|
||||
break
|
||||
case .generic, .privateLink:
|
||||
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
}
|
||||
}
|
||||
} else if let _ = view.peers[view.peerId] as? TelegramGroup {
|
||||
switch mode {
|
||||
@ -630,13 +630,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
|
||||
// switch mode {
|
||||
// case .initialSetup:
|
||||
// break
|
||||
// case .generic, .privateLink:
|
||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
// }
|
||||
switch mode {
|
||||
case .initialSetup:
|
||||
break
|
||||
case .generic, .privateLink:
|
||||
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
}
|
||||
case .generic, .initialSetup:
|
||||
let selectedType: CurrentChannelType
|
||||
if let current = state.selectedType {
|
||||
@ -729,13 +729,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
|
||||
// switch mode {
|
||||
// case .initialSetup:
|
||||
// break
|
||||
// case .generic, .privateLink:
|
||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
// }
|
||||
switch mode {
|
||||
case .initialSetup:
|
||||
break
|
||||
case .generic, .privateLink:
|
||||
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -943,27 +943,27 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
||||
})
|
||||
})))
|
||||
|
||||
// items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||
// }, action: { _, f in
|
||||
// f(.dismissWithoutContent)
|
||||
//
|
||||
// let _ = (context.account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||
// if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
|
||||
// if let cachedData = cachedData as? CachedChannelData {
|
||||
// return cachedData.exportedInvitation
|
||||
// } else if let cachedData = cachedData as? CachedGroupData {
|
||||
// return cachedData.exportedInvitation
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// } |> deliverOnMainQueue).start(next: { invite in
|
||||
// if let invite = invite {
|
||||
// let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
// presentControllerImpl?(controller, nil)
|
||||
// }
|
||||
// })
|
||||
// })))
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
let _ = (context.account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||
if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.exportedInvitation
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
return cachedData.exportedInvitation
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} |> deliverOnMainQueue).start(next: { invite in
|
||||
if let invite = invite {
|
||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}
|
||||
})
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
@ -1006,7 +1006,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: nil)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
}, manageInviteLinks: {
|
||||
let controller = inviteLinkListController(context: context, peerId: peerId)
|
||||
let controller = inviteLinkListController(context: context, peerId: peerId, admin: nil)
|
||||
pushControllerImpl?(controller)
|
||||
})
|
||||
|
||||
|
@ -230,6 +230,16 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
|
||||
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: false)
|
||||
case let .channelAdminLogEventActionToggleGroupCallSetting(joinMuted):
|
||||
action = .updateGroupCallSettings(joinMuted: joinMuted == .boolTrue)
|
||||
case .channelAdminLogEventActionParticipantJoinByInvite:
|
||||
break
|
||||
case .channelAdminLogEventActionExportedInviteDelete:
|
||||
break
|
||||
case .channelAdminLogEventActionExportedInviteRevoke:
|
||||
break
|
||||
case .channelAdminLogEventActionExportedInviteEdit:
|
||||
break
|
||||
case .channelAdminLogEventActionParticipantVolume:
|
||||
break
|
||||
}
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
if let action = action {
|
||||
|
@ -7,65 +7,6 @@ import MtProtoKit
|
||||
import SyncCore
|
||||
|
||||
|
||||
public func ensuredExistingPeerExportedInvitation(account: Account, peerId: PeerId, revokeExisted: Bool = false) -> Signal<ExportedInvitation?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
if let _ = peer as? TelegramChannel {
|
||||
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, cachedData.exportedInvitation != nil && !revokeExisted {
|
||||
return .single(cachedData.exportedInvitation)
|
||||
} else {
|
||||
return account.network.request(Api.functions.messages.exportChatInvite(flags: 0, peer: inputPeer, expireDate: nil, usageLimit: nil))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
|
||||
return account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||
if let invitation = ExportedInvitation(apiExportedInvite: result) {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedExportedInvitation(invitation)
|
||||
} else {
|
||||
return CachedChannelData().withUpdatedExportedInvitation(invitation)
|
||||
}
|
||||
})
|
||||
return invitation
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let _ = peer as? TelegramGroup {
|
||||
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData, cachedData.exportedInvitation != nil && !revokeExisted {
|
||||
return .single(cachedData.exportedInvitation)
|
||||
} else {
|
||||
return account.network.request(Api.functions.messages.exportChatInvite(flags: 0, peer: inputPeer, expireDate: nil, usageLimit: nil))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
|
||||
return account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||
if let invitation = ExportedInvitation(apiExportedInvite: result) {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedExportedInvitation(invitation)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
return invitation
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
|
||||
public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
@ -122,8 +63,7 @@ public enum CreatePeerExportedInvitationError {
|
||||
}
|
||||
|
||||
public func createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> {
|
||||
return .fail(.generic)
|
||||
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var flags: Int32 = 0
|
||||
if let _ = expireDate {
|
||||
@ -146,7 +86,7 @@ public func createPeerExportedInvitation(account: Account, peerId: PeerId, expir
|
||||
}
|
||||
}
|
||||
|> castError(CreatePeerExportedInvitationError.self)
|
||||
|> switchToLatest*/
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum EditPeerExportedInvitationError {
|
||||
@ -154,8 +94,7 @@ public enum EditPeerExportedInvitationError {
|
||||
}
|
||||
|
||||
public func editPeerExportedInvitation(account: Account, peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> {
|
||||
return .fail(.generic)
|
||||
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var flags: Int32 = 0
|
||||
if let _ = expireDate {
|
||||
@ -188,7 +127,7 @@ public func editPeerExportedInvitation(account: Account, peerId: PeerId, link: S
|
||||
}
|
||||
}
|
||||
|> castError(EditPeerExportedInvitationError.self)
|
||||
|> switchToLatest*/
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum RevokePeerExportedInvitationError {
|
||||
@ -196,8 +135,7 @@ public enum RevokePeerExportedInvitationError {
|
||||
}
|
||||
|
||||
public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> {
|
||||
return .fail(.generic)
|
||||
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
let flags: Int32 = (1 << 2)
|
||||
return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: nil, usageLimit: nil))
|
||||
@ -224,7 +162,7 @@ public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link:
|
||||
}
|
||||
}
|
||||
|> castError(RevokePeerExportedInvitationError.self)
|
||||
|> switchToLatest*/
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public struct ExportedInvitations : Equatable {
|
||||
@ -232,10 +170,9 @@ public struct ExportedInvitations : Equatable {
|
||||
public let totalCount: Int32
|
||||
}
|
||||
|
||||
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
|
||||
return .single(nil)
|
||||
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, adminId: PeerId? = nil, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let adminPeer = transaction.getPeer(adminId ?? account.peerId), let adminId = apiInputUser(adminPeer) {
|
||||
var flags: Int32 = 0
|
||||
if let _ = offsetLink {
|
||||
flags |= (1 << 2)
|
||||
@ -243,7 +180,7 @@ public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: B
|
||||
if revoked {
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetDate: offsetLink?.date, offsetLink: offsetLink?.link, limit: 50))
|
||||
return account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: adminId, offsetDate: offsetLink?.date, offsetLink: offsetLink?.link, limit: 50))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
||||
return .single(nil)
|
||||
@ -277,7 +214,7 @@ public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: B
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
} |> switchToLatest*/
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
|
||||
@ -286,8 +223,7 @@ public enum DeletePeerExportedInvitationError {
|
||||
}
|
||||
|
||||
public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<Never, DeletePeerExportedInvitationError> {
|
||||
return .fail(.generic)
|
||||
/*return account.postbox.transaction { transaction -> Signal<Never, DeletePeerExportedInvitationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<Never, DeletePeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.deleteExportedChatInvite(peer: inputPeer, link: link))
|
||||
|> mapError { _ in return DeletePeerExportedInvitationError.generic }
|
||||
@ -297,14 +233,13 @@ public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link:
|
||||
}
|
||||
}
|
||||
|> castError(DeletePeerExportedInvitationError.self)
|
||||
|> switchToLatest*/
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return .complete()
|
||||
/*return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.deleteRevokedExportedChatInvites(peer: inputPeer))
|
||||
return account.network.request(Api.functions.messages.deleteRevokedExportedChatInvites(peer: inputPeer, adminId: .inputUserEmpty))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
@ -313,7 +248,7 @@ public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: Pe
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest*/
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
private let cachedPeerExportedInvitationsCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
||||
@ -377,6 +312,7 @@ private final class PeerExportedInvitationsContextImpl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: PeerId
|
||||
private let adminId: PeerId
|
||||
private let revoked: Bool
|
||||
private var forceUpdate: Bool
|
||||
private let disposable = MetaDisposable()
|
||||
@ -388,36 +324,41 @@ private final class PeerExportedInvitationsContextImpl {
|
||||
private var results: [ExportedInvitation] = []
|
||||
private var count: Int32
|
||||
private var populateCache: Bool = true
|
||||
private var isMainList: Bool
|
||||
|
||||
let state = Promise<PeerExportedInvitationsState>()
|
||||
|
||||
init(queue: Queue, account: Account, peerId: PeerId, revoked: Bool, forceUpdate: Bool) {
|
||||
init(queue: Queue, account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
self.adminId = adminId ?? account.peerId
|
||||
self.revoked = revoked
|
||||
self.forceUpdate = forceUpdate
|
||||
self.isMainList = adminId == nil
|
||||
|
||||
self.count = 0
|
||||
|
||||
self.isLoadingMore = true
|
||||
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
|
||||
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked))) as? CachedPeerExportedInvitations
|
||||
if adminId == nil {
|
||||
self.isLoadingMore = true
|
||||
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
|
||||
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked))) 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.loadedFromCache = true
|
||||
}
|
||||
strongSelf.loadMore()
|
||||
}))
|
||||
}
|
||||
|> 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.loadedFromCache = true
|
||||
}
|
||||
strongSelf.loadMore()
|
||||
}))
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
@ -433,17 +374,18 @@ private final class PeerExportedInvitationsContextImpl {
|
||||
}
|
||||
|
||||
func loadMore() {
|
||||
/*if self.isLoadingMore {
|
||||
if self.isLoadingMore {
|
||||
return
|
||||
}
|
||||
self.isLoadingMore = true
|
||||
let account = self.account
|
||||
let peerId = self.peerId
|
||||
let adminId = self.adminId
|
||||
let revoked = self.revoked
|
||||
var lastResult = self.results.last
|
||||
|
||||
if self.forceUpdate {
|
||||
self.populateCache = true
|
||||
self.populateCache = self.isMainList
|
||||
self.forceUpdate = false
|
||||
lastResult = nil
|
||||
} else if self.loadedFromCache {
|
||||
@ -452,11 +394,11 @@ private final class PeerExportedInvitationsContextImpl {
|
||||
}
|
||||
let populateCache = self.populateCache
|
||||
|
||||
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
self.disposable.set((self.account.postbox.transaction { transaction -> (peerId: Api.InputPeer?, adminId: Api.InputUser?) in
|
||||
return (transaction.getPeer(peerId).flatMap(apiInputPeer), transaction.getPeer(adminId).flatMap(apiInputUser))
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in
|
||||
if let inputPeer = inputPeer {
|
||||
|> mapToSignal { inputPeer, adminId -> Signal<([ExportedInvitation], Int32), NoError> in
|
||||
if let inputPeer = inputPeer, let adminId = adminId {
|
||||
let offsetLink = lastResult?.link
|
||||
let offsetDate = lastResult?.date
|
||||
var flags: Int32 = 0
|
||||
@ -466,7 +408,7 @@ private final class PeerExportedInvitationsContextImpl {
|
||||
if revoked {
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetDate: offsetDate, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
|
||||
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: adminId, offsetDate: offsetDate, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
||||
return .single(nil)
|
||||
@ -527,7 +469,7 @@ private final class PeerExportedInvitationsContextImpl {
|
||||
strongSelf.loadMore()
|
||||
}
|
||||
}))
|
||||
self.updateState()*/
|
||||
self.updateState()
|
||||
}
|
||||
|
||||
public func add(_ invite: ExportedInvitation) {
|
||||
@ -600,10 +542,10 @@ public final class PeerExportedInvitationsContext {
|
||||
}
|
||||
}
|
||||
|
||||
public init(account: Account, peerId: PeerId, revoked: Bool, forceUpdate: Bool) {
|
||||
public init(account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId, revoked: revoked, forceUpdate: forceUpdate)
|
||||
return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId, adminId: adminId, revoked: revoked, forceUpdate: forceUpdate)
|
||||
})
|
||||
}
|
||||
|
||||
@ -783,7 +725,7 @@ private final class PeerInvitationImportersContextImpl {
|
||||
if self.isLoadingMore {
|
||||
return
|
||||
}
|
||||
/*self.isLoadingMore = true
|
||||
self.isLoadingMore = true
|
||||
let account = self.account
|
||||
let peerId = self.peerId
|
||||
let link = self.link
|
||||
@ -871,7 +813,7 @@ private final class PeerInvitationImportersContextImpl {
|
||||
}
|
||||
strongSelf.updateState()
|
||||
}))
|
||||
self.updateState()*/
|
||||
self.updateState()
|
||||
}
|
||||
|
||||
private func updateState() {
|
||||
@ -908,3 +850,62 @@ public final class PeerInvitationImportersContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ExportedInvitationCreator : Equatable {
|
||||
public let peer: RenderedPeer
|
||||
public let count: Int32
|
||||
}
|
||||
|
||||
public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<[ExportedInvitationCreator], NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var isCreator = false
|
||||
if let peer = peer as? TelegramGroup, case .creator = peer.role {
|
||||
isCreator = true
|
||||
} else if let peer = peer as? TelegramChannel, peer.flags.contains(.isCreator) {
|
||||
isCreator = true
|
||||
}
|
||||
if !isCreator {
|
||||
return .single([])
|
||||
} else {
|
||||
return account.network.request(Api.functions.messages.getAdminsWithInvites(peer: inputPeer))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.ChatAdminsWithInvites?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<[ExportedInvitationCreator], NoError> in
|
||||
return account.postbox.transaction { transaction -> [ExportedInvitationCreator] in
|
||||
if let result = result, case let .chatAdminsWithInvites(admins, users) = result {
|
||||
var creators: [ExportedInvitationCreator] = []
|
||||
var peers: [Peer] = []
|
||||
var peersMap: [PeerId: Peer] = [:]
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
peersMap[telegramUser.id] = telegramUser
|
||||
}
|
||||
|
||||
for case let .chatAdminWithInvites(adminId, invitesCount) in admins {
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId)
|
||||
if let peer = peersMap[peerId], peerId != account.peerId {
|
||||
creators.append(ExportedInvitationCreator(peer: RenderedPeer(peer: peer), count: invitesCount))
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
return creators
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
||||
}
|
||||
|
||||
switch action {
|
||||
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL:
|
||||
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL:
|
||||
break
|
||||
case let .messageActionChannelMigrateFrom(_, chatId):
|
||||
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
|
||||
@ -371,7 +371,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
|
||||
extension StoreMessage {
|
||||
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
|
||||
switch apiMessage {
|
||||
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, ttlPeriod):
|
||||
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, _):
|
||||
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
|
||||
|
||||
let peerId: PeerId
|
||||
@ -468,21 +468,13 @@ extension StoreMessage {
|
||||
|
||||
var consumableContent: (Bool, Bool)? = nil
|
||||
|
||||
var addedAutoremoveAttribute = false
|
||||
if let ttlPeriod = ttlPeriod {
|
||||
addedAutoremoveAttribute = true
|
||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttlPeriod, countdownBeginTime: date))
|
||||
}
|
||||
|
||||
if let media = media {
|
||||
let (mediaValue, expirationTimer) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
if let mediaValue = mediaValue {
|
||||
medias.append(mediaValue)
|
||||
|
||||
if let expirationTimer = expirationTimer, expirationTimer > 0 {
|
||||
if !addedAutoremoveAttribute {
|
||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil))
|
||||
}
|
||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil))
|
||||
|
||||
consumableContent = (true, false)
|
||||
}
|
||||
|
@ -300,8 +300,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
}
|
||||
}
|
||||
|
||||
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(chatFull.ttl))
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
||||
let previous: CachedGroupData
|
||||
if let current = current as? CachedGroupData {
|
||||
@ -317,7 +315,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
.withUpdatedAbout(chatFull.about)
|
||||
.withUpdatedFlags(flags)
|
||||
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
|
||||
.withUpdatedInvitedBy(invitedBy)
|
||||
.withUpdatedPhoto(photo)
|
||||
.withUpdatedActiveCall(updatedActiveCall)
|
||||
@ -358,7 +355,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
}
|
||||
|
||||
switch fullChat {
|
||||
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, ttlPeriod):
|
||||
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, _):
|
||||
var channelFlags = CachedChannelFlags()
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
channelFlags.insert(.canDisplayParticipants)
|
||||
@ -513,8 +510,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
|
||||
minAvailableMessageIdUpdated = previous.minAvailableMessageId != minAvailableMessageId
|
||||
|
||||
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(ttlPeriod))
|
||||
|
||||
return previous.withUpdatedFlags(channelFlags)
|
||||
.withUpdatedAbout(about)
|
||||
.withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: participantsCount, adminCount: adminsCount, bannedCount: bannedCount, kickedCount: kickedCount))
|
||||
@ -529,7 +524,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
.withUpdatedSlowModeTimeout(slowmodeSeconds)
|
||||
.withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate)
|
||||
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
|
||||
.withUpdatedStatsDatacenterId(statsDc ?? 0)
|
||||
.withUpdatedInvitedBy(invitedBy)
|
||||
.withUpdatedPhoto(photo)
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -654,17 +654,17 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
discussionPeer = peer
|
||||
}
|
||||
|
||||
// if currentInvitationsContext == nil {
|
||||
// var canManageInvitations = false
|
||||
// if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
|
||||
// canManageInvitations = true
|
||||
// }
|
||||
// if canManageInvitations {
|
||||
// let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
|
||||
// invitationsContextPromise.set(.single(invitationsContext))
|
||||
// invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
||||
// }
|
||||
// }
|
||||
if currentInvitationsContext == nil {
|
||||
var canManageInvitations = false
|
||||
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
|
||||
canManageInvitations = true
|
||||
}
|
||||
if canManageInvitations {
|
||||
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
|
||||
invitationsContextPromise.set(.single(invitationsContext))
|
||||
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
||||
}
|
||||
}
|
||||
|
||||
return PeerInfoScreenData(
|
||||
peer: peerView.peers[peerId],
|
||||
@ -811,19 +811,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
}
|
||||
|
||||
// if currentInvitationsContext == nil {
|
||||
// var canManageInvitations = false
|
||||
// if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
|
||||
// canManageInvitations = true
|
||||
// } else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
|
||||
// canManageInvitations = true
|
||||
// }
|
||||
// if canManageInvitations {
|
||||
// let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
|
||||
// invitationsContextPromise.set(.single(invitationsContext))
|
||||
// invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
||||
// }
|
||||
// }
|
||||
if currentInvitationsContext == nil {
|
||||
var canManageInvitations = false
|
||||
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
|
||||
canManageInvitations = true
|
||||
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
|
||||
canManageInvitations = true
|
||||
}
|
||||
if canManageInvitations {
|
||||
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
|
||||
invitationsContextPromise.set(.single(invitationsContext))
|
||||
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
||||
}
|
||||
}
|
||||
|
||||
return PeerInfoScreenData(
|
||||
peer: peerView.peers[groupId],
|
||||
|
@ -931,8 +931,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
var directionIsToRight: Bool?
|
||||
if abs(velocity.x) > 10.0 {
|
||||
directionIsToRight = velocity.x < 0.0
|
||||
} else if abs(transitionFraction) > 0.5 {
|
||||
directionIsToRight = transitionFraction < 0.0
|
||||
} else if abs(self.transitionFraction) > 0.5 {
|
||||
directionIsToRight = self.transitionFraction < 0.0
|
||||
}
|
||||
var updatedIndex = self.currentIndex
|
||||
if let directionIsToRight = directionIsToRight {
|
||||
|
@ -1224,9 +1224,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
invitesText = ""
|
||||
}
|
||||
|
||||
// items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||
// interaction.editingOpenInviteLinksSetup()
|
||||
// }))
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||
interaction.editingOpenInviteLinksSetup()
|
||||
}))
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, action: {
|
||||
interaction.editingOpenDiscussionGroupSetup()
|
||||
@ -1326,9 +1326,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
invitesText = ""
|
||||
}
|
||||
|
||||
// items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||
// interaction.editingOpenInviteLinksSetup()
|
||||
// }))
|
||||
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||
interaction.editingOpenInviteLinksSetup()
|
||||
}))
|
||||
}
|
||||
|
||||
if cachedData.flags.contains(.canChangeUsername) {
|
||||
@ -1428,9 +1428,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
invitesText = ""
|
||||
}
|
||||
|
||||
// items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||
// interaction.editingOpenInviteLinksSetup()
|
||||
// }))
|
||||
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), 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: {
|
||||
interaction.editingOpenPreHistorySetup()
|
||||
@ -3808,7 +3808,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
private func editingOpenInviteLinksSetup() {
|
||||
self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId))
|
||||
self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId, admin: nil))
|
||||
}
|
||||
|
||||
private func editingOpenDiscussionGroupSetup() {
|
||||
|
@ -490,36 +490,73 @@ public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? {
|
||||
return nil
|
||||
}
|
||||
|
||||
private struct UrlHandlingConfiguration {
|
||||
static var defaultValue: UrlHandlingConfiguration {
|
||||
return UrlHandlingConfiguration(token: nil, domains: [])
|
||||
}
|
||||
|
||||
public let token: String?
|
||||
public let domains: [String]
|
||||
|
||||
fileprivate init(token: String?, domains: [String]) {
|
||||
self.token = token
|
||||
self.domains = domains
|
||||
}
|
||||
|
||||
static func with(appConfiguration: AppConfiguration) -> UrlHandlingConfiguration {
|
||||
if let data = appConfiguration.data, let token = data["autologin_token"] as? String, let domains = data["autologin_domains"] as? [String] {
|
||||
return UrlHandlingConfiguration(token: token, domains: domains)
|
||||
} else {
|
||||
return .defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveUrlImpl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
|
||||
let schemes = ["http://", "https://", ""]
|
||||
for basePath in baseTelegramMePaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
|
||||
return resolveInternalUrl(account: account, url: internalUrl)
|
||||
|> map { resolved -> ResolvedUrl in
|
||||
if let resolved = resolved {
|
||||
return resolved
|
||||
} else {
|
||||
return .externalUrl(url)
|
||||
|
||||
return account.postbox.transaction { transaction -> Signal<ResolvedUrl, NoError> in
|
||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
|
||||
let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration)
|
||||
|
||||
var url = url
|
||||
if let urlValue = URL(string: url), let host = urlValue.host, urlHandlingConfiguration.domains.contains(host), var components = URLComponents(string: url) {
|
||||
components.scheme = "https"
|
||||
var queryItems = components.queryItems ?? []
|
||||
queryItems.append(URLQueryItem(name: "autologin_token", value: urlHandlingConfiguration.token))
|
||||
components.queryItems = queryItems
|
||||
url = components.url?.absoluteString ?? url
|
||||
}
|
||||
|
||||
for basePath in baseTelegramMePaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
|
||||
return resolveInternalUrl(account: account, url: internalUrl)
|
||||
|> map { resolved -> ResolvedUrl in
|
||||
if let resolved = resolved {
|
||||
return resolved
|
||||
} else {
|
||||
return .externalUrl(url)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.externalUrl(url))
|
||||
}
|
||||
} else {
|
||||
return .single(.externalUrl(url))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for basePath in baseTelegraPhPaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
return resolveInstantViewUrl(account: account, url: url)
|
||||
for basePath in baseTelegraPhPaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
return resolveInstantViewUrl(account: account, url: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(.externalUrl(url))
|
||||
return .single(.externalUrl(url))
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user