Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mike Renoir 2023-12-06 22:33:34 +04:00
commit 3df7111853
47 changed files with 1144 additions and 186 deletions

View File

@ -10614,3 +10614,7 @@ Sorry for the inconvenience.";
"MediaEditor.Shortcut.Location" = "Location";
"MediaEditor.Shortcut.Reaction" = "Reaction";
"MediaEditor.Shortcut.Audio" = "Audio";
"BoostGift.WinnersTitle" = "WINNERS";
"BoostGift.Winners" = "Show Winners";
"BoostGift.WinnersInfo" = "Choose whether to make the list of winners public when the giveaway ends.";

View File

@ -142,6 +142,8 @@ public final class ViewController: UIViewController {
self.callState.lifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: 82.0))
self.callState.remoteVideo = nil
self.callState.localVideo = nil
self.callState.isMicrophoneMuted = false
self.callState.isRemoteBatteryLow = false
self.update(transition: .spring(duration: 0.4))
}
callScreenView.backAction = { [weak self] in
@ -151,6 +153,12 @@ public final class ViewController: UIViewController {
self.callState.isMicrophoneMuted = !self.callState.isMicrophoneMuted
self.update(transition: .spring(duration: 0.4))
}
callScreenView.closeAction = { [weak self] in
guard let self else {
return
}
self.callScreenView?.speakerAction?()
}
}
private func update(transition: Transition) {

View File

@ -1573,6 +1573,56 @@ public extension ContainedViewLayoutTransition {
}
}
func updateLineWidth(layer: CAShapeLayer, lineWidth: CGFloat, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if layer.lineWidth == lineWidth {
completion?(true)
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "lineWidth")
layer.lineWidth = lineWidth
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let fromLineWidth = layer.lineWidth
layer.lineWidth = lineWidth
layer.animate(from: fromLineWidth as NSNumber, to: lineWidth as NSNumber, keyPath: "lineWidth", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func updateStrokeColor(layer: CAShapeLayer, strokeColor: UIColor, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if layer.strokeColor.flatMap(UIColor.init(cgColor:)) == strokeColor {
completion?(true)
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "strokeColor")
layer.strokeColor = strokeColor.cgColor
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let fromStrokeColor = layer.strokeColor ?? UIColor.clear.cgColor
layer.strokeColor = strokeColor.cgColor
layer.animate(from: fromStrokeColor, to: strokeColor.cgColor, keyPath: "strokeColor", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func attachAnimation(view: UIView, id: String, completion: @escaping (Bool) -> Void) {
switch self {
case .immediate:

View File

@ -925,3 +925,68 @@ public func drawSvgPath(_ context: CGContext, path: StaticString, strokeOnMove:
}
}
}
public func convertSvgPath(_ path: StaticString) throws -> CGPath {
var index: UnsafePointer<UInt8> = path.utf8Start
let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount)
var currentPoint = CGPoint()
let result = CGMutablePath()
while index < end {
let c = index.pointee
index = index.successor()
if c == 77 { // M
let x = try readCGFloat(&index, end: end, separator: 44)
let y = try readCGFloat(&index, end: end, separator: 32)
//print("Move to \(x), \(y)")
currentPoint = CGPoint(x: x, y: y)
result.move(to: currentPoint)
} else if c == 76 { // L
let x = try readCGFloat(&index, end: end, separator: 44)
let y = try readCGFloat(&index, end: end, separator: 32)
//print("Line to \(x), \(y)")
currentPoint = CGPoint(x: x, y: y)
result.addLine(to: currentPoint)
} else if c == 72 { // H
let x = try readCGFloat(&index, end: end, separator: 32)
//print("Move to \(x), \(y)")
currentPoint = CGPoint(x: x, y: currentPoint.y)
result.addLine(to: currentPoint)
} else if c == 86 { // V
let y = try readCGFloat(&index, end: end, separator: 32)
//print("Move to \(x), \(y)")
currentPoint = CGPoint(x: currentPoint.x, y: y)
result.addLine(to: currentPoint)
} else if c == 67 { // C
let x1 = try readCGFloat(&index, end: end, separator: 44)
let y1 = try readCGFloat(&index, end: end, separator: 32)
let x2 = try readCGFloat(&index, end: end, separator: 44)
let y2 = try readCGFloat(&index, end: end, separator: 32)
let x = try readCGFloat(&index, end: end, separator: 44)
let y = try readCGFloat(&index, end: end, separator: 32)
currentPoint = CGPoint(x: x, y: y)
result.addCurve(to: currentPoint, control1: CGPoint(x: x1, y: y1), control2: CGPoint(x: x2, y: y2))
} else if c == 90 { // Z
if index != end && index.pointee != 32 {
throw ParsingError.Generic
}
} else if c == 83 { // S
if index != end && index.pointee != 32 {
throw ParsingError.Generic
}
} else if c == 32 { // space
continue
} else {
throw ParsingError.Generic
}
}
return result
}

View File

@ -575,6 +575,8 @@ private final class PendingInAppPurchaseState: Codable {
case additionalPeerIds
case countries
case onlyNewSubscribers
case showWinners
case prizeDescription
case randomId
case untilDate
}
@ -593,7 +595,7 @@ private final class PendingInAppPurchaseState: Codable {
case restore
case gift(peerId: EnginePeer.Id)
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?)
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32)
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
@ -621,6 +623,8 @@ private final class PendingInAppPurchaseState: Codable {
additionalPeerIds: try container.decode([Int64].self, forKey: .randomId).map { EnginePeer.Id($0) },
countries: try container.decodeIfPresent([String].self, forKey: .countries) ?? [],
onlyNewSubscribers: try container.decode(Bool.self, forKey: .onlyNewSubscribers),
showWinners: try container.decodeIfPresent(Bool.self, forKey: .showWinners) ?? false,
prizeDescription: try container.decodeIfPresent(String.self, forKey: .prizeDescription),
randomId: try container.decode(Int64.self, forKey: .randomId),
untilDate: try container.decode(Int32.self, forKey: .untilDate)
)
@ -646,12 +650,14 @@ private final class PendingInAppPurchaseState: Codable {
try container.encode(PurposeType.giftCode.rawValue, forKey: .type)
try container.encode(peerIds.map { $0.toInt64() }, forKey: .peers)
try container.encodeIfPresent(boostPeer?.toInt64(), forKey: .boostPeer)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate):
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate):
try container.encode(PurposeType.giveaway.rawValue, forKey: .type)
try container.encode(boostPeer.toInt64(), forKey: .boostPeer)
try container.encode(additionalPeerIds.map { $0.toInt64() }, forKey: .additionalPeerIds)
try container.encode(countries, forKey: .countries)
try container.encode(onlyNewSubscribers, forKey: .onlyNewSubscribers)
try container.encode(showWinners, forKey: .showWinners)
try container.encode(prizeDescription, forKey: .prizeDescription)
try container.encode(randomId, forKey: .randomId)
try container.encode(untilDate, forKey: .untilDate)
}
@ -669,8 +675,8 @@ private final class PendingInAppPurchaseState: Codable {
self = .gift(peerId: peerId)
case let .giftCode(peerIds, boostPeer, _, _):
self = .giftCode(peerIds: peerIds, boostPeer: boostPeer)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate, _, _):
self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _):
self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate)
}
}
@ -687,8 +693,8 @@ private final class PendingInAppPurchaseState: Codable {
return .gift(peerId: peerId, currency: currency, amount: amount)
case let .giftCode(peerIds, boostPeer):
return .giftCode(peerIds: peerIds, boostPeer: boostPeer, currency: currency, amount: amount)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate):
return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate):
return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
}
}
}

View File

@ -88,5 +88,6 @@
- (void)_commitLocked;
- (void)setHidesPanelOnLock;
- (UIView *)createLockPanelView;
@end

View File

@ -115,7 +115,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
UIImageView *_innerIconView;
UIView *_lockPanelWrapperView;
UIImageView *_lockPanelView;
UIView *_lockPanelView;
UIImageView *_lockArrowView;
TGModernConversationInputLockView *_lockView;
UIImage *_previousIcon;
@ -265,7 +265,9 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
if (!update)
return;
_lockPanelView.image = [self panelBackgroundImage];
if ([_lockPanelView isKindOfClass:[UIImageView class]]) {
((UIImageView *)_lockPanelView).image = [self panelBackgroundImage];
}
_lockArrowView.image = TGTintedImage(TGComponentsImageNamed(@"VideoRecordArrow"), self.pallete != nil ? self.pallete.lockColor : UIColorRGB(0x9597a0));
_lockView.color = self.pallete.lockColor;
@ -341,6 +343,13 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
return stopButtonImage;
}
- (UIView *)createLockPanelView {
UIImageView *view = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 72.0f)];
view.userInteractionEnabled = true;
view.image = [self panelBackgroundImage];
return view;
}
- (void)animateIn {
if (!_locked) {
_lockView.lockness = 0.0f;
@ -373,9 +382,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
_lockPanelWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 72.0f)];
[[_presentation view] addSubview:_lockPanelWrapperView];
_lockPanelView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 72.0f)];
_lockPanelView.userInteractionEnabled = true;
_lockPanelView.image = [self panelBackgroundImage];
_lockPanelView = [self createLockPanelView];
[_lockPanelWrapperView addSubview:_lockPanelView];

View File

