mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
a5bd955643
commit
78b02192cf
@ -8891,3 +8891,5 @@ Sorry for the inconvenience.";
|
|||||||
"Settings.RaiseToListenInfo" = "Raise to Listen allows you to quickly listen and reply to incoming audio messages by raising the phone to your ear.";
|
"Settings.RaiseToListenInfo" = "Raise to Listen allows you to quickly listen and reply to incoming audio messages by raising the phone to your ear.";
|
||||||
|
|
||||||
"Login.CodeSentCallText" = "Calling **%@** to dictate the code.";
|
"Login.CodeSentCallText" = "Calling **%@** to dictate the code.";
|
||||||
|
|
||||||
|
"Premium.Purchase.OnlyOneSubscriptionAllowed" = "You have already purchased Telegram Premium for another account. You can only have one Telegram Premium subscription on one Apple ID.";
|
||||||
|
@ -308,11 +308,17 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
return signal
|
return signal
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getValidTransactionIds() -> [String] {
|
public struct ReceiptPurchase: Equatable {
|
||||||
|
public let productId: String
|
||||||
|
public let transactionId: String
|
||||||
|
public let expirationDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getReceiptPurchases() -> [ReceiptPurchase] {
|
||||||
guard let data = getReceiptData(), let receipt = parseReceipt(data) else {
|
guard let data = getReceiptData(), let receipt = parseReceipt(data) else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return receipt.purchases.map { $0.transactionId }
|
return receipt.purchases.map { ReceiptPurchase(productId: $0.productId, transactionId: $0.transactionId, expirationDate: $0.expirationDate) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +365,6 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
|||||||
switch transaction.transactionState {
|
switch transaction.transactionState {
|
||||||
case .purchased:
|
case .purchased:
|
||||||
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased")
|
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased")
|
||||||
|
|
||||||
transactionState = .purchased(transactionId: transaction.transactionIdentifier)
|
transactionState = .purchased(transactionId: transaction.transactionIdentifier)
|
||||||
transactionsToAssign.append(transaction)
|
transactionsToAssign.append(transaction)
|
||||||
case .restored:
|
case .restored:
|
||||||
|
@ -7,6 +7,7 @@ private struct Asn1Tag {
|
|||||||
static let sequence: Int32 = 0x10
|
static let sequence: Int32 = 0x10
|
||||||
static let set: Int32 = 0x11
|
static let set: Int32 = 0x11
|
||||||
static let utf8String: Int32 = 0x0c
|
static let utf8String: Int32 = 0x0c
|
||||||
|
static let date: Int32 = 0x16
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Asn1Entry {
|
private struct Asn1Entry {
|
||||||
@ -124,10 +125,12 @@ struct Receipt {
|
|||||||
fileprivate struct Tag {
|
fileprivate struct Tag {
|
||||||
static let productIdentifier: Int32 = 1702
|
static let productIdentifier: Int32 = 1702
|
||||||
static let transactionIdentifier: Int32 = 1703
|
static let transactionIdentifier: Int32 = 1703
|
||||||
|
static let expirationDate: Int32 = 1708
|
||||||
}
|
}
|
||||||
|
|
||||||
let productId: String
|
let productId: String
|
||||||
let transactionId: String
|
let transactionId: String
|
||||||
|
let expirationDate: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
let purchases: [Purchase]
|
let purchases: [Purchase]
|
||||||
@ -192,6 +195,27 @@ func parseReceipt(_ data: Data) -> Receipt? {
|
|||||||
return Receipt(purchases: purchases)
|
return Receipt(purchases: purchases)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func parseRfc3339Date(_ str: String) -> Date? {
|
||||||
|
let posixLocale = Locale(identifier: "en_US_POSIX")
|
||||||
|
|
||||||
|
let formatter1 = DateFormatter()
|
||||||
|
formatter1.locale = posixLocale
|
||||||
|
formatter1.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssX5"
|
||||||
|
formatter1.timeZone = TimeZone(secondsFromGMT: 0)
|
||||||
|
|
||||||
|
let result = formatter1.date(from: str)
|
||||||
|
if result != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatter2 = DateFormatter()
|
||||||
|
formatter2.locale = posixLocale
|
||||||
|
formatter2.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSX5"
|
||||||
|
formatter2.timeZone = TimeZone(secondsFromGMT: 0)
|
||||||
|
|
||||||
|
return formatter2.date(from: str)
|
||||||
|
}
|
||||||
|
|
||||||
private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
||||||
let root = parse(data)
|
let root = parse(data)
|
||||||
guard root.tag == Asn1Tag.set else {
|
guard root.tag == Asn1Tag.set else {
|
||||||
@ -200,6 +224,7 @@ private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
|||||||
|
|
||||||
var productId: String?
|
var productId: String?
|
||||||
var transactionId: String?
|
var transactionId: String?
|
||||||
|
var expirationDate: Date?
|
||||||
|
|
||||||
let receiptAttributes = parseSequence(root.data)
|
let receiptAttributes = parseSequence(root.data)
|
||||||
for attribute in receiptAttributes {
|
for attribute in receiptAttributes {
|
||||||
@ -219,12 +244,16 @@ private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
|||||||
let valEntry = parse(value)
|
let valEntry = parse(value)
|
||||||
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
||||||
transactionId = String(bytes: valEntry.data, encoding: .utf8)
|
transactionId = String(bytes: valEntry.data, encoding: .utf8)
|
||||||
|
case Receipt.Purchase.Tag.expirationDate:
|
||||||
|
let valEntry = parse(value)
|
||||||
|
guard valEntry.tag == Asn1Tag.date else { return nil }
|
||||||
|
expirationDate = parseRfc3339Date(String(bytes: valEntry.data, encoding: .utf8) ?? "")
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard let productId, let transactionId else {
|
guard let productId, let transactionId, let expirationDate else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return Receipt.Purchase(productId: productId, transactionId: transactionId)
|
return Receipt.Purchase(productId: productId, transactionId: transactionId, expirationDate: expirationDate)
|
||||||
}
|
}
|
||||||
|
@ -1196,14 +1196,28 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
let otherPeerName: String?
|
let otherPeerName: String?
|
||||||
let products: [PremiumProduct]?
|
let products: [PremiumProduct]?
|
||||||
let selectedProductId: String?
|
let selectedProductId: String?
|
||||||
let validTransactionIds: [String]
|
let validPurchases: [InAppPurchaseManager.ReceiptPurchase]
|
||||||
let promoConfiguration: PremiumPromoConfiguration?
|
let promoConfiguration: PremiumPromoConfiguration?
|
||||||
let present: (ViewController) -> Void
|
let present: (ViewController) -> Void
|
||||||
let selectProduct: (String) -> Void
|
let selectProduct: (String) -> Void
|
||||||
let buy: () -> Void
|
let buy: () -> Void
|
||||||
let updateIsFocused: (Bool) -> Void
|
let updateIsFocused: (Bool) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, source: PremiumSource, isPremium: Bool?, justBought: Bool, otherPeerName: String?, products: [PremiumProduct]?, selectedProductId: String?, validTransactionIds: [String], promoConfiguration: PremiumPromoConfiguration?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) {
|
init(
|
||||||
|
context: AccountContext,
|
||||||
|
source: PremiumSource,
|
||||||
|
isPremium: Bool?,
|
||||||
|
justBought: Bool,
|
||||||
|
otherPeerName: String?,
|
||||||
|
products: [PremiumProduct]?,
|
||||||
|
selectedProductId: String?,
|
||||||
|
validPurchases: [InAppPurchaseManager.ReceiptPurchase],
|
||||||
|
promoConfiguration: PremiumPromoConfiguration?,
|
||||||
|
present: @escaping (ViewController) -> Void,
|
||||||
|
selectProduct: @escaping (String) -> Void,
|
||||||
|
buy: @escaping () -> Void,
|
||||||
|
updateIsFocused: @escaping (Bool) -> Void
|
||||||
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.source = source
|
self.source = source
|
||||||
self.isPremium = isPremium
|
self.isPremium = isPremium
|
||||||
@ -1211,7 +1225,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
self.otherPeerName = otherPeerName
|
self.otherPeerName = otherPeerName
|
||||||
self.products = products
|
self.products = products
|
||||||
self.selectedProductId = selectedProductId
|
self.selectedProductId = selectedProductId
|
||||||
self.validTransactionIds = validTransactionIds
|
self.validPurchases = validPurchases
|
||||||
self.promoConfiguration = promoConfiguration
|
self.promoConfiguration = promoConfiguration
|
||||||
self.present = present
|
self.present = present
|
||||||
self.selectProduct = selectProduct
|
self.selectProduct = selectProduct
|
||||||
@ -1241,7 +1255,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
if lhs.selectedProductId != rhs.selectedProductId {
|
if lhs.selectedProductId != rhs.selectedProductId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.validTransactionIds != rhs.validTransactionIds {
|
if lhs.validPurchases != rhs.validPurchases {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.promoConfiguration != rhs.promoConfiguration {
|
if lhs.promoConfiguration != rhs.promoConfiguration {
|
||||||
@ -1256,7 +1270,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
|
|
||||||
var products: [PremiumProduct]?
|
var products: [PremiumProduct]?
|
||||||
var selectedProductId: String?
|
var selectedProductId: String?
|
||||||
var validTransactionIds: [String] = []
|
var validPurchases: [InAppPurchaseManager.ReceiptPurchase] = []
|
||||||
|
|
||||||
var isPremium: Bool?
|
var isPremium: Bool?
|
||||||
|
|
||||||
@ -1276,7 +1290,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
|
|
||||||
var canUpgrade: Bool {
|
var canUpgrade: Bool {
|
||||||
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
|
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
|
||||||
if self.validTransactionIds.contains(transactionId) {
|
if self.validPurchases.contains(where: { $0.transactionId == transactionId }) {
|
||||||
return products.first(where: { $0.months > current.months }) != nil
|
return products.first(where: { $0.months > current.months }) != nil
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -1373,7 +1387,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
let state = context.state
|
let state = context.state
|
||||||
state.products = context.component.products
|
state.products = context.component.products
|
||||||
state.selectedProductId = context.component.selectedProductId
|
state.selectedProductId = context.component.selectedProductId
|
||||||
state.validTransactionIds = context.component.validTransactionIds
|
state.validPurchases = context.component.validPurchases
|
||||||
state.isPremium = context.component.isPremium
|
state.isPremium = context.component.isPremium
|
||||||
|
|
||||||
let theme = environment.theme
|
let theme = environment.theme
|
||||||
@ -1986,7 +2000,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
private(set) var products: [PremiumProduct]?
|
private(set) var products: [PremiumProduct]?
|
||||||
private(set) var selectedProductId: String?
|
private(set) var selectedProductId: String?
|
||||||
fileprivate var validTransactionIds: [String] = []
|
fileprivate var validPurchases: [InAppPurchaseManager.ReceiptPurchase] = []
|
||||||
|
|
||||||
var isPremium: Bool?
|
var isPremium: Bool?
|
||||||
var otherPeerName: String?
|
var otherPeerName: String?
|
||||||
@ -2015,7 +2029,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
var canUpgrade: Bool {
|
var canUpgrade: Bool {
|
||||||
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
|
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
|
||||||
if self.validTransactionIds.contains(transactionId) {
|
if self.validPurchases.contains(where: { $0.transactionId == transactionId }) {
|
||||||
return products.first(where: { $0.months > current.months }) != nil
|
return products.first(where: { $0.months > current.months }) != nil
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -2036,7 +2050,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.validTransactionIds = context.inAppPurchaseManager?.getValidTransactionIds() ?? []
|
self.validPurchases = context.inAppPurchaseManager?.getReceiptPurchases() ?? []
|
||||||
|
|
||||||
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
|
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
|
||||||
if let inAppPurchaseManager = context.inAppPurchaseManager {
|
if let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||||
@ -2136,7 +2150,28 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }), !self.inProgress else {
|
let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }), !self.inProgress else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
let isUpgrade = self.products?.first(where: { $0.isCurrent }) != nil
|
let isUpgrade = self.products?.first(where: { $0.isCurrent }) != nil
|
||||||
|
|
||||||
|
var hasActiveSubsciption = false
|
||||||
|
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_receipt_check"] {
|
||||||
|
|
||||||
|
} else if !self.validPurchases.isEmpty && !isUpgrade {
|
||||||
|
let now = Date()
|
||||||
|
for purchase in self.validPurchases.reversed() {
|
||||||
|
if (purchase.productId.hasSuffix(".monthly") || purchase.productId.hasSuffix(".annual")) && purchase.expirationDate > now {
|
||||||
|
hasActiveSubsciption = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasActiveSubsciption {
|
||||||
|
let errorText = presentationData.strings.Premium_Purchase_OnlyOneSubscriptionAllowed
|
||||||
|
let alertController = textAlertController(context: self.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||||
|
self.present(alertController)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
addAppLogEvent(postbox: self.context.account.postbox, type: "premium.promo_screen_accept")
|
addAppLogEvent(postbox: self.context.account.postbox, type: "premium.promo_screen_accept")
|
||||||
|
|
||||||
@ -2173,7 +2208,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
|
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
|
||||||
|
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
||||||
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||||
strongSelf.present(alertController)
|
strongSelf.present(alertController)
|
||||||
@ -2198,7 +2232,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
strongSelf.updateInProgress(false)
|
strongSelf.updateInProgress(false)
|
||||||
strongSelf.updated(transition: .immediate)
|
strongSelf.updated(transition: .immediate)
|
||||||
|
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
var errorText: String?
|
var errorText: String?
|
||||||
switch error {
|
switch error {
|
||||||
case .generic:
|
case .generic:
|
||||||
@ -2475,7 +2508,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
otherPeerName: state.otherPeerName,
|
otherPeerName: state.otherPeerName,
|
||||||
products: state.products,
|
products: state.products,
|
||||||
selectedProductId: state.selectedProductId,
|
selectedProductId: state.selectedProductId,
|
||||||
validTransactionIds: state.validTransactionIds,
|
validPurchases: state.validPurchases,
|
||||||
promoConfiguration: state.promoConfiguration,
|
promoConfiguration: state.promoConfiguration,
|
||||||
present: context.component.present,
|
present: context.component.present,
|
||||||
selectProduct: { [weak state] productId in
|
selectProduct: { [weak state] productId in
|
||||||
|
@ -188,7 +188,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
|||||||
case .x0_5:
|
case .x0_5:
|
||||||
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||||
case .x1:
|
case .x1:
|
||||||
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.controlColor)))
|
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
|
||||||
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
|
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
|
||||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
|
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
|
||||||
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
|
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
|
||||||
@ -378,7 +378,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
|||||||
case .x0_5:
|
case .x0_5:
|
||||||
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||||
case .x1:
|
case .x1:
|
||||||
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.controlColor)))
|
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
|
||||||
case .x1_5:
|
case .x1_5:
|
||||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||||
case .x2:
|
case .x2:
|
||||||
@ -511,6 +511,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
|||||||
if let rate = self.playbackBaseRate {
|
if let rate = self.playbackBaseRate {
|
||||||
switch rate {
|
switch rate {
|
||||||
case .x1:
|
case .x1:
|
||||||
|
nextRate = .x1_5
|
||||||
|
case .x1_5:
|
||||||
nextRate = .x2
|
nextRate = .x2
|
||||||
default:
|
default:
|
||||||
nextRate = .x1
|
nextRate = .x1
|
||||||
|
@ -687,41 +687,41 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
}
|
}
|
||||||
strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
|
strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
|
||||||
|
|
||||||
var hasTooltip = false
|
// var hasTooltip = false
|
||||||
strongSelf.forEachController({ controller in
|
// strongSelf.forEachController({ controller in
|
||||||
if let controller = controller as? UndoOverlayController {
|
// if let controller = controller as? UndoOverlayController {
|
||||||
hasTooltip = true
|
// hasTooltip = true
|
||||||
controller.dismissWithCommitAction()
|
// controller.dismissWithCommitAction()
|
||||||
}
|
// }
|
||||||
return true
|
// return true
|
||||||
})
|
// })
|
||||||
|
//
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let slowdown: Bool?
|
// let slowdown: Bool?
|
||||||
if baseRate == .x1 {
|
// if baseRate == .x1 {
|
||||||
slowdown = true
|
// slowdown = true
|
||||||
} else if baseRate == .x2 {
|
// } else if baseRate == .x2 {
|
||||||
slowdown = false
|
// slowdown = false
|
||||||
} else {
|
// } else {
|
||||||
slowdown = nil
|
// slowdown = nil
|
||||||
}
|
// }
|
||||||
if let slowdown = slowdown {
|
// if let slowdown = slowdown {
|
||||||
strongSelf.present(
|
// strongSelf.present(
|
||||||
UndoOverlayController(
|
// UndoOverlayController(
|
||||||
presentationData: presentationData,
|
// presentationData: presentationData,
|
||||||
content: .audioRate(
|
// content: .audioRate(
|
||||||
slowdown: slowdown,
|
// slowdown: slowdown,
|
||||||
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
// text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||||
),
|
// ),
|
||||||
elevatedLayout: false,
|
// elevatedLayout: false,
|
||||||
animateInAsReplacement: hasTooltip,
|
// animateInAsReplacement: hasTooltip,
|
||||||
action: { action in
|
// action: { action in
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
),
|
// ),
|
||||||
in: .current
|
// in: .current
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
mediaAccessoryPanel.togglePlayPause = { [weak self] in
|
mediaAccessoryPanel.togglePlayPause = { [weak self] in
|
||||||
|
@ -35,6 +35,47 @@ private func generateCollapseIcon(theme: PresentationTheme) -> UIImage? {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage? {
|
||||||
|
return generateImage(CGSize(width: 30.0, height: 16.0), rotatedContext: { size, context in
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let lineWidth = 1.0 + UIScreenPixel
|
||||||
|
context.setLineWidth(lineWidth)
|
||||||
|
context.setStrokeColor(color.cgColor)
|
||||||
|
|
||||||
|
|
||||||
|
let string = NSMutableAttributedString(string: rate, font: Font.with(size: 11.0, design: .round, weight: .bold), textColor: color)
|
||||||
|
|
||||||
|
var offset = CGPoint(x: 1.0, y: 0.0)
|
||||||
|
var width: CGFloat
|
||||||
|
if rate.count >= 3 {
|
||||||
|
if rate == "0.5X" {
|
||||||
|
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||||
|
offset.x += -0.5
|
||||||
|
} else {
|
||||||
|
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||||
|
offset.x += -0.3
|
||||||
|
}
|
||||||
|
width = 29.0
|
||||||
|
} else {
|
||||||
|
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||||
|
width = 19.0
|
||||||
|
offset.x += -0.3
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - width) / 2.0), y: 0.0, width: width, height: 16.0).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 2.0, height: 2.0))
|
||||||
|
context.addPath(path.cgPath)
|
||||||
|
context.strokePath()
|
||||||
|
|
||||||
|
let boundingRect = string.boundingRect(with: size, options: [], context: nil)
|
||||||
|
string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + UIScreenPixel + floor((size.height - boundingRect.height) / 2.0)))
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private let digitsSet = CharacterSet(charactersIn: "0123456789")
|
private let digitsSet = CharacterSet(charactersIn: "0123456789")
|
||||||
private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat {
|
private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat {
|
||||||
let text: String
|
let text: String
|
||||||
@ -375,8 +416,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let baseRate: AudioPlaybackRate
|
let baseRate: AudioPlaybackRate
|
||||||
if !value.status.baseRate.isEqual(to: 1.0) {
|
if value.status.baseRate.isEqual(to: 2.0) {
|
||||||
baseRate = .x2
|
baseRate = .x2
|
||||||
|
} else if value.status.baseRate.isEqual(to: 1.5) {
|
||||||
|
baseRate = .x1_5
|
||||||
} else {
|
} else {
|
||||||
baseRate = .x1
|
baseRate = .x1
|
||||||
}
|
}
|
||||||
@ -715,10 +758,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateOrderButton(_ order: MusicPlaybackSettingsOrder) {
|
private func updateOrderButton(_ order: MusicPlaybackSettingsOrder) {
|
||||||
let baseColor = self.presentationData.theme.list.itemSecondaryTextColor
|
|
||||||
switch order {
|
switch order {
|
||||||
case .regular:
|
case .regular:
|
||||||
self.orderButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/OrderReverse"), color: baseColor)
|
self.orderButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/OrderReverse"), color: self.presentationData.theme.list.itemSecondaryTextColor)
|
||||||
case .reversed:
|
case .reversed:
|
||||||
self.orderButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/OrderReverse"), color: self.presentationData.theme.list.itemAccentColor)
|
self.orderButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/OrderReverse"), color: self.presentationData.theme.list.itemAccentColor)
|
||||||
case .random:
|
case .random:
|
||||||
@ -740,10 +782,12 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
|
|
||||||
private func updateRateButton(_ baseRate: AudioPlaybackRate) {
|
private func updateRateButton(_ baseRate: AudioPlaybackRate) {
|
||||||
switch baseRate {
|
switch baseRate {
|
||||||
case .x2:
|
case .x2:
|
||||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerMaximizedRateActiveIcon(self.presentationData.theme), for: [])
|
self.rateButton.setImage(optionsRateImage(rate: "2X", color: self.presentationData.theme.list.itemAccentColor), for: [])
|
||||||
default:
|
case .x1_5:
|
||||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerMaximizedRateInactiveIcon(self.presentationData.theme), for: [])
|
self.rateButton.setImage(optionsRateImage(rate: "1.5X", color: self.presentationData.theme.list.itemAccentColor), for: [])
|
||||||
|
default:
|
||||||
|
self.rateButton.setImage(optionsRateImage(rate: "1X", color: self.presentationData.theme.list.itemSecondaryTextColor), for: [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -959,12 +1003,14 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
if let currentRate = self.currentRate {
|
if let currentRate = self.currentRate {
|
||||||
switch currentRate {
|
switch currentRate {
|
||||||
case .x1:
|
case .x1:
|
||||||
|
nextRate = .x1_5
|
||||||
|
case .x1_5:
|
||||||
nextRate = .x2
|
nextRate = .x2
|
||||||
default:
|
default:
|
||||||
nextRate = .x1
|
nextRate = .x1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nextRate = .x2
|
nextRate = .x1_5
|
||||||
}
|
}
|
||||||
self.control?(.setBaseRate(nextRate))
|
self.control?(.setBaseRate(nextRate))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user