Merge commit '0456336a34f789b8c3707552e6dfc9c3aa101d79'

This commit is contained in:
Ali 2021-02-14 20:45:20 +04:00
commit e5ce9ee0ce
18 changed files with 3029 additions and 2777 deletions

View File

@ -6077,3 +6077,5 @@ Sorry for the inconvenience.";
"Channel.AdminLog.MessageChangedAutoremoveTimeoutSet" = "%1$@ set auto-remove timer to %2$@"; "Channel.AdminLog.MessageChangedAutoremoveTimeoutSet" = "%1$@ set auto-remove timer to %2$@";
"Channel.AdminLog.MessageChangedAutoremoveTimeoutRemove" = "%1$@ disabled auto-remove timer"; "Channel.AdminLog.MessageChangedAutoremoveTimeoutRemove" = "%1$@ disabled auto-remove timer";
"ChannelInfo.InviteLink.RevokeAlert.Text" = "Are you sure you want to revoke this link? Once you do, no one will be able to join the channel using it.";

View File

@ -179,7 +179,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
if self.effectView == nil { if self.effectView == nil {
let effectView: UIVisualEffectView let effectView: UIVisualEffectView
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
if self.presentationData.theme.overallDarkAppearance { if self.presentationData.theme.rootController.keyboardColor == .dark {
effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterialDark)) effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterialDark))
} else { } else {
effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterialLight)) effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterialLight))

View File

@ -578,7 +578,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
propertyAnimator?.stopAnimation(true) propertyAnimator?.stopAnimation(true)
} }
self.effectView.effect = makeCustomZoomBlurEffect(isLight: !self.presentationData.theme.overallDarkAppearance) self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light)
self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor * UIView.animationDurationFactor(), curve: .easeInOut, animations: { self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor * UIView.animationDurationFactor(), curve: .easeInOut, animations: {
}) })
@ -596,7 +596,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
} }
} else { } else {
UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: {
self.effectView.effect = makeCustomZoomBlurEffect(isLight: !self.presentationData.theme.overallDarkAppearance) self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light)
}, completion: { [weak self] _ in }, completion: { [weak self] _ in
self?.didCompleteAnimationIn = true self?.didCompleteAnimationIn = true
self?.actionsContainerNode.animateIn() self?.actionsContainerNode.animateIn()
@ -1106,7 +1106,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
propertyAnimator?.stopAnimation(true) propertyAnimator?.stopAnimation(true)
} }
} }
self.effectView.effect = makeCustomZoomBlurEffect(isLight: !self.presentationData.theme.overallDarkAppearance) self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light)
self.dimNode.alpha = 1.0 self.dimNode.alpha = 1.0
} }
self.dimNode.isHidden = false self.dimNode.isHidden = false

View File