@ -53,6 +53,8 @@ private enum CreateGiveawaySection: Int32 {
case subscriptions
case channels
case users
case winners
case prizeDescription
case time
case duration
}
@ -92,6 +94,14 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case usersNew(PresentationTheme, String, String, Bool)
case usersInfo(PresentationTheme, String)
case winnersHeader(PresentationTheme, String)
case winners(PresentationTheme, String, Bool)
case winnersInfo(PresentationTheme, String)
case prizeDescriptionHeader(PresentationTheme, String)
case prizeDescription(PresentationTheme, String, String)
case prizeDescriptionInfo(PresentationTheme, String)
case timeHeader(PresentationTheme, String)
case timeExpiryDate(PresentationTheme, PresentationDateTimeFormat, Int32?, Bool)
case timeCustomPicker(PresentationTheme, PresentationDateTimeFormat, Int32?, Int32?, Int32?, Bool, Bool)
@ -113,6 +123,10 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
return CreateGiveawaySection.channels.rawValue
case .usersHeader, .usersAll, .usersNew, .usersInfo:
return CreateGiveawaySection.users.rawValue
case .winnersHeader, .winners, .winnersInfo:
return CreateGiveawaySection.winners.rawValue
case .prizeDescriptionHeader, .prizeDescription, .prizeDescriptionInfo:
return CreateGiveawaySection.prizeDescription.rawValue
case .timeHeader, .timeExpiryDate, .timeCustomPicker, .timeInfo:
return CreateGiveawaySection.time.rawValue
case .durationHeader, .duration, .durationInfo:
@ -154,20 +168,32 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
return 104
case .usersInfo:
return 105
case .timeHeader:
case .winnersHeader:
return 106
case .timeExpiryDate:
case .winners:
return 107
case .timeCustomPicker:
case .winnersInfo:
return 108
case .timeInfo:
case .prizeDescriptionHeader:
return 109
case .durationHeader:
case .prizeDescription:
return 110
case .prizeDescriptionInfo:
return 111
case .timeHeader:
return 112
case .timeExpiryDate:
return 113
case .timeCustomPicker:
return 114
case .timeInfo:
return 115
case .durationHeader:
return 116
case let .duration(index, _, _, _, _, _, _, _):
return 111 + index
return 117 + index
case .durationInfo:
return 120
return 130
}
}
@ -269,7 +295,42 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
return false
}
case let .winnersHeader(lhsTheme, lhsText):
if case let .winnersHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .winners(lhsTheme, lhsText, lhsValue):
if case let .winners(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .winnersInfo(lhsTheme, lhsText):
if case let .winnersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .prizeDescriptionHeader(lhsTheme, lhsText):
if case let .prizeDescriptionHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .prizeDescription(lhsTheme, lhsPlaceholder, lhsValue):
if case let .prizeDescription(rhsTheme, rhsPlaceholder, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue {
return true
} else {
return false
}
case let .prizeDescriptionInfo(lhsTheme, lhsText):
if case let .prizeDescriptionInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .timeHeader(lhsTheme, lhsText):
if case let .timeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -423,6 +484,30 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
})
case let .usersInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .winnersHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .winners(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.showWinners = value
return updatedState
}
})
case let .winnersInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .prizeDescriptionHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .prizeDescription(_, placeholder, value):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.prizeDescription = value
return updatedState
}
}, action: {})
case let .prizeDescriptionInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .timeHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .timeExpiryDate(theme, dateTimeFormat, value, active):
@ -617,6 +702,14 @@ private func createGiveawayControllerEntries(
entries.append(.usersNew(presentationData.theme, presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible))
entries.append(.usersInfo(presentationData.theme, presentationData.strings.BoostGift_LimitSubscribersInfo))
entries.append(.winnersHeader(presentationData.theme, presentationData.strings.BoostGift_WinnersTitle.uppercased()))
entries.append(.winners(presentationData.theme, presentationData.strings.BoostGift_Winners, state.showWinners))
entries.append(.winnersInfo(presentationData.theme, presentationData.strings.BoostGift_WinnersInfo))
entries.append(.prizeDescriptionHeader(presentationData.theme, "Additional Prizes".uppercased()))
entries.append(.prizeDescription(presentationData.theme, "Prize Description (Optional)", state.prizeDescription))
entries.append(.prizeDescriptionInfo(presentationData.theme, "Provide description of any additional prizes you plan to award to the winners, in addition to Telegram Premium."))
entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased()))
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate, state.pickingExpiryDate, state.pickingExpiryTime))
entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string))
@ -680,11 +773,13 @@ private struct CreateGiveawayControllerState: Equatable {
var mode: Mode
var subscriptions: Int32
var channels: [EnginePeer.Id]
var peers: [EnginePeer.Id]
var channels: [EnginePeer.Id] = []
var peers: [EnginePeer.Id] = []
var selectedMonths: Int32?
var countries: [String]
var onlyNewEligible: Bool
var countries: [String] = []
var onlyNewEligible: Bool = false
var showWinners: Bool = false
var prizeDescription: String = ""
var time: Int32
var pickingExpiryTime = false
var pickingExpiryDate = false
@ -722,7 +817,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let minDate = currentTime + 60 * 30
let maxDate = currentTime + context.userLimits.maxGiveawayPeriodSeconds
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, channels: [], peers: [], countries: [], onlyNewEligible: false, time: expiryTime)
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, time: expiryTime)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
@ -948,7 +1043,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let quantity: Int32
switch state.mode {
case .giveaway:
purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount)
purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, showWinners: state.showWinners, prizeDescription: state.prizeDescription.isEmpty ? nil : state.prizeDescription, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount)
quantity = selectedProduct.giftOption.storeQuantity
case .gift:
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount)
@ -1040,7 +1135,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
return updatedState
}
let _ = (context.engine.payments.launchPrepaidGiveaway(peerId: peerId, id: prepaidGiveaway.id, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time)
let _ = (context.engine.payments.launchPrepaidGiveaway(peerId: peerId, id: prepaidGiveaway.id, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, showWinners: state.showWinners, prizeDescription: state.prizeDescription.isEmpty ? nil : state.prizeDescription, randomId: Int64.random(in: .min ..< .max), untilDate: state.time)
|> deliverOnMainQueue).startStandalone(completed: {
if let controller, let navigationController = controller.navigationController as? NavigationController {
var controllers = navigationController.viewControllers

View File

@ -414,7 +414,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) }
dict[-1551868097] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) }
dict[2090038758] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) }
dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) }
dict[-1502273946] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) }
dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) }
dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) }
@ -542,7 +542,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) }
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
dict[-1186937242] = { return Api.MessageMedia.parse_messageMediaGeoLive($0) }
dict[1478887012] = { return Api.MessageMedia.parse_messageMediaGiveaway($0) }
dict[-626162256] = { return Api.MessageMedia.parse_messageMediaGiveaway($0) }
dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
@ -1176,7 +1176,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1222446760] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) }
dict[-1362048039] = { return Api.payments.ExportedInvoice.parse_exportedInvoice($0) }
dict[1130879648] = { return Api.payments.GiveawayInfo.parse_giveawayInfo($0) }
dict[13456752] = { return Api.payments.GiveawayInfo.parse_giveawayInfoResults($0) }
dict[-1966612121] = { return Api.payments.GiveawayInfo.parse_giveawayInfoResults($0) }
dict[-1610250415] = { return Api.payments.PaymentForm.parse_paymentForm($0) }
dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) }
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }

View File

