mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into experimental-2
This commit is contained in:
commit
d9581782c1
@ -5902,6 +5902,9 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"InviteLink.InviteLinkCopiedText" = "Invite link copied to clipboard";
|
"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.Delivered" = "Delivered";
|
||||||
"Conversation.ChecksTooltip.Read" = "Read";
|
"Conversation.ChecksTooltip.Read" = "Read";
|
||||||
|
|
||||||
|
@ -474,7 +474,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
|
|||||||
var headerInsets = layout.insets(options: [.input])
|
var headerInsets = layout.insets(options: [.input])
|
||||||
headerInsets.top += actualNavigationBarHeight
|
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 {
|
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)))
|
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 {
|
} else {
|
||||||
|
@ -12,15 +12,15 @@ final class InviteContactsCountPanelNode: ASDisplayNode {
|
|||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
private let button: SolidRoundedButtonNode
|
private let button: SolidRoundedButtonNode
|
||||||
|
|
||||||
private var validLayout: (CGFloat, CGFloat)?
|
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
||||||
|
|
||||||
var count: Int = 0 {
|
var count: Int = 0 {
|
||||||
didSet {
|
didSet {
|
||||||
if self.count != oldValue && self.count > 0 {
|
if self.count != oldValue && self.count > 0 {
|
||||||
self.button.title = self.strings.Contacts_InviteContacts(Int32(self.count))
|
self.button.title = self.strings.Contacts_InviteContacts(Int32(self.count))
|
||||||
|
|
||||||
if let (width, bottomInset) = self.validLayout {
|
if let (width, sideInset, bottomInset) = self.validLayout {
|
||||||
let _ = self.updateLayout(width: width, bottomInset: bottomInset, transition: .immediate)
|
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 {
|
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
self.validLayout = (width, bottomInset)
|
self.validLayout = (width, sideInset, bottomInset)
|
||||||
let topInset: CGFloat = 9.0
|
let topInset: CGFloat = 9.0
|
||||||
var bottomInset = bottomInset
|
var bottomInset = bottomInset
|
||||||
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
|
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 buttonWidth = width - buttonInset * 2.0
|
||||||
let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition)
|
let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition)
|
||||||
transition.updateFrame(node: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight))
|
transition.updateFrame(node: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight))
|
||||||
|
@ -5,9 +5,6 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
|
|
||||||
private let textFont = Font.regular(13.0)
|
|
||||||
private let selectedTextFont = Font.bold(13.0)
|
|
||||||
|
|
||||||
public final class DatePickerTheme: Equatable {
|
public final class DatePickerTheme: Equatable {
|
||||||
public let backgroundColor: UIColor
|
public let backgroundColor: UIColor
|
||||||
public let textColor: UIColor
|
public let textColor: UIColor
|
||||||
@ -15,18 +12,16 @@ public final class DatePickerTheme: Equatable {
|
|||||||
public let accentColor: UIColor
|
public let accentColor: UIColor
|
||||||
public let disabledColor: UIColor
|
public let disabledColor: UIColor
|
||||||
public let selectionColor: UIColor
|
public let selectionColor: UIColor
|
||||||
public let selectedCurrentTextColor: UIColor
|
public let selectionTextColor: UIColor
|
||||||
public let secondarySelectionColor: 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.backgroundColor = backgroundColor
|
||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
self.secondaryTextColor = secondaryTextColor
|
self.secondaryTextColor = secondaryTextColor
|
||||||
self.accentColor = accentColor
|
self.accentColor = accentColor
|
||||||
self.disabledColor = disabledColor
|
self.disabledColor = disabledColor
|
||||||
self.selectionColor = selectionColor
|
self.selectionColor = selectionColor
|
||||||
self.selectedCurrentTextColor = selectedCurrentTextColor
|
self.selectionTextColor = selectionTextColor
|
||||||
self.secondarySelectionColor = secondarySelectionColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: DatePickerTheme, rhs: DatePickerTheme) -> Bool {
|
public static func ==(lhs: DatePickerTheme, rhs: DatePickerTheme) -> Bool {
|
||||||
@ -45,31 +40,26 @@ public final class DatePickerTheme: Equatable {
|
|||||||
if lhs.selectionColor != rhs.selectionColor {
|
if lhs.selectionColor != rhs.selectionColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.selectedCurrentTextColor != rhs.selectedCurrentTextColor {
|
if lhs.selectionTextColor != rhs.selectionTextColor {
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.secondarySelectionColor != rhs.secondarySelectionColor {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//public extension DatePickerTheme {
|
public extension DatePickerTheme {
|
||||||
// convenience init(theme: PresentationTheme) {
|
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)
|
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 class SegmentedControlItemNode: HighlightTrackingButtonNode {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0)
|
private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0)
|
||||||
private let upperLimitDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
|
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 dayFont = Font.regular(13.0)
|
||||||
private let dateFont = Font.with(size: 13.0, design: .regular, traits: .monospacedNumbers)
|
private let dateFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers)
|
||||||
private let selectedDateFont = Font.bold(13.0)
|
private let selectedDateFont = Font.with(size: 17.0, design: .regular, traits: [.bold, .monospacedNumbers])
|
||||||
|
|
||||||
private let calendar = Calendar(identifier: .gregorian)
|
private let calendar = Calendar(identifier: .gregorian)
|
||||||
|
|
||||||
@ -81,12 +71,46 @@ private func monthForDate(_ date: Date) -> Date {
|
|||||||
return calendar.date(from: components)!
|
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 {
|
class MonthNode: ASDisplayNode {
|
||||||
private let month: Date
|
private let month: Date
|
||||||
|
|
||||||
var theme: DatePickerTheme {
|
var theme: DatePickerTheme {
|
||||||
didSet {
|
didSet {
|
||||||
|
self.selectionNode.image = generateStretchableFilledCircleImage(diameter: 44.0, color: self.theme.selectionColor)
|
||||||
if let size = self.validSize {
|
if let size = self.validSize {
|
||||||
self.updateLayout(size: size)
|
self.updateLayout(size: size)
|
||||||
}
|
}
|
||||||
@ -136,6 +160,7 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.selectionNode = ASImageNode()
|
self.selectionNode = ASImageNode()
|
||||||
self.selectionNode.displaysAsynchronously = false
|
self.selectionNode.displaysAsynchronously = false
|
||||||
self.selectionNode.displayWithoutProcessing = true
|
self.selectionNode.displayWithoutProcessing = true
|
||||||
|
self.selectionNode.image = generateStretchableFilledCircleImage(diameter: 44.0, color: theme.selectionColor)
|
||||||
|
|
||||||
self.dateNodes = (0..<42).map { _ in ImmediateTextNode() }
|
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
|
self.numberOfDays = calendar.range(of: .day, in: .month, for: month)!.count
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.selectionNode)
|
self.addSubnode(self.selectionNode)
|
||||||
self.dateNodes.forEach { self.addSubnode($0) }
|
self.dateNodes.forEach { self.addSubnode($0) }
|
||||||
}
|
}
|
||||||
@ -157,6 +182,10 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var started = false
|
var started = false
|
||||||
var count = 0
|
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 {
|
for i in 0 ..< 42 {
|
||||||
let row: Int = Int(floor(Float(i) / 7.0))
|
let row: Int = Int(floor(Float(i) / 7.0))
|
||||||
let col: Int = i % 7
|
let col: Int = i % 7
|
||||||
@ -164,49 +193,55 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if !started && weekday == self.startWeekday {
|
if !started && weekday == self.startWeekday {
|
||||||
started = true
|
started = true
|
||||||
}
|
}
|
||||||
|
weekday += 1
|
||||||
if started {
|
if started {
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
var isAvailableDate = true
|
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 {
|
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 {
|
if date < minimumDate {
|
||||||
isAvailableDate = false
|
isAvailableDate = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let maximumDate = self.maximumDate {
|
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 {
|
if date > maximumDate {
|
||||||
isAvailableDate = false
|
isAvailableDate = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var isSelectedDate = false
|
let isToday = calendar.isDateInToday(date)
|
||||||
var isSelectedAndCurrentDate = false
|
let isSelected = self.date.flatMap { calendar.isDate(date, equalTo: $0, toGranularity: .day) } ?? false
|
||||||
|
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
if !isAvailableDate {
|
if isSelected {
|
||||||
color = self.theme.disabledColor
|
color = self.theme.selectionTextColor
|
||||||
} else if isSelectedAndCurrentDate {
|
} else if isToday {
|
||||||
color = .white
|
|
||||||
} else if isSelectedDate {
|
|
||||||
color = self.theme.accentColor
|
color = self.theme.accentColor
|
||||||
|
} else if !isAvailableDate {
|
||||||
|
color = self.theme.disabledColor
|
||||||
} else {
|
} else {
|
||||||
color = self.theme.textColor
|
color = self.theme.textColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let textNode = self.dateNodes[i]
|
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)
|
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 {
|
if count == self.numberOfDays {
|
||||||
break
|
break
|
||||||
@ -232,40 +267,75 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private let timeTitleNode: ImmediateTextNode
|
private let timeTitleNode: ImmediateTextNode
|
||||||
private let timeFieldNode: ASImageNode
|
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 monthButtonNode: HighlightTrackingButtonNode
|
||||||
private let monthTextNode: ImmediateTextNode
|
private let monthTextNode: ImmediateTextNode
|
||||||
private let monthArrowNode: ASImageNode
|
private let monthArrowNode: ASImageNode
|
||||||
|
|
||||||
private let previousButtonNode: HighlightableButtonNode
|
private let previousButtonNode: HighlightableButtonNode
|
||||||
private let nextButtonNode: HighlightableButtonNode
|
private let nextButtonNode: HighlightableButtonNode
|
||||||
|
|
||||||
private let dayNodes: [ImmediateTextNode]
|
private var transitionFraction: CGFloat = 0.0
|
||||||
private var previousMonthNode: MonthNode?
|
|
||||||
private var currentMonthNode: MonthNode?
|
|
||||||
private var nextMonthNode: MonthNode?
|
|
||||||
private let scrollNode: ASScrollNode
|
|
||||||
|
|
||||||
private var gestureRecognizer: UIPanGestureRecognizer?
|
private var gestureRecognizer: UIPanGestureRecognizer?
|
||||||
private var gestureSelectedIndex: Int?
|
private var gestureSelectedIndex: Int?
|
||||||
|
|
||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
public var maximumDate: Date? {
|
public var maximumDate: Date {
|
||||||
didSet {
|
get {
|
||||||
|
return self.state.maxDate
|
||||||
}
|
}
|
||||||
}
|
set {
|
||||||
public var minimumDate: Date = telegramReleaseDate {
|
guard newValue != self.maximumDate else {
|
||||||
didSet {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public var date: Date = Date() {
|
|
||||||
didSet {
|
|
||||||
guard self.date != oldValue else {
|
|
||||||
return
|
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 {
|
if let size = self.validLayout {
|
||||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||||
}
|
}
|
||||||
@ -282,33 +352,74 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.timeFieldNode.displaysAsynchronously = false
|
self.timeFieldNode.displaysAsynchronously = false
|
||||||
self.timeFieldNode.displayWithoutProcessing = true
|
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.monthButtonNode = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
self.monthTextNode = ImmediateTextNode()
|
self.monthTextNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.monthArrowNode = ASImageNode()
|
self.monthArrowNode = ASImageNode()
|
||||||
self.monthArrowNode.displaysAsynchronously = false
|
self.monthArrowNode.displaysAsynchronously = false
|
||||||
self.monthArrowNode.displayWithoutProcessing = true
|
self.monthArrowNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.previousButtonNode = HighlightableButtonNode()
|
self.previousButtonNode = HighlightableButtonNode()
|
||||||
|
self.previousButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -10.0, bottom: -6.0, right: -10.0)
|
||||||
self.nextButtonNode = HighlightableButtonNode()
|
self.nextButtonNode = HighlightableButtonNode()
|
||||||
|
self.nextButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -10.0, bottom: -6.0, right: -10.0)
|
||||||
self.dayNodes = (0..<7).map { _ in ImmediateTextNode() }
|
|
||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
self.backgroundColor = theme.backgroundColor
|
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.monthTextNode)
|
||||||
self.addSubnode(self.monthArrowNode)
|
self.addSubnode(self.monthArrowNode)
|
||||||
self.addSubnode(self.monthButtonNode)
|
self.addSubnode(self.monthButtonNode)
|
||||||
|
|
||||||
self.addSubnode(self.previousButtonNode)
|
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
|
||||||
self.addSubnode(self.nextButtonNode)
|
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() {
|
override public func didLoad() {
|
||||||
@ -316,15 +427,62 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
|
||||||
self.scrollNode.view.isPagingEnabled = true
|
self.contentNode.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
|
||||||
self.scrollNode.view.delegate = self
|
self.contentNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateState(_ state: State, animated: Bool) {
|
private func updateState(_ state: State, animated: Bool) {
|
||||||
|
let previousState = self.state
|
||||||
self.state = 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) {
|
public func updateTheme(_ theme: DatePickerTheme) {
|
||||||
@ -334,55 +492,262 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.backgroundColor = self.theme.backgroundColor
|
self.backgroundColor = self.theme.backgroundColor
|
||||||
}
|
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
|
||||||
|
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
|
||||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
self.nextButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: false), for: .normal)
|
||||||
self.view.window?.endEditing(true)
|
|
||||||
}
|
for (_, monthNode) in self.monthNodes {
|
||||||
|
monthNode.theme = theme
|
||||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
}
|
||||||
if !decelerate {
|
|
||||||
if let size = self.validLayout {
|
if let size = self.validLayout {
|
||||||
self.updateLayout(size: size, transition: .immediate)
|
self.updateLayout(size: size, transition: .immediate)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if let size = self.validLayout {
|
|
||||||
self.updateLayout(size: size, transition: .immediate)
|
}
|
||||||
|
|
||||||
|
@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) {
|
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
self.validLayout = size
|
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)
|
let month = monthForDate(self.state.selectedMonth)
|
||||||
self.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: scrollSize)
|
let components = calendar.dateComponents([.month, .year], from: month)
|
||||||
self.scrollNode.view.contentSize = CGSize(width: scrollSize.width * 3.0, height: scrollSize.height)
|
|
||||||
self.scrollNode.view.contentOffset = CGPoint(x: scrollSize.width, y: 0.0)
|
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 {
|
for i in 0 ..< self.dayNodes.count {
|
||||||
let dayNode = self.dayNodes[i]
|
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)
|
let textSize = dayNode.updateLayout(size)
|
||||||
dayNode.attributedText = NSAttributedString(string: shortStringForDayOfWeek(strings: self.strings, day: day), font: dayFont, textColor: theme.secondaryTextColor)
|
let cellFrame = CGRect(x: daysSideInset + CGFloat(i) * cellSize, y: 40.0, width: cellSize, height: cellSize)
|
||||||
let size = dayNode.updateLayout(size)
|
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 = CGRect(origin: CGPoint(x: CGFloat(i) * 20.0, y: 0.0), size: size)
|
|
||||||
|
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)
|
var updatedDescriptor: UIFontDescriptor? = descriptor.withSymbolicTraits(symbolicTraits)
|
||||||
if traits.contains(.monospacedNumbers) {
|
if traits.contains(.monospacedNumbers) {
|
||||||
updatedDescriptor = descriptor.addingAttributes([
|
updatedDescriptor = updatedDescriptor?.addingAttributes([
|
||||||
UIFontDescriptor.AttributeName.featureSettings: [
|
UIFontDescriptor.AttributeName.featureSettings: [
|
||||||
[UIFontDescriptor.FeatureKey.featureIdentifier:
|
[UIFontDescriptor.FeatureKey.featureIdentifier:
|
||||||
kNumberSpacingType,
|
kNumberSpacingType,
|
||||||
|
@ -49,6 +49,7 @@ swift_library(
|
|||||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||||
"//submodules/SectionHeaderItem:SectionHeaderItem",
|
"//submodules/SectionHeaderItem:SectionHeaderItem",
|
||||||
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
|
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
|
||||||
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -193,7 +193,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
|||||||
arguments.dismissInput()
|
arguments.dismissInput()
|
||||||
arguments.updateState { state in
|
arguments.updateState { state in
|
||||||
var updatedState = state
|
var updatedState = state
|
||||||
updatedState.pickingTimeLimit = !state.pickingTimeLimit
|
// updatedState.pickingTimeLimit = !state.pickingTimeLimit
|
||||||
return updatedState
|
return updatedState
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -149,7 +149,7 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
}, viewAction: {
|
}, viewAction: {
|
||||||
})
|
})
|
||||||
case let .links(_, _, invites):
|
case let .links(_, _, invites):
|
||||||
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, share: true, sectionId: 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)
|
interaction.copyLink(invite)
|
||||||
}, contextAction: { invite, _ in
|
}, contextAction: { invite, _ in
|
||||||
interaction.shareLink(invite)
|
interaction.shareLink(invite)
|
||||||
@ -218,7 +218,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = Node(context: self.context, peerId: self.peerId, controller: self)
|
self.displayNode = Node(context: self.context, peerId: self.peerId, controller: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var didAppearOnce: Bool = false
|
private var didAppearOnce: Bool = false
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
public override func viewDidAppear(_ animated: Bool) {
|
public override func viewDidAppear(_ animated: Bool) {
|
||||||
@ -288,7 +288,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self.presentationDataPromise = Promise(self.presentationData)
|
self.presentationDataPromise = Promise(self.presentationData)
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
|
self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: false)
|
||||||
|
|
||||||
self.dimNode = ASDisplayNode()
|
self.dimNode = ASDisplayNode()
|
||||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
@ -348,16 +348,16 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
// items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||||
// return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||||
// }, action: { _, f in
|
}, action: { _, f in
|
||||||
// f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
//
|
|
||||||
// if let invite = invite {
|
if let invite = invite {
|
||||||
// let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||||
// self?.controller?.present(controller, in: .window(.root))
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
// }
|
}
|
||||||
// })))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
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)
|
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))
|
let shareController = ShareController(context: context, subject: .url(invite.link))
|
||||||
self?.controller?.present(shareController, in: .window(.root))
|
self?.controller?.present(shareController, in: .window(.root))
|
||||||
}, manageLinks: { [weak self] in
|
}, manageLinks: { [weak self] in
|
||||||
let controller = inviteLinkListController(context: context, peerId: peerId)
|
let controller = inviteLinkListController(context: context, peerId: peerId, admin: nil)
|
||||||
self?.controller?.parentNavigationController?.pushViewController(controller)
|
self?.controller?.parentNavigationController?.pushViewController(controller)
|
||||||
self?.controller?.dismiss()
|
self?.controller?.dismiss()
|
||||||
})
|
})
|
||||||
@ -403,8 +403,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
|
let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
|
||||||
|
|
||||||
let peerView = context.account.postbox.peerView(id: peerId)
|
let peerView = context.account.postbox.peerView(id: peerId)
|
||||||
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
|
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, self.invitesContext.state)
|
||||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, invites)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view, invites in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view, invites in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var entries: [InviteLinkInviteEntry] = []
|
var entries: [InviteLinkInviteEntry] = []
|
||||||
@ -423,19 +422,19 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
entries.append(.mainLink(presentationData.theme, mainInvite))
|
entries.append(.mainLink(presentationData.theme, mainInvite))
|
||||||
}
|
}
|
||||||
|
|
||||||
// let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
|
let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
|
||||||
// var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
// for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
|
for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
|
||||||
// var invitesPair: [ExportedInvitation] = []
|
var invitesPair: [ExportedInvitation] = []
|
||||||
// invitesPair.append(additionalInvites[i])
|
invitesPair.append(additionalInvites[i])
|
||||||
// if i + 1 < additionalInvites.count {
|
if i + 1 < additionalInvites.count {
|
||||||
// invitesPair.append(additionalInvites[i + 1])
|
invitesPair.append(additionalInvites[i + 1])
|
||||||
// }
|
}
|
||||||
// entries.append(.links(index, presentationData.theme, invitesPair))
|
entries.append(.links(index, presentationData.theme, invitesPair))
|
||||||
// index += 1
|
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)
|
let previousEntries = previousEntries.swap(entries)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import AppBundle
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import ItemListPeerActionItem
|
import ItemListPeerActionItem
|
||||||
|
import ItemListPeerItem
|
||||||
import ShareController
|
import ShareController
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
||||||
@ -30,9 +31,10 @@ private final class InviteLinkListControllerArguments {
|
|||||||
let createLink: () -> Void
|
let createLink: () -> Void
|
||||||
let openLink: (ExportedInvitation) -> Void
|
let openLink: (ExportedInvitation) -> Void
|
||||||
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
|
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
|
||||||
|
let openAdmin: (ExportedInvitationCreator) -> Void
|
||||||
let deleteAllRevokedLinks: () -> 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.context = context
|
||||||
self.shareMainLink = shareMainLink
|
self.shareMainLink = shareMainLink
|
||||||
self.openMainLink = openMainLink
|
self.openMainLink = openMainLink
|
||||||
@ -41,6 +43,7 @@ private final class InviteLinkListControllerArguments {
|
|||||||
self.createLink = createLink
|
self.createLink = createLink
|
||||||
self.openLink = openLink
|
self.openLink = openLink
|
||||||
self.linkContextAction = linkContextAction
|
self.linkContextAction = linkContextAction
|
||||||
|
self.openAdmin = openAdmin
|
||||||
self.deleteAllRevokedLinks = deleteAllRevokedLinks
|
self.deleteAllRevokedLinks = deleteAllRevokedLinks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,6 +52,7 @@ private enum InviteLinksListSection: Int32 {
|
|||||||
case header
|
case header
|
||||||
case mainLink
|
case mainLink
|
||||||
case links
|
case links
|
||||||
|
case admins
|
||||||
case revokedLinks
|
case revokedLinks
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,11 +61,16 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
case mainLinkHeader(PresentationTheme, String)
|
case mainLinkHeader(PresentationTheme, String)
|
||||||
case mainLink(PresentationTheme, ExportedInvitation?, [Peer], Int32, Bool)
|
case mainLink(PresentationTheme, ExportedInvitation?, [Peer], Int32, Bool)
|
||||||
|
case mainLinkOtherInfo(PresentationTheme, String)
|
||||||
|
|
||||||
case linksHeader(PresentationTheme, String)
|
case linksHeader(PresentationTheme, String)
|
||||||
case linksCreate(PresentationTheme, String)
|
case linksCreate(PresentationTheme, String)
|
||||||
case links(Int32, PresentationTheme, [ExportedInvitation]?)
|
case links(Int32, PresentationTheme, [ExportedInvitation]?, Int)
|
||||||
case linksInfo(PresentationTheme, String)
|
case linksInfo(PresentationTheme, String)
|
||||||
|
|
||||||
|
case adminsHeader(PresentationTheme, String)
|
||||||
|
case admin(Int32, PresentationTheme, ExportedInvitationCreator)
|
||||||
|
|
||||||
case revokedLinksHeader(PresentationTheme, String)
|
case revokedLinksHeader(PresentationTheme, String)
|
||||||
case revokedLinksDeleteAll(PresentationTheme, String)
|
case revokedLinksDeleteAll(PresentationTheme, String)
|
||||||
case revokedLinks(Int32, PresentationTheme, [ExportedInvitation]?)
|
case revokedLinks(Int32, PresentationTheme, [ExportedInvitation]?)
|
||||||
@ -70,10 +79,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case .header:
|
case .header:
|
||||||
return InviteLinksListSection.header.rawValue
|
return InviteLinksListSection.header.rawValue
|
||||||
case .mainLinkHeader, .mainLink:
|
case .mainLinkHeader, .mainLink, .mainLinkOtherInfo:
|
||||||
return InviteLinksListSection.mainLink.rawValue
|
return InviteLinksListSection.mainLink.rawValue
|
||||||
case .linksHeader, .linksCreate, .links, .linksInfo:
|
case .linksHeader, .linksCreate, .links, .linksInfo:
|
||||||
return InviteLinksListSection.links.rawValue
|
return InviteLinksListSection.links.rawValue
|
||||||
|
case .adminsHeader, .admin:
|
||||||
|
return InviteLinksListSection.admins.rawValue
|
||||||
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLinks:
|
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLinks:
|
||||||
return InviteLinksListSection.revokedLinks.rawValue
|
return InviteLinksListSection.revokedLinks.rawValue
|
||||||
}
|
}
|
||||||
@ -87,20 +98,26 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
return 1
|
return 1
|
||||||
case .mainLink:
|
case .mainLink:
|
||||||
return 2
|
return 2
|
||||||
case .linksHeader:
|
case .mainLinkOtherInfo:
|
||||||
return 3
|
return 3
|
||||||
case .linksCreate:
|
case .linksHeader:
|
||||||
return 4
|
return 4
|
||||||
case let .links(index, _, _):
|
case .linksCreate:
|
||||||
return 5 + index
|
return 5
|
||||||
|
case let .links(index, _, _, _):
|
||||||
|
return 6 + index
|
||||||
case .linksInfo:
|
case .linksInfo:
|
||||||
return 10000
|
return 10000
|
||||||
case .revokedLinksHeader:
|
case .adminsHeader:
|
||||||
return 10001
|
return 10001
|
||||||
|
case let .admin(index, _, _):
|
||||||
|
return 10002 + index
|
||||||
|
case .revokedLinksHeader:
|
||||||
|
return 20001
|
||||||
case .revokedLinksDeleteAll:
|
case .revokedLinksDeleteAll:
|
||||||
return 10002
|
return 20002
|
||||||
case let .revokedLinks(index, _, _):
|
case let .revokedLinks(index, _, _):
|
||||||
return 10003 + index
|
return 20003 + index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +141,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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):
|
case let .linksHeader(lhsTheme, lhsText):
|
||||||
if case let .linksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .linksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -136,8 +159,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .links(lhsIndex, lhsTheme, lhsLinks):
|
case let .links(lhsIndex, lhsTheme, lhsLinks, lhsCount):
|
||||||
if case let .links(rhsIndex, rhsTheme, rhsLinks) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLinks == rhsLinks {
|
if case let .links(rhsIndex, rhsTheme, rhsLinks, rhsCount) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLinks == rhsLinks, lhsCount == rhsCount {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -148,6 +171,18 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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):
|
case let .revokedLinksHeader(lhsTheme, lhsText):
|
||||||
if case let .revokedLinksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .revokedLinksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -196,20 +231,28 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
arguments.openLink(invite)
|
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):
|
case let .linksHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .linksCreate(theme, text):
|
case let .linksCreate(theme, text):
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, hasSeparator: false, sectionId: self.section, editing: false, action: {
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, hasSeparator: false, sectionId: self.section, editing: false, action: {
|
||||||
arguments.createLink()
|
arguments.createLink()
|
||||||
})
|
})
|
||||||
case let .links(_, _, invites):
|
case let .links(_, _, invites, count):
|
||||||
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, count: count, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||||
arguments.openLink(invite)
|
arguments.openLink(invite)
|
||||||
}, contextAction: { invite, node in
|
}, contextAction: { invite, node in
|
||||||
arguments.linkContextAction(invite, node, nil)
|
arguments.linkContextAction(invite, node, nil)
|
||||||
})
|
})
|
||||||
case let .linksInfo(_, text):
|
case let .linksInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
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):
|
case let .revokedLinksHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .revokedLinksDeleteAll(theme, text):
|
case let .revokedLinksDeleteAll(theme, text):
|
||||||
@ -217,7 +260,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
arguments.deleteAllRevokedLinks()
|
arguments.deleteAllRevokedLinks()
|
||||||
})
|
})
|
||||||
case let .revokedLinks(_, _, invites):
|
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)
|
arguments.openLink(invite)
|
||||||
}, contextAction: { invite, node in
|
}, contextAction: { invite, node in
|
||||||
arguments.linkContextAction(invite, node, nil)
|
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] = []
|
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?
|
let mainInvite: ExportedInvitation?
|
||||||
var isPublic = false
|
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)
|
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
|
isPublic = true
|
||||||
} else if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) {
|
} else if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) {
|
||||||
mainInvite = invite
|
mainInvite = invite
|
||||||
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation {
|
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation, admin == nil {
|
||||||
mainInvite = invite
|
mainInvite = invite
|
||||||
} else if let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation {
|
} else if let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation, admin == nil {
|
||||||
mainInvite = invite
|
mainInvite = invite
|
||||||
} else {
|
} else {
|
||||||
mainInvite = nil
|
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))
|
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(.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]?
|
var additionalInvites: [ExportedInvitation]?
|
||||||
if let invites = invites {
|
if let invites = invites {
|
||||||
additionalInvites = invites.filter { $0.link != mainInvite?.link }
|
additionalInvites = invites.filter { $0.link != mainInvite?.link }
|
||||||
@ -275,16 +324,34 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
|||||||
if i + 1 < additionalInvites.count {
|
if i + 1 < additionalInvites.count {
|
||||||
invitesPair.append(additionalInvites[i + 1])
|
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
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries.append(.linksInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
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 {
|
if let revokedInvites = revokedInvites, !revokedInvites.isEmpty {
|
||||||
entries.append(.revokedLinksHeader(presentationData.theme, presentationData.strings.InviteLink_RevokedLinks.uppercased()))
|
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
|
var index: Int32 = 0
|
||||||
for i in stride(from: 0, to: revokedInvites.endIndex, by: 2) {
|
for i in stride(from: 0, to: revokedInvites.endIndex, by: 2) {
|
||||||
var invitesPair: [ExportedInvitation] = []
|
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 pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||||
@ -326,8 +393,16 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
|
|
||||||
var getControllerImpl: (() -> ViewController?)?
|
var getControllerImpl: (() -> ViewController?)?
|
||||||
|
|
||||||
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
|
let adminId = admin?.peer.peer?.id
|
||||||
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: true, forceUpdate: true)
|
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 arguments = InviteLinkListControllerArguments(context: context, shareMainLink: { invite in
|
||||||
let shareController = ShareController(context: context, subject: .url(invite.link))
|
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)
|
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
presentInGlobalOverlayImpl?(contextController)
|
presentInGlobalOverlayImpl?(contextController)
|
||||||
|
}, openAdmin: { admin in
|
||||||
|
let controller = inviteLinkListController(context: context, peerId: peerId, admin: admin)
|
||||||
|
pushControllerImpl?(controller)
|
||||||
}, deleteAllRevokedLinks: {
|
}, deleteAllRevokedLinks: {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let controller = ActionSheetController(presentationData: presentationData)
|
let controller = ActionSheetController(presentationData: presentationData)
|
||||||
@ -596,9 +674,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
let previousRevokedInvites = Atomic<PeerExportedInvitationsState?>(value: nil)
|
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
|
|> 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)
|
let previousRevokedInvites = previousRevokedInvites.swap(invites)
|
||||||
|
|
||||||
var crossfade = false
|
var crossfade = false
|
||||||
@ -606,8 +684,15 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
|
|||||||
crossfade = true
|
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 title: ItemListControllerTitle
|
||||||
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)
|
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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var updateTimer: SwiftSignalKit.Timer?
|
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 action: (() -> Void)?
|
||||||
var contextAction: ((ASDisplayNode) -> Void)?
|
var contextAction: ((ASDisplayNode) -> Void)?
|
||||||
@ -214,44 +214,50 @@ private class ItemNode: ASDisplayNode {
|
|||||||
self.contextAction?(self.extractedContainerNode)
|
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 currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
|
||||||
let availability = invitationAvailability(invite)
|
let availability = invite.flatMap { invitationAvailability($0) } ?? 0.0
|
||||||
let transitionFraction: CGFloat
|
let transitionFraction: CGFloat
|
||||||
let color: ItemBackgroundColor
|
let color: ItemBackgroundColor
|
||||||
let nextColor: 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
|
color = .gray
|
||||||
nextColor = .gray
|
nextColor = .gray
|
||||||
transitionFraction = 0.0
|
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
|
let previousParams = self.params
|
||||||
self.params = (size, wide, invite, color, presentationData)
|
self.params = (size, wide, invite, color, presentationData)
|
||||||
|
|
||||||
let previousExpireDate = previousParams?.invite.expireDate
|
let previousExpireDate = previousParams?.invite?.expireDate
|
||||||
if previousExpireDate != invite.expireDate {
|
if previousExpireDate != invite?.expireDate {
|
||||||
self.updateTimer?.invalidate()
|
self.updateTimer?.invalidate()
|
||||||
self.updateTimer = nil
|
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 timeout = min(2.0, max(0.001, Double(expireDate - currentTime)))
|
||||||
let updateTimer = SwiftSignalKit.Timer(timeout: timeout, repeat: true, completion: { [weak self] in
|
let updateTimer = SwiftSignalKit.Timer(timeout: timeout, repeat: true, completion: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -272,8 +278,13 @@ private class ItemNode: ASDisplayNode {
|
|||||||
let bottomColor = color.colors.bottom
|
let bottomColor = color.colors.bottom
|
||||||
let nextTopColor = nextColor.colors.top
|
let nextTopColor = nextColor.colors.top
|
||||||
let nextBottomColor = nextColor.colors.bottom
|
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 let (_, _, previousInvite, previousColor, _) = previousParams, previousInvite == invite {
|
||||||
if previousColor != color && color == .red {
|
if previousColor != color && color == .red {
|
||||||
if let snapshotView = self.wrapperNode.view.snapshotContentTree() {
|
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 secondaryTextColor = nextColor.colors.text.mixedWith(color.colors.text, alpha: transitionFraction)
|
||||||
|
|
||||||
let itemWidth = wide ? size.width : floor((size.width - itemSpacing) / 2.0)
|
let itemWidth = wide ? size.width : floor((size.width - itemSpacing) / 2.0)
|
||||||
var inviteLink = invite.link.replacingOccurrences(of: "https://", with: "")
|
var inviteLink = invite?.link.replacingOccurrences(of: "https://", with: "") ?? ""
|
||||||
if !wide {
|
if !wide {
|
||||||
inviteLink = inviteLink.replacingOccurrences(of: "joinchat/", with: "joinchat/\n")
|
inviteLink = inviteLink.replacingOccurrences(of: "joinchat/", with: "joinchat/\n")
|
||||||
inviteLink = inviteLink.replacingOccurrences(of: "join/", with: "join/\n")
|
inviteLink = inviteLink.replacingOccurrences(of: "join/", with: "join/\n")
|
||||||
@ -314,65 +325,72 @@ private class ItemNode: ASDisplayNode {
|
|||||||
self.buttonIconNode.image = share ? shareIcon : moreIcon
|
self.buttonIconNode.image = share ? shareIcon : moreIcon
|
||||||
|
|
||||||
var subtitleText: String = ""
|
var subtitleText: String = ""
|
||||||
if let count = invite.count {
|
if let invite = invite {
|
||||||
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
|
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 {
|
} else {
|
||||||
subtitleText = [.red, .gray].contains(color) ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
|
self.iconNode.isHidden = true
|
||||||
}
|
self.buttonIconNode.isHidden = true
|
||||||
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.frame = CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)
|
self.iconNode.frame = CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)
|
||||||
@ -407,7 +425,7 @@ private class ItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class InviteLinksGridNode: ASDisplayNode {
|
class InviteLinksGridNode: ASDisplayNode {
|
||||||
private var items: [ExportedInvitation] = []
|
private var items: [ExportedInvitation]?
|
||||||
private var itemNodes: [String: ItemNode] = [:]
|
private var itemNodes: [String: ItemNode] = [:]
|
||||||
|
|
||||||
var action: ((ExportedInvitation) -> Void)?
|
var action: ((ExportedInvitation) -> Void)?
|
||||||
@ -418,7 +436,7 @@ class InviteLinksGridNode: ASDisplayNode {
|
|||||||
return result
|
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
|
self.items = items
|
||||||
|
|
||||||
var contentSize: CGSize = size
|
var contentSize: CGSize = size
|
||||||
@ -428,24 +446,37 @@ class InviteLinksGridNode: ASDisplayNode {
|
|||||||
|
|
||||||
var validIds = Set<String>()
|
var validIds = Set<String>()
|
||||||
|
|
||||||
for i in 0 ..< self.items.count {
|
let count = items?.count ?? count
|
||||||
let invite = self.items[i]
|
|
||||||
validIds.insert(invite.link)
|
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 itemNode: ItemNode?
|
||||||
var wasAdded = false
|
var wasAdded = false
|
||||||
if let current = self.itemNodes[invite.link] {
|
|
||||||
|
if let current = self.itemNodes[id] {
|
||||||
itemNode = current
|
itemNode = current
|
||||||
} else {
|
} else {
|
||||||
wasAdded = true
|
wasAdded = true
|
||||||
let addedItemNode = ItemNode()
|
let addedItemNode = ItemNode()
|
||||||
itemNode = addedItemNode
|
itemNode = addedItemNode
|
||||||
self.itemNodes[invite.link] = addedItemNode
|
self.itemNodes[id] = addedItemNode
|
||||||
self.addSubnode(addedItemNode)
|
self.addSubnode(addedItemNode)
|
||||||
}
|
}
|
||||||
if let itemNode = itemNode {
|
if let itemNode = itemNode {
|
||||||
let col = CGFloat(i % 2)
|
let col = CGFloat(i % 2)
|
||||||
let row = floor(CGFloat(i) / 2.0)
|
let row = floor(CGFloat(i) / 2.0)
|
||||||
let wide = (i == self.items.count - 1 && (self.items.count % 2) != 0)
|
let wide = (i == 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)
|
let itemSize = itemNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: size.height), wide: wide, share: share, invite: invite, presentationData: presentationData, transition: transition)
|
||||||
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: 4.0 + row * (122.0 + itemSpacing)), size: itemSize)
|
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: 4.0 + row * (122.0 + itemSpacing)), size: itemSize)
|
||||||
if !wide && col > 0 {
|
if !wide && col > 0 {
|
||||||
@ -460,10 +491,14 @@ class InviteLinksGridNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: itemNode, frame: itemFrame)
|
transition.updateFrame(node: itemNode, frame: itemFrame)
|
||||||
}
|
}
|
||||||
itemNode.action = { [weak self] in
|
itemNode.action = { [weak self] in
|
||||||
self?.action?(invite)
|
if let invite = invite {
|
||||||
|
self?.action?(invite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
itemNode.contextAction = { [weak self] node in
|
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 {
|
public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let invites: [ExportedInvitation]?
|
let invites: [ExportedInvitation]?
|
||||||
|
let count: Int
|
||||||
let share: Bool
|
let share: Bool
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
@ -20,6 +21,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
|||||||
public init(
|
public init(
|
||||||
presentationData: ItemListPresentationData,
|
presentationData: ItemListPresentationData,
|
||||||
invites: [ExportedInvitation]?,
|
invites: [ExportedInvitation]?,
|
||||||
|
count: Int,
|
||||||
share: Bool,
|
share: Bool,
|
||||||
sectionId: ItemListSectionId,
|
sectionId: ItemListSectionId,
|
||||||
style: ItemListStyle,
|
style: ItemListStyle,
|
||||||
@ -29,6 +31,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
|||||||
) {
|
) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.invites = invites
|
self.invites = invites
|
||||||
|
self.count = count
|
||||||
self.share = share
|
self.share = share
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
@ -133,13 +136,14 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
|
|||||||
let topInset: CGFloat
|
let topInset: CGFloat
|
||||||
if case .plain = item.style, case .otherSection = neighbors.top {
|
if case .plain = item.style, case .otherSection = neighbors.top {
|
||||||
topInset = 16.0
|
topInset = 16.0
|
||||||
|
} else if case .blocks = item.style, case .sameSection(true) = neighbors.top {
|
||||||
|
topInset = 16.0
|
||||||
} else {
|
} else {
|
||||||
topInset = 4.0
|
topInset = 4.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var height: CGFloat
|
var height: CGFloat
|
||||||
let count = item.invites?.count ?? 0
|
let count = item.invites?.count ?? item.count
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
if count % 2 == 0 {
|
if count % 2 == 0 {
|
||||||
height = topInset + 122.0 + 6.0
|
height = topInset + 122.0 + 6.0
|
||||||
@ -176,7 +180,7 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
|
|||||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites ?? [], 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.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset - 4.0), size: gridSize)
|
||||||
strongSelf.gridNode.action = { invite in
|
strongSelf.gridNode.action = { invite in
|
||||||
item.tapAction?(invite)
|
item.tapAction?(invite)
|
||||||
|
@ -10,6 +10,7 @@ import TelegramPresentationData
|
|||||||
import ItemListUI
|
import ItemListUI
|
||||||
import SolidRoundedButtonNode
|
import SolidRoundedButtonNode
|
||||||
import AnimatedAvatarSetNode
|
import AnimatedAvatarSetNode
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
private func actionButtonImage(color: UIColor) -> UIImage? {
|
private func actionButtonImage(color: UIColor) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
|
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 var avatarsContent: AnimatedAvatarSetContext.Content?
|
||||||
private let avatarsNode: AnimatedAvatarSetNode
|
private let avatarsNode: AnimatedAvatarSetNode
|
||||||
private let invitedPeersNode: TextNode
|
private let invitedPeersNode: TextNode
|
||||||
|
private var peersPlaceholderNode: ShimmerEffectNode?
|
||||||
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
|
||||||
private let activateArea: AccessibilityAreaNode
|
private let activateArea: AccessibilityAreaNode
|
||||||
|
|
||||||
@ -180,7 +183,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
self.avatarsContext = AnimatedAvatarSetContext()
|
self.avatarsContext = AnimatedAvatarSetContext()
|
||||||
self.avatarsNode = AnimatedAvatarSetNode()
|
self.avatarsNode = AnimatedAvatarSetNode()
|
||||||
self.invitedPeersNode = TextNode()
|
self.invitedPeersNode = TextNode()
|
||||||
|
|
||||||
self.activateArea = AccessibilityAreaNode()
|
self.activateArea = AccessibilityAreaNode()
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
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.fieldNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor)
|
||||||
strongSelf.addressButtonIconNode.image = actionButtonImage(color: item.presentationData.theme.list.itemInputField.controlColor)
|
strongSelf.addressButtonIconNode.image = actionButtonImage(color: item.presentationData.theme.list.itemInputField.controlColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = addressApply()
|
let _ = addressApply()
|
||||||
let _ = invitedPeersApply()
|
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.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.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.shareButtonNode?.isHidden = !item.displayButton
|
||||||
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
|
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
|
||||||
strongSelf.avatarsNode.isHidden = !item.displayImporters
|
strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil
|
||||||
strongSelf.invitedPeersNode.isHidden = !item.displayImporters
|
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) {
|
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
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)
|
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
|
strongSelf.labelNode.frame = labelFrame
|
||||||
} else {
|
} 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)
|
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
|||||||
case let .privateLinkHeader(_, title):
|
case let .privateLinkHeader(_, title):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||||
case let .privateLink(_, invite, displayImporters):
|
case let .privateLink(_, invite, displayImporters):
|
||||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, 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 {
|
if let invite = invite {
|
||||||
arguments.copyLink(invite)
|
arguments.copyLink(invite)
|
||||||
}
|
}
|
||||||
@ -598,13 +598,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
} else {
|
} else {
|
||||||
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
|
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
|
||||||
}
|
}
|
||||||
// switch mode {
|
switch mode {
|
||||||
// case .initialSetup:
|
case .initialSetup:
|
||||||
// break
|
break
|
||||||
// case .generic, .privateLink:
|
case .generic, .privateLink:
|
||||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
case .privateChannel:
|
case .privateChannel:
|
||||||
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
|
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
|
||||||
@ -615,13 +615,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
} else {
|
} else {
|
||||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp))
|
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp))
|
||||||
}
|
}
|
||||||
// switch mode {
|
switch mode {
|
||||||
// case .initialSetup:
|
case .initialSetup:
|
||||||
// break
|
break
|
||||||
// case .generic, .privateLink:
|
case .generic, .privateLink:
|
||||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
} else if let _ = view.peers[view.peerId] as? TelegramGroup {
|
} else if let _ = view.peers[view.peerId] as? TelegramGroup {
|
||||||
switch mode {
|
switch mode {
|
||||||
@ -630,13 +630,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||||
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
||||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
|
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
|
||||||
// switch mode {
|
switch mode {
|
||||||
// case .initialSetup:
|
case .initialSetup:
|
||||||
// break
|
break
|
||||||
// case .generic, .privateLink:
|
case .generic, .privateLink:
|
||||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||||
// }
|
}
|
||||||
case .generic, .initialSetup:
|
case .generic, .initialSetup:
|
||||||
let selectedType: CurrentChannelType
|
let selectedType: CurrentChannelType
|
||||||
if let current = state.selectedType {
|
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(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||||
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
||||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
|
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
|
||||||
// switch mode {
|
switch mode {
|
||||||
// case .initialSetup:
|
case .initialSetup:
|
||||||
// break
|
break
|
||||||
// case .generic, .privateLink:
|
case .generic, .privateLink:
|
||||||
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||||
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
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
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||||
// return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||||
// }, action: { _, f in
|
}, action: { _, f in
|
||||||
// f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
//
|
|
||||||
// let _ = (context.account.postbox.transaction { transaction -> ExportedInvitation? in
|
let _ = (context.account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||||
// if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
|
||||||
// if let cachedData = cachedData as? CachedChannelData {
|
if let cachedData = cachedData as? CachedChannelData {
|
||||||
// return cachedData.exportedInvitation
|
return cachedData.exportedInvitation
|
||||||
// } else if let cachedData = cachedData as? CachedGroupData {
|
} else if let cachedData = cachedData as? CachedGroupData {
|
||||||
// return cachedData.exportedInvitation
|
return cachedData.exportedInvitation
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// return nil
|
return nil
|
||||||
// } |> deliverOnMainQueue).start(next: { invite in
|
} |> deliverOnMainQueue).start(next: { invite in
|
||||||
// if let invite = invite {
|
if let invite = invite {
|
||||||
// let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||||
// presentControllerImpl?(controller, nil)
|
presentControllerImpl?(controller, nil)
|
||||||
// }
|
}
|
||||||
// })
|
})
|
||||||
// })))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
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)
|
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)
|
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: nil)
|
||||||
presentInGlobalOverlayImpl?(contextController)
|
presentInGlobalOverlayImpl?(contextController)
|
||||||
}, manageInviteLinks: {
|
}, manageInviteLinks: {
|
||||||
let controller = inviteLinkListController(context: context, peerId: peerId)
|
let controller = inviteLinkListController(context: context, peerId: peerId, admin: nil)
|
||||||
pushControllerImpl?(controller)
|
pushControllerImpl?(controller)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4120,6 +4120,19 @@ public extension Api {
|
|||||||
var result: Api.Bool?
|
var result: Api.Bool?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
public static func getAdminsWithInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ChatAdminsWithInvites>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(958457583)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
return (FunctionDescription(name: "messages.getAdminsWithInvites", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatAdminsWithInvites? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.messages.ChatAdminsWithInvites?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.messages.ChatAdminsWithInvites
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
@ -7,65 +7,6 @@ import MtProtoKit
|
|||||||
import SyncCore
|
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> {
|
public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
||||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
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> {
|
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) {
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if let _ = expireDate {
|
if let _ = expireDate {
|
||||||
@ -146,7 +86,7 @@ public func createPeerExportedInvitation(account: Account, peerId: PeerId, expir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> castError(CreatePeerExportedInvitationError.self)
|
|> castError(CreatePeerExportedInvitationError.self)
|
||||||
|> switchToLatest*/
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EditPeerExportedInvitationError {
|
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> {
|
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) {
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if let _ = expireDate {
|
if let _ = expireDate {
|
||||||
@ -188,7 +127,7 @@ public func editPeerExportedInvitation(account: Account, peerId: PeerId, link: S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> castError(EditPeerExportedInvitationError.self)
|
|> castError(EditPeerExportedInvitationError.self)
|
||||||
|> switchToLatest*/
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RevokePeerExportedInvitationError {
|
public enum RevokePeerExportedInvitationError {
|
||||||
@ -196,8 +135,7 @@ public enum RevokePeerExportedInvitationError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<ExportedInvitation?, 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) {
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
let flags: Int32 = (1 << 2)
|
let flags: Int32 = (1 << 2)
|
||||||
return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: nil, usageLimit: nil))
|
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)
|
|> castError(RevokePeerExportedInvitationError.self)
|
||||||
|> switchToLatest*/
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ExportedInvitations : Equatable {
|
public struct ExportedInvitations : Equatable {
|
||||||
@ -233,9 +171,8 @@ public struct ExportedInvitations : Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
|
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
|
||||||
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let accountPeer = transaction.getPeer(account.peerId), let adminId = apiInputUser(accountPeer) {
|
||||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if let _ = offsetLink {
|
if let _ = offsetLink {
|
||||||
flags |= (1 << 2)
|
flags |= (1 << 2)
|
||||||
@ -243,7 +180,7 @@ public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: B
|
|||||||
if revoked {
|
if revoked {
|
||||||
flags |= (1 << 3)
|
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)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -277,7 +214,7 @@ public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: B
|
|||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
} |> switchToLatest*/
|
} |> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -286,8 +223,7 @@ public enum DeletePeerExportedInvitationError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<Never, 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) {
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
return account.network.request(Api.functions.messages.deleteExportedChatInvite(peer: inputPeer, link: link))
|
return account.network.request(Api.functions.messages.deleteExportedChatInvite(peer: inputPeer, link: link))
|
||||||
|> mapError { _ in return DeletePeerExportedInvitationError.generic }
|
|> mapError { _ in return DeletePeerExportedInvitationError.generic }
|
||||||
@ -297,12 +233,11 @@ public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> castError(DeletePeerExportedInvitationError.self)
|
|> castError(DeletePeerExportedInvitationError.self)
|
||||||
|> switchToLatest*/
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
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) {
|
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))
|
||||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
@ -313,7 +248,7 @@ public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: Pe
|
|||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> switchToLatest*/
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
private let cachedPeerExportedInvitationsCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
private let cachedPeerExportedInvitationsCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
||||||
@ -377,6 +312,7 @@ private final class PeerExportedInvitationsContextImpl {
|
|||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let peerId: PeerId
|
private let peerId: PeerId
|
||||||
|
private let adminId: PeerId
|
||||||
private let revoked: Bool
|
private let revoked: Bool
|
||||||
private var forceUpdate: Bool
|
private var forceUpdate: Bool
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
@ -388,36 +324,41 @@ private final class PeerExportedInvitationsContextImpl {
|
|||||||
private var results: [ExportedInvitation] = []
|
private var results: [ExportedInvitation] = []
|
||||||
private var count: Int32
|
private var count: Int32
|
||||||
private var populateCache: Bool = true
|
private var populateCache: Bool = true
|
||||||
|
private var isMainList: Bool
|
||||||
|
|
||||||
let state = Promise<PeerExportedInvitationsState>()
|
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.queue = queue
|
||||||
self.account = account
|
self.account = account
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
|
self.adminId = adminId ?? account.peerId
|
||||||
self.revoked = revoked
|
self.revoked = revoked
|
||||||
self.forceUpdate = forceUpdate
|
self.forceUpdate = forceUpdate
|
||||||
|
self.isMainList = adminId == nil
|
||||||
|
|
||||||
self.count = 0
|
self.count = 0
|
||||||
|
|
||||||
self.isLoadingMore = true
|
if adminId == nil {
|
||||||
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
|
self.isLoadingMore = true
|
||||||
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked))) as? CachedPeerExportedInvitations
|
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()
|
self.loadMore()
|
||||||
}
|
}
|
||||||
@ -433,17 +374,18 @@ private final class PeerExportedInvitationsContextImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadMore() {
|
func loadMore() {
|
||||||
/*if self.isLoadingMore {
|
if self.isLoadingMore {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.isLoadingMore = true
|
self.isLoadingMore = true
|
||||||
let account = self.account
|
let account = self.account
|
||||||
let peerId = self.peerId
|
let peerId = self.peerId
|
||||||
|
let adminId = self.adminId
|
||||||
let revoked = self.revoked
|
let revoked = self.revoked
|
||||||
var lastResult = self.results.last
|
var lastResult = self.results.last
|
||||||
|
|
||||||
if self.forceUpdate {
|
if self.forceUpdate {
|
||||||
self.populateCache = true
|
self.populateCache = self.isMainList
|
||||||
self.forceUpdate = false
|
self.forceUpdate = false
|
||||||
lastResult = nil
|
lastResult = nil
|
||||||
} else if self.loadedFromCache {
|
} else if self.loadedFromCache {
|
||||||
@ -452,11 +394,11 @@ private final class PeerExportedInvitationsContextImpl {
|
|||||||
}
|
}
|
||||||
let populateCache = self.populateCache
|
let populateCache = self.populateCache
|
||||||
|
|
||||||
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
self.disposable.set((self.account.postbox.transaction { transaction -> (peerId: Api.InputPeer?, adminId: Api.InputUser?) in
|
||||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
return (transaction.getPeer(peerId).flatMap(apiInputPeer), transaction.getPeer(adminId).flatMap(apiInputUser))
|
||||||
}
|
}
|
||||||
|> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in
|
|> mapToSignal { inputPeer, adminId -> Signal<([ExportedInvitation], Int32), NoError> in
|
||||||
if let inputPeer = inputPeer {
|
if let inputPeer = inputPeer, let adminId = adminId {
|
||||||
let offsetLink = lastResult?.link
|
let offsetLink = lastResult?.link
|
||||||
let offsetDate = lastResult?.date
|
let offsetDate = lastResult?.date
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
@ -466,7 +408,7 @@ private final class PeerExportedInvitationsContextImpl {
|
|||||||
if revoked {
|
if revoked {
|
||||||
flags |= (1 << 3)
|
flags |= (1 << 3)
|
||||||
}
|
}
|
||||||
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, 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)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -527,7 +469,7 @@ private final class PeerExportedInvitationsContextImpl {
|
|||||||
strongSelf.loadMore()
|
strongSelf.loadMore()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
self.updateState()*/
|
self.updateState()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func add(_ invite: ExportedInvitation) {
|
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
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
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 {
|
if self.isLoadingMore {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
/*self.isLoadingMore = true
|
self.isLoadingMore = true
|
||||||
let account = self.account
|
let account = self.account
|
||||||
let peerId = self.peerId
|
let peerId = self.peerId
|
||||||
let link = self.link
|
let link = self.link
|
||||||
@ -871,7 +813,7 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
}
|
}
|
||||||
strongSelf.updateState()
|
strongSelf.updateState()
|
||||||
}))
|
}))
|
||||||
self.updateState()*/
|
self.updateState()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func 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 {
|
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
|
break
|
||||||
case let .messageActionChannelMigrateFrom(_, chatId):
|
case let .messageActionChannelMigrateFrom(_, chatId):
|
||||||
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
|
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
|
||||||
@ -371,7 +371,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
|
|||||||
extension StoreMessage {
|
extension StoreMessage {
|
||||||
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
|
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
|
||||||
switch apiMessage {
|
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 resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
|
||||||
|
|
||||||
let peerId: PeerId
|
let peerId: PeerId
|
||||||
@ -468,21 +468,13 @@ extension StoreMessage {
|
|||||||
|
|
||||||
var consumableContent: (Bool, Bool)? = nil
|
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 {
|
if let media = media {
|
||||||
let (mediaValue, expirationTimer) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
let (mediaValue, expirationTimer) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||||
if let mediaValue = mediaValue {
|
if let mediaValue = mediaValue {
|
||||||
medias.append(mediaValue)
|
medias.append(mediaValue)
|
||||||
|
|
||||||
if let expirationTimer = expirationTimer, expirationTimer > 0 {
|
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)
|
consumableContent = (true, false)
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||||||
PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case let .messageActionSetMessagesTTL(period):
|
case .messageActionSetMessagesTTL(period: let period):
|
||||||
return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period))
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,11 +206,8 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
let peerStatusSettings = PeerStatusSettings(apiSettings: userFull.settings)
|
let peerStatusSettings = PeerStatusSettings(apiSettings: userFull.settings)
|
||||||
|
|
||||||
let hasScheduledMessages = (userFull.flags & 1 << 12) != 0
|
let hasScheduledMessages = (userFull.flags & 1 << 12) != 0
|
||||||
|
|
||||||
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(userFull.ttlPeriod)
|
|
||||||
|
|
||||||
return previous.withUpdatedAbout(userFull.about).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFull.commonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages)
|
return previous.withUpdatedAbout(userFull.about).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFull.commonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||||
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
@ -300,8 +297,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(chatFull.ttlPeriod)
|
|
||||||
|
|
||||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
||||||
let previous: CachedGroupData
|
let previous: CachedGroupData
|
||||||
if let current = current as? CachedGroupData {
|
if let current = current as? CachedGroupData {
|
||||||
@ -317,7 +312,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
.withUpdatedAbout(chatFull.about)
|
.withUpdatedAbout(chatFull.about)
|
||||||
.withUpdatedFlags(flags)
|
.withUpdatedFlags(flags)
|
||||||
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||||
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
|
|
||||||
.withUpdatedInvitedBy(invitedBy)
|
.withUpdatedInvitedBy(invitedBy)
|
||||||
.withUpdatedPhoto(photo)
|
.withUpdatedPhoto(photo)
|
||||||
.withUpdatedActiveCall(updatedActiveCall)
|
.withUpdatedActiveCall(updatedActiveCall)
|
||||||
@ -358,7 +352,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch fullChat {
|
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()
|
var channelFlags = CachedChannelFlags()
|
||||||
if (flags & (1 << 3)) != 0 {
|
if (flags & (1 << 3)) != 0 {
|
||||||
channelFlags.insert(.canDisplayParticipants)
|
channelFlags.insert(.canDisplayParticipants)
|
||||||
@ -513,8 +507,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
|
|
||||||
minAvailableMessageIdUpdated = previous.minAvailableMessageId != minAvailableMessageId
|
minAvailableMessageIdUpdated = previous.minAvailableMessageId != minAvailableMessageId
|
||||||
|
|
||||||
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(ttlPeriod)
|
|
||||||
|
|
||||||
return previous.withUpdatedFlags(channelFlags)
|
return previous.withUpdatedFlags(channelFlags)
|
||||||
.withUpdatedAbout(about)
|
.withUpdatedAbout(about)
|
||||||
.withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: participantsCount, adminCount: adminsCount, bannedCount: bannedCount, kickedCount: kickedCount))
|
.withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: participantsCount, adminCount: adminsCount, bannedCount: bannedCount, kickedCount: kickedCount))
|
||||||
@ -529,7 +521,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
.withUpdatedSlowModeTimeout(slowmodeSeconds)
|
.withUpdatedSlowModeTimeout(slowmodeSeconds)
|
||||||
.withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate)
|
.withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate)
|
||||||
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||||
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
|
|
||||||
.withUpdatedStatsDatacenterId(statsDc ?? 0)
|
.withUpdatedStatsDatacenterId(statsDc ?? 0)
|
||||||
.withUpdatedInvitedBy(invitedBy)
|
.withUpdatedInvitedBy(invitedBy)
|
||||||
.withUpdatedPhoto(photo)
|
.withUpdatedPhoto(photo)
|
||||||
|
@ -57,14 +57,14 @@ class UpdateMessageService: NSObject, MTMessageService {
|
|||||||
if groups.count != 0 {
|
if groups.count != 0 {
|
||||||
self.putNext(groups)
|
self.putNext(groups)
|
||||||
}
|
}
|
||||||
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
|
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities):
|
||||||
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil, ttlPeriod: ttlPeriod)
|
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil, ttlPeriod: nil)
|
||||||
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
||||||
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
||||||
if groups.count != 0 {
|
if groups.count != 0 {
|
||||||
self.putNext(groups)
|
self.putNext(groups)
|
||||||
}
|
}
|
||||||
case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
|
case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities):
|
||||||
let generatedFromId: Api.Peer
|
let generatedFromId: Api.Peer
|
||||||
if (Int(flags) & 1 << 1) != 0 {
|
if (Int(flags) & 1 << 1) != 0 {
|
||||||
generatedFromId = Api.Peer.peerUser(userId: self.peerId.id)
|
generatedFromId = Api.Peer.peerUser(userId: self.peerId.id)
|
||||||
@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService {
|
|||||||
|
|
||||||
let generatedPeerId = Api.Peer.peerUser(userId: userId)
|
let generatedPeerId = Api.Peer.peerUser(userId: userId)
|
||||||
|
|
||||||
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil, ttlPeriod: ttlPeriod)
|
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil, ttlPeriod: nil)
|
||||||
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
||||||
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
||||||
if groups.count != 0 {
|
if groups.count != 0 {
|
||||||
@ -82,7 +82,7 @@ class UpdateMessageService: NSObject, MTMessageService {
|
|||||||
}
|
}
|
||||||
case .updatesTooLong:
|
case .updatesTooLong:
|
||||||
self.pipe.putNext([.reset])
|
self.pipe.putNext([.reset])
|
||||||
case let .updateShortSentMessage(_, _, pts, ptsCount, _, _, _, _):
|
case let .updateShortSentMessage(_, _, pts, ptsCount, _, _, _):
|
||||||
self.pipe.putNext([.updatePts(pts: pts, ptsCount: ptsCount)])
|
self.pipe.putNext([.updatePts(pts: pts, ptsCount: ptsCount)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
discussionPeer = peer
|
||||||
}
|
}
|
||||||
|
|
||||||
// if currentInvitationsContext == nil {
|
if currentInvitationsContext == nil {
|
||||||
// var canManageInvitations = false
|
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)) {
|
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
|
canManageInvitations = true
|
||||||
// }
|
}
|
||||||
// if canManageInvitations {
|
if canManageInvitations {
|
||||||
// let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
|
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
|
||||||
// invitationsContextPromise.set(.single(invitationsContext))
|
invitationsContextPromise.set(.single(invitationsContext))
|
||||||
// invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
return PeerInfoScreenData(
|
return PeerInfoScreenData(
|
||||||
peer: peerView.peers[peerId],
|
peer: peerView.peers[peerId],
|
||||||
@ -811,19 +811,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if currentInvitationsContext == nil {
|
if currentInvitationsContext == nil {
|
||||||
// var canManageInvitations = false
|
var canManageInvitations = false
|
||||||
// if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
|
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
|
||||||
// canManageInvitations = true
|
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)) {
|
} 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
|
canManageInvitations = true
|
||||||
// }
|
}
|
||||||
// if canManageInvitations {
|
if canManageInvitations {
|
||||||
// let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
|
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
|
||||||
// invitationsContextPromise.set(.single(invitationsContext))
|
invitationsContextPromise.set(.single(invitationsContext))
|
||||||
// invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
return PeerInfoScreenData(
|
return PeerInfoScreenData(
|
||||||
peer: peerView.peers[groupId],
|
peer: peerView.peers[groupId],
|
||||||
|
@ -931,8 +931,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
|||||||
var directionIsToRight: Bool?
|
var directionIsToRight: Bool?
|
||||||
if abs(velocity.x) > 10.0 {
|
if abs(velocity.x) > 10.0 {
|
||||||
directionIsToRight = velocity.x < 0.0
|
directionIsToRight = velocity.x < 0.0
|
||||||
} else if abs(transitionFraction) > 0.5 {
|
} else if abs(self.transitionFraction) > 0.5 {
|
||||||
directionIsToRight = transitionFraction < 0.0
|
directionIsToRight = self.transitionFraction < 0.0
|
||||||
}
|
}
|
||||||
var updatedIndex = self.currentIndex
|
var updatedIndex = self.currentIndex
|
||||||
if let directionIsToRight = directionIsToRight {
|
if let directionIsToRight = directionIsToRight {
|
||||||
|
@ -1224,9 +1224,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
invitesText = ""
|
invitesText = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||||
// interaction.editingOpenInviteLinksSetup()
|
interaction.editingOpenInviteLinksSetup()
|
||||||
// }))
|
}))
|
||||||
|
|
||||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, action: {
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, action: {
|
||||||
interaction.editingOpenDiscussionGroupSetup()
|
interaction.editingOpenDiscussionGroupSetup()
|
||||||
@ -1326,9 +1326,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
invitesText = ""
|
invitesText = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||||
// interaction.editingOpenInviteLinksSetup()
|
interaction.editingOpenInviteLinksSetup()
|
||||||
// }))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cachedData.flags.contains(.canChangeUsername) {
|
if cachedData.flags.contains(.canChangeUsername) {
|
||||||
@ -1428,9 +1428,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
invitesText = ""
|
invitesText = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
|
||||||
// interaction.editingOpenInviteLinksSetup()
|
interaction.editingOpenInviteLinksSetup()
|
||||||
// }))
|
}))
|
||||||
|
|
||||||
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistory, action: {
|
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistory, action: {
|
||||||
interaction.editingOpenPreHistorySetup()
|
interaction.editingOpenPreHistorySetup()
|
||||||
@ -3808,7 +3808,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func editingOpenInviteLinksSetup() {
|
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() {
|
private func editingOpenDiscussionGroupSetup() {
|
||||||
|
@ -490,36 +490,73 @@ public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? {
|
|||||||
return nil
|
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> {
|
public func resolveUrlImpl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
|
||||||
let schemes = ["http://", "https://", ""]
|
let schemes = ["http://", "https://", ""]
|
||||||
for basePath in baseTelegramMePaths {
|
|
||||||
for scheme in schemes {
|
return account.postbox.transaction { transaction -> Signal<ResolvedUrl, NoError> in
|
||||||
let basePrefix = scheme + basePath + "/"
|
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
|
||||||
if url.lowercased().hasPrefix(basePrefix) {
|
let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration)
|
||||||
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
|
|
||||||
return resolveInternalUrl(account: account, url: internalUrl)
|
var url = url
|
||||||
|> map { resolved -> ResolvedUrl in
|
if let urlValue = URL(string: url), let host = urlValue.host, urlHandlingConfiguration.domains.contains(host), var components = URLComponents(string: url) {
|
||||||
if let resolved = resolved {
|
components.scheme = "https"
|
||||||
return resolved
|
var queryItems = components.queryItems ?? []
|
||||||
} else {
|
queryItems.append(URLQueryItem(name: "autologin_token", value: urlHandlingConfiguration.token))
|
||||||
return .externalUrl(url)
|
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 basePath in baseTelegraPhPaths {
|
for scheme in schemes {
|
||||||
for scheme in schemes {
|
let basePrefix = scheme + basePath
|
||||||
let basePrefix = scheme + basePath
|
if url.lowercased().hasPrefix(basePrefix) {
|
||||||
if url.lowercased().hasPrefix(basePrefix) {
|
return resolveInstantViewUrl(account: account, url: url)
|
||||||
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> {
|
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user