@ -12,6 +12,7 @@ swift_library(
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/SegmentedControlNode:SegmentedControlNode", "//submodules/SegmentedControlNode:SegmentedControlNode",
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
import TelegramPresentationData import TelegramPresentationData
import TelegramStringFormatting import TelegramStringFormatting
import SegmentedControlNode import SegmentedControlNode
import DirectionalPanGesture
public final class DatePickerTheme: Equatable { public final class DatePickerTheme: Equatable {
public let backgroundColor: UIColor public let backgroundColor: UIColor
@ -69,7 +70,11 @@ private let dayFont = Font.regular(13.0)
private let dateFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers) 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 selectedDateFont = Font.with(size: 17.0, design: .regular, traits: [.bold, .monospacedNumbers])
private let calendar = Calendar(identifier: .gregorian) private var calendar: Calendar = {
var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale.current
return calendar
}()
private func monthForDate(_ date: Date) -> Date { private func monthForDate(_ date: Date) -> Date {
var components = calendar.dateComponents([.year, .month], from: date) var components = calendar.dateComponents([.year, .month], from: date)
@ -300,9 +305,6 @@ public final class DatePickerNode: ASDisplayNode {
private var transitionFraction: CGFloat = 0.0 private var transitionFraction: CGFloat = 0.0
private var gestureRecognizer: UIPanGestureRecognizer?
private var gestureSelectedIndex: Int?
private var validLayout: CGSize? private var validLayout: CGSize?
public var valueUpdated: ((Date) -> Void)? public var valueUpdated: ((Date) -> Void)?
@ -479,7 +481,10 @@ public final class DatePickerNode: ASDisplayNode {
self.view.disablesInteractiveTransitionGestureRecognizer = true self.view.disablesInteractiveTransitionGestureRecognizer = true
self.contentNode.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) let panGesture = DirectionalPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
panGesture.direction = .horizontal
self.contentNode.view.addGestureRecognizer(panGesture)
self.contentNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) self.contentNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
} }
@ -499,6 +504,7 @@ public final class DatePickerNode: ASDisplayNode {
} }
} }
self.pickerNode.date = self.state.date self.pickerNode.date = self.state.date
self.timePickerNode.date = self.state.date
if let size = self.validLayout { if let size = self.validLayout {
self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .spring) : .immediate) self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .spring) : .immediate)
@ -714,15 +720,17 @@ public final class DatePickerNode: ASDisplayNode {
let daysSideInset: CGFloat = 12.0 let daysSideInset: CGFloat = 12.0
let cellSize: CGFloat = floor((size.width - daysSideInset * 2.0) / 7.0) let cellSize: CGFloat = floor((size.width - daysSideInset * 2.0) / 7.0)
var dayIndex: Int32 = Int32(calendar.firstWeekday) - 1
for i in 0 ..< self.dayNodes.count { for i in 0 ..< self.dayNodes.count {
let dayNode = self.dayNodes[i] let dayNode = self.dayNodes[i]
dayNode.attributedText = NSAttributedString(string: shortStringForDayOfWeek(strings: self.strings, day: Int32(i)).uppercased(), font: dayFont, textColor: theme.secondaryTextColor) dayNode.attributedText = NSAttributedString(string: shortStringForDayOfWeek(strings: self.strings, day: dayIndex % 7).uppercased(), font: dayFont, textColor: theme.secondaryTextColor)
let textSize = dayNode.updateLayout(size) let textSize = dayNode.updateLayout(size)
let cellFrame = CGRect(x: daysSideInset + CGFloat(i) * cellSize, y: topInset - 38.0, width: cellSize, height: cellSize) let cellFrame = CGRect(x: daysSideInset + CGFloat(i) * cellSize, y: topInset - 38.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) 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 dayNode.frame = textFrame
dayIndex += 1
} }
let containerSize = CGSize(width: size.width, height: size.height - topInset) let containerSize = CGSize(width: size.width, height: size.height - topInset)
@ -887,20 +895,124 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
} }
} }
private class TimeInputView: UIView, UIKeyInput {
override var canBecomeFirstResponder: Bool {
return true
}
var keyboardType: UIKeyboardType = .numberPad
var text: String = ""
var hasText: Bool {
return !self.text.isEmpty
}
var textUpdated: ((String) -> Void)?
private let nonDigits = CharacterSet.decimalDigits.inverted
func insertText(_ text: String) {
if text.rangeOfCharacter(from: nonDigits) != nil {
return
}
var updatedText = self.text
updatedText.append(updatedText)
updatedText = String(updatedText.suffix(4))
self.text = updatedText
self.textUpdated?(self.text)
}
func deleteBackward() {
var updatedText = self.text
updatedText.removeLast()
self.text = updatedText
self.textUpdated?(self.text)
}
}
private class TimeInputNode: ASDisplayNode {
var textUpdated: ((String) -> Void)? {
didSet {
if let view = self.view as? TimeInputView {
view.textUpdated = self.textUpdated
}
}
}
override init() {
super.init()
self.setViewBlock { () -> UIView in
return TimeInputView()
}
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap)))
if let view = self.view as? TimeInputView {
view.textUpdated = self.textUpdated
}
}
@objc private func handleTap() {
self.view.becomeFirstResponder()
}
}
public func stringTimestamp(hours: Int32, minutes: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String {
switch dateTimeFormat.timeFormat {
case .regular:
let hourString: String
if hours == 0 {
hourString = "12"
} else if hours > 12 {
hourString = "\(hours - 12)"
} else {
hourString = "\(hours)"
}
let periodString: String
if hours >= 12 {
periodString = "PM"
} else {
periodString = "AM"
}
if minutes >= 10 {
return "\(hourString) \(minutes) \(periodString)"
} else {
return "\(hourString):0\(minutes) \(periodString)"
}
case .military:
return String(format: "%02d %02d", arguments: [Int(hours), Int(minutes)])
}
}
private final class TimePickerNode: ASDisplayNode { private final class TimePickerNode: ASDisplayNode {
private var theme: DatePickerTheme private var theme: DatePickerTheme
private let dateTimeFormat: PresentationDateTimeFormat private let dateTimeFormat: PresentationDateTimeFormat
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private let colonNode: ImmediateTextNode
private let inputNode: TimeInputNode
private let amPMSelectorNode: SegmentedControlNode private let amPMSelectorNode: SegmentedControlNode
var date: Date var date: Date {
didSet {
if let size = self.validLayout {
self.updateLayout(size: size)
}
}
}
var minDate: Date? var minDate: Date?
var maxDate: Date? var maxDate: Date?
var valueChanged: ((Date) -> Void)? var valueChanged: ((Date) -> Void)?
private var validLayout: CGSize?
init(theme: DatePickerTheme, dateTimeFormat: PresentationDateTimeFormat, date: Date) { init(theme: DatePickerTheme, dateTimeFormat: PresentationDateTimeFormat, date: Date) {
self.theme = theme self.theme = theme
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
@ -911,6 +1023,8 @@ private final class TimePickerNode: ASDisplayNode {
self.backgroundNode.cornerRadius = 9.0 self.backgroundNode.cornerRadius = 9.0
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
self.colonNode = ImmediateTextNode()
self.inputNode = TimeInputNode()
let hours = calendar.component(.hour, from: date) let hours = calendar.component(.hour, from: date)
@ -920,6 +1034,8 @@ private final class TimePickerNode: ASDisplayNode {
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
// self.addSubnode(self.colonNode)
// self.addSubnode(self.inputNode)
self.addSubnode(self.amPMSelectorNode) self.addSubnode(self.amPMSelectorNode)
self.amPMSelectorNode.selectedIndexChanged = { index in self.amPMSelectorNode.selectedIndexChanged = { index in
@ -934,6 +1050,10 @@ private final class TimePickerNode: ASDisplayNode {
self.valueChanged?(newDate) self.valueChanged?(newDate)
} }
} }
self.inputNode.textUpdated = { text in
}
} }
func updateTheme(_ theme: DatePickerTheme) { func updateTheme(_ theme: DatePickerTheme) {
@ -952,9 +1072,17 @@ private final class TimePickerNode: ASDisplayNode {
let string = stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: self.dateTimeFormat).replacingOccurrences(of: " AM", with: "").replacingOccurrences(of: " PM", with: "") let string = stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: self.dateTimeFormat).replacingOccurrences(of: " AM", with: "").replacingOccurrences(of: " PM", with: "")
self.textNode.attributedText = NSAttributedString(string: string, font: Font.with(size: 21.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]), textColor: self.theme.textColor) self.textNode.attributedText = NSAttributedString(string: string, font: Font.with(size: 21.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]), textColor: self.theme.textColor)
self.colonNode.attributedText = NSAttributedString(string: ":", font: Font.with(size: 21.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]), textColor: self.theme.textColor)
let textSize = self.textNode.updateLayout(size) let textSize = self.textNode.updateLayout(size)
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - textSize.width) / 2.0), y: floorToScreenPixels((self.backgroundNode.frame.height - textSize.height) / 2.0)), size: textSize) self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - textSize.width) / 2.0), y: floorToScreenPixels((self.backgroundNode.frame.height - textSize.height) / 2.0)), size: textSize)
let colonSize = self.colonNode.updateLayout(size)
self.colonNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - colonSize.width) / 2.0) - 7.0, y: floorToScreenPixels((self.backgroundNode.frame.height - colonSize.height) / 2.0) - 2.0), size: colonSize)
self.inputNode.frame = self.backgroundNode.frame
if self.dateTimeFormat.timeFormat == .military { if self.dateTimeFormat.timeFormat == .military {
contentSize = self.backgroundNode.frame.size contentSize = self.backgroundNode.frame.size
self.amPMSelectorNode.isHidden = true self.amPMSelectorNode.isHidden = true

View File

@ -2,11 +2,18 @@ import Foundation
import UIKit import UIKit
public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer { public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
public enum Direction {
case horizontal
case vertical
}
private var validatedGesture = false private var validatedGesture = false
private var firstLocation: CGPoint = CGPoint() private var firstLocation: CGPoint = CGPoint()
public var shouldBegin: ((CGPoint) -> Bool)? public var shouldBegin: ((CGPoint) -> Bool)?
public var direction: Direction = .vertical
override public init(target: Any?, action: Selector?) { override public init(target: Any?, action: Selector?) {
super.init(target: target, action: action) super.init(target: target, action: action)
@ -46,10 +53,19 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
let absTranslationY: CGFloat = abs(translation.y) let absTranslationY: CGFloat = abs(translation.y)
if !self.validatedGesture { if !self.validatedGesture {
if absTranslationX > 4.0 && absTranslationX > absTranslationY * 2.0 { switch self.direction {
self.state = .failed case .horizontal:
} else if absTranslationY > 2.0 && absTranslationX * 2.0 < absTranslationY { if absTranslationY > 4.0 && absTranslationY > absTranslationX * 2.0 {
self.validatedGesture = true self.state = .failed
} else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
self.validatedGesture = true
}
case .vertical:
if absTranslationX > 4.0 && absTranslationX > absTranslationY * 2.0 {
self.state = .failed
} else if absTranslationY > 2.0 && absTranslationX * 2.0 < absTranslationY {
self.validatedGesture = true
}
} }
} }

View File

@ -44,7 +44,11 @@ func isValidNumberOfUsers(_ number: String) -> Bool {
if number.rangeOfCharacter(from: invalidAmountCharacters) != nil || number == "0" { if number.rangeOfCharacter(from: invalidAmountCharacters) != nil || number == "0" {
return false return false
} }
return true if let _ = Int32(number) {
return true
} else {
return false
}
} }
private enum InviteLinksEditEntry: ItemListNodeEntry { private enum InviteLinksEditEntry: ItemListNodeEntry {
@ -55,7 +59,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
case timeInfo(PresentationTheme, String) case timeInfo(PresentationTheme, String)
case usageHeader(PresentationTheme, String) case usageHeader(PresentationTheme, String)
case usagePicker(PresentationTheme, InviteLinkUsageLimit) case usagePicker(PresentationTheme, PresentationDateTimeFormat, InviteLinkUsageLimit)
case usageCustomPicker(PresentationTheme, Int32?, Bool, Bool) case usageCustomPicker(PresentationTheme, Int32?, Bool, Bool)
case usageInfo(PresentationTheme, String) case usageInfo(PresentationTheme, String)
@ -135,8 +139,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .usagePicker(lhsTheme, lhsValue): case let .usagePicker(lhsTheme, lhsDateTimeFormat, lhsValue):
if case let .usagePicker(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue { if case let .usagePicker(rhsTheme, rhsDateTimeFormat, rhsValue) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsValue == rhsValue {
return true return true
} else { } else {
return false return false
@ -209,8 +213,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .usageHeader(_, text): case let .usageHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .usagePicker(_, value): case let .usagePicker(_, dateTimeFormat, value):
return ItemListInviteLinkUsageLimitItem(theme: presentationData.theme, strings: presentationData.strings, value: value, enabled: true, sectionId: self.section, updated: { value in return ItemListInviteLinkUsageLimitItem(theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, value: value, enabled: true, sectionId: self.section, updated: { value in
arguments.dismissInput() arguments.dismissInput()
arguments.updateState({ state in arguments.updateState({ state in
var updatedState = state var updatedState = state
@ -234,7 +238,9 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
} }
arguments.updateState { state in arguments.updateState { state in
var updatedState = state var updatedState = state
updatedState.usage = InviteLinkUsageLimit(value: Int32(updatedText)) if let value = Int32(updatedText) {
updatedState.usage = InviteLinkUsageLimit(value: value)
}
return updatedState return updatedState
} }
}, shouldUpdateText: { text in }, shouldUpdateText: { text in
@ -288,7 +294,7 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
entries.append(.timeInfo(presentationData.theme, presentationData.strings.InviteLink_Create_TimeLimitInfo)) entries.append(.timeInfo(presentationData.theme, presentationData.strings.InviteLink_Create_TimeLimitInfo))
entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased())) entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased()))
entries.append(.usagePicker(presentationData.theme, state.usage)) entries.append(.usagePicker(presentationData.theme, presentationData.dateTimeFormat, state.usage))
var customValue = false var customValue = false
if case .custom = state.usage { if case .custom = state.usage {
@ -358,42 +364,51 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
guard let invite = invite else { guard let invite = invite else {
return return
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let _ = (context.account.postbox.loadedPeerWithId(peerId)
let controller = ActionSheetController(presentationData: presentationData) |> deliverOnMainQueue).start(next: { peer in
let dismissAction: () -> Void = { [weak controller] in let isGroup: Bool
controller?.dismissAnimated() if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
} isGroup = false
controller.setItemGroups([ } else {
ActionSheetItemGroup(items: [ isGroup = true
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text), }
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { let presentationData = context.sharedContext.currentPresentationData.with { $0 }
dismissAction() let controller = ActionSheetController(presentationData: presentationData)
dismissImpl?() let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
dismissImpl?()
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link)
|> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic)) |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
|> deliverOnMainQueue).start(next: { invite in |> deliverOnMainQueue).start(next: { invite in
switch invite { switch invite {
case .none: case .none:
completion?(nil) completion?(nil)
case let .update(invitation): case let .update(invitation):
completion?(invitation) completion?(invitation)
case let .replace(_, invitation): case let .replace(_, invitation):
completion?(invitation) completion?(invitation)
} }
}, error: { _ in }, error: { _ in
updateState { state in updateState { state in
var updatedState = state var updatedState = state
updatedState.updating = false updatedState.updating = false
return updatedState return updatedState
} }
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
})
}) })
}) ]),
]), ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ])
]) presentControllerImpl?(controller, nil)
presentControllerImpl?(controller, nil) })
}) })
let previousState = Atomic<InviteLinkEditControllerState?>(value: nil) let previousState = Atomic<InviteLinkEditControllerState?>(value: nil)

