Merge commit '246ead72ffcc8ba56a45d80bc961c61f096941e4' into experimental-2 [skip ci]

This commit is contained in:
Ali 2021-02-06 00:37:05 +04:00
commit c01467dac0
25 changed files with 3857 additions and 3282 deletions

View File

@ -5902,6 +5902,9 @@ Sorry for the inconvenience.";
"InviteLink.InviteLinkCopiedText" = "Invite link copied to clipboard";
"InviteLink.OtherAdminsLinks" = "Invite Links Created By Other Admins";
"InviteLink.OtherPermanentLinkInfo" = "**%1$@** can see this link and use it to invite new members to **%2$@**.";
"Conversation.ChecksTooltip.Delivered" = "Delivered";
"Conversation.ChecksTooltip.Read" = "Read";

View File

@ -474,7 +474,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
var headerInsets = layout.insets(options: [.input])
headerInsets.top += actualNavigationBarHeight
let countPanelHeight = self.countPanelNode.updateLayout(width: layout.size.width, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
let countPanelHeight = self.countPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
if self.selectionState.selectedContactIndices.isEmpty {
transition.updateFrame(node: self.countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: countPanelHeight)))
} else {

View File

@ -12,15 +12,15 @@ final class InviteContactsCountPanelNode: ASDisplayNode {
private let separatorNode: ASDisplayNode
private let button: SolidRoundedButtonNode
private var validLayout: (CGFloat, CGFloat)?
private var validLayout: (CGFloat, CGFloat, CGFloat)?
var count: Int = 0 {
didSet {
if self.count != oldValue && self.count > 0 {
self.button.title = self.strings.Contacts_InviteContacts(Int32(self.count))
if let (width, bottomInset) = self.validLayout {
let _ = self.updateLayout(width: width, bottomInset: bottomInset, transition: .immediate)
if let (width, sideInset, bottomInset) = self.validLayout {
let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate)
}
}
}
@ -47,13 +47,13 @@ final class InviteContactsCountPanelNode: ASDisplayNode {
}
}
func updateLayout(width: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = (width, bottomInset)
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = (width, sideInset, bottomInset)
let topInset: CGFloat = 9.0
var bottomInset = bottomInset
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
let buttonInset: CGFloat = 16.0
let buttonInset: CGFloat = 16.0 + sideInset
let buttonWidth = width - buttonInset * 2.0
let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition)
transition.updateFrame(node: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight))

View File

@ -5,9 +5,6 @@ import AsyncDisplayKit
import TelegramPresentationData
import TelegramStringFormatting
private let textFont = Font.regular(13.0)
private let selectedTextFont = Font.bold(13.0)
public final class DatePickerTheme: Equatable {
public let backgroundColor: UIColor
public let textColor: UIColor
@ -15,18 +12,16 @@ public final class DatePickerTheme: Equatable {
public let accentColor: UIColor
public let disabledColor: UIColor
public let selectionColor: UIColor
public let selectedCurrentTextColor: UIColor
public let secondarySelectionColor: UIColor
public let selectionTextColor: UIColor
public init(backgroundColor: UIColor, textColor: UIColor, secondaryTextColor: UIColor, accentColor: UIColor, disabledColor: UIColor, selectionColor: UIColor, selectedCurrentTextColor: UIColor, secondarySelectionColor: UIColor) {
public init(backgroundColor: UIColor, textColor: UIColor, secondaryTextColor: UIColor, accentColor: UIColor, disabledColor: UIColor, selectionColor: UIColor, selectionTextColor: UIColor) {
self.backgroundColor = backgroundColor
self.textColor = textColor
self.secondaryTextColor = secondaryTextColor
self.accentColor = accentColor
self.disabledColor = disabledColor
self.selectionColor = selectionColor
self.selectedCurrentTextColor = selectedCurrentTextColor
self.secondarySelectionColor = secondarySelectionColor
self.selectionTextColor = selectionTextColor
}
public static func ==(lhs: DatePickerTheme, rhs: DatePickerTheme) -> Bool {
@ -45,31 +40,26 @@ public final class DatePickerTheme: Equatable {
if lhs.selectionColor != rhs.selectionColor {
return false
}
if lhs.selectedCurrentTextColor != rhs.selectedCurrentTextColor {
return false
}
if lhs.secondarySelectionColor != rhs.secondarySelectionColor {
if lhs.selectionTextColor != rhs.selectionTextColor {
return false
}
return true
}
}
//public extension DatePickerTheme {
// convenience init(theme: PresentationTheme) {
// self.init(backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor, foregroundColor: theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .black, textColor: theme.rootController.navigationBar.segmentedTextColor, dividerColor: theme.rootController.navigationBar.segmentedDividerColor)
// }
//}
private class SegmentedControlItemNode: HighlightTrackingButtonNode {
public extension DatePickerTheme {
convenience init(theme: PresentationTheme) {
self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemDisabledTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor)
}
}
private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0)
private let upperLimitDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
private let controlFont = Font.regular(17.0)
private let dayFont = Font.regular(13.0)
private let dateFont = Font.with(size: 13.0, design: .regular, traits: .monospacedNumbers)
private let selectedDateFont = Font.bold(13.0)
private let dateFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers)
private let selectedDateFont = Font.with(size: 17.0, design: .regular, traits: [.bold, .monospacedNumbers])
private let calendar = Calendar(identifier: .gregorian)
@ -81,12 +71,46 @@ private func monthForDate(_ date: Date) -> Date {
return calendar.date(from: components)!
}
public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
private func generateSmallArrowImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 7.0, height: 12.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.beginPath()
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height / 2.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})
}
private func generateNavigationArrowImage(color: UIColor, mirror: Bool) -> UIImage? {
return generateImage(CGSize(width: 10.0, height: 17.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.beginPath()
if mirror {
context.translateBy(x: 5.0, y: 8.5)
context.scaleBy(x: -1.0, y: 1.0)
context.translateBy(x: -5.0, y: -8.5)
}
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height / 2.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})
}
public final class DatePickerNode: ASDisplayNode {
class MonthNode: ASDisplayNode {
private let month: Date
var theme: DatePickerTheme {
didSet {
self.selectionNode.image = generateStretchableFilledCircleImage(diameter: 44.0, color: self.theme.selectionColor)
if let size = self.validSize {
self.updateLayout(size: size)
}
@ -136,6 +160,7 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
self.selectionNode = ASImageNode()
self.selectionNode.displaysAsynchronously = false
self.selectionNode.displayWithoutProcessing = true
self.selectionNode.image = generateStretchableFilledCircleImage(diameter: 44.0, color: theme.selectionColor)
self.dateNodes = (0..<42).map { _ in ImmediateTextNode() }
@ -147,7 +172,7 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
self.numberOfDays = calendar.range(of: .day, in: .month, for: month)!.count
super.init()
self.addSubnode(self.selectionNode)
self.dateNodes.forEach { self.addSubnode($0) }
}
@ -157,6 +182,10 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
var started = false
var count = 0
let sideInset: CGFloat = 12.0
let cellSize: CGFloat = floor((size.width - sideInset * 2.0) / 7.0)
self.selectionNode.isHidden = true
for i in 0 ..< 42 {
let row: Int = Int(floor(Float(i) / 7.0))
let col: Int = i % 7
@ -164,49 +193,55 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
if !started && weekday == self.startWeekday {
started = true
}
weekday += 1
if started {
count += 1
var isAvailableDate = true
var components = calendar.dateComponents([.year, .month], from: self.month)
components.day = count
components.hour = 0
components.minute = 0
let date = calendar.date(from: components)!
if let minimumDate = self.minimumDate {
var components = calendar.dateComponents([.year, .month], from: self.month)
components.day = count
components.hour = 0
components.minute = 0
let date = calendar.date(from: components)!
if date < minimumDate {
isAvailableDate = false
}
}
if let maximumDate = self.maximumDate {
var components = calendar.dateComponents([.year, .month], from: self.month)
components.day = count
components.hour = 0
components.minute = 0
let date = calendar.date(from: components)!
if date > maximumDate {
isAvailableDate = false
}
}
var isSelectedDate = false
var isSelectedAndCurrentDate = false
let isToday = calendar.isDateInToday(date)
let isSelected = self.date.flatMap { calendar.isDate(date, equalTo: $0, toGranularity: .day) } ?? false
let color: UIColor
if !isAvailableDate {
color = self.theme.disabledColor
} else if isSelectedAndCurrentDate {
color = .white
} else if isSelectedDate {
if isSelected {
color = self.theme.selectionTextColor
} else if isToday {
color = self.theme.accentColor
} else if !isAvailableDate {
color = self.theme.disabledColor
} else {
color = self.theme.textColor
}
let textNode = self.dateNodes[i]
textNode.attributedText = NSAttributedString(string: "\(count)", font: dateFont, textColor: color)
textNode.attributedText = NSAttributedString(string: "\(count)", font: isSelected ? selectedDateFont : dateFont, textColor: color)
let textSize = textNode.updateLayout(size)
textNode.frame = CGRect(origin: CGPoint(x: CGFloat(col) * 20.0, y: CGFloat(row) * 20.0), size: textSize)
let cellFrame = CGRect(x: sideInset + CGFloat(col) * cellSize, y: 0.0 + CGFloat(row) * cellSize, width: cellSize, height: cellSize)
let textFrame = CGRect(origin: CGPoint(x: cellFrame.minX + floor((cellFrame.width - textSize.width) / 2.0), y: cellFrame.minY + floor((cellFrame.height - textSize.height) / 2.0)), size: textSize)
textNode.frame = textFrame
if isSelected {
self.selectionNode.isHidden = false
let selectionSize = CGSize(width: 44.0, height: 44.0)
self.selectionNode.frame = CGRect(origin: CGPoint(x: cellFrame.minX + floor((cellFrame.width - selectionSize.width) / 2.0), y: cellFrame.minY + floor((cellFrame.height - selectionSize.height) / 2.0)), size: selectionSize)
}
if count == self.numberOfDays {
break
@ -232,40 +267,75 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
private let timeTitleNode: ImmediateTextNode
private let timeFieldNode: ASImageNode
private let dayNodes: [ImmediateTextNode]
private var currentIndex = 0
private var months: [Date] = []
private var monthNodes: [Date: MonthNode] = [:]
private let contentNode: ASDisplayNode
private let pickerBackgroundNode: ASDisplayNode
private var pickerNode: MonthPickerNode
private let monthButtonNode: HighlightTrackingButtonNode
private let monthTextNode: ImmediateTextNode
private let monthArrowNode: ASImageNode
private let previousButtonNode: HighlightableButtonNode
private let nextButtonNode: HighlightableButtonNode
private let dayNodes: [ImmediateTextNode]
private var previousMonthNode: MonthNode?
private var currentMonthNode: MonthNode?
private var nextMonthNode: MonthNode?
private let scrollNode: ASScrollNode
private var transitionFraction: CGFloat = 0.0
private var gestureRecognizer: UIPanGestureRecognizer?
private var gestureSelectedIndex: Int?
private var validLayout: CGSize?
public var maximumDate: Date? {
didSet {
public var maximumDate: Date {
get {
return self.state.maxDate
}
}
public var minimumDate: Date = telegramReleaseDate {
didSet {
}
}
public var date: Date = Date() {
didSet {
guard self.date != oldValue else {
set {
guard newValue != self.maximumDate else {
return
}
let updatedState = State(minDate: self.state.minDate, maxDate: newValue, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
self.updateState(updatedState, animated: false)
if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate)
}
}
}
public var minimumDate: Date {
get {
return self.state.minDate
}
set {
guard newValue != self.minimumDate else {
return
}
let updatedState = State(minDate: newValue, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
self.updateState(updatedState, animated: false)
if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate)
}
}
}
public var date: Date {
get {
return self.state.date
}
set {
guard newValue != self.date else {
return
}
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: newValue, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
self.updateState(updatedState, animated: false)
if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate)
}
@ -282,33 +352,74 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
self.timeFieldNode.displaysAsynchronously = false
self.timeFieldNode.displayWithoutProcessing = true
self.dayNodes = (0..<7).map { _ in ImmediateTextNode() }
self.contentNode = ASDisplayNode()
self.pickerBackgroundNode = ASDisplayNode()
self.pickerBackgroundNode.alpha = 0.0
self.pickerBackgroundNode.backgroundColor = theme.backgroundColor
self.pickerBackgroundNode.isUserInteractionEnabled = false
self.pickerNode = MonthPickerNode(theme: theme, strings: strings, date: self.state.date, yearRange: 2013 ..< 2038, valueChanged: { date in
})
self.monthButtonNode = HighlightTrackingButtonNode()
self.monthTextNode = ImmediateTextNode()
self.monthArrowNode = ASImageNode()
self.monthArrowNode.displaysAsynchronously = false
self.monthArrowNode.displayWithoutProcessing = true
self.previousButtonNode = HighlightableButtonNode()
self.previousButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -10.0, bottom: -6.0, right: -10.0)
self.nextButtonNode = HighlightableButtonNode()
self.dayNodes = (0..<7).map { _ in ImmediateTextNode() }
self.scrollNode = ASScrollNode()
self.nextButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -10.0, bottom: -6.0, right: -10.0)
super.init()
self.clipsToBounds = true
self.backgroundColor = theme.backgroundColor
self.addSubnode(self.contentNode)
self.dayNodes.forEach { self.addSubnode($0) }
self.addSubnode(self.previousButtonNode)
self.addSubnode(self.nextButtonNode)
self.addSubnode(self.pickerBackgroundNode)
self.pickerBackgroundNode.addSubnode(self.pickerNode)
self.addSubnode(self.monthTextNode)
self.addSubnode(self.monthArrowNode)
self.addSubnode(self.monthButtonNode)
self.addSubnode(self.previousButtonNode)
self.addSubnode(self.nextButtonNode)
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
self.nextButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: false), for: .normal)
self.addSubnode(self.scrollNode)
self.setupItems()
self.monthButtonNode.addTarget(self, action: #selector(self.monthButtonPressed), forControlEvents: .touchUpInside)
self.monthButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.monthTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.monthTextNode.alpha = 0.4
strongSelf.monthArrowNode.layer.removeAnimation(forKey: "opacity")
strongSelf.monthArrowNode.alpha = 0.4
} else {
strongSelf.monthTextNode.alpha = 1.0
strongSelf.monthTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.monthArrowNode.alpha = 1.0
strongSelf.monthArrowNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.previousButtonNode.addTarget(self, action: #selector(self.previousButtonPressed), forControlEvents: .touchUpInside)
self.nextButtonNode.addTarget(self, action: #selector(self.nextButtonPressed), forControlEvents: .touchUpInside)
}
override public func didLoad() {
@ -316,15 +427,62 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
self.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.isPagingEnabled = true
self.scrollNode.view.delegate = self
self.contentNode.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
self.contentNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
private func updateState(_ state: State, animated: Bool) {
let previousState = self.state
self.state = state
if let size = self.validLayout {
self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
if previousState.minDate != state.minDate || previousState.maxDate != state.maxDate {
self.setupItems()
} else if previousState.selectedMonth != state.selectedMonth {
for i in 0 ..< self.months.count {
if self.months[i].timeIntervalSince1970 > state.selectedMonth.timeIntervalSince1970 {
self.currentIndex = max(0, min(self.months.count - 1, i - 1))
break
}
}
}
if let size = self.validLayout {
self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .spring) : .immediate)
}
}
private func setupItems() {
let startMonth = monthForDate(self.state.minDate)
let endMonth = monthForDate(self.state.maxDate)
let selectedMonth = monthForDate(self.state.selectedMonth)
let calendar = Calendar.current
var currentIndex = 0
var months: [Date] = [startMonth]
var index = 1
var nextMonth = startMonth
while true {
if let month = calendar.date(byAdding: .month, value: 1, to: nextMonth) {
nextMonth = month
if nextMonth == selectedMonth {
currentIndex = index
}
if nextMonth >= endMonth {
break
} else {
months.append(nextMonth)
}
index += 1
} else {
break
}
}
self.months = months
self.currentIndex = currentIndex
}
public func updateTheme(_ theme: DatePickerTheme) {
@ -334,55 +492,262 @@ public final class DatePickerNode: ASDisplayNode, UIScrollViewDelegate {
self.theme = theme
self.backgroundColor = self.theme.backgroundColor
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.view.window?.endEditing(true)
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
self.nextButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: false), for: .normal)
for (_, monthNode) in self.monthNodes {
monthNode.theme = theme
}
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
self.view.window?.endEditing(true)
case .changed:
let translation = recognizer.translation(in: self.view)
var transitionFraction = translation.x / self.bounds.width
if self.currentIndex <= 0 {
transitionFraction = min(0.0, transitionFraction)
}
if self.currentIndex >= self.months.count - 1 {
transitionFraction = max(0.0, transitionFraction)
}
self.transitionFraction = transitionFraction
if let size = self.validLayout {
let topInset: CGFloat = 78.0
let containerSize = CGSize(width: size.width, height: size.height - topInset)
self.updateItems(size: containerSize, transition: .animated(duration: 0.3, curve: .spring))
}
case .cancelled, .ended:
let velocity = recognizer.velocity(in: self.view)
var directionIsToRight: Bool?
if abs(velocity.x) > 10.0 {
directionIsToRight = velocity.x < 0.0
} else if abs(self.transitionFraction) > 0.5 {
directionIsToRight = self.transitionFraction < 0.0
}
var updatedIndex = self.currentIndex
if let directionIsToRight = directionIsToRight {
if directionIsToRight {
updatedIndex = min(updatedIndex + 1, self.months.count - 1)
} else {
updatedIndex = max(updatedIndex - 1, 0)
}
}
self.currentIndex = updatedIndex
self.transitionFraction = 0.0
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.months[updatedIndex])
self.updateState(updatedState, animated: true)
default:
break
}
}
private func updateItems(size: CGSize, update: Bool = false, transition: ContainedViewLayoutTransition) {
var validIds: [Date] = []
if self.currentIndex >= 0 && self.currentIndex < self.months.count {
let preloadSpan: Int = 1
for i in max(0, self.currentIndex - preloadSpan) ... min(self.currentIndex + preloadSpan, self.months.count - 1) {
validIds.append(self.months[i])
var itemNode: MonthNode?
var wasAdded = false
if let current = self.monthNodes[self.months[i]] {
itemNode = current
current.updateLayout(size: size)
} else {
wasAdded = true
let addedItemNode = MonthNode(theme: self.theme, month: self.months[i], minimumDate: self.minimumDate, maximumDate: self.maximumDate, date: self.date)
itemNode = addedItemNode
self.monthNodes[self.months[i]] = addedItemNode
self.contentNode.addSubnode(addedItemNode)
}
if let itemNode = itemNode {
let indexOffset = CGFloat(i - self.currentIndex)
let itemFrame = CGRect(origin: CGPoint(x: indexOffset * size.width + self.transitionFraction * size.width, y: 0.0), size: size)
if wasAdded {
itemNode.frame = itemFrame
itemNode.updateLayout(size: size)
} else {
transition.updateFrame(node: itemNode, frame: itemFrame)
itemNode.updateLayout(size: size)
}
}
}
}
var removeIds: [Date] = []
for (id, _) in self.monthNodes {
if !validIds.contains(id) {
removeIds.append(id)
}
}
for id in removeIds {
if let itemNode = self.monthNodes.removeValue(forKey: id) {
itemNode.removeFromSupernode()
}
}
}
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
let topInset: CGFloat = 60.0
let topInset: CGFloat = 78.0
let sideInset: CGFloat = 16.0
let scrollSize = CGSize(width: size.width, height: size.height - topInset)
self.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: scrollSize)
self.scrollNode.view.contentSize = CGSize(width: scrollSize.width * 3.0, height: scrollSize.height)
self.scrollNode.view.contentOffset = CGPoint(x: scrollSize.width, y: 0.0)
let month = monthForDate(self.state.selectedMonth)
let components = calendar.dateComponents([.month, .year], from: month)
self.monthTextNode.attributedText = NSAttributedString(string: stringForMonth(strings: self.strings, month: components.month.flatMap { Int32($0) - 1 } ?? 0, ofYear: components.year.flatMap { Int32($0) - 1900 } ?? 100), font: controlFont, textColor: theme.textColor)
let monthSize = self.monthTextNode.updateLayout(size)
let monthTextFrame = CGRect(x: sideInset, y: 10.0, width: monthSize.width, height: monthSize.height)
self.monthTextNode.frame = monthTextFrame
self.monthArrowNode.frame = CGRect(x: monthTextFrame.maxX + 10.0, y: monthTextFrame.minY + 4.0, width: 7.0, height: 12.0)
self.monthButtonNode.frame = monthTextFrame.inset(by: UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -30.0))
self.previousButtonNode.frame = CGRect(x: size.width - sideInset - 54.0, y: monthTextFrame.minY + 1.0, width: 10.0, height: 17.0)
self.nextButtonNode.frame = CGRect(x: size.width - sideInset - 13.0, y: monthTextFrame.minY + 1.0, width: 10.0, height: 17.0)
let daysSideInset: CGFloat = 12.0
let cellSize: CGFloat = floor((size.width - daysSideInset * 2.0) / 7.0)
for i in 0 ..< self.dayNodes.count {
let dayNode = self.dayNodes[i]
dayNode.attributedText = NSAttributedString(string: shortStringForDayOfWeek(strings: self.strings, day: Int32(i)).uppercased(), font: dayFont, textColor: theme.secondaryTextColor)
let day = Int32(i)
dayNode.attributedText = NSAttributedString(string: shortStringForDayOfWeek(strings: self.strings, day: day), font: dayFont, textColor: theme.secondaryTextColor)
let size = dayNode.updateLayout(size)
dayNode.frame = CGRect(origin: CGPoint(x: CGFloat(i) * 20.0, y: 0.0), size: size)
let textSize = dayNode.updateLayout(size)
let cellFrame = CGRect(x: daysSideInset + CGFloat(i) * cellSize, y: 40.0, width: cellSize, height: cellSize)
let textFrame = CGRect(origin: CGPoint(x: cellFrame.minX + floor((cellFrame.width - textSize.width) / 2.0), y: cellFrame.minY + floor((cellFrame.height - textSize.height) / 2.0)), size: textSize)
dayNode.frame = textFrame
}
let containerSize = CGSize(width: size.width, height: size.height - topInset)
self.contentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: containerSize)
self.updateItems(size: containerSize, transition: transition)
self.pickerBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.pickerBackgroundNode.isUserInteractionEnabled = self.state.displayingMonthSelection
transition.updateAlpha(node: self.pickerBackgroundNode, alpha: self.state.displayingMonthSelection ? 1.0 : 0.0)
self.pickerNode.frame = CGRect(x: sideInset, y: topInset, width: size.width - sideInset * 2.0, height: 180.0)
}
@objc private func monthButtonPressed() {
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: !self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
self.updateState(updatedState, animated: true)
}
@objc private func previousButtonPressed() {
guard let month = calendar.date(byAdding: .month, value: -1, to: self.state.selectedMonth), let size = self.validLayout else {
return
}
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: month)
self.updateState(updatedState, animated: false)
self.contentNode.layer.animatePosition(from: CGPoint(x: -size.width, y: 0.0), to: CGPoint(), duration: 0.3, additive: true)
}
@objc private func nextButtonPressed() {
guard let month = calendar.date(byAdding: .month, value: 1, to: self.state.selectedMonth), let size = self.validLayout else {
return
}
let updatedState = State(minDate: self.state.minDate, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: month)
self.updateState(updatedState, animated: false)
self.contentNode.layer.animatePosition(from: CGPoint(x: size.width, y: 0.0), to: CGPoint(), duration: 0.3, additive: true)
}
}
private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPickerViewDataSource {
private let theme: DatePickerTheme
private let strings: PresentationStrings
private var date: Date
private var yearRange: Range<Int> {
didSet {
self.pickerView.reloadAllComponents()
}
}
@objc private func monthButtonPressed(_ button: SegmentedControlItemNode) {
private let valueChanged: (Date) -> Void
private let pickerView: UIPickerView
init(theme: DatePickerTheme, strings: PresentationStrings, date: Date, yearRange: Range<Int>, valueChanged: @escaping (Date) -> Void) {
self.theme = theme
self.strings = strings
self.date = date
self.yearRange = yearRange
self.valueChanged = valueChanged
self.pickerView = UIPickerView()
super.init()
self.pickerView.delegate = self
self.pickerView.dataSource = self
self.view.addSubview(self.pickerView)
self.pickerView.reloadAllComponents()
// self.pickerView.selectRow(index, inComponent: 0, animated: false)
}
@objc private func previousButtonPressed(_ button: SegmentedControlItemNode) {
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 180.0)
}
@objc private func nextButtonPressed(_ button: SegmentedControlItemNode) {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 1 {
return self.yearRange.count
} else {
return 12
}
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 40.0
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let string: String
if component == 1 {
string = "\(self.yearRange.startIndex + row)"
} else {
string = stringForMonth(strings: self.strings, month: Int32(row))
}
return NSAttributedString(string: string, font: Font.medium(15.0), textColor: self.theme.textColor)
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// self.valueChanged(timeoutValues[row])
}
override func layout() {
super.layout()
self.pickerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.bounds.size.width, height: 180.0))
}
}