@ -604,7 +604,7 @@ public extension Api {
indirect enum InputStorePaymentPurpose: TypeConstructorDescription {
case inputStorePaymentGiftPremium(userId: Api.InputUser, currency: String, amount: Int64)
case inputStorePaymentPremiumGiftCode(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64)
case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
case inputStorePaymentPremiumSubscription(flags: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
@ -631,9 +631,9 @@ public extension Api {
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
break
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let randomId, let untilDate, let currency, let amount):
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let prizeDescription, let randomId, let untilDate, let currency, let amount):
if boxed {
buffer.appendInt32(2090038758)
buffer.appendInt32(369444042)
}
serializeInt32(flags, buffer: buffer, boxed: false)
boostPeer.serialize(buffer, true)
@ -647,6 +647,7 @@ public extension Api {
for item in countriesIso2! {
serializeString(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 4) != 0 {serializeString(prizeDescription!, buffer: buffer, boxed: false)}
serializeInt64(randomId, buffer: buffer, boxed: false)
serializeInt32(untilDate, buffer: buffer, boxed: false)
serializeString(currency, buffer: buffer, boxed: false)
@ -667,8 +668,8 @@ public extension Api {
return ("inputStorePaymentGiftPremium", [("userId", userId as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount):
return ("inputStorePaymentPremiumGiftCode", [("flags", flags as Any), ("users", users as Any), ("boostPeer", boostPeer as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let randomId, let untilDate, let currency, let amount):
return ("inputStorePaymentPremiumGiveaway", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("additionalPeers", additionalPeers as Any), ("countriesIso2", countriesIso2 as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let prizeDescription, let randomId, let untilDate, let currency, let amount):
return ("inputStorePaymentPremiumGiveaway", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("additionalPeers", additionalPeers as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentPremiumSubscription(let flags):
return ("inputStorePaymentPremiumSubscription", [("flags", flags as Any)])
}
@ -735,24 +736,27 @@ public extension Api {
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
} }
var _5: Int64?
_5 = reader.readInt64()
var _6: Int32?
_6 = reader.readInt32()
var _7: String?
_7 = parseString(reader)
var _8: Int64?
_8 = reader.readInt64()
var _5: String?
if Int(_1!) & Int(1 << 4) != 0 {_5 = parseString(reader) }
var _6: Int64?
_6 = reader.readInt64()
var _7: Int32?
_7 = reader.readInt32()
var _8: String?
_8 = parseString(reader)
var _9: Int64?
_9 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, additionalPeers: _3, countriesIso2: _4, randomId: _5!, untilDate: _6!, currency: _7!, amount: _8!)
let _c9 = _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, additionalPeers: _3, countriesIso2: _4, prizeDescription: _5, randomId: _6!, untilDate: _7!, currency: _8!, amount: _9!)
}
else {
return nil

View File

@ -697,7 +697,7 @@ public extension Api {
case messageMediaGame(game: Api.Game)
case messageMediaGeo(geo: Api.GeoPoint)
case messageMediaGeoLive(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?)
case messageMediaGiveaway(flags: Int32, channels: [Int64], countriesIso2: [String]?, quantity: Int32, months: Int32, untilDate: Int32)
case messageMediaGiveaway(flags: Int32, channels: [Int64], countriesIso2: [String]?, prizeDescription: String?, quantity: Int32, months: Int32, untilDate: Int32)
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?)
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
@ -762,9 +762,9 @@ public extension Api {
serializeInt32(period, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)}
break
case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let quantity, let months, let untilDate):
case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let prizeDescription, let quantity, let months, let untilDate):
if boxed {
buffer.appendInt32(1478887012)
buffer.appendInt32(-626162256)
}
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
@ -777,6 +777,7 @@ public extension Api {
for item in countriesIso2! {
serializeString(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 3) != 0 {serializeString(prizeDescription!, buffer: buffer, boxed: false)}
serializeInt32(quantity, buffer: buffer, boxed: false)
serializeInt32(months, buffer: buffer, boxed: false)
serializeInt32(untilDate, buffer: buffer, boxed: false)
@ -862,8 +863,8 @@ public extension Api {
return ("messageMediaGeo", [("geo", geo as Any)])
case .messageMediaGeoLive(let flags, let geo, let heading, let period, let proximityNotificationRadius):
return ("messageMediaGeoLive", [("flags", flags as Any), ("geo", geo as Any), ("heading", heading as Any), ("period", period as Any), ("proximityNotificationRadius", proximityNotificationRadius as Any)])
case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let quantity, let months, let untilDate):
return ("messageMediaGiveaway", [("flags", flags as Any), ("channels", channels as Any), ("countriesIso2", countriesIso2 as Any), ("quantity", quantity as Any), ("months", months as Any), ("untilDate", untilDate as Any)])
case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let prizeDescription, let quantity, let months, let untilDate):
return ("messageMediaGiveaway", [("flags", flags as Any), ("channels", channels as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("quantity", quantity as Any), ("months", months as Any), ("untilDate", untilDate as Any)])
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia):
return ("messageMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("receiptMsgId", receiptMsgId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)])
case .messageMediaPhoto(let flags, let photo, let ttlSeconds):
@ -1007,20 +1008,23 @@ public extension Api {
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
} }
var _4: Int32?
_4 = reader.readInt32()
var _4: String?
if Int(_1!) & Int(1 << 3) != 0 {_4 = parseString(reader) }
var _5: Int32?
_5 = reader.readInt32()
var _6: Int32?
_6 = reader.readInt32()
var _7: Int32?
_7 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.MessageMedia.messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, quantity: _4!, months: _5!, untilDate: _6!)
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.MessageMedia.messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, prizeDescription: _4, quantity: _5!, months: _6!, untilDate: _7!)
}
else {
return nil

View File

@ -911,7 +911,7 @@ public extension Api.payments {
public extension Api.payments {
enum GiveawayInfo: TypeConstructorDescription {
case giveawayInfo(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?)
case giveawayInfoResults(flags: Int32, startDate: Int32, giftCodeSlug: String?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32)
case giveawayInfoResults(flags: Int32, startDate: Int32, giftCodeSlug: String?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32, winners: [Api.User]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -925,9 +925,9 @@ public extension Api.payments {
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(adminDisallowedChatId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {serializeString(disallowedCountry!, buffer: buffer, boxed: false)}
break
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount):
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount, let winners):
if boxed {
buffer.appendInt32(13456752)
buffer.appendInt32(-1966612121)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(startDate, buffer: buffer, boxed: false)
@ -935,6 +935,11 @@ public extension Api.payments {
serializeInt32(finishDate, buffer: buffer, boxed: false)
serializeInt32(winnersCount, buffer: buffer, boxed: false)
serializeInt32(activatedCount, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(winners!.count))
for item in winners! {
item.serialize(buffer, true)
}}
break
}
}
@ -943,8 +948,8 @@ public extension Api.payments {
switch self {
case .giveawayInfo(let flags, let startDate, let joinedTooEarlyDate, let adminDisallowedChatId, let disallowedCountry):
return ("giveawayInfo", [("flags", flags as Any), ("startDate", startDate as Any), ("joinedTooEarlyDate", joinedTooEarlyDate as Any), ("adminDisallowedChatId", adminDisallowedChatId as Any), ("disallowedCountry", disallowedCountry as Any)])
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount):
return ("giveawayInfoResults", [("flags", flags as Any), ("startDate", startDate as Any), ("giftCodeSlug", giftCodeSlug as Any), ("finishDate", finishDate as Any), ("winnersCount", winnersCount as Any), ("activatedCount", activatedCount as Any)])
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount, let winners):
return ("giveawayInfoResults", [("flags", flags as Any), ("startDate", startDate as Any), ("giftCodeSlug", giftCodeSlug as Any), ("finishDate", finishDate as Any), ("winnersCount", winnersCount as Any), ("activatedCount", activatedCount as Any), ("winners", winners as Any)])
}
}
@ -984,14 +989,19 @@ public extension Api.payments {
_5 = reader.readInt32()
var _6: Int32?
_6 = reader.readInt32()
var _7: [Api.User]?
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, finishDate: _4!, winnersCount: _5!, activatedCount: _6!)
let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, finishDate: _4!, winnersCount: _5!, activatedCount: _6!, winners: _7)
}
else {
return nil

View File

@ -136,7 +136,7 @@ public final class CallController: ViewController {
override public func loadDisplayNode() {
if self.sharedContext.immediateExperimentalUISettings.callUIV2 {
self.displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
self.displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), easyDebugAccess: self.easyDebugAccess, call: self.call)
} else {
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
}

View File

@ -29,8 +29,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
private let callScreen: PrivateCallScreen
private var callScreenState: PrivateCallScreen.State?
private var shouldStayHiddenUntilConnection: Bool = false
private var callStartTimestamp: Double?
private var callState: PresentationCallState?
@ -67,7 +65,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
presentationData: PresentationData,
statusBar: StatusBar,
debugInfo: Signal<(String, String), NoError>,
shouldStayHiddenUntilConnection: Bool = false,
easyDebugAccess: Bool,
call: PresentationCall
) {
@ -80,8 +77,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.containerView = UIView()
self.callScreen = PrivateCallScreen()
self.shouldStayHiddenUntilConnection = shouldStayHiddenUntilConnection
super.init()
self.view.addSubview(self.containerView)
@ -123,6 +118,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
}
self.back?()
}
self.callScreen.closeAction = { [weak self] in
guard let self else {
return
}
self.dismissedInteractively?()
}
self.callScreenState = PrivateCallScreen.State(
lifecycleState: .connecting,
@ -130,7 +131,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
shortName: " ",
avatarImage: nil,
audioOutput: .internalSpeaker,
isMicrophoneMuted: false,
isLocalAudioMuted: false,
isRemoteAudioMuted: false,
localVideo: nil,
remoteVideo: nil,
isRemoteBatteryLow: false
@ -145,8 +147,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
return
}
self.isMuted = isMuted
if callScreenState.isMicrophoneMuted != isMuted {
callScreenState.isMicrophoneMuted = isMuted
if callScreenState.isLocalAudioMuted != isMuted {
callScreenState.isLocalAudioMuted = isMuted
self.callScreenState = callScreenState
self.update(transition: .animated(duration: 0.3, curve: .spring))
}
@ -373,6 +375,13 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
callScreenState.isRemoteBatteryLow = false
}
switch callState.remoteAudioState {
case .muted:
callScreenState.isRemoteAudioMuted = true
case .active:
callScreenState.isRemoteAudioMuted = false
}
if self.callScreenState != callScreenState {
self.callScreenState = callScreenState
self.update(transition: .animated(duration: 0.35, curve: .spring))
@ -393,6 +402,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
return
}
callScreenState.name = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
callScreenState.shortName = peer.compactDisplayTitle
if self.currentPeer?.smallProfileImage != peer.smallProfileImage {
self.peerAvatarDisposable?.dispose()
@ -460,16 +470,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.containerView.layer.removeAnimation(forKey: "scale")
self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
if !self.shouldStayHiddenUntilConnection {
self.containerView.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3)
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
self.containerView.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3)
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
func animateOut(completion: @escaping () -> Void) {
self.statusBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
if !self.shouldStayHiddenUntilConnection || self.containerView.alpha > 0.0 {
if self.containerView.alpha > 0.0 {
self.containerView.layer.allowsGroupOpacity = true
self.containerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
self?.containerView.layer.allowsGroupOpacity = false
@ -499,7 +507,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
transition.updateFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(view: self.callScreen, frame: CGRect(origin: CGPoint(), size: layout.size))
if let callScreenState = self.callScreenState {
if var callScreenState = self.callScreenState {
if case .terminated = callScreenState.lifecycleState {
callScreenState.isLocalAudioMuted = false
callScreenState.isRemoteAudioMuted = false
callScreenState.isRemoteBatteryLow = false
callScreenState.localVideo = nil
callScreenState.remoteVideo = nil
}
self.callScreen.update(
size: layout.size,
insets: layout.insets(options: [.statusBar]),

View File

@ -422,12 +422,15 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
case let .messageMediaStory(flags, peerId, id, _):
let isMention = (flags & (1 << 1)) != 0
return (TelegramMediaStory(storyId: StoryId(peerId: peerId.peerId, id: id), isMention: isMention), nil, nil, nil, nil)
case let .messageMediaGiveaway(apiFlags, channels, countries, quantity, months, untilDate):
case let .messageMediaGiveaway(apiFlags, channels, countries, prizeDescription, quantity, months, untilDate):
var flags: TelegramMediaGiveaway.Flags = []
if (apiFlags & (1 << 0)) != 0 {
flags.insert(.onlyNewSubscribers)
}
return (TelegramMediaGiveaway(flags: flags, channelPeerIds: channels.map { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, countries: countries ?? [], quantity: quantity, months: months, untilDate: untilDate), nil, nil, nil, nil)
if (apiFlags & (1 << 2)) != 0 {
flags.insert(.showWinners)
}
return (TelegramMediaGiveaway(flags: flags, channelPeerIds: channels.map { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, countries: countries ?? [], quantity: quantity, months: months, untilDate: untilDate, prizeDescription: prizeDescription), nil, nil, nil, nil)
}
}

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 167
return 168
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -9,6 +9,7 @@ public final class TelegramMediaGiveaway: Media, Equatable {
}
public static let onlyNewSubscribers = Flags(rawValue: 1 << 0)
public static let showWinners = Flags(rawValue: 1 << 1)
}
public var id: MediaId? {
@ -24,14 +25,16 @@ public final class TelegramMediaGiveaway: Media, Equatable {
public let quantity: Int32
public let months: Int32
public let untilDate: Int32
public init(flags: Flags, channelPeerIds: [PeerId], countries: [String], quantity: Int32, months: Int32, untilDate: Int32) {
public let prizeDescription: String?
public init(flags: Flags, channelPeerIds: [PeerId], countries: [String], quantity: Int32, months: Int32, untilDate: Int32, prizeDescription: String?) {
self.flags = flags
self.channelPeerIds = channelPeerIds
self.countries = countries
self.quantity = quantity
self.months = months
self.untilDate = untilDate
self.prizeDescription = prizeDescription
}
public init(decoder: PostboxDecoder) {
@ -41,6 +44,7 @@ public final class TelegramMediaGiveaway: Media, Equatable {
self.quantity = decoder.decodeInt32ForKey("qty", orElse: 0)
self.months = decoder.decodeInt32ForKey("mts", orElse: 0)
self.untilDate = decoder.decodeInt32ForKey("unt", orElse: 0)
self.prizeDescription = decoder.decodeOptionalStringForKey("des")
}
public func encode(_ encoder: PostboxEncoder) {
@ -50,6 +54,11 @@ public final class TelegramMediaGiveaway: Media, Equatable {
encoder.encodeInt32(self.quantity, forKey: "qty")
encoder.encodeInt32(self.months, forKey: "mts")
encoder.encodeInt32(self.untilDate, forKey: "unt")
if let prizeDescription = self.prizeDescription {
encoder.encodeString(prizeDescription, forKey: "des")
} else {
encoder.encodeNil(forKey: "des")
}
}
public func isLikelyToBeUpdated() -> Bool {
@ -78,6 +87,9 @@ public final class TelegramMediaGiveaway: Media, Equatable {
if self.untilDate != other.untilDate {
return false
}
if self.prizeDescription != other.prizeDescription {
return false
}
return true
}

View File

@ -16,7 +16,7 @@ public enum AppStoreTransactionPurpose {
case restore
case gift(peerId: EnginePeer.Id, currency: String, amount: Int64)
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64)
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
}
private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
@ -59,7 +59,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount)
}
case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate, currency, amount):
case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount):
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return .complete()
@ -68,6 +68,9 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 0)
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
@ -80,7 +83,10 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
if !countries.isEmpty {
flags |= (1 << 2)
}
return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
if let _ = prizeDescription {
flags |= (1 << 4)
}
return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
}
|> switchToLatest
}

View File

@ -7,7 +7,7 @@ import TelegramApi
public enum BotPaymentInvoiceSource {
case message(MessageId)
case slug(String)
case premiumGiveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, option: PremiumGiftCodeOption)
case premiumGiveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, option: PremiumGiftCodeOption)
}
@ -214,7 +214,7 @@ private func _internal_parseInputInvoice(transaction: Transaction, source: BotPa
return .inputInvoiceMessage(peer: inputPeer, msgId: messageId.id)
case let .slug(slug):
return .inputInvoiceSlug(slug: slug)
case let .premiumGiveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate, currency, amount, option):
case let .premiumGiveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount, option):
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return nil
}
@ -222,6 +222,9 @@ private func _internal_parseInputInvoice(transaction: Transaction, source: BotPa
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 3)
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
@ -234,7 +237,11 @@ private func _internal_parseInputInvoice(transaction: Transaction, source: BotPa
if !countries.isEmpty {
flags |= (1 << 2)
}
let input: Api.InputStorePaymentPurpose = .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
if let _ = prizeDescription {
flags |= (1 << 4)
}
let inputPurpose: Api.InputStorePaymentPurpose = .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
flags = 0
@ -247,7 +254,7 @@ private func _internal_parseInputInvoice(transaction: Transaction, source: BotPa
let option: Api.PremiumGiftCodeOption = .premiumGiftCodeOption(flags: flags, users: option.users, months: option.months, storeProduct: option.storeProductId, storeQuantity: option.storeQuantity, currency: option.currency, amount: option.amount)
return .inputInvoicePremiumGiftCode(purpose: input, option: option)
return .inputInvoicePremiumGiftCode(purpose: inputPurpose, option: option)
}
}
@ -526,7 +533,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa
}
}
}
case let .premiumGiveaway(_, _, _, _, randomId, _, _, _, _):
case let .premiumGiveaway(_, _, _, _, _, _, randomId, _, _, _, _):
if message.globallyUniqueId == randomId {
if case let .Id(id) = message.id {
receiptMessageId = id

View File

@ -120,7 +120,7 @@ func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, m
} else {
return .ongoing(startDate: startDate, status: .notQualified)
}
case let .giveawayInfoResults(flags, startDate, giftCodeSlug, finishDate, winnersCount, activatedCount):
case let .giveawayInfoResults(flags, startDate, giftCodeSlug, finishDate, winnersCount, activatedCount, _):
let status: PremiumGiveawayInfo.ResultStatus
if (flags & (1 << 1)) != 0 {
status = .refunded
@ -201,18 +201,19 @@ public enum LaunchPrepaidGiveawayError {
case generic
}
func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, LaunchPrepaidGiveawayError> {
func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32) -> Signal<Never, LaunchPrepaidGiveawayError> {
return account.postbox.transaction { transaction -> Signal<Never, LaunchPrepaidGiveawayError> in
var flags: Int32 = 0
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 3)
}
var inputPeer: Api.InputPeer?
if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) {
inputPeer = apiPeer
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
@ -222,15 +223,16 @@ func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, id
}
}
}
if !countries.isEmpty {
flags |= (1 << 2)
}
if let _ = prizeDescription {
flags |= (1 << 4)
}
guard let inputPeer = inputPeer else {
return .complete()
}
return account.network.request(Api.functions.payments.launchPrepaidGiveaway(peer: inputPeer, giveawayId: id, purpose: .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: inputPeer, additionalPeers: additionalPeers, countriesIso2: countries, randomId: randomId, untilDate: untilDate, currency: "", amount: 0)))
return account.network.request(Api.functions.payments.launchPrepaidGiveaway(peer: inputPeer, giveawayId: id, purpose: .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: inputPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: "", amount: 0)))
|> mapError { _ -> LaunchPrepaidGiveawayError in
return .generic
}