View File

@ -366,25 +366,34 @@ public final class InviteLinkInviteController: ViewController {
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = ActionSheetController(presentationData: presentationData) let _ = (context.account.postbox.loadedPeerWithId(peerId)
let dismissAction: () -> Void = { [weak controller] in |> deliverOnMainQueue).start(next: { peer in
controller?.dismissAnimated() let isGroup: Bool
} if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
controller.setItemGroups([ isGroup = false
ActionSheetItemGroup(items: [ } else {
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text), isGroup = true
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { }
dismissAction() let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
if let invite = invite { if let invite = invite {
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
}) })
} }
}) })
]), ]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
]) ])
self?.controller?.present(controller, in: .window(.root)) self?.controller?.present(controller, in: .window(.root))
})
}))) })))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture)

View File

@ -297,7 +297,7 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
mainInvite = nil mainInvite = nil
} }
entries.append(.mainLinkHeader(presentationData.theme, isPublic ? presentationData.strings.InviteLink_PublicLink.uppercased() : presentationData.strings.InviteLink_PermanentLink.uppercased())) entries.append(.mainLinkHeader(presentationData.theme, isPublic ? presentationData.strings.InviteLink_PublicLink.uppercased() : presentationData.strings.InviteLink_InviteLink.uppercased()))
let importersCount: Int32 let importersCount: Int32
if let count = importers?.count { if let count = importers?.count {
@ -477,52 +477,62 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = ActionSheetController(presentationData: presentationData) let _ = (context.account.postbox.loadedPeerWithId(peerId)
let dismissAction: () -> Void = { [weak controller] in |> deliverOnMainQueue).start(next: { peer in
controller?.dismissAnimated() let isGroup: Bool
} if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
controller.setItemGroups([ isGroup = false
ActionSheetItemGroup(items: [ } else {
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text), isGroup = true
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { }
dismissAction()
var revoke = false let controller = ActionSheetController(presentationData: presentationData)
updateState { state in let dismissAction: () -> Void = { [weak controller] in
if !state.revokingPrivateLink { controller?.dismissAnimated()
revoke = true }
var updatedState = state controller.setItemGroups([
updatedState.revokingPrivateLink = true ActionSheetItemGroup(items: [
return updatedState ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
} else { ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
return state dismissAction()
}
} var revoke = false
if revoke { updateState { state in
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in if !state.revokingPrivateLink {
updateState { state in revoke = true
var updatedState = state var updatedState = state
updatedState.revokingPrivateLink = false updatedState.revokingPrivateLink = true
return updatedState return updatedState
} else {
return state
} }
if let result = result { }
switch result { if revoke {
case let .update(newInvite): revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in
invitesContext.remove(newInvite) updateState { state in
revokedInvitesContext.add(newInvite) var updatedState = state
case let .replace(previousInvite, newInvite): updatedState.revokingPrivateLink = false
revokedInvitesContext.add(previousInvite) return updatedState
invitesContext.remove(previousInvite)
invitesContext.add(newInvite)
} }
} if let result = result {
})) switch result {
} case let .update(newInvite):
}) invitesContext.remove(newInvite)
]), revokedInvitesContext.add(newInvite)
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) case let .replace(previousInvite, newInvite):
]) revokedInvitesContext.add(previousInvite)
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) invitesContext.remove(previousInvite)
invitesContext.add(newInvite)
}
}
}))
}
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
}))) })))
} }
@ -646,27 +656,36 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
}, action: { _, f in }, action: { _, f in
f(.default) f(.default)
let controller = ActionSheetController(presentationData: presentationData) let _ = (context.account.postbox.loadedPeerWithId(peerId)
let dismissAction: () -> Void = { [weak controller] in |> deliverOnMainQueue).start(next: { peer in
controller?.dismissAnimated() let isGroup: Bool
} if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
controller.setItemGroups([ isGroup = false
ActionSheetItemGroup(items: [ } else {
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text), isGroup = true
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { }
dismissAction() let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})) }))
invitesContext.remove(invite) invitesContext.remove(invite)
revokedInvitesContext.add(invite.withUpdated(isRevoked: true)) revokedInvitesContext.add(invite.withUpdated(isRevoked: true))
}) })
]), ]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
]) ])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
}))) })))
} }