View File

@ -45,7 +45,7 @@ public struct Font {
}
var updatedDescriptor: UIFontDescriptor? = descriptor.withSymbolicTraits(symbolicTraits)
if traits.contains(.monospacedNumbers) {
updatedDescriptor = descriptor.addingAttributes([
updatedDescriptor = updatedDescriptor?.addingAttributes([
UIFontDescriptor.AttributeName.featureSettings: [
[UIFontDescriptor.FeatureKey.featureIdentifier:
kNumberSpacingType,

View File

@ -32,7 +32,7 @@
(int)sourceSampleRate,
0,
NULL);
_ratio = MAX(1, destinationSampleRate / sourceSampleRate) * MAX(1, destinationChannelCount / sourceChannelCount) * 2;
_ratio = MAX(1, destinationSampleRate / MAX(sourceSampleRate, 1)) * MAX(1, destinationChannelCount / sourceChannelCount) * 2;
swr_init(_context);
}
return self;

View File

@ -49,6 +49,7 @@ swift_library(
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/SectionHeaderItem:SectionHeaderItem",
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
"//submodules/ShimmerEffect:ShimmerEffect",
],
visibility = [
"//visibility:public",

View File

@ -193,7 +193,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
arguments.dismissInput()
arguments.updateState { state in
var updatedState = state
updatedState.pickingTimeLimit = !state.pickingTimeLimit
// updatedState.pickingTimeLimit = !state.pickingTimeLimit
return updatedState
}
})

View File

@ -149,7 +149,7 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
}, viewAction: {
})
case let .links(_, _, invites):
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, share: true, sectionId: 1, style: .plain, tapAction: { invite in
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, count: 0, share: true, sectionId: 1, style: .plain, tapAction: { invite in
interaction.copyLink(invite)
}, contextAction: { invite, _ in
interaction.shareLink(invite)
@ -218,7 +218,7 @@ public final class InviteLinkInviteController: ViewController {
override public func loadDisplayNode() {
self.displayNode = Node(context: self.context, peerId: self.peerId, controller: self)
}
private var didAppearOnce: Bool = false
private var isDismissed: Bool = false
public override func viewDidAppear(_ animated: Bool) {
@ -288,7 +288,7 @@ public final class InviteLinkInviteController: ViewController {
self.presentationDataPromise = Promise(self.presentationData)
self.controller = controller
self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: false)
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
@ -348,16 +348,16 @@ public final class InviteLinkInviteController: ViewController {
}
})))
// items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
// }, action: { _, f in
// f(.dismissWithoutContent)
//
// if let invite = invite {
// let controller = InviteLinkQRCodeController(context: context, invite: invite)
// self?.controller?.present(controller, in: .window(.root))
// }
// })))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
if let invite = invite {
let controller = InviteLinkQRCodeController(context: context, invite: invite)
self?.controller?.present(controller, in: .window(.root))
}
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
@ -395,7 +395,7 @@ public final class InviteLinkInviteController: ViewController {
let shareController = ShareController(context: context, subject: .url(invite.link))
self?.controller?.present(shareController, in: .window(.root))
}, manageLinks: { [weak self] in
let controller = inviteLinkListController(context: context, peerId: peerId)
let controller = inviteLinkListController(context: context, peerId: peerId, admin: nil)
self?.controller?.parentNavigationController?.pushViewController(controller)
self?.controller?.dismiss()
})
@ -403,8 +403,7 @@ public final class InviteLinkInviteController: ViewController {
let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
let peerView = context.account.postbox.peerView(id: peerId)
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, invites)
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, self.invitesContext.state)
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view, invites in
if let strongSelf = self {
var entries: [InviteLinkInviteEntry] = []
@ -423,19 +422,19 @@ public final class InviteLinkInviteController: ViewController {
entries.append(.mainLink(presentationData.theme, mainInvite))
}
// let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
// var index: Int32 = 0
// for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
// var invitesPair: [ExportedInvitation] = []
// invitesPair.append(additionalInvites[i])
// if i + 1 < additionalInvites.count {
// invitesPair.append(additionalInvites[i + 1])
// }
// entries.append(.links(index, presentationData.theme, invitesPair))
// index += 1
// }
let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
var index: Int32 = 0
for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
var invitesPair: [ExportedInvitation] = []
invitesPair.append(additionalInvites[i])
if i + 1 < additionalInvites.count {
invitesPair.append(additionalInvites[i + 1])
}
entries.append(.links(index, presentationData.theme, invitesPair))
index += 1
}
// entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, additionalInvites.isEmpty))
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, additionalInvites.isEmpty))
let previousEntries = previousEntries.swap(entries)