View File

@ -62,8 +62,8 @@ public extension TelegramEngine {
return _internal_getPremiumGiveawayInfo(account: self.account, peerId: peerId, messageId: messageId)
}
public func launchPrepaidGiveaway(peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, LaunchPrepaidGiveawayError> {
return _internal_launchPrepaidGiveaway(account: self.account, peerId: peerId, id: id, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
public func launchPrepaidGiveaway(peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32) -> Signal<Never, LaunchPrepaidGiveawayError> {
return _internal_launchPrepaidGiveaway(account: self.account, peerId: peerId, id: id, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate)
}
}
}

View File

@ -3,7 +3,11 @@ import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
import LinkPresentation
#if os(iOS)
import UIKit
#endif
import CoreServices
public enum WebpagePreviewResult: Equatable {
public struct Result: Equatable {
@ -15,8 +19,8 @@ public enum WebpagePreviewResult: Equatable {
case result(Result?)
}
public func webpagePreview(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal<WebpagePreviewResult, NoError> {
return webpagePreviewWithProgress(account: account, urls: urls)
public func webpagePreview(account: Account, urls: [String], webpageId: MediaId? = nil, forPeerId: PeerId? = nil) -> Signal<WebpagePreviewResult, NoError> {
return webpagePreviewWithProgress(account: account, urls: urls, webpageId: webpageId, forPeerId: forPeerId)
|> mapToSignal { next -> Signal<WebpagePreviewResult, NoError> in
if case let .result(result) = next {
return .single(.result(result))
@ -35,7 +39,7 @@ public func normalizedWebpagePreviewUrl(url: String) -> String {
return url
}
public func webpagePreviewWithProgress(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal<WebpagePreviewWithProgressResult, NoError> {
public func webpagePreviewWithProgress(account: Account, urls: [String], webpageId: MediaId? = nil, forPeerId: PeerId? = nil) -> Signal<WebpagePreviewWithProgressResult, NoError> {
return account.postbox.transaction { transaction -> Signal<WebpagePreviewWithProgressResult, NoError> in
if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage, let url = webpage.content.url {
var sourceUrl = url
@ -44,6 +48,108 @@ public func webpagePreviewWithProgress(account: Account, urls: [String], webpage
}
return .single(.result(WebpagePreviewResult.Result(webpage: webpage, sourceUrl: sourceUrl)))
} else {
if #available(iOS 13.0, *) {
if let forPeerId, forPeerId.namespace == Namespaces.Peer.SecretChat, let sourceUrl = urls.first, let url = URL(string: sourceUrl) {
let localHosts: [String] = [
"twitter.com",
"www.twitter.com",
"instagram.com",
"www.instagram.com",
"tiktok.com",
"www.tiktok.com"
]
if let host = url.host?.lowercased(), localHosts.contains(host) {
return Signal { subscriber in
subscriber.putNext(.progress(0.0))
let metadataProvider = LPMetadataProvider()
metadataProvider.shouldFetchSubresources = true
metadataProvider.startFetchingMetadata(for: url, completionHandler: { metadata, _ in
if let metadata = metadata {
let completeWithImage: (Data?) -> Void = { imageData in
var image: TelegramMediaImage?
if let imageData, let parsedImage = UIImage(data: imageData) {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
account.postbox.mediaBox.storeResourceData(resource.id, data: imageData)
image = TelegramMediaImage(
imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)),
representations: [
TelegramMediaImageRepresentation(
dimensions: PixelDimensions(width: Int32(parsedImage.size.width), height: Int32(parsedImage.size.height)),
resource: resource,
progressiveSizes: [],
immediateThumbnailData: nil,
hasVideo: false,
isPersonal: false
)
],
immediateThumbnailData: nil,
reference: nil,
partialReference: nil,
flags: []
)
}
var webpageType: String?
if image != nil {
webpageType = "photo"
}
let webpage = TelegramMediaWebpage(
webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: Int64.random(in: Int64.min ... Int64.max)),
content: .Loaded(TelegramMediaWebpageLoadedContent(
url: sourceUrl,
displayUrl: metadata.url?.absoluteString ?? sourceUrl,
hash: 0,
type: webpageType,
websiteName: nil,
title: metadata.title,
text: metadata.value(forKey: "_summary") as? String,
embedUrl: nil,
embedType: nil,
embedSize: nil,
duration: nil,
author: nil,
isMediaLargeByDefault: true,
image: image,
file: nil,
story: nil,
attributes: [],
instantPage: nil
))
)
subscriber.putNext(.result(WebpagePreviewResult.Result(
webpage: webpage,
sourceUrl: sourceUrl
)))
subscriber.putCompletion()
}
if let imageProvider = metadata.imageProvider {
imageProvider.loadFileRepresentation(forTypeIdentifier: kUTTypeImage as String, completionHandler: { imageUrl, _ in
guard let imageUrl, let imageData = try? Data(contentsOf: imageUrl) else {
completeWithImage(nil)
return
}
completeWithImage(imageData)
})
} else {
completeWithImage(nil)
}
} else {
subscriber.putNext(.result(nil))
subscriber.putCompletion()
}
})
return ActionDisposable {
metadataProvider.cancel()
}
}
}
}
}
return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: urls.joined(separator: " "), entities: nil), info: .progress)
|> `catch` { _ -> Signal<NetworkRequestResult<Api.MessageMedia>, NoError> in
return .single(.result(.messageMediaEmpty))

View File

@ -67,6 +67,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/AppBundle",
"//submodules/UIKitRuntimeUtils",
],
visibility = [
"//visibility:public",

View File

@ -58,8 +58,10 @@ final class ButtonGroupView: OverlayMaskContainerView {
private var buttons: [Button]?
private var buttonViews: [Button.Content.Key: ContentOverlayButton] = [:]
private var noticeViews: [AnyHashable: NoticeView] = [:]
private var closeButtonView: CloseButtonView?
var closePressed: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
@ -79,7 +81,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
return result
}
func update(size: CGSize, insets: UIEdgeInsets, controlsHidden: Bool, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat {
func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat {
self.buttons = buttons
let buttonSize: CGFloat = 56.0
@ -163,6 +165,46 @@ final class ButtonGroupView: OverlayMaskContainerView {
}
var buttonX: CGFloat = floor((size.width - buttonSize * CGFloat(buttons.count) - buttonSpacing * CGFloat(buttons.count - 1)) * 0.5)
if displayClose {
let closeButtonView: CloseButtonView
var closeButtonTransition = transition
var animateIn = false
if let current = self.closeButtonView {
closeButtonView = current
} else {
closeButtonTransition = closeButtonTransition.withAnimation(.none)
animateIn = true
closeButtonView = CloseButtonView()
self.closeButtonView = closeButtonView
self.addSubview(closeButtonView)
closeButtonView.pressAction = { [weak self] in
guard let self else {
return
}
self.closePressed?()
}
}
let closeButtonSize = CGSize(width: minWidth, height: buttonSize)
closeButtonView.update(text: "Close", size: closeButtonSize, transition: closeButtonTransition)
closeButtonTransition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: floor((size.width - closeButtonSize.width) * 0.5), y: buttonY), size: closeButtonSize))
if animateIn && !transition.animation.isImmediate {
closeButtonView.animateIn()
}
} else {
if let closeButtonView = self.closeButtonView {
self.closeButtonView = nil
if !transition.animation.isImmediate {
closeButtonView.animateOut(completion: { [weak closeButtonView] in
closeButtonView?.removeFromSuperview()
})
} else {
closeButtonView.removeFromSuperview()
}
}
}
for button in buttons {
let title: String
let image: UIImage?
@ -213,9 +255,10 @@ final class ButtonGroupView: OverlayMaskContainerView {
Transition.immediate.setScale(view: buttonView, scale: 0.001)
buttonView.alpha = 0.0
transition.setScale(view: buttonView, scale: 1.0)
transition.setAlpha(view: buttonView, alpha: 1.0)
}
transition.setAlpha(view: buttonView, alpha: displayClose ? 0.0 : 1.0)
buttonTransition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonX, y: buttonY), size: CGSize(width: buttonSize, height: buttonSize)))
buttonView.update(size: CGSize(width: buttonSize, height: buttonSize), image: image, isSelected: isActive, isDestructive: isDestructive, title: title, transition: buttonTransition)
buttonX += buttonSize + buttonSpacing

View File

@ -0,0 +1,185 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import UIKitRuntimeUtils
final class CloseButtonView: HighlightTrackingButton, OverlayMaskContainerViewProtocol {
private struct Params: Equatable {
var text: String
var size: CGSize
init(text: String, size: CGSize) {
self.text = text
self.size = size
}
}
private let backdropBackgroundView: RoundedCornersView
private let backgroundView: RoundedCornersView
private let backgroundMaskView: UIView
private let backgroundClippingView: UIView
private let duration: Double = 5.0
private var fillTime: Double = 0.0
private let backgroundTextView: TextView
private let backgroundTextClippingView: UIView
private let textView: TextView
var pressAction: (() -> Void)?
private var params: Params?
private var updateDisplayLink: SharedDisplayLinkDriver.Link?
let maskContents: UIView
override static var layerClass: AnyClass {
return MirroringLayer.self
}
override init(frame: CGRect) {
self.backdropBackgroundView = RoundedCornersView(color: .white, smoothCorners: true)
self.backdropBackgroundView.update(cornerRadius: 12.0, transition: .immediate)
self.backgroundView = RoundedCornersView(color: .white, smoothCorners: true)
self.backgroundView.update(cornerRadius: 12.0, transition: .immediate)
self.backgroundView.isUserInteractionEnabled = false
self.backgroundMaskView = UIView()
self.backgroundMaskView.backgroundColor = .white
self.backgroundView.mask = self.backgroundMaskView
if let filter = makeLuminanceToAlphaFilter() {
self.backgroundMaskView.layer.filters = [filter]
}
self.backgroundClippingView = UIView()
self.backgroundClippingView.clipsToBounds = true
self.backgroundClippingView.layer.cornerRadius = 12.0
self.backgroundTextClippingView = UIView()
self.backgroundTextClippingView.clipsToBounds = true
self.backgroundTextView = TextView()
self.textView = TextView()
self.maskContents = UIView()
self.maskContents.addSubview(self.backdropBackgroundView)
super.init(frame: frame)
(self.layer as? MirroringLayer)?.targetLayer = self.maskContents.layer
self.backgroundTextClippingView.addSubview(self.backgroundTextView)
self.backgroundTextClippingView.isUserInteractionEnabled = false
self.addSubview(self.backgroundTextClippingView)
self.backgroundClippingView.addSubview(self.backgroundView)
self.backgroundClippingView.isUserInteractionEnabled = false
self.addSubview(self.backgroundClippingView)
self.backgroundMaskView.addSubview(self.textView)
self.internalHighligthedChanged = { [weak self] highlighted in
if let self, self.bounds.width > 0.0 {
let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
if highlighted {
self.layer.removeAnimation(forKey: "sublayerTransform")
let transition = Transition(animation: .curve(duration: 0.15, curve: .easeInOut))
transition.setScale(layer: self.layer, scale: topScale)
} else {
let t = self.layer.presentation()?.transform ?? layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
let transition = Transition(animation: .none)
transition.setScale(layer: self.layer, scale: 1.0)
self.layer.animateScale(from: currentScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] completed in
guard let self, completed else {
return
}
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
})
}
}
}
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
(self.layer as? MirroringLayer)?.didEnterHierarchy = { [weak self] in
guard let self else {
return
}
if self.fillTime < self.duration && self.updateDisplayLink == nil {
self.updateDisplayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
guard let self else {
return
}
self.fillTime = min(self.duration, self.fillTime + deltaTime)
if let params = self.params {
self.update(params: params, transition: .immediate)
}
if self.fillTime >= self.duration {
self.updateDisplayLink = nil
}
})
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
self.pressAction?()
}
func animateIn() {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
func animateOut(completion: @escaping () -> Void) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completion()
})
}
func update(text: String, size: CGSize, transition: Transition) {
let params = Params(text: text, size: size)
if self.params == params {
return
}
self.params = params
self.update(params: params, transition: transition)
}
private func update(params: Params, transition: Transition) {
let fillFraction: CGFloat = CGFloat(self.fillTime / self.duration)
let sideInset: CGFloat = 12.0
let textSize = self.textView.update(string: params.text, fontSize: 17.0, fontWeight: UIFont.Weight.semibold.rawValue, color: .black, constrainedWidth: params.size.width - sideInset * 2.0, transition: .immediate)
let _ = self.backgroundTextView.update(string: params.text, fontSize: 17.0, fontWeight: UIFont.Weight.semibold.rawValue, color: .white, constrainedWidth: params.size.width - sideInset * 2.0, transition: .immediate)
transition.setFrame(view: self.backdropBackgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size))
transition.setFrame(view: self.backgroundMaskView, frame: CGRect(origin: CGPoint(), size: params.size))
let progressWidth: CGFloat = max(0.0, min(params.size.width, floorToScreenPixels(fillFraction * params.size.width)))
let backgroundClippingFrame = CGRect(origin: CGPoint(x: progressWidth, y: 0.0), size: CGSize(width: params.size.width - progressWidth, height: params.size.height))
transition.setPosition(view: self.backgroundClippingView, position: backgroundClippingFrame.center)
transition.setBounds(view: self.backgroundClippingView, bounds: CGRect(origin: CGPoint(x: backgroundClippingFrame.minX, y: 0.0), size: backgroundClippingFrame.size))
let backgroundTextClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: progressWidth, height: params.size.height))
transition.setPosition(view: self.backgroundTextClippingView, position: backgroundTextClippingFrame.center)
transition.setBounds(view: self.backgroundTextClippingView, bounds: CGRect(origin: CGPoint(), size: backgroundTextClippingFrame.size))
let textFrame = CGRect(origin: CGPoint(x: floor((params.size.width - textSize.width) * 0.5), y: floor((params.size.height - textSize.height) * 0.5)), size: textSize)
transition.setFrame(view: self.textView, frame: textFrame)
transition.setFrame(view: self.backgroundTextView, frame: textFrame)
}
}

View File

@ -63,8 +63,7 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
if highlighted {
self.layer.removeAnimation(forKey: "opacity")
self.layer.removeAnimation(forKey: "transform")
self.layer.removeAnimation(forKey: "sublayerTransform")
let transition = Transition(animation: .curve(duration: 0.15, curve: .easeInOut))
transition.setScale(layer: self.layer, scale: topScale)
} else {

View File

@ -5,10 +5,10 @@ import ComponentFlow
final class EmojiExpandedInfoView: OverlayMaskContainerView {
private struct Params: Equatable {
var constrainedWidth: CGFloat
var width: CGFloat
init(constrainedWidth: CGFloat) {
self.constrainedWidth = constrainedWidth
init(width: CGFloat) {
self.width = width
}
}
@ -129,8 +129,8 @@ final class EmojiExpandedInfoView: OverlayMaskContainerView {
return nil
}
func update(constrainedWidth: CGFloat, transition: Transition) -> CGSize {
let params = Params(constrainedWidth: constrainedWidth)
func update(width: CGFloat, transition: Transition) -> CGSize {
let params = Params(width: width)
if let currentLayout = self.currentLayout, currentLayout.params == params {
return currentLayout.size
}
@ -142,16 +142,12 @@ final class EmojiExpandedInfoView: OverlayMaskContainerView {
private func update(params: Params, transition: Transition) -> CGSize {
let buttonHeight: CGFloat = 56.0
var constrainedWidth = params.constrainedWidth
constrainedWidth = min(constrainedWidth, 300.0)
let titleSize = self.titleView.update(string: self.title, fontSize: 16.0, fontWeight: 0.3, alignment: .center, color: .white, constrainedWidth: params.width - 16.0 * 2.0, transition: transition)
let textSize = self.textView.update(string: self.text, fontSize: 16.0, fontWeight: 0.0, alignment: .center, color: .white, constrainedWidth: params.width - 16.0 * 2.0, transition: transition)
let titleSize = self.titleView.update(string: self.title, fontSize: 16.0, fontWeight: 0.3, alignment: .center, color: .white, constrainedWidth: constrainedWidth - 16.0 * 2.0, transition: transition)
let textSize = self.textView.update(string: self.text, fontSize: 16.0, fontWeight: 0.0, alignment: .center, color: .white, constrainedWidth: constrainedWidth - 16.0 * 2.0, transition: transition)
let contentWidth: CGFloat = max(titleSize.width, textSize.width) + 26.0 * 2.0
let contentHeight = 78.0 + titleSize.height + 10.0 + textSize.height + 22.0 + buttonHeight
let size = CGSize(width: contentWidth, height: contentHeight)
let size = CGSize(width: params.width, height: contentHeight)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))

View File

@ -198,8 +198,8 @@ final class PrivateCallVideoLayer: MetalEngineSubjectLayer, MetalEngineSubject {
encoder.setFragmentTexture(blurredTexture, index: 0)
var brightness: Float = 1.0
var saturation: Float = 1.2
var brightness: Float = 0.7
var saturation: Float = 1.3
var overlay: SIMD4<Float> = SIMD4<Float>(1.0, 1.0, 1.0, 0.2)
encoder.setFragmentBytes(&brightness, length: 4, index: 0)
encoder.setFragmentBytes(&saturation, length: 4, index: 1)

View File

@ -0,0 +1,73 @@
import Foundation
import UIKit
import Display
import ComponentFlow
final class RatingView: OverlayMaskContainerView {
private let backgroundView: RoundedCornersView
private let textContainer: UIView
private let textView: TextView
override init(frame: CGRect) {
self.backgroundView = RoundedCornersView(color: .white)
self.textContainer = UIView()
self.textContainer.clipsToBounds = true
self.textView = TextView()
super.init(frame: frame)
self.clipsToBounds = true
self.maskContents.addSubview(self.backgroundView)
self.textContainer.addSubview(self.textView)
self.addSubview(self.textContainer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateIn() {
let delay: Double = 0.2
self.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.textView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
self.backgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.backgroundView.layer.animateFrame(from: CGRect(origin: CGPoint(x: (self.bounds.width - self.bounds.height) * 0.5, y: 0.0), size: CGSize(width: self.bounds.height, height: self.bounds.height)), to: self.backgroundView.frame, duration: 0.5, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
self.textContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
self.textContainer.layer.cornerRadius = self.bounds.height * 0.5
self.textContainer.layer.animateFrame(from: CGRect(origin: CGPoint(x: (self.bounds.width - self.bounds.height) * 0.5, y: 0.0), size: CGSize(width: self.bounds.height, height: self.bounds.height)), to: self.textContainer.frame, duration: 0.5, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] completed in
guard let self, completed else {
return
}
self.textContainer.layer.cornerRadius = 0.0
})
}
func animateOut(completion: @escaping () -> Void) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completion()
})
self.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
}
func update(text: String, constrainedWidth: CGFloat, transition: Transition) -> CGSize {
let sideInset: CGFloat = 12.0
let verticalInset: CGFloat = 6.0
let textSize = self.textView.update(string: text, fontSize: 15.0, fontWeight: 0.0, color: .white, constrainedWidth: constrainedWidth - sideInset * 2.0, transition: .immediate)
let size = CGSize(width: textSize.width + sideInset * 2.0, height: textSize.height + verticalInset * 2.0)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundView.update(cornerRadius: floor(size.height * 0.5), transition: transition)
transition.setFrame(view: self.textContainer, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: self.textView, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: textSize))
return size
}
}

View File

@ -5,17 +5,16 @@ import ComponentFlow
final class RoundedCornersView: UIImageView {
private let color: UIColor
private let smoothCorners: Bool
private var currentCornerRadius: CGFloat?
private var cornerImage: UIImage?
init(color: UIColor) {
init(color: UIColor, smoothCorners: Bool = false) {
self.color = color
self.smoothCorners = smoothCorners
super.init(image: nil)
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .circular
}
}
required init?(coder: NSCoder) {
@ -26,10 +25,23 @@ final class RoundedCornersView: UIImageView {
guard let cornerRadius = self.currentCornerRadius else {
return
}
if let cornerImage = self.cornerImage, cornerImage.size.height == cornerRadius * 2.0 {
if self.smoothCorners {
let size = CGSize(width: cornerRadius * 2.0 + 10.0, height: cornerRadius * 2.0 + 10.0)
if let cornerImage = self.cornerImage, cornerImage.size == size {
} else {
self.cornerImage = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: cornerRadius).cgPath)
context.setFillColor(self.color.cgColor)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: Int(cornerRadius) + 5, topCapHeight: Int(cornerRadius) + 5)
}
} else {
let size = CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0)
self.cornerImage = generateStretchableFilledCircleImage(diameter: size.width, color: self.color)
if let cornerImage = self.cornerImage, cornerImage.size == size {
} else {
self.cornerImage = generateStretchableFilledCircleImage(diameter: size.width, color: self.color)
}
}
self.image = self.cornerImage
self.clipsToBounds = false
@ -52,6 +64,14 @@ final class RoundedCornersView: UIImageView {
if let previousCornerRadius, self.layer.animation(forKey: "cornerRadius") == nil {
self.layer.cornerRadius = previousCornerRadius
}
if #available(iOS 13.0, *) {
if self.smoothCorners {
self.layer.cornerCurve = .continuous
} else {
self.layer.cornerCurve = .circular
}
}
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius, completion: { [weak self] completed in
guard let self, completed else {
return

View File

@ -100,7 +100,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
public var shortName: String
public var avatarImage: UIImage?
public var audioOutput: AudioOutput
public var isMicrophoneMuted: Bool
public var isLocalAudioMuted: Bool
public var isRemoteAudioMuted: Bool
public var localVideo: VideoSource?
public var remoteVideo: VideoSource?
public var isRemoteBatteryLow: Bool
@ -111,7 +112,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
shortName: String,
avatarImage: UIImage?,
audioOutput: AudioOutput,
isMicrophoneMuted: Bool,
isLocalAudioMuted: Bool,
isRemoteAudioMuted: Bool,
localVideo: VideoSource?,
remoteVideo: VideoSource?,
isRemoteBatteryLow: Bool
@ -121,7 +123,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.shortName = shortName
self.avatarImage = avatarImage
self.audioOutput = audioOutput
self.isMicrophoneMuted = isMicrophoneMuted
self.isLocalAudioMuted = isLocalAudioMuted
self.isRemoteAudioMuted = isRemoteAudioMuted
self.localVideo = localVideo
self.remoteVideo = remoteVideo
self.isRemoteBatteryLow = isRemoteBatteryLow
@ -143,7 +146,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
if lhs.audioOutput != rhs.audioOutput {
return false
}
if lhs.isMicrophoneMuted != rhs.isMicrophoneMuted {
if lhs.isLocalAudioMuted != rhs.isLocalAudioMuted {
return false
}
if lhs.isRemoteAudioMuted != rhs.isRemoteAudioMuted {
return false
}
if lhs.localVideo !== rhs.localVideo {
@ -224,6 +230,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
public var microhoneMuteAction: (() -> Void)?
public var endCallAction: (() -> Void)?
public var backAction: (() -> Void)?
public var closeAction: (() -> Void)?
public override init(frame: CGRect) {
self.overlayContentsView = UIView()
@ -264,10 +271,6 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.avatarTransformLayer.addSublayer(self.avatarLayer)
self.layer.addSublayer(self.avatarTransformLayer)
/*let edgeTestLayer = EdgeTestLayer()
edgeTestLayer.frame = CGRect(origin: CGPoint(x: 20.0, y: 100.0), size: CGSize(width: 100.0, height: 100.0))
self.layer.addSublayer(edgeTestLayer)*/
self.addSubview(self.videoContainerBackgroundView)
self.overlayContentsView.mask = self.maskContents
@ -310,6 +313,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
self.backAction?()
}
self.buttonGroupView.closePressed = { [weak self] in
guard let self else {
return
}
self.closeAction?()
}
}
public required init?(coder: NSCoder) {
@ -497,6 +507,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
let backgroundFrame = CGRect(origin: CGPoint(), size: params.size)
let wideContentWidth: CGFloat
if params.size.width < 500.0 {
wideContentWidth = params.size.width - 44.0 * 2.0
} else {
wideContentWidth = 400.0
}
var activeVideoSources: [(VideoContainerView.Key, VideoSource)] = []
if self.swapLocalAndRemoteVideo {
if let activeLocalVideoSource = self.activeLocalVideoSource {
@ -554,7 +571,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
self.videoAction?()
}),
ButtonGroupView.Button(content: .microphone(isMuted: params.state.isMicrophoneMuted), action: { [weak self] in
ButtonGroupView.Button(content: .microphone(isMuted: params.state.isLocalAudioMuted), action: { [weak self] in
guard let self else {
return
}
@ -584,9 +601,12 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
var notices: [ButtonGroupView.Notice] = []
if params.state.isMicrophoneMuted {
if params.state.isLocalAudioMuted {
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "Your microphone is turned off"))
}
if params.state.isRemoteAudioMuted {
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "\(params.state.shortName)'s microphone is turned off"))
}
if params.state.remoteVideo != nil && params.state.localVideo == nil {
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), text: "Your camera is turned off"))
}
@ -594,7 +614,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), text: "\(params.state.shortName)'s battery is low"))
}
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, controlsHidden: currentAreControlsHidden, buttons: buttons, notices: notices, transition: transition)
var displayClose = false
if case .terminated = params.state.lifecycleState {
displayClose = true
}
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, buttons: buttons, notices: notices, transition: transition)
var expandedEmojiKeyRect: CGRect?
if self.isEmojiKeyExpanded {
@ -632,7 +656,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
}
let emojiExpandedInfoSize = emojiExpandedInfoView.update(constrainedWidth: params.size.width - (params.insets.left + 16.0) * 2.0, transition: emojiExpandedInfoTransition)
let emojiExpandedInfoSize = emojiExpandedInfoView.update(width: wideContentWidth, transition: emojiExpandedInfoTransition)
let emojiExpandedInfoFrame = CGRect(origin: CGPoint(x: floor((params.size.width - emojiExpandedInfoSize.width) * 0.5), y: params.insets.top + 73.0), size: emojiExpandedInfoSize)
emojiExpandedInfoTransition.setPosition(view: emojiExpandedInfoView, position: CGPoint(x: emojiExpandedInfoFrame.minX + emojiExpandedInfoView.layer.anchorPoint.x * emojiExpandedInfoFrame.width, y: emojiExpandedInfoFrame.minY + emojiExpandedInfoView.layer.anchorPoint.y * emojiExpandedInfoFrame.height))
emojiExpandedInfoTransition.setBounds(view: emojiExpandedInfoView, bounds: CGRect(origin: CGPoint(), size: emojiExpandedInfoFrame.size))

View File

@ -33,6 +33,9 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
private let prizeTitleNode: TextNode
private let prizeTextNode: TextNode
private let additionalPrizeTitleNode: TextNode
private let additionalPrizeTextNode: TextNode
private let participantsTitleNode: TextNode
private let participantsTextNode: TextNode
@ -81,6 +84,9 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
self.prizeTitleNode = TextNode()
self.prizeTextNode = TextNode()
self.additionalPrizeTitleNode = TextNode()
self.additionalPrizeTextNode = TextNode()
self.participantsTitleNode = TextNode()
self.participantsTextNode = TextNode()
@ -101,6 +107,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
self.addSubnode(self.prizeTitleNode)
self.addSubnode(self.prizeTextNode)
self.addSubnode(self.additionalPrizeTitleNode)
self.addSubnode(self.additionalPrizeTextNode)
self.addSubnode(self.participantsTitleNode)
self.addSubnode(self.participantsTextNode)
self.addSubnode(self.countriesTextNode)
@ -181,6 +189,9 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let makePrizeTitleLayout = TextNode.asyncLayout(self.prizeTitleNode)
let makePrizeTextLayout = TextNode.asyncLayout(self.prizeTextNode)
let makeAdditionalPrizeTitleLayout = TextNode.asyncLayout(self.additionalPrizeTitleNode)
let makeAdditionalPrizeTextLayout = TextNode.asyncLayout(self.additionalPrizeTextNode)
let makeParticipantsTitleLayout = TextNode.asyncLayout(self.participantsTitleNode)
let makeParticipantsTextLayout = TextNode.asyncLayout(self.participantsTextNode)
@ -232,6 +243,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let prizeTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_PrizeTitle, font: titleFont, textColor: textColor)
var prizeTextString: NSAttributedString?
var additionalPrizeTitleString: NSAttributedString?
var additionalPrizeTextString: NSAttributedString?
if let giveaway {
prizeTextString = parseMarkdownIntoAttributedString(item.presentationData.strings.Chat_Giveaway_Message_PrizeText(
item.presentationData.strings.Chat_Giveaway_Message_Subscriptions(giveaway.quantity),
@ -244,6 +257,11 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
return ("URL", url)
}
), textAlignment: .center)
if let prizeDescription = giveaway.prizeDescription {
additionalPrizeTitleString = NSAttributedString(string: "Additional Prize", font: titleFont, textColor: textColor)
additionalPrizeTextString = NSAttributedString(string: prizeDescription, font: textFont, textColor: textColor)
}
}
let participantsTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_ParticipantsTitle, font: titleFont, textColor: textColor)
@ -316,6 +334,10 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let (prizeTextLayout, prizeTextApply) = makePrizeTextLayout(TextNodeLayoutArguments(attributedString: prizeTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (additionalPrizeTitleLayout, additionalPrizeTitleApply) = makeAdditionalPrizeTitleLayout(TextNodeLayoutArguments(attributedString: additionalPrizeTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (additionalPrizeTextLayout, additionalPrizeTextApply) = makeAdditionalPrizeTextLayout(TextNodeLayoutArguments(attributedString: additionalPrizeTextString, backgroundColor: nil, maximumNumberOfLines: 6, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (participantsTitleLayout, participantsTitleApply) = makeParticipantsTitleLayout(TextNodeLayoutArguments(attributedString: participantsTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (participantsTextLayout, participantsTextApply) = makeParticipantsTextLayout(TextNodeLayoutArguments(attributedString: participantsTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
@ -423,6 +445,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
}
maxContentWidth = max(maxContentWidth, prizeTitleLayout.size.width)
maxContentWidth = max(maxContentWidth, prizeTextLayout.size.width)
maxContentWidth = max(maxContentWidth, additionalPrizeTitleLayout.size.width)
maxContentWidth = max(maxContentWidth, additionalPrizeTextLayout.size.width)
maxContentWidth = max(maxContentWidth, participantsTitleLayout.size.width)
maxContentWidth = max(maxContentWidth, participantsTextLayout.size.width)
maxContentWidth = max(maxContentWidth, dateTitleLayout.size.width)
@ -453,6 +477,9 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
var layoutSize = CGSize(width: boundingWidth, height: 49.0 + prizeTitleLayout.size.height + prizeTextLayout.size.height + participantsTitleLayout.size.height + participantsTextLayout.size.height + dateTitleLayout.size.height + dateTextLayout.size.height + buttonSize.height + buttonSpacing + 120.0)
if additionalPrizeTextLayout.size.height > 0.0 {
layoutSize.height += additionalPrizeTitleLayout.size.height + additionalPrizeTextLayout.size.height + 7.0
}
if countriesTextLayout.size.height > 0.0 {
layoutSize.height += countriesTextLayout.size.height + 7.0
}
@ -473,10 +500,12 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
strongSelf.giveaway = giveaway
strongSelf.updateVisibility()
let _ = badgeTextApply()
let _ = prizeTitleApply()
let _ = prizeTextApply()
let _ = additionalPrizeTitleApply()
let _ = additionalPrizeTextApply()
let _ = participantsTitleApply()
let _ = participantsTextApply()
let _ = countriesTextApply()
@ -502,12 +531,19 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
}
originY += 112.0
strongSelf.prizeTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - prizeTitleLayout.size.width) / 2.0), y: originY), size: prizeTitleLayout.size)
originY += prizeTitleLayout.size.height + smallSpacing
strongSelf.prizeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - prizeTextLayout.size.width) / 2.0), y: originY), size: prizeTextLayout.size)
originY += prizeTextLayout.size.height + largeSpacing
if additionalPrizeTextLayout.size.height > 0.0 {
strongSelf.additionalPrizeTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - additionalPrizeTitleLayout.size.width) / 2.0), y: originY), size: additionalPrizeTitleLayout.size)
originY += additionalPrizeTitleLayout.size.height + smallSpacing
strongSelf.additionalPrizeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - additionalPrizeTextLayout.size.width) / 2.0), y: originY), size: additionalPrizeTextLayout.size)
originY += additionalPrizeTextLayout.size.height + largeSpacing
}
strongSelf.participantsTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTitleLayout.size.width) / 2.0), y: originY), size: participantsTitleLayout.size)
originY += participantsTitleLayout.size.height + smallSpacing
strongSelf.participantsTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTextLayout.size.width) / 2.0), y: originY), size: participantsTextLayout.size)

View File

@ -445,6 +445,15 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
(self.micLockValue as? LockView)?.updateTheme(theme)
}
public override func createLockPanelView() -> UIView! {
if self.hidesOnLock {
let view = WrapperBlurrredBackgroundView(frame: CGRect(origin: .zero, size: CGSize(width: 40.0, height: 72.0)))
return view
} else {
return super.createLockPanelView()
}
}
public func cancelRecording() {
self.isEnabled = false
self.isEnabled = true
@ -572,3 +581,31 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
}
}
}
private class WrapperBlurrredBackgroundView: UIView {
let view: BlurredBackgroundView
override init(frame: CGRect) {
let view = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.5), enableBlur: true)
view.frame = CGRect(origin: .zero, size: frame.size)
view.update(size: frame.size, cornerRadius: frame.width / 2.0, transition: .immediate)
self.view = view
super.init(frame: frame)
self.addSubview(view)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var frame: CGRect {
get {
return super.frame
} set {
super.frame = newValue
self.view.update(size: newValue.size, cornerRadius: newValue.width / 2.0, transition: .immediate)
}
}
}

View File

@ -65,6 +65,7 @@ final class LockView: UIButton, TGModernConversationInputMicButtonLock {
[
"Rectangle.Заливка 1": theme.chat.inputPanel.panelBackgroundColor,
"Rectangle.Rectangle.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Rectangle 2.Rectangle.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Path.Path.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Path 4.Path 4.Обводка 1": theme.chat.inputPanel.panelControlAccentColor
].forEach { key, value in

View File

@ -97,6 +97,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
let contextSourceNode: ContextReferenceContentNode
private let textNode: ImmediateTextNode
private let iconNode: ASImageNode
private let backIconLayer: SimpleShapeLayer
private var animationNode: MoreIconNode?
private let backgroundNode: NavigationBackgroundNode
@ -117,6 +118,15 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.backIconLayer = SimpleShapeLayer()
self.backIconLayer.lineWidth = 3.0
self.backIconLayer.lineCap = .round
self.backIconLayer.lineJoin = .round
self.backIconLayer.strokeColor = UIColor.white.cgColor
self.backIconLayer.fillColor = nil
self.backIconLayer.isHidden = true
self.backIconLayer.path = try? convertSvgPath("M10.5,2 L1.5,11 L10.5,20 ")
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true)
super.init(pointerStyle: .insetRectangle(-8.0, 2.0))
@ -128,6 +138,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
self.contextSourceNode.addSubnode(self.backgroundNode)
self.contextSourceNode.addSubnode(self.textNode)
self.contextSourceNode.addSubnode(self.iconNode)
self.contextSourceNode.layer.addSublayer(self.backIconLayer)
self.addSubnode(self.containerNode)
@ -146,13 +157,43 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
self.action?(self.contextSourceNode, nil)
}
func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, transition: ContainedViewLayoutTransition) {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var boundingRect = self.bounds
if self.textNode.alpha != 0.0 {
boundingRect = boundingRect.union(self.textNode.frame)
}
boundingRect = boundingRect.insetBy(dx: -8.0, dy: -4.0)
if boundingRect.contains(point) {
return super.hitTest(self.bounds.center, with: event)
} else {
return nil
}
}
func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) {
self.contentsColor = contentsColor
self.backgroundNode.updateColor(color: backgroundColor, transition: transition)
transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor)
transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor)
transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor)
switch self.key {
case .back:
transition.updateAlpha(layer: self.textNode.layer, alpha: canBeExpanded ? 1.0 : 0.0)
transition.updateTransformScale(node: self.textNode, scale: canBeExpanded ? 1.0 : 0.001)
var iconTransform = CATransform3DIdentity
iconTransform = CATransform3DScale(iconTransform, canBeExpanded ? 1.0 : 0.8, canBeExpanded ? 1.0 : 0.8, 1.0)
iconTransform = CATransform3DTranslate(iconTransform, canBeExpanded ? -7.0 : 0.0, 0.0, 0.0)
transition.updateTransform(node: self.iconNode, transform: CATransform3DGetAffineTransform(iconTransform))
transition.updateTransform(layer: self.backIconLayer, transform: CATransform3DGetAffineTransform(iconTransform))
transition.updateLineWidth(layer: self.backIconLayer, lineWidth: canBeExpanded ? 3.0 : 2.075)
default:
break
}
if let animationNode = self.animationNode {
transition.updateTintColor(layer: animationNode.imageNode.layer, color: self.contentsColor)
@ -184,9 +225,9 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
var animationState: MoreIconNodeState = .more
switch key {
case .back:
text = ""
text = presentationData.strings.Common_Back
accessibilityText = presentationData.strings.Common_Back
icon = NavigationBar.thinBackArrowImage
icon = NavigationBar.backArrowImage(color: .white)
case .edit:
text = presentationData.strings.Common_Edit
accessibilityText = text
@ -270,11 +311,19 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
}
let inset: CGFloat = 0.0
var textInset: CGFloat = 0.0
switch key {
case .back:
textInset += 11.0
default:
break
}
let resultSize: CGSize
let textFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - textSize.height) / 2.0)), size: textSize)
self.textNode.frame = textFrame
let textFrame = CGRect(origin: CGPoint(x: inset + textInset, y: floor((height - textSize.height) / 2.0)), size: textSize)
self.textNode.position = textFrame.center
self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
if let animationNode = self.animationNode {
let animationSize = CGSize(width: 30.0, height: 30.0)
@ -286,7 +335,20 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size)
resultSize = size
} else if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
let iconFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
self.iconNode.position = iconFrame.center
self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
if case .back = key {
self.backIconLayer.position = iconFrame.center
self.backIconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
self.iconNode.isHidden = true
self.backIconLayer.isHidden = false
} else {
self.iconNode.isHidden = false
self.backIconLayer.isHidden = true
}
let size = CGSize(width: image.size.width + inset * 2.0, height: height)
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)

View File

@ -36,18 +36,21 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
private var backgroundContentColor: UIColor = .clear
private var contentsColor: UIColor = .white
private var canBeExpanded: Bool = false
var performAction: ((PeerInfoHeaderNavigationButtonKey, ContextReferenceContentNode?, ContextGesture?) -> Void)?
func updateContentsColor(backgroundContentColor: UIColor, contentsColor: UIColor, transition: ContainedViewLayoutTransition) {
func updateContentsColor(backgroundContentColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) {
self.backgroundContentColor = backgroundContentColor
self.contentsColor = contentsColor
for (_, button) in self.leftButtonNodes {
button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, transition: transition)
button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition)
transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: canBeExpanded ? -8.0 : 0.0, y: 0.0))
}
for (_, button) in self.rightButtonNodes {
button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, transition: transition)
button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition)
transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: canBeExpanded ? 8.0 : 0.0, y: 0.0))
}
}
@ -106,7 +109,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
buttonNode.frame = buttonFrame
buttonNode.alpha = 0.0
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, transition: .immediate)
buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate)
} else {
transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
@ -202,7 +205,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
}
let alphaFactor: CGFloat = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction)
if wasAdded {
buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, transition: .immediate)
buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate)
if key == .moreToSearch {
buttonNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)

View File

@ -553,6 +553,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let navigationContentsAccentColor: UIColor
let navigationContentsPrimaryColor: UIColor
let navigationContentsSecondaryColor: UIColor
let navigationContentsCanBeExpanded: Bool
let contentButtonBackgroundColor: UIColor
let contentButtonForegroundColor: UIColor
@ -640,6 +641,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
navigationContentsAccentColor = collapsedHeaderNavigationContentsAccentColor
navigationContentsPrimaryColor = collapsedHeaderNavigationContentsPrimaryColor
navigationContentsSecondaryColor = collapsedHeaderNavigationContentsSecondaryColor
navigationContentsCanBeExpanded = true
contentButtonBackgroundColor = collapsedHeaderContentButtonBackgroundColor
contentButtonForegroundColor = collapsedHeaderContentButtonForegroundColor
@ -651,6 +654,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
contentButtonBackgroundColor = expandedAvatarContentButtonBackgroundColor
contentButtonForegroundColor = expandedAvatarContentButtonForegroundColor
navigationContentsCanBeExpanded = false
headerButtonBackgroundColor = expandedAvatarHeaderButtonBackgroundColor
} else {
let effectiveTransitionFraction: CGFloat = innerBackgroundTransitionFraction < 0.5 ? 0.0 : 1.0
@ -659,6 +664,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
navigationContentsPrimaryColor = regularNavigationContentsPrimaryColor.mixedWith(collapsedHeaderNavigationContentsPrimaryColor, alpha: effectiveTransitionFraction)
navigationContentsSecondaryColor = regularNavigationContentsSecondaryColor.mixedWith(collapsedHeaderNavigationContentsSecondaryColor, alpha: effectiveTransitionFraction)
if peer?.profileColor != nil {
navigationContentsCanBeExpanded = effectiveTransitionFraction == 1.0
} else {
navigationContentsCanBeExpanded = true
}
contentButtonBackgroundColor = regularContentButtonBackgroundColor//.mixedWith(collapsedHeaderContentButtonBackgroundColor, alpha: effectiveTransitionFraction)
contentButtonForegroundColor = regularContentButtonForegroundColor//.mixedWith(collapsedHeaderContentButtonForegroundColor, alpha: effectiveTransitionFraction)
@ -775,7 +786,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.titleExpandedCredibilityIconSize = expandedIconSize
}
self.navigationButtonContainer.updateContentsColor(backgroundContentColor: headerButtonBackgroundColor, contentsColor: navigationContentsAccentColor, transition: navigationTransition)
self.navigationButtonContainer.updateContentsColor(backgroundContentColor: headerButtonBackgroundColor, contentsColor: navigationContentsAccentColor, canBeExpanded: navigationContentsCanBeExpanded, transition: navigationTransition)
self.titleNode.updateTintColor(color: navigationContentsPrimaryColor, transition: navigationTransition)
self.subtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: navigationTransition)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -844,7 +844,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
return
}
if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState, let detectedUrl = updatedUrlPreviewState.detectedUrls.first {
if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil, forPeerId: selfController.chatLocation.peerId), let updatedUrlPreviewState, let detectedUrl = updatedUrlPreviewState.detectedUrls.first {
if let webpage = webpageCache[detectedUrl] {
progress?.set(.single(false))

View File

@ -220,7 +220,7 @@ func updateChatPresentationInterfaceStateImpl(
}
}
if let (updatedUrlPreviewState, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: selfController.context, currentQuery: selfController.urlPreviewQueryState?.0) {
if let (updatedUrlPreviewState, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: selfController.context, currentQuery: selfController.urlPreviewQueryState?.0, forPeerId: selfController.chatLocation.peerId) {
selfController.urlPreviewQueryState?.1.dispose()
var inScope = true
var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)?
@ -301,7 +301,7 @@ func updateChatPresentationInterfaceStateImpl(
let isEditingMedia: Bool = updatedChatPresentationInterfaceState.editMessageState?.content != .plaintext
let editingUrlPreviewText: NSAttributedString? = isEditingMedia ? nil : updatedChatPresentationInterfaceState.interfaceState.editMessage?.inputState.inputText
if let (updatedEditingUrlPreviewState, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: selfController.context, currentQuery: selfController.editingUrlPreviewQueryState?.0) {
if let (updatedEditingUrlPreviewState, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: selfController.context, currentQuery: selfController.editingUrlPreviewQueryState?.0, forPeerId: selfController.chatLocation.peerId) {
selfController.editingUrlPreviewQueryState?.1.dispose()
var inScope = true
var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)?

View File

@ -794,7 +794,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return false
}
switch action.action {
case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults:
case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText:
for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil)))

View File

@ -510,7 +510,7 @@ struct UrlPreviewState {
var detectedUrls: [String]
}
func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError>)? {
func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?, forPeerId: PeerId?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError>)? {
guard let _ = inputText else {
if currentQuery != nil {
return (nil, .single({ _ in return nil }))
@ -522,7 +522,7 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco
let detectedUrls = detectUrls(inputText)
if detectedUrls != (currentQuery?.detectedUrls ?? []) {
if !detectedUrls.isEmpty {
return (UrlPreviewState(detectedUrls: detectedUrls), webpagePreview(account: context.account, urls: detectedUrls)
return (UrlPreviewState(detectedUrls: detectedUrls), webpagePreview(account: context.account, urls: detectedUrls, forPeerId: forPeerId)
|> mapToSignal { result -> Signal<(TelegramMediaWebpage, String)?, NoError> in
guard case let .result(webpageResult) = result else {
return .complete()

View File

@ -132,8 +132,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
private var groupCallDisposable: Disposable?
private var callController: CallController?
private var call: PresentationCall?
public let hasOngoingCall = ValuePromise<Bool>(false)
private let callState = Promise<PresentationCallState?>(nil)
private var awaitingCallConnectionDisposable: Disposable?
private var groupCallController: VoiceChatController?
public var currentGroupCallController: ViewController? {
@ -741,26 +743,49 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.callDisposable = (callManager.currentCallSignal
|> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self {
if call !== strongSelf.callController?.call {
strongSelf.callController?.dismiss()
strongSelf.callController = nil
strongSelf.hasOngoingCall.set(false)
guard let self else {
return
}
if call !== self.call {
self.call = call
self.callController?.dismiss()
self.callController = nil
self.hasOngoingCall.set(false)
if let call {
self.callState.set(call.state
|> map(Optional.init))
self.hasOngoingCall.set(true)
setNotificationCall(call)
if let call = call {
mainWindow.hostView.containerView.endEditing(true)
let callController = CallController(sharedContext: strongSelf, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
strongSelf.callController = callController
strongSelf.mainWindow?.present(callController, on: .calls)
strongSelf.callState.set(call.state
|> map(Optional.init))
strongSelf.hasOngoingCall.set(true)
setNotificationCall(call)
} else {
strongSelf.callState.set(.single(nil))
strongSelf.hasOngoingCall.set(false)
setNotificationCall(nil)
if !call.isOutgoing && call.isIntegratedWithCallKit {
self.awaitingCallConnectionDisposable = (call.state
|> filter { state in
switch state.state {
case .ringing:
return false
default:
return true
}
}
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {
return
}
self.presentControllerWithCurrentCall()
})
} else{
self.presentControllerWithCurrentCall()
}
} else {
self.callState.set(.single(nil))
self.hasOngoingCall.set(false)
self.awaitingCallConnectionDisposable?.dispose()
self.awaitingCallConnectionDisposable = nil
setNotificationCall(nil)
}
}
})
@ -951,6 +976,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.callDisposable?.dispose()
self.groupCallDisposable?.dispose()
self.callStateDisposable?.dispose()
self.awaitingCallConnectionDisposable?.dispose()
}
private var didPerformAccountSettingsImport = false
@ -1010,6 +1036,27 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
}
private func presentControllerWithCurrentCall() {
guard let call = self.call else {
return
}
if let currentCallController = self.callController {
if currentCallController.call === call {
self.navigateToCurrentCall()
return
} else {
self.callController = nil
currentCallController.dismiss()
}
}
self.mainWindow?.hostView.containerView.endEditing(true)
let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
self.callController = callController
self.mainWindow?.present(callController, on: .calls)
}
public func updateNotificationTokensRegistration() {
let sandbox: Bool
#if DEBUG

@ -1 +1 @@
Subproject commit 8f41ea265404dea86f2444a47343993ccdc3a64e
Subproject commit 8f2f1b90209b014071453079c1f28e115ee8de12

View File

@ -312,7 +312,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
}
let webView = WebAppWebView()
let webView = WebAppWebView(account: context.account)
webView.alpha = 0.0
webView.navigationDelegate = self
webView.uiDelegate = self
@ -418,8 +418,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
})
})
self.setupWebView()
}
deinit {
@ -434,6 +432,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
override func didLoad() {
super.didLoad()
self.setupWebView()
guard let webView = self.webView else {
return
}

View File

@ -3,6 +3,7 @@ import UIKit
import Display
import WebKit
import SwiftSignalKit
import TelegramCore
private let findActiveElementY = """
function getOffset(el) {
@ -91,8 +92,22 @@ function disconnectObserver() {
final class WebAppWebView: WKWebView {
var handleScriptMessage: (WKScriptMessage) -> Void = { _ in }
init() {
init(account: Account) {
let configuration = WKWebViewConfiguration()
let uuid: UUID
if let current = UserDefaults.standard.object(forKey: "TelegramWebStoreUUID_\(account.id.int64)") as? String {
uuid = UUID(uuidString: current)!
} else {
uuid = UUID()
UserDefaults.standard.set(uuid.uuidString, forKey: "TelegramWebStoreUUID_\(account.id.int64)")
}
if #available(iOS 17.0, *) {
configuration.websiteDataStore = WKWebsiteDataStore(forIdentifier: uuid)
}
let contentController = WKUserContentController()
var handleScriptMessageImpl: ((WKScriptMessage) -> Void)?