View File

@ -41,6 +41,11 @@ public final class InviteLinkQRCodeController: ViewController {
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private var initialBrightness: CGFloat?
private var brightnessArguments: (Double, Double, CGFloat, CGFloat)?
private var animator: ConstantDisplayLinkAnimator?
private let idleTimerExtensionDisposable = MetaDisposable() private let idleTimerExtensionDisposable = MetaDisposable()
public init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) { public init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) {
@ -64,6 +69,11 @@ public final class InviteLinkQRCodeController: ViewController {
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension()) self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
self.statusBar.statusBarStyle = .Ignore self.statusBar.statusBarStyle = .Ignore
self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateBrightness()
})
self.animator?.isPaused = true
} }
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {
@ -73,6 +83,7 @@ public final class InviteLinkQRCodeController: ViewController {
deinit { deinit {
self.presentationDataDisposable?.dispose() self.presentationDataDisposable?.dispose()
self.idleTimerExtensionDisposable.dispose() self.idleTimerExtensionDisposable.dispose()
self.animator?.invalidate()
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
@ -85,20 +96,43 @@ public final class InviteLinkQRCodeController: ViewController {
} }
} }
override public func loadView() {
super.loadView()
}
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
if !self.animatedIn { if !self.animatedIn {
self.animatedIn = true self.animatedIn = true
self.controllerNode.animateIn() self.controllerNode.animateIn()
self.initialBrightness = UIScreen.main.brightness
self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, 1.0)
self.updateBrightness()
}
}
private func updateBrightness() {
if let (startTime, duration, initial, target) = self.brightnessArguments {
self.animator?.isPaused = false
let t = CGFloat(max(0.0, min(1.0, (CACurrentMediaTime() - startTime) / duration)))
let value = initial + (target - initial) * t
UIScreen.main.brightness = value
if t >= 1.0 {
self.brightnessArguments = nil
self.animator?.isPaused = true
}
} else {
self.animator?.isPaused = true
} }
} }
override public func dismiss(completion: (() -> Void)? = nil) { override public func dismiss(completion: (() -> Void)? = nil) {
if UIScreen.main.brightness > 0.99, let initialBrightness = self.initialBrightness {
self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, initialBrightness)
self.updateBrightness()
}
self.controllerNode.animateOut(completion: completion) self.controllerNode.animateOut(completion: completion)
} }
@ -344,7 +378,7 @@ public final class InviteLinkQRCodeController: ViewController {
self.containerLayout = (layout, navigationBarHeight) self.containerLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar, .input]) var insets = layout.insets(options: [.statusBar, .input])
insets.top = max(10.0, insets.top) insets.top = 48.0
let makeImageLayout = self.qrImageNode.asyncLayout() let makeImageLayout = self.qrImageNode.asyncLayout()
let imageSide: CGFloat = 240.0 let imageSide: CGFloat = 240.0

View File

@ -511,26 +511,35 @@ public final class InviteLinkViewController: ViewController {
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = ActionSheetController(presentationData: presentationData) let _ = (context.account.postbox.loadedPeerWithId(peerId)
let dismissAction: () -> Void = { [weak controller] in |> deliverOnMainQueue).start(next: { peer in
controller?.dismissAnimated() let isGroup: Bool
} if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
controller.setItemGroups([ isGroup = false
ActionSheetItemGroup(items: [ } else {
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text), isGroup = true
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { }
dismissAction() let controller = ActionSheetController(presentationData: presentationData)
self?.controller?.dismiss() let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
self?.controller?.dismiss()
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})
self?.controller?.revokedInvitationsContext?.remove(invite)
}) })
]),
self?.controller?.revokedInvitationsContext?.remove(invite) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
}) ])
]), self?.controller?.present(controller, in: .window(.root))
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) })
])
self?.controller?.present(controller, in: .window(.root))
}))) })))
} }

View File

@ -89,14 +89,16 @@ enum InviteLinkUsageLimit: Equatable {
final class ItemListInviteLinkUsageLimitItem: ListViewItem, ItemListItem { final class ItemListInviteLinkUsageLimitItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat
let value: InviteLinkUsageLimit let value: InviteLinkUsageLimit
let enabled: Bool let enabled: Bool
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let updated: (InviteLinkUsageLimit) -> Void let updated: (InviteLinkUsageLimit) -> Void
init(theme: PresentationTheme, strings: PresentationStrings, value: InviteLinkUsageLimit, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (InviteLinkUsageLimit) -> Void) { init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, value: InviteLinkUsageLimit, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (InviteLinkUsageLimit) -> Void) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.value = value self.value = value
self.enabled = enabled self.enabled = enabled
self.sectionId = sectionId self.sectionId = sectionId
@ -285,7 +287,7 @@ private final class ItemListInviteLinkUsageLimitItemNode: ListViewItemNode {
let customTextString: String let customTextString: String
if case let .custom(value) = item.value { if case let .custom(value) = item.value {
customTextString = "\(value)" customTextString = compactNumericCountString(Int(value), decimalSeparator: item.dateTimeFormat.decimalSeparator)
} else { } else {
customTextString = "" customTextString = ""
} }

View File

@ -988,50 +988,60 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = ActionSheetController(presentationData: presentationData) let _ = (context.account.postbox.loadedPeerWithId(peerId)
let dismissAction: () -> Void = { [weak controller] in |> deliverOnMainQueue).start(next: { peer in
controller?.dismissAnimated() let isGroup: Bool
} if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
controller.setItemGroups([ isGroup = false
ActionSheetItemGroup(items: [ } else {
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text), isGroup = true
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { }
dismissAction()
let _ = (context.account.postbox.transaction { transaction -> String? in let controller = ActionSheetController(presentationData: presentationData)
if let cachedData = transaction.getPeerCachedData(peerId: peerId) { let dismissAction: () -> Void = { [weak controller] in
if let cachedData = cachedData as? CachedChannelData { controller?.dismissAnimated()
return cachedData.exportedInvitation?.link }
} else if let cachedData = cachedData as? CachedGroupData { controller.setItemGroups([
return cachedData.exportedInvitation?.link ActionSheetItemGroup(items: [
} ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
} ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
return nil dismissAction()
} |> deliverOnMainQueue).start(next: { link in
if let link = link { let _ = (context.account.postbox.transaction { transaction -> String? in
var revoke = false if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
updateState { state in if let cachedData = cachedData as? CachedChannelData {
if !state.revokingPrivateLink { return cachedData.exportedInvitation?.link
revoke = true } else if let cachedData = cachedData as? CachedGroupData {
return state.withUpdatedRevokingPrivateLink(true) return cachedData.exportedInvitation?.link
} else {
return state
} }
} }
if revoke { return nil
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: link) |> deliverOnMainQueue).start(completed: { } |> deliverOnMainQueue).start(next: { link in
updateState { if let link = link {
$0.withUpdatedRevokingPrivateLink(false) var revoke = false
updateState { state in
if !state.revokingPrivateLink {
revoke = true
return state.withUpdatedRevokingPrivateLink(true)
} else {
return state
} }
})) }
if revoke {
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: link) |> deliverOnMainQueue).start(completed: {
updateState {
$0.withUpdatedRevokingPrivateLink(false)
}
}))
}
} }
} })
}) })
}) ]),
]), ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ])
]) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) })
}))) })))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: nil) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: nil)

View File

@ -71,7 +71,7 @@ public final class PermissionContentNode: ASDisplayNode {
if case let .animation(animation) = icon { if case let .animation(animation) = icon {
self.animationNode = AnimatedStickerNode() self.animationNode = AnimatedStickerNode()
if let path = getAppBundle().path(forResource: animation, ofType: "tgs") { if let path = getAppBundle().path(forResource: animation, ofType: "tgs") {
self.animationNode?.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 320, height: 320, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) self.animationNode?.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 320, height: 320, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode?.visibility = true self.animationNode?.visibility = true
} }
self.nearbyIconNode = nil self.nearbyIconNode = nil

View File

@ -873,6 +873,12 @@ extension PresentationThemeDecoding {
} else if number === kCFBooleanFalse as NSNumber { } else if number === kCFBooleanFalse as NSNumber {
return false return false
} }
} else if let string = value as? String {
if string.lowercased() == "true" {
return true
} else if string.lowercased() == "false" {
return false
}
} }
throw PresentationThemeDecodingError.typeMismatch throw PresentationThemeDecodingError.typeMismatch

View File

@ -413,7 +413,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0 let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
} else { } else {
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)] let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
let titleString = strings.Notification_VoiceChatStarted(authorName) let titleString = strings.Notification_VoiceChatStarted(authorName)
attributedString = addAttributesToStringWithRanges(titleString, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) attributedString = addAttributesToStringWithRanges(titleString, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
} }