View File

@ -18,6 +18,7 @@ import AppBundle
import ContextUI
import TelegramStringFormatting
import ItemListPeerActionItem
import ItemListPeerItem
import ShareController
import UndoUI
@ -30,9 +31,10 @@ private final class InviteLinkListControllerArguments {
let createLink: () -> Void
let openLink: (ExportedInvitation) -> Void
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
let openAdmin: (ExportedInvitationCreator) -> Void
let deleteAllRevokedLinks: () -> Void
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, openAdmin: @escaping (ExportedInvitationCreator) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
self.context = context
self.shareMainLink = shareMainLink
self.openMainLink = openMainLink
@ -41,6 +43,7 @@ private final class InviteLinkListControllerArguments {
self.createLink = createLink
self.openLink = openLink
self.linkContextAction = linkContextAction
self.openAdmin = openAdmin
self.deleteAllRevokedLinks = deleteAllRevokedLinks
}
}
@ -49,6 +52,7 @@ private enum InviteLinksListSection: Int32 {
case header
case mainLink
case links
case admins
case revokedLinks
}
@ -57,11 +61,16 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case mainLinkHeader(PresentationTheme, String)
case mainLink(PresentationTheme, ExportedInvitation?, [Peer], Int32, Bool)
case mainLinkOtherInfo(PresentationTheme, String)
case linksHeader(PresentationTheme, String)
case linksCreate(PresentationTheme, String)
case links(Int32, PresentationTheme, [ExportedInvitation]?)
case links(Int32, PresentationTheme, [ExportedInvitation]?, Int)
case linksInfo(PresentationTheme, String)
case adminsHeader(PresentationTheme, String)
case admin(Int32, PresentationTheme, ExportedInvitationCreator)
case revokedLinksHeader(PresentationTheme, String)
case revokedLinksDeleteAll(PresentationTheme, String)
case revokedLinks(Int32, PresentationTheme, [ExportedInvitation]?)
@ -70,10 +79,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
switch self {
case .header:
return InviteLinksListSection.header.rawValue
case .mainLinkHeader, .mainLink:
case .mainLinkHeader, .mainLink, .mainLinkOtherInfo:
return InviteLinksListSection.mainLink.rawValue
case .linksHeader, .linksCreate, .links, .linksInfo:
return InviteLinksListSection.links.rawValue
case .adminsHeader, .admin:
return InviteLinksListSection.admins.rawValue
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLinks:
return InviteLinksListSection.revokedLinks.rawValue
}
@ -87,20 +98,26 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return 1
case .mainLink:
return 2
case .linksHeader:
case .mainLinkOtherInfo:
return 3
case .linksCreate:
case .linksHeader:
return 4
case let .links(index, _, _):
return 5 + index
case .linksCreate:
return 5
case let .links(index, _, _, _):
return 6 + index
case .linksInfo:
return 10000
case .revokedLinksHeader:
case .adminsHeader:
return 10001
case let .admin(index, _, _):
return 10002 + index
case .revokedLinksHeader:
return 20001
case .revokedLinksDeleteAll:
return 10002
return 20002
case let .revokedLinks(index, _, _):
return 10003 + index
return 20003 + index
}
}
@ -124,6 +141,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else {
return false
}
case let .mainLinkOtherInfo(lhsTheme, lhsText):
if case let .mainLinkOtherInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .linksHeader(lhsTheme, lhsText):
if case let .linksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -136,8 +159,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else {
return false
}
case let .links(lhsIndex, lhsTheme, lhsLinks):
if case let .links(rhsIndex, rhsTheme, rhsLinks) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLinks == rhsLinks {
case let .links(lhsIndex, lhsTheme, lhsLinks, lhsCount):
if case let .links(rhsIndex, rhsTheme, rhsLinks, rhsCount) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLinks == rhsLinks, lhsCount == rhsCount {
return true
} else {
return false
@ -148,6 +171,18 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else {
return false
}
case let .adminsHeader(lhsTheme, lhsText):
if case let .adminsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .admin(lhsIndex, lhsTheme, lhsCreator):
if case let .admin(rhsIndex, rhsTheme, rhsCreator) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsCreator == rhsCreator {
return true
} else {
return false
}
case let .revokedLinksHeader(lhsTheme, lhsText):
if case let .revokedLinksHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -196,20 +231,28 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
arguments.openLink(invite)
}
})
case let .mainLinkOtherInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: nil, style: .blocks, tag: nil)
case let .linksHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .linksCreate(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, hasSeparator: false, sectionId: self.section, editing: false, action: {
arguments.createLink()
})
case let .links(_, _, invites):
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
case let .links(_, _, invites, count):
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, count: count, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
arguments.openLink(invite)
}, contextAction: { invite, node in
arguments.linkContextAction(invite, node, nil)
})
case let .linksInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .adminsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .admin(_, _, creator):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: creator.peer.peer!, height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: .disclosure("\(creator.count)"), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openAdmin(creator)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil)
case let .revokedLinksHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .revokedLinksDeleteAll(theme, text):
@ -217,7 +260,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
arguments.deleteAllRevokedLinks()
})
case let .revokedLinks(_, _, invites):
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
return ItemListInviteLinkGridItem(presentationData: presentationData, invites: invites, count: 0, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
arguments.openLink(invite)
}, contextAction: { invite, node in
arguments.linkContextAction(invite, node, nil)
@ -226,22 +269,23 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
}
}
private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?) -> [InviteLinksListEntry] {
private func inviteLinkListControllerEntries(presentationData: PresentationData, view: PeerView, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?, creators: [ExportedInvitationCreator], admin: ExportedInvitationCreator?) -> [InviteLinksListEntry] {
var entries: [InviteLinksListEntry] = []
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
if admin == nil {
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
}
let mainInvite: ExportedInvitation?
var isPublic = false
if let peer = peerViewMainPeer(view), let address = peer.addressName, !address.isEmpty {
if let peer = peerViewMainPeer(view), let address = peer.addressName, !address.isEmpty && admin == nil {
mainInvite = ExportedInvitation(link: "t.me/\(address)", isPermanent: true, isRevoked: false, adminId: PeerId(0), date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil)
isPublic = true
} else if let invites = invites, let invite = invites.first(where: { $0.isPermanent && !$0.isRevoked }) {
mainInvite = invite
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation {
} else if let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation, admin == nil {
mainInvite = invite
} else if let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation {
} else if let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation, admin == nil {
mainInvite = invite
} else {
mainInvite = nil
@ -259,10 +303,15 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
}
entries.append(.mainLink(presentationData.theme, mainInvite, importers?.importers.prefix(3).compactMap { $0.peer.peer } ?? [], importersCount, isPublic))
if let adminPeer = admin?.peer.peer, let peer = peerViewMainPeer(view) {
let string = presentationData.strings.InviteLink_OtherPermanentLinkInfo(adminPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))
entries.append(.mainLinkOtherInfo(presentationData.theme, string.0))
}
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
if admin == nil {
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
}
var additionalInvites: [ExportedInvitation]?
if let invites = invites {
additionalInvites = invites.filter { $0.link != mainInvite?.link }
@ -275,16 +324,34 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
if i + 1 < additionalInvites.count {
invitesPair.append(additionalInvites[i + 1])
}
entries.append(.links(index, presentationData.theme, invitesPair))
entries.append(.links(index, presentationData.theme, invitesPair, invitesPair.count))
index += 1
}
} else if let admin = admin {
var index: Int32 = 0
for _ in stride(from: 0, to: admin.count, by: 2) {
entries.append(.links(index, presentationData.theme, nil, 1))
index += 1
}
}
entries.append(.linksInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
if !creators.isEmpty {
entries.append(.adminsHeader(presentationData.theme, presentationData.strings.InviteLink_OtherAdminsLinks.uppercased()))
var index: Int32 = 0
for creator in creators {
if let _ = creator.peer.peer {
entries.append(.admin(index, presentationData.theme, creator))
index += 1
}
}
}
if let revokedInvites = revokedInvites, !revokedInvites.isEmpty {
entries.append(.revokedLinksHeader(presentationData.theme, presentationData.strings.InviteLink_RevokedLinks.uppercased()))
entries.append(.revokedLinksDeleteAll(presentationData.theme, presentationData.strings.InviteLink_DeleteAllRevokedLinks))
if admin == nil {
entries.append(.revokedLinksDeleteAll(presentationData.theme, presentationData.strings.InviteLink_DeleteAllRevokedLinks))
}
var index: Int32 = 0
for i in stride(from: 0, to: revokedInvites.endIndex, by: 2) {
var invitesPair: [ExportedInvitation] = []
@ -305,7 +372,7 @@ private struct InviteLinkListControllerState: Equatable {
}
public func inviteLinkListController(context: AccountContext, peerId: PeerId) -> ViewController {
public func inviteLinkListController(context: AccountContext, peerId: PeerId, admin: ExportedInvitationCreator?) -> ViewController {
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
@ -326,8 +393,16 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
var getControllerImpl: (() -> ViewController?)?
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: true, forceUpdate: true)
let adminId = admin?.peer.peer?.id
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: false, forceUpdate: false)
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true)
let creators: Signal<[ExportedInvitationCreator], NoError>
if adminId == nil {
creators = .single([]) |> then(peerExportedInvitationsCreators(account: context.account, peerId: peerId))
} else {
creators = .single([])
}
let arguments = InviteLinkListControllerArguments(context: context, shareMainLink: { invite in
let shareController = ShareController(context: context, subject: .url(invite.link))
@ -546,6 +621,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, openAdmin: { admin in
let controller = inviteLinkListController(context: context, peerId: peerId, admin: admin)
pushControllerImpl?(controller)
}, deleteAllRevokedLinks: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData)
@ -596,9 +674,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
}
let previousRevokedInvites = Atomic<PeerExportedInvitationsState?>(value: nil)
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state)
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state, creators)
|> deliverOnMainQueue
|> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in
|> map { presentationData, view, importersContext, importers, invites, revokedInvites, creators -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previousRevokedInvites = previousRevokedInvites.swap(invites)
var crossfade = false
@ -606,8 +684,15 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
crossfade = true
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.invitations, revokedInvites: revokedInvites.invitations, importers: importers), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: false)
let title: ItemListControllerTitle
if let admin = admin, let peer = admin.peer.peer {
title = .textWithSubtitle(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), "\(admin.count) invite links")
} else {
title = .text(presentationData.strings.InviteLink_Title)
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.hasLoadedOnce ? invites.invitations : nil, revokedInvites: revokedInvites.invitations, importers: importers, creators: creators, admin: admin), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: false)
return (controllerState, (listState, arguments))
}

View File

@ -112,7 +112,7 @@ private class ItemNode: ASDisplayNode {
private var updateTimer: SwiftSignalKit.Timer?
private var params: (size: CGSize, wide: Bool, invite: ExportedInvitation, color: ItemBackgroundColor, presentationData: ItemListPresentationData)?
private var params: (size: CGSize, wide: Bool, invite: ExportedInvitation?, color: ItemBackgroundColor, presentationData: ItemListPresentationData)?
var action: (() -> Void)?
var contextAction: ((ASDisplayNode) -> Void)?
@ -214,44 +214,50 @@ private class ItemNode: ASDisplayNode {
self.contextAction?(self.extractedContainerNode)
}
func update(size: CGSize, wide: Bool, share: Bool, invite: ExportedInvitation, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
func update(size: CGSize, wide: Bool, share: Bool, invite: ExportedInvitation?, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let availability = invitationAvailability(invite)
let availability = invite.flatMap { invitationAvailability($0) } ?? 0.0
let transitionFraction: CGFloat
let color: ItemBackgroundColor
let nextColor: ItemBackgroundColor
if invite.isRevoked {
if let invite = invite {
if invite.isRevoked {
color = .gray
nextColor = .gray
transitionFraction = 0.0
} else if invite.expireDate == nil && invite.usageLimit == nil {
color = .blue
nextColor = .blue
transitionFraction = 0.0
} else if availability >= 0.5 {
color = .green
nextColor = .yellow
transitionFraction = (availability - 0.5) / 0.5
} else if availability > 0.0 {
color = .yellow
nextColor = .red
transitionFraction = availability / 0.5
} else {
color = .red
nextColor = .red
transitionFraction = 0.0
}
} else {
color = .gray
nextColor = .gray
transitionFraction = 0.0
} else if invite.expireDate == nil && invite.usageLimit == nil {
color = .blue
nextColor = .blue
transitionFraction = 0.0
} else if availability >= 0.5 {
color = .green
nextColor = .yellow
transitionFraction = (availability - 0.5) / 0.5
} else if availability > 0.0 {
color = .yellow
nextColor = .red
transitionFraction = availability / 0.5
} else {
color = .red
nextColor = .red
transitionFraction = 0.0
}
let previousParams = self.params
self.params = (size, wide, invite, color, presentationData)
let previousExpireDate = previousParams?.invite.expireDate
if previousExpireDate != invite.expireDate {
let previousExpireDate = previousParams?.invite?.expireDate
if previousExpireDate != invite?.expireDate {
self.updateTimer?.invalidate()
self.updateTimer = nil
if let expireDate = invite.expireDate, availability > 0.0 {
if let expireDate = invite?.expireDate, availability > 0.0 {
let timeout = min(2.0, max(0.001, Double(expireDate - currentTime)))
let updateTimer = SwiftSignalKit.Timer(timeout: timeout, repeat: true, completion: { [weak self] in
if let strongSelf = self {
@ -272,8 +278,13 @@ private class ItemNode: ASDisplayNode {
let bottomColor = color.colors.bottom
let nextTopColor = nextColor.colors.top
let nextBottomColor = nextColor.colors.bottom
let colors: NSArray = [nextTopColor.mixedWith(topColor, alpha: transitionFraction).cgColor, nextBottomColor.mixedWith(bottomColor, alpha: transitionFraction).cgColor]
let colors: NSArray
if let invite = invite {
colors = [nextTopColor.mixedWith(topColor, alpha: transitionFraction).cgColor, nextBottomColor.mixedWith(bottomColor, alpha: transitionFraction).cgColor]
} else {
colors = [UIColor(rgb: 0xf2f2f7).cgColor, UIColor(rgb: 0xf2f2f7).cgColor]
}
if let (_, _, previousInvite, previousColor, _) = previousParams, previousInvite == invite {
if previousColor != color && color == .red {
if let snapshotView = self.wrapperNode.view.snapshotContentTree() {
@ -298,7 +309,7 @@ private class ItemNode: ASDisplayNode {
let secondaryTextColor = nextColor.colors.text.mixedWith(color.colors.text, alpha: transitionFraction)
let itemWidth = wide ? size.width : floor((size.width - itemSpacing) / 2.0)
var inviteLink = invite.link.replacingOccurrences(of: "https://", with: "")
var inviteLink = invite?.link.replacingOccurrences(of: "https://", with: "") ?? ""
if !wide {
inviteLink = inviteLink.replacingOccurrences(of: "joinchat/", with: "joinchat/\n")
inviteLink = inviteLink.replacingOccurrences(of: "join/", with: "join/\n")
@ -314,65 +325,72 @@ private class ItemNode: ASDisplayNode {
self.buttonIconNode.image = share ? shareIcon : moreIcon
var subtitleText: String = ""
if let count = invite.count {
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
if let invite = invite {
if let count = invite.count {
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
} else {
subtitleText = [.red, .gray].contains(color) ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
}
if invite.isRevoked {
if !subtitleText.isEmpty {
subtitleText += ""
}
subtitleText += presentationData.strings.InviteLink_Revoked
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
} else if let expireDate = invite.expireDate, currentTime >= expireDate {
if !subtitleText.isEmpty {
subtitleText += ""
}
if share {
subtitleText = presentationData.strings.InviteLink_Expired
} else {
subtitleText += presentationData.strings.InviteLink_Expired
}
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
} else if let usageLimit = invite.usageLimit, let count = invite.count, count >= usageLimit {
if !subtitleText.isEmpty {
subtitleText += ""
}
if share {
subtitleText = presentationData.strings.InviteLink_UsageLimitReached
} else {
subtitleText += presentationData.strings.InviteLink_UsageLimitReached
}
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
} else if let expireDate = invite.expireDate {
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Flame"), color: .white)
let timerNode: TimerNode
if let current = self.timerNode {
timerNode = current
} else {
timerNode = TimerNode()
timerNode.isUserInteractionEnabled = false
self.timerNode = timerNode
self.addSubnode(timerNode)
}
timerNode.update(color: UIColor.white, creationTimestamp: invite.startDate ?? invite.date, deadlineTimestamp: expireDate)
if share {
subtitleText = presentationData.strings.InviteLink_TapToCopy
}
} else {
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Link"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
if share {
subtitleText = presentationData.strings.InviteLink_TapToCopy
}
}
self.iconNode.isHidden = false
self.buttonIconNode.isHidden = false
} else {
subtitleText = [.red, .gray].contains(color) ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
}
if invite.isRevoked {
if !subtitleText.isEmpty {
subtitleText += ""
}
subtitleText += presentationData.strings.InviteLink_Revoked
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
} else if let expireDate = invite.expireDate, currentTime >= expireDate {
if !subtitleText.isEmpty {
subtitleText += ""
}
if share {
subtitleText = presentationData.strings.InviteLink_Expired
} else {
subtitleText += presentationData.strings.InviteLink_Expired
}
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
} else if let usageLimit = invite.usageLimit, let count = invite.count, count >= usageLimit {
if !subtitleText.isEmpty {
subtitleText += ""
}
if share {
subtitleText = presentationData.strings.InviteLink_UsageLimitReached
} else {
subtitleText += presentationData.strings.InviteLink_UsageLimitReached
}
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
} else if let expireDate = invite.expireDate {
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Flame"), color: .white)
let timerNode: TimerNode
if let current = self.timerNode {
timerNode = current
} else {
timerNode = TimerNode()
timerNode.isUserInteractionEnabled = false
self.timerNode = timerNode
self.addSubnode(timerNode)
}
timerNode.update(color: UIColor.white, creationTimestamp: invite.startDate ?? invite.date, deadlineTimestamp: expireDate)
if share {
subtitleText = presentationData.strings.InviteLink_TapToCopy
}
} else {
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Link"), color: .white)
self.timerNode?.removeFromSupernode()
self.timerNode = nil
if share {
subtitleText = presentationData.strings.InviteLink_TapToCopy
}
self.iconNode.isHidden = true
self.buttonIconNode.isHidden = true
}
self.iconNode.frame = CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)
@ -407,7 +425,7 @@ private class ItemNode: ASDisplayNode {
}
class InviteLinksGridNode: ASDisplayNode {
private var items: [ExportedInvitation] = []
private var items: [ExportedInvitation]?
private var itemNodes: [String: ItemNode] = [:]
var action: ((ExportedInvitation) -> Void)?
@ -418,7 +436,7 @@ class InviteLinksGridNode: ASDisplayNode {
return result
}
func update(size: CGSize, safeInset: CGFloat, items: [ExportedInvitation], share: Bool, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
func update(size: CGSize, safeInset: CGFloat, items: [ExportedInvitation]?, count: Int, share: Bool, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
self.items = items
var contentSize: CGSize = size
@ -428,24 +446,37 @@ class InviteLinksGridNode: ASDisplayNode {
var validIds = Set<String>()
for i in 0 ..< self.items.count {
let invite = self.items[i]
validIds.insert(invite.link)
let count = items?.count ?? count
for i in 0 ..< count {
let invite: ExportedInvitation?
let id: String
if let items = items, i < items.count {
invite = items[i]
id = invite!.link
} else {
invite = nil
id = "placeholder_\(i)"
}
validIds.insert(id)
var itemNode: ItemNode?
var wasAdded = false
if let current = self.itemNodes[invite.link] {
if let current = self.itemNodes[id] {
itemNode = current
} else {
wasAdded = true
let addedItemNode = ItemNode()
itemNode = addedItemNode
self.itemNodes[invite.link] = addedItemNode
self.itemNodes[id] = addedItemNode
self.addSubnode(addedItemNode)
}
if let itemNode = itemNode {
let col = CGFloat(i % 2)
let row = floor(CGFloat(i) / 2.0)
let wide = (i == self.items.count - 1 && (self.items.count % 2) != 0)
let wide = (i == count - 1 && (count % 2) != 0)
let itemSize = itemNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: size.height), wide: wide, share: share, invite: invite, presentationData: presentationData, transition: transition)
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: 4.0 + row * (122.0 + itemSpacing)), size: itemSize)
if !wide && col > 0 {
@ -460,10 +491,14 @@ class InviteLinksGridNode: ASDisplayNode {
transition.updateFrame(node: itemNode, frame: itemFrame)
}
itemNode.action = { [weak self] in
self?.action?(invite)
if let invite = invite {
self?.action?(invite)
}
}
itemNode.contextAction = { [weak self] node in
self?.contextAction?(node, invite)
if let invite = invite {
self?.contextAction?(node, invite)
}
}
}
}

View File

@ -10,6 +10,7 @@ import ItemListUI
public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let invites: [ExportedInvitation]?
let count: Int
let share: Bool
public let sectionId: ItemListSectionId
let style: ItemListStyle
@ -20,6 +21,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
public init(
presentationData: ItemListPresentationData,
invites: [ExportedInvitation]?,
count: Int,
share: Bool,
sectionId: ItemListSectionId,
style: ItemListStyle,
@ -29,6 +31,7 @@ public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
) {
self.presentationData = presentationData
self.invites = invites
self.count = count
self.share = share
self.sectionId = sectionId
self.style = style
@ -133,13 +136,14 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
let topInset: CGFloat
if case .plain = item.style, case .otherSection = neighbors.top {
topInset = 16.0
} else if case .blocks = item.style, case .sameSection(true) = neighbors.top {
topInset = 16.0
} else {
topInset = 4.0
}
var height: CGFloat
let count = item.invites?.count ?? 0
let count = item.invites?.count ?? item.count
if count > 0 {
if count % 2 == 0 {
height = topInset + 122.0 + 6.0
@ -176,7 +180,7 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
}
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites ?? [], share: item.share, presentationData: item.presentationData, transition: .immediate)
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites, count: item.count, share: item.share, presentationData: item.presentationData, transition: .immediate)
strongSelf.gridNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset - 4.0), size: gridSize)
strongSelf.gridNode.action = { invite in
item.tapAction?(invite)

View File

@ -10,6 +10,7 @@ import TelegramPresentationData
import ItemListUI
import SolidRoundedButtonNode
import AnimatedAvatarSetNode
import ShimmerEffect
private func actionButtonImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
@ -132,6 +133,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
private var avatarsContent: AnimatedAvatarSetContext.Content?
private let avatarsNode: AnimatedAvatarSetNode
private let invitedPeersNode: TextNode
private var peersPlaceholderNode: ShimmerEffectNode?
private var absoluteLocation: (CGRect, CGSize)?
private let activateArea: AccessibilityAreaNode
@ -180,7 +183,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
self.avatarsContext = AnimatedAvatarSetContext()
self.avatarsNode = AnimatedAvatarSetNode()
self.invitedPeersNode = TextNode()
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
@ -355,7 +358,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
strongSelf.fieldNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor)
strongSelf.addressButtonIconNode.image = actionButtonImage(color: item.presentationData.theme.list.itemInputField.controlColor)
}
let _ = addressApply()
let _ = invitedPeersApply()
@ -470,12 +473,45 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
strongSelf.invitedPeersNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: fieldFrame.maxY + 92.0), size: invitedPeersLayout.size)
strongSelf.avatarsButtonNode.frame = CGRect(x: floorToScreenPixels((params.width - totalWidth) / 2.0), y: fieldFrame.maxY + 87.0, width: totalWidth, height: 32.0)
strongSelf.avatarsButtonNode.isUserInteractionEnabled = !item.peers.isEmpty
strongSelf.avatarsButtonNode.isUserInteractionEnabled = !item.peers.isEmpty && item.invite != nil
strongSelf.addressButtonNode.isUserInteractionEnabled = item.invite != nil
strongSelf.fieldButtonNode.isUserInteractionEnabled = item.invite != nil
strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0
strongSelf.shareButtonNode?.isUserInteractionEnabled = item.invite != nil
strongSelf.shareButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
strongSelf.shareButtonNode?.isHidden = !item.displayButton
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
strongSelf.avatarsNode.isHidden = !item.displayImporters
strongSelf.invitedPeersNode.isHidden = !item.displayImporters
strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil
strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil
if item.invite == nil {
let shimmerNode: ShimmerEffectNode
if let current = strongSelf.peersPlaceholderNode {
shimmerNode = current
} else {
shimmerNode = ShimmerEffectNode()
strongSelf.peersPlaceholderNode = shimmerNode
strongSelf.insertSubnode(shimmerNode, belowSubnode: strongSelf.fieldNode)
}
shimmerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
if let (rect, size) = strongSelf.absoluteLocation {
shimmerNode.updateAbsoluteRect(rect, within: size)
}
var shapes: [ShimmerEffectNode.Shape] = []
let lineWidth: CGFloat = 180.0
let lineDiameter: CGFloat = 10.0
let titleFrame = strongSelf.invitedPeersNode.frame
shapes.append(.roundedRectLine(startPoint: CGPoint(x: floor(titleFrame.center.x - lineWidth / 2.0), y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: lineWidth, diameter: lineDiameter))
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize)
} else if let shimmerNode = strongSelf.peersPlaceholderNode {
strongSelf.peersPlaceholderNode = nil
shimmerNode.removeFromSupernode()
}
}
})
}
@ -492,4 +528,13 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
var rect = rect
rect.origin.y += self.insets.top
self.absoluteLocation = (rect, containerSize)
if let shimmerNode = self.peersPlaceholderNode {
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
}
}
}

View File

@ -1086,7 +1086,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
} else {
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset - rightInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
}

View File

@ -293,7 +293,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case let .privateLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .privateLink(_, invite, displayImporters):
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
if let invite = invite {
arguments.copyLink(invite)
}
@ -598,13 +598,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
} else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
}
// switch mode {
// case .initialSetup:
// break
// case .generic, .privateLink:
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
// }
switch mode {
case .initialSetup:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
case .privateChannel:
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
@ -615,13 +615,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
} else {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp))
}
// switch mode {
// case .initialSetup:
// break
// case .generic, .privateLink:
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
// }
switch mode {
case .initialSetup:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
} else if let _ = view.peers[view.peerId] as? TelegramGroup {
switch mode {
@ -630,13 +630,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
// switch mode {
// case .initialSetup:
// break
// case .generic, .privateLink:
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
// }
switch mode {
case .initialSetup:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
case .generic, .initialSetup:
let selectedType: CurrentChannelType
if let current = state.selectedType {
@ -729,13 +729,13 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
// switch mode {
// case .initialSetup:
// break
// case .generic, .privateLink:
// entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
// entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
// }
switch mode {
case .initialSetup:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
}
}
@ -943,27 +943,27 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
})
})))
// items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
// }, action: { _, f in
// f(.dismissWithoutContent)
//
// let _ = (context.account.postbox.transaction { transaction -> ExportedInvitation? in
// if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
// if let cachedData = cachedData as? CachedChannelData {
// return cachedData.exportedInvitation
// } else if let cachedData = cachedData as? CachedGroupData {
// return cachedData.exportedInvitation
// }
// }
// return nil
// } |> deliverOnMainQueue).start(next: { invite in
// if let invite = invite {
// let controller = InviteLinkQRCodeController(context: context, invite: invite)
// presentControllerImpl?(controller, nil)
// }
// })
// })))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
let _ = (context.account.postbox.transaction { transaction -> ExportedInvitation? in
if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
if let cachedData = cachedData as? CachedChannelData {
return cachedData.exportedInvitation
} else if let cachedData = cachedData as? CachedGroupData {
return cachedData.exportedInvitation
}
}
return nil
} |> deliverOnMainQueue).start(next: { invite in
if let invite = invite {
let controller = InviteLinkQRCodeController(context: context, invite: invite)
presentControllerImpl?(controller, nil)
}
})
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
@ -1006,7 +1006,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: nil)
presentInGlobalOverlayImpl?(contextController)
}, manageInviteLinks: {
let controller = inviteLinkListController(context: context, peerId: peerId)
let controller = inviteLinkListController(context: context, peerId: peerId, admin: nil)
pushControllerImpl?(controller)
})

View File

@ -230,6 +230,16 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: false)
case let .channelAdminLogEventActionToggleGroupCallSetting(joinMuted):
action = .updateGroupCallSettings(joinMuted: joinMuted == .boolTrue)
case .channelAdminLogEventActionParticipantJoinByInvite:
break
case .channelAdminLogEventActionExportedInviteDelete:
break
case .channelAdminLogEventActionExportedInviteRevoke:
break
case .channelAdminLogEventActionExportedInviteEdit:
break
case .channelAdminLogEventActionParticipantVolume:
break
}
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
if let action = action {

View File

@ -7,65 +7,6 @@ import MtProtoKit
import SyncCore
public func ensuredExistingPeerExportedInvitation(account: Account, peerId: PeerId, revokeExisted: Bool = false) -> Signal<ExportedInvitation?, NoError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
if let _ = peer as? TelegramChannel {
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, cachedData.exportedInvitation != nil && !revokeExisted {
return .single(cachedData.exportedInvitation)
} else {
return account.network.request(Api.functions.messages.exportChatInvite(flags: 0, peer: inputPeer, expireDate: nil, usageLimit: nil))
|> retryRequest
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
return account.postbox.transaction { transaction -> ExportedInvitation? in
if let invitation = ExportedInvitation(apiExportedInvite: result) {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedChannelData {
return current.withUpdatedExportedInvitation(invitation)
} else {
return CachedChannelData().withUpdatedExportedInvitation(invitation)
}
})
return invitation
} else {
return nil
}
}
}
}
} else if let _ = peer as? TelegramGroup {
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData, cachedData.exportedInvitation != nil && !revokeExisted {
return .single(cachedData.exportedInvitation)
} else {
return account.network.request(Api.functions.messages.exportChatInvite(flags: 0, peer: inputPeer, expireDate: nil, usageLimit: nil))
|> retryRequest
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
return account.postbox.transaction { transaction -> ExportedInvitation? in
if let invitation = ExportedInvitation(apiExportedInvite: result) {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedGroupData {
return current.withUpdatedExportedInvitation(invitation)
} else {
return current
}
})
return invitation
} else {
return nil
}
}
}
}
} else {
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
@ -122,8 +63,7 @@ public enum CreatePeerExportedInvitationError {
}
public func createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> {
return .fail(.generic)
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> in
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var flags: Int32 = 0
if let _ = expireDate {
@ -146,7 +86,7 @@ public func createPeerExportedInvitation(account: Account, peerId: PeerId, expir
}
}
|> castError(CreatePeerExportedInvitationError.self)
|> switchToLatest*/
|> switchToLatest
}
public enum EditPeerExportedInvitationError {
@ -154,8 +94,7 @@ public enum EditPeerExportedInvitationError {
}
public func editPeerExportedInvitation(account: Account, peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> {
return .fail(.generic)
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> in
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var flags: Int32 = 0
if let _ = expireDate {
@ -188,7 +127,7 @@ public func editPeerExportedInvitation(account: Account, peerId: PeerId, link: S
}
}
|> castError(EditPeerExportedInvitationError.self)
|> switchToLatest*/
|> switchToLatest
}
public enum RevokePeerExportedInvitationError {
@ -196,8 +135,7 @@ public enum RevokePeerExportedInvitationError {
}
public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> {
return .fail(.generic)
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> in
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
let flags: Int32 = (1 << 2)
return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: nil, usageLimit: nil))
@ -224,7 +162,7 @@ public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link:
}
}
|> castError(RevokePeerExportedInvitationError.self)
|> switchToLatest*/
|> switchToLatest
}
public struct ExportedInvitations : Equatable {
@ -232,10 +170,9 @@ public struct ExportedInvitations : Equatable {
public let totalCount: Int32
}
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
return .single(nil)
/*return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, adminId: PeerId? = nil, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let adminPeer = transaction.getPeer(adminId ?? account.peerId), let adminId = apiInputUser(adminPeer) {
var flags: Int32 = 0
if let _ = offsetLink {
flags |= (1 << 2)
@ -243,7 +180,7 @@ public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: B
if revoked {
flags |= (1 << 3)
}
return account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetDate: offsetLink?.date, offsetLink: offsetLink?.link, limit: 50))
return account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: adminId, offsetDate: offsetLink?.date, offsetLink: offsetLink?.link, limit: 50))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
return .single(nil)
@ -277,7 +214,7 @@ public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: B
} else {
return .single(nil)
}
} |> switchToLatest*/
} |> switchToLatest
}
@ -286,8 +223,7 @@ public enum DeletePeerExportedInvitationError {
}
public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<Never, DeletePeerExportedInvitationError> {
return .fail(.generic)
/*return account.postbox.transaction { transaction -> Signal<Never, DeletePeerExportedInvitationError> in
return account.postbox.transaction { transaction -> Signal<Never, DeletePeerExportedInvitationError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.deleteExportedChatInvite(peer: inputPeer, link: link))
|> mapError { _ in return DeletePeerExportedInvitationError.generic }
@ -297,14 +233,13 @@ public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link:
}
}
|> castError(DeletePeerExportedInvitationError.self)
|> switchToLatest*/
|> switchToLatest
}
public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
return .complete()
/*return account.postbox.transaction { transaction -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.deleteRevokedExportedChatInvites(peer: inputPeer))
return account.network.request(Api.functions.messages.deleteRevokedExportedChatInvites(peer: inputPeer, adminId: .inputUserEmpty))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
@ -313,7 +248,7 @@ public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: Pe
return .complete()
}
}
|> switchToLatest*/
|> switchToLatest
}
private let cachedPeerExportedInvitationsCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
@ -377,6 +312,7 @@ private final class PeerExportedInvitationsContextImpl {
private let queue: Queue
private let account: Account
private let peerId: PeerId
private let adminId: PeerId
private let revoked: Bool
private var forceUpdate: Bool
private let disposable = MetaDisposable()
@ -388,36 +324,41 @@ private final class PeerExportedInvitationsContextImpl {
private var results: [ExportedInvitation] = []
private var count: Int32
private var populateCache: Bool = true
private var isMainList: Bool
let state = Promise<PeerExportedInvitationsState>()
init(queue: Queue, account: Account, peerId: PeerId, revoked: Bool, forceUpdate: Bool) {
init(queue: Queue, account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) {
self.queue = queue
self.account = account
self.peerId = peerId
self.adminId = adminId ?? account.peerId
self.revoked = revoked
self.forceUpdate = forceUpdate
self.isMainList = adminId == nil
self.count = 0
self.isLoadingMore = true
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked))) as? CachedPeerExportedInvitations
if adminId == nil {
self.isLoadingMore = true
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked))) as? CachedPeerExportedInvitations
}
|> deliverOn(self.queue)).start(next: { [weak self] cachedResult in
guard let strongSelf = self else {
return
}
strongSelf.isLoadingMore = false
if let cachedResult = cachedResult {
strongSelf.results = cachedResult.invitations
strongSelf.count = cachedResult.count
strongSelf.hasLoadedOnce = true
strongSelf.canLoadMore = cachedResult.canLoadMore
strongSelf.loadedFromCache = true
}
strongSelf.loadMore()
}))
}
|> deliverOn(self.queue)).start(next: { [weak self] cachedResult in
guard let strongSelf = self else {
return
}
strongSelf.isLoadingMore = false
if let cachedResult = cachedResult {
strongSelf.results = cachedResult.invitations
strongSelf.count = cachedResult.count
strongSelf.hasLoadedOnce = true
strongSelf.canLoadMore = cachedResult.canLoadMore
strongSelf.loadedFromCache = true
}
strongSelf.loadMore()
}))
self.loadMore()
}
@ -433,17 +374,18 @@ private final class PeerExportedInvitationsContextImpl {
}
func loadMore() {
/*if self.isLoadingMore {
if self.isLoadingMore {
return
}
self.isLoadingMore = true
let account = self.account
let peerId = self.peerId
let adminId = self.adminId
let revoked = self.revoked
var lastResult = self.results.last
if self.forceUpdate {
self.populateCache = true
self.populateCache = self.isMainList
self.forceUpdate = false
lastResult = nil
} else if self.loadedFromCache {
@ -452,11 +394,11 @@ private final class PeerExportedInvitationsContextImpl {
}
let populateCache = self.populateCache
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
self.disposable.set((self.account.postbox.transaction { transaction -> (peerId: Api.InputPeer?, adminId: Api.InputUser?) in
return (transaction.getPeer(peerId).flatMap(apiInputPeer), transaction.getPeer(adminId).flatMap(apiInputUser))
}
|> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in
if let inputPeer = inputPeer {
|> mapToSignal { inputPeer, adminId -> Signal<([ExportedInvitation], Int32), NoError> in
if let inputPeer = inputPeer, let adminId = adminId {
let offsetLink = lastResult?.link
let offsetDate = lastResult?.date
var flags: Int32 = 0
@ -466,7 +408,7 @@ private final class PeerExportedInvitationsContextImpl {
if revoked {
flags |= (1 << 3)
}
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetDate: offsetDate, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: adminId, offsetDate: offsetDate, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
return .single(nil)
@ -527,7 +469,7 @@ private final class PeerExportedInvitationsContextImpl {
strongSelf.loadMore()
}
}))
self.updateState()*/
self.updateState()
}
public func add(_ invite: ExportedInvitation) {
@ -600,10 +542,10 @@ public final class PeerExportedInvitationsContext {
}
}
public init(account: Account, peerId: PeerId, revoked: Bool, forceUpdate: Bool) {
public init(account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId, revoked: revoked, forceUpdate: forceUpdate)
return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId, adminId: adminId, revoked: revoked, forceUpdate: forceUpdate)
})
}
@ -783,7 +725,7 @@ private final class PeerInvitationImportersContextImpl {
if self.isLoadingMore {
return
}
/*self.isLoadingMore = true
self.isLoadingMore = true
let account = self.account
let peerId = self.peerId
let link = self.link
@ -871,7 +813,7 @@ private final class PeerInvitationImportersContextImpl {
}
strongSelf.updateState()
}))
self.updateState()*/
self.updateState()
}
private func updateState() {
@ -908,3 +850,62 @@ public final class PeerInvitationImportersContext {
}
}
}
public struct ExportedInvitationCreator : Equatable {
public let peer: RenderedPeer
public let count: Int32
}
public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> {
return account.postbox.transaction { transaction -> Signal<[ExportedInvitationCreator], NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var isCreator = false
if let peer = peer as? TelegramGroup, case .creator = peer.role {
isCreator = true
} else if let peer = peer as? TelegramChannel, peer.flags.contains(.isCreator) {
isCreator = true
}
if !isCreator {
return .single([])
} else {
return account.network.request(Api.functions.messages.getAdminsWithInvites(peer: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ChatAdminsWithInvites?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[ExportedInvitationCreator], NoError> in
return account.postbox.transaction { transaction -> [ExportedInvitationCreator] in
if let result = result, case let .chatAdminsWithInvites(admins, users) = result {
var creators: [ExportedInvitationCreator] = []
var peers: [Peer] = []
var peersMap: [PeerId: Peer] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
peersMap[telegramUser.id] = telegramUser
}
for case let .chatAdminWithInvites(adminId, invitesCount) in admins {
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId)
if let peer = peersMap[peerId], peerId != account.peerId {
creators.append(ExportedInvitationCreator(peer: RenderedPeer(peer: peer), count: invitesCount))
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
return creators
} else {
return []
}
}
}
}
} else {
return .single([])
}
} |> switchToLatest
}

View File

@ -192,7 +192,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
}
switch action {
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL:
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL:
break
case let .messageActionChannelMigrateFrom(_, chatId):
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
@ -371,7 +371,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
extension StoreMessage {
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
switch apiMessage {
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, ttlPeriod):
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, _):
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
let peerId: PeerId
@ -468,21 +468,13 @@ extension StoreMessage {
var consumableContent: (Bool, Bool)? = nil
var addedAutoremoveAttribute = false
if let ttlPeriod = ttlPeriod {
addedAutoremoveAttribute = true
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttlPeriod, countdownBeginTime: date))
}
if let media = media {
let (mediaValue, expirationTimer) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
if let mediaValue = mediaValue {
medias.append(mediaValue)
if let expirationTimer = expirationTimer, expirationTimer > 0 {
if !addedAutoremoveAttribute {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil))
}
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil))
consumableContent = (true, false)
}

View File

@ -300,8 +300,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
}
}
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(chatFull.ttl))
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
let previous: CachedGroupData
if let current = current as? CachedGroupData {
@ -317,7 +315,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
.withUpdatedAbout(chatFull.about)
.withUpdatedFlags(flags)
.withUpdatedHasScheduledMessages(hasScheduledMessages)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedInvitedBy(invitedBy)
.withUpdatedPhoto(photo)
.withUpdatedActiveCall(updatedActiveCall)
@ -358,7 +355,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
}
switch fullChat {
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, ttlPeriod):
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, _):
var channelFlags = CachedChannelFlags()
if (flags & (1 << 3)) != 0 {
channelFlags.insert(.canDisplayParticipants)
@ -513,8 +510,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
minAvailableMessageIdUpdated = previous.minAvailableMessageId != minAvailableMessageId
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(ttlPeriod))
return previous.withUpdatedFlags(channelFlags)
.withUpdatedAbout(about)
.withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: participantsCount, adminCount: adminsCount, bannedCount: bannedCount, kickedCount: kickedCount))
@ -529,7 +524,6 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
.withUpdatedSlowModeTimeout(slowmodeSeconds)
.withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate)
.withUpdatedHasScheduledMessages(hasScheduledMessages)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedStatsDatacenterId(statsDc ?? 0)
.withUpdatedInvitedBy(invitedBy)
.withUpdatedPhoto(photo)

View File

@ -654,17 +654,17 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
discussionPeer = peer
}
// if currentInvitationsContext == nil {
// var canManageInvitations = false
// if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
// canManageInvitations = true
// }
// if canManageInvitations {
// let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
// invitationsContextPromise.set(.single(invitationsContext))
// invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
// }
// }
if currentInvitationsContext == nil {
var canManageInvitations = false
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
canManageInvitations = true
}
if canManageInvitations {
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
}
}
return PeerInfoScreenData(
peer: peerView.peers[peerId],
@ -811,19 +811,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
}
}
// if currentInvitationsContext == nil {
// var canManageInvitations = false
// if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
// canManageInvitations = true
// } else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
// canManageInvitations = true
// }
// if canManageInvitations {
// let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
// invitationsContextPromise.set(.single(invitationsContext))
// invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
// }
// }
if currentInvitationsContext == nil {
var canManageInvitations = false
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
canManageInvitations = true
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
canManageInvitations = true
}
if canManageInvitations {
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
}
}
return PeerInfoScreenData(
peer: peerView.peers[groupId],

View File

@ -931,8 +931,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
var directionIsToRight: Bool?
if abs(velocity.x) > 10.0 {
directionIsToRight = velocity.x < 0.0
} else if abs(transitionFraction) > 0.5 {
directionIsToRight = transitionFraction < 0.0
} else if abs(self.transitionFraction) > 0.5 {
directionIsToRight = self.transitionFraction < 0.0
}
var updatedIndex = self.currentIndex
if let directionIsToRight = directionIsToRight {

View File

@ -1224,9 +1224,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
invitesText = ""
}
// items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
// interaction.editingOpenInviteLinksSetup()
// }))
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
interaction.editingOpenInviteLinksSetup()
}))
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, action: {
interaction.editingOpenDiscussionGroupSetup()
@ -1326,9 +1326,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
invitesText = ""
}
// items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
// interaction.editingOpenInviteLinksSetup()
// }))
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
interaction.editingOpenInviteLinksSetup()
}))
}
if cachedData.flags.contains(.canChangeUsername) {
@ -1428,9 +1428,9 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
invitesText = ""
}
// items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
// interaction.editingOpenInviteLinksSetup()
// }))
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
interaction.editingOpenInviteLinksSetup()
}))
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistory, action: {
interaction.editingOpenPreHistorySetup()
@ -3808,7 +3808,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
private func editingOpenInviteLinksSetup() {
self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId))
self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId, admin: nil))
}
private func editingOpenDiscussionGroupSetup() {

View File

@ -490,36 +490,73 @@ public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? {
return nil
}
private struct UrlHandlingConfiguration {
static var defaultValue: UrlHandlingConfiguration {
return UrlHandlingConfiguration(token: nil, domains: [])
}
public let token: String?
public let domains: [String]
fileprivate init(token: String?, domains: [String]) {
self.token = token
self.domains = domains
}
static func with(appConfiguration: AppConfiguration) -> UrlHandlingConfiguration {
if let data = appConfiguration.data, let token = data["autologin_token"] as? String, let domains = data["autologin_domains"] as? [String] {
return UrlHandlingConfiguration(token: token, domains: domains)
} else {
return .defaultValue
}
}
}
public func resolveUrlImpl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
let schemes = ["http://", "https://", ""]
for basePath in baseTelegramMePaths {
for scheme in schemes {
let basePrefix = scheme + basePath + "/"
if url.lowercased().hasPrefix(basePrefix) {
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
return resolveInternalUrl(account: account, url: internalUrl)
|> map { resolved -> ResolvedUrl in
if let resolved = resolved {
return resolved
} else {
return .externalUrl(url)
return account.postbox.transaction { transaction -> Signal<ResolvedUrl, NoError> in
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration)
var url = url
if let urlValue = URL(string: url), let host = urlValue.host, urlHandlingConfiguration.domains.contains(host), var components = URLComponents(string: url) {
components.scheme = "https"
var queryItems = components.queryItems ?? []
queryItems.append(URLQueryItem(name: "autologin_token", value: urlHandlingConfiguration.token))
components.queryItems = queryItems
url = components.url?.absoluteString ?? url
}
for basePath in baseTelegramMePaths {
for scheme in schemes {
let basePrefix = scheme + basePath + "/"
if url.lowercased().hasPrefix(basePrefix) {
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
return resolveInternalUrl(account: account, url: internalUrl)
|> map { resolved -> ResolvedUrl in
if let resolved = resolved {
return resolved
} else {
return .externalUrl(url)
}
}
} else {
return .single(.externalUrl(url))
}
} else {
return .single(.externalUrl(url))
}
}
}
}
for basePath in baseTelegraPhPaths {
for scheme in schemes {
let basePrefix = scheme + basePath
if url.lowercased().hasPrefix(basePrefix) {
return resolveInstantViewUrl(account: account, url: url)
for basePath in baseTelegraPhPaths {
for scheme in schemes {
let basePrefix = scheme + basePath
if url.lowercased().hasPrefix(basePrefix) {
return resolveInstantViewUrl(account: account, url: url)
}
}
}
}
return .single(.externalUrl(url))
return .single(.externalUrl(url))
} |> switchToLatest
}
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {