Various improvements

This commit is contained in:
Ilya Laktyushin 2025-03-24 05:27:50 +04:00
parent a651bb589d
commit a8b02015ce
7 changed files with 319 additions and 39 deletions

View File

@ -14096,3 +14096,13 @@ Sorry for the inconvenience.";
"Gift.Unpin.Unpin" = "Unpin"; "Gift.Unpin.Unpin" = "Unpin";
"ChatList.Search.Ad" = "Ad"; "ChatList.Search.Ad" = "Ad";
"Login.Fee.Title" = "SMS Fee";
"Login.Fee.SmsCost.Title" = "High SMS Costs";
"Login.Fee.SmsCost.Text" = "Telecom providers in your country (%@) charge Telegram very high prices for SMS.";
"Login.Fee.Verification.Title" = "Verification Required";
"Login.Fee.Verification.Text" = "Telegram needs to send you an SMS with a verification code to confirm your phone number.";
"Login.Fee.Support.Title" = "Support via [Telegram Premium >]()";
"Login.Fee.Support.Text" = "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs.";
"Login.Fee.SignUp" = "Sign Up for %@";
"Login.Fee.GetPremiumForAWeek" = "Get Telegram Premium for 1 week";

View File

@ -848,7 +848,13 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
let pasteSize = self.pasteButton.measure(layout.size) let pasteSize = self.pasteButton.measure(layout.size)
let pasteButtonSize = CGSize(width: pasteSize.width + 16.0, height: 24.0) let pasteButtonSize = CGSize(width: pasteSize.width + 16.0, height: 24.0)
transition.updateFrame(node: self.pasteButton, frame: CGRect(origin: CGPoint(x: layout.size.width - 40.0 - pasteButtonSize.width, y: self.textField.frame.midY - pasteButtonSize.height / 2.0), size: pasteButtonSize)) let pasteOriginX: CGFloat
if case .compact = layout.metrics.widthClass {
pasteOriginX = layout.size.width - 40.0 - pasteButtonSize.width
} else {
pasteOriginX = self.textField.frame.maxX + 32.0
}
transition.updateFrame(node: self.pasteButton, frame: CGRect(origin: CGPoint(x: pasteOriginX, y: self.textField.frame.midY - pasteButtonSize.height / 2.0), size: pasteButtonSize))
self.hintArrowNode.isHidden = true self.hintArrowNode.isHidden = true
} else if case .word = codeType { } else if case .word = codeType {
self.hintButtonNode.alpha = 0.0 self.hintButtonNode.alpha = 0.0

View File

@ -198,7 +198,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
let titleSize = self.title.update( let titleSize = self.title.update(
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "SMS Fee", font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor))) MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Login_Fee_Title, font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)))
), ),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
@ -218,9 +218,9 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
AnyComponentWithIdentity( AnyComponentWithIdentity(
id: "cost", id: "cost",
component: AnyComponent(ParagraphComponent( component: AnyComponent(ParagraphComponent(
title: "High SMS Costs", title: environment.strings.Login_Fee_SmsCost_Title,
titleColor: textColor, titleColor: textColor,
text: "Telecom providers in your country (\(countryName)) charge Telegram very high prices for SMS.", text: environment.strings.Login_Fee_SmsCost_Text(countryName).string,
textColor: secondaryTextColor, textColor: secondaryTextColor,
iconName: "Premium/Authorization/Cost", iconName: "Premium/Authorization/Cost",
iconColor: linkColor iconColor: linkColor
@ -231,9 +231,9 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
AnyComponentWithIdentity( AnyComponentWithIdentity(
id: "verification", id: "verification",
component: AnyComponent(ParagraphComponent( component: AnyComponent(ParagraphComponent(
title: "Verification Required", title: environment.strings.Login_Fee_Verification_Title,
titleColor: textColor, titleColor: textColor,
text: "Telegram needs to send you an SMS with a verification code to confirm your phone number.", text: environment.strings.Login_Fee_Verification_Text,
textColor: secondaryTextColor, textColor: secondaryTextColor,
iconName: "Premium/Authorization/Verification", iconName: "Premium/Authorization/Verification",
iconColor: linkColor iconColor: linkColor
@ -242,11 +242,11 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
) )
items.append( items.append(
AnyComponentWithIdentity( AnyComponentWithIdentity(
id: "withdrawal", id: "support",
component: AnyComponent(ParagraphComponent( component: AnyComponent(ParagraphComponent(
title: "Support via [Telegram Premium >]()", title: environment.strings.Login_Fee_Support_Title,
titleColor: textColor, titleColor: textColor,
text: "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs.", text: environment.strings.Login_Fee_Support_Text,
textColor: secondaryTextColor, textColor: secondaryTextColor,
iconName: "Premium/Authorization/Support", iconName: "Premium/Authorization/Support",
iconColor: linkColor, iconColor: linkColor,
@ -315,7 +315,8 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
} else { } else {
priceString = "" priceString = ""
} }
let buttonString = "Sign up for \(priceString)"
let buttonString = environment.strings.Login_Fee_SignUp(priceString).string
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let buttonSize = self.button.update( let buttonSize = self.button.update(
transition: transition, transition: transition,
@ -331,7 +332,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
component: AnyComponent( component: AnyComponent(
VStack([ VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))), AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Get Telegram Premium for 1 week", font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center))))) AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Login_Fee_GetPremiumForAWeek, font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)))))
], spacing: 1.0) ], spacing: 1.0)
) )
), ),

View File

@ -300,6 +300,8 @@ private final class ScrollContent: CombinedComponent {
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 0, maximumNumberOfLines: 0,
lineSpacing: 0.2, lineSpacing: 0.2,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
@ -598,6 +600,7 @@ private final class ParagraphComponent: CombinedComponent {
horizontalAlignment: .natural, horizontalAlignment: .natural,
maximumNumberOfLines: 0, maximumNumberOfLines: 0,
lineSpacing: 0.2, lineSpacing: 0.2,
highlightColor: accentColor.withAlphaComponent(0.1),
highlightAction: { attributes in highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
@ -949,6 +952,7 @@ public class AdsInfoScreen: ViewController {
context: controller.context, context: controller.context,
theme: self.presentationData.theme, theme: self.presentationData.theme,
title: self.presentationData.strings.AdsInfo_Understood, title: self.presentationData.strings.AdsInfo_Understood,
showBackground: controller.mode != .search,
action: { [weak self] in action: { [weak self] in
guard let self else { guard let self else {
return return
@ -992,7 +996,7 @@ public class AdsInfoScreen: ViewController {
} }
private var defaultTopInset: CGFloat { private var defaultTopInset: CGFloat {
guard let layout = self.currentLayout else { guard let layout = self.currentLayout, let controller = self.controller else {
return 210.0 return 210.0
} }
if case .compact = layout.metrics.widthClass { if case .compact = layout.metrics.widthClass {
@ -1006,9 +1010,13 @@ public class AdsInfoScreen: ViewController {
let contentHeight = self.containerExternalState.contentHeight let contentHeight = self.containerExternalState.contentHeight
let footerHeight = self.footerHeight let footerHeight = self.footerHeight
if contentHeight > 0.0 { if contentHeight > 0.0 {
let delta = (layout.size.height - defaultTopInset - containerTopInset) - contentHeight - footerHeight - 16.0 if case .search = controller.mode {
if delta > 0.0 { return (layout.size.height - containerTopInset) - contentHeight
defaultTopInset += delta } else {
let delta = (layout.size.height - defaultTopInset - containerTopInset) - contentHeight - footerHeight - 16.0
if delta > 0.0 {
defaultTopInset += delta
}
} }
} }
return defaultTopInset return defaultTopInset
@ -1029,7 +1037,7 @@ public class AdsInfoScreen: ViewController {
} }
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) { @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
guard let layout = self.currentLayout else { guard let layout = self.currentLayout, let controller = self.controller else {
return return
} }
@ -1064,6 +1072,9 @@ public class AdsInfoScreen: ViewController {
let contentOffset = scrollView?.contentOffset.y ?? 0.0 let contentOffset = scrollView?.contentOffset.y ?? 0.0
var translation = recognizer.translation(in: self.view).y var translation = recognizer.translation(in: self.view).y
if case .search = controller.mode {
translation = max(0.0, translation)
}
var currentOffset = topInset + translation var currentOffset = topInset + translation
@ -1111,8 +1122,12 @@ public class AdsInfoScreen: ViewController {
let contentOffset = scrollView?.contentOffset.y ?? 0.0 let contentOffset = scrollView?.contentOffset.y ?? 0.0
let translation = recognizer.translation(in: self.view).y var translation = recognizer.translation(in: self.view).y
var velocity = recognizer.velocity(in: self.view) var velocity = recognizer.velocity(in: self.view)
if case .search = controller.mode {
translation = max(0.0, translation)
velocity.y = max(0.0, velocity.y)
}
if self.isExpanded { if self.isExpanded {
if contentOffset > 0.1 { if contentOffset > 0.1 {
@ -1435,12 +1450,14 @@ private final class FooterComponent: Component {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let title: String let title: String
let showBackground: Bool
let action: () -> Void let action: () -> Void
init(context: AccountContext, theme: PresentationTheme, title: String, action: @escaping () -> Void) { init(context: AccountContext, theme: PresentationTheme, title: String, showBackground: Bool, action: @escaping () -> Void) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.title = title self.title = title
self.showBackground = showBackground
self.action = action self.action = action
} }
@ -1454,6 +1471,9 @@ private final class FooterComponent: Component {
if lhs.title != rhs.title { if lhs.title != rhs.title {
return false return false
} }
if lhs.showBackground != rhs.showBackground {
return false
}
return true return true
} }
@ -1494,6 +1514,9 @@ private final class FooterComponent: Component {
self.separator.backgroundColor = component.theme.rootController.tabBar.separatorColor.cgColor self.separator.backgroundColor = component.theme.rootController.tabBar.separatorColor.cgColor
transition.setFrame(layer: self.separator, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: UIScreenPixel))) transition.setFrame(layer: self.separator, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: UIScreenPixel)))
self.backgroundView.isHidden = !component.showBackground
self.separator.isHidden = !component.showBackground
let buttonSize = self.button.update( let buttonSize = self.button.update(
transition: .immediate, transition: .immediate,
component: AnyComponent( component: AnyComponent(

View File

@ -1868,15 +1868,15 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
guard let self, let navigationController = self.navigationController as? NavigationController else { guard let self, let navigationController = self.navigationController as? NavigationController else {
return return
} }
self.dismissAnimated()
let _ = (context.engine.privacy.requestAccountPrivacySettings() let _ = (context.engine.privacy.requestAccountPrivacySettings()
|> deliverOnMainQueue).start(next: { [weak self, weak navigationController] privacySettings in |> deliverOnMainQueue).start(next: { [weak navigationController] privacySettings in
let controller = context.sharedContext.makeIncomingMessagePrivacyScreen(context: context, value: privacySettings.globalSettings.nonContactChatsPrivacy, exceptions: privacySettings.noPaidMessages, update: { settingValue in let controller = context.sharedContext.makeIncomingMessagePrivacyScreen(context: context, value: privacySettings.globalSettings.nonContactChatsPrivacy, exceptions: privacySettings.noPaidMessages, update: { settingValue in
let _ = context.engine.privacy.updateNonContactChatsPrivacy(value: settingValue).start() let _ = context.engine.privacy.updateNonContactChatsPrivacy(value: settingValue).start()
}) })
navigationController?.pushViewController(controller) Queue.mainQueue().after(0.4) {
navigationController?.pushViewController(controller)
Queue.mainQueue().after(1.0) {
self?.dismissAnimated()
} }
}) })
} }

View File

@ -1558,25 +1558,46 @@ public final class WebAppController: ViewController, AttachmentContainable {
controller.parentController()?.lockOrientation = lock controller.parentController()?.lockOrientation = lock
} }
case "web_app_device_storage_save_key": case "web_app_device_storage_save_key":
if let json, let requestId = json["req_id"] as? String, let key = json["key"] as? String, let value = json["value"] { if let json, let requestId = json["req_id"] as? String {
var effectiveValue: String? if let key = json["key"] as? String {
if let stringValue = value as? String { let value = json["value"]
effectiveValue = stringValue
var effectiveValue: String?
if let stringValue = value as? String {
effectiveValue = stringValue
} else if value is NSNull {
effectiveValue = nil
} else {
let data: JSON = [
"req_id": requestId,
"error": "VALUE_INVALID"
]
self.webView?.sendEvent(name: "device_storage_failed", data: data.string)
return
}
let _ = self.context.engine.peers.setBotStorageValue(peerId: controller.botId, key: key, value: effectiveValue).start(error: { [weak self] error in
var errorValue = "UNKNOWN_ERROR"
if case .quotaExceeded = error {
errorValue = "QUOTA_EXCEEDED"
}
let data: JSON = [
"req_id": requestId,
"error": errorValue
]
self?.webView?.sendEvent(name: "device_storage_failed", data: data.string)
}, completed: { [weak self] in
let data: JSON = [
"req_id": requestId
]
self?.webView?.sendEvent(name: "device_storage_key_saved", data: data.string)
})
} else { } else {
effectiveValue = nil
}
let _ = self.context.engine.peers.setBotStorageValue(peerId: controller.botId, key: key, value: effectiveValue).start(error: { [weak self] _ in
let data: JSON = [ let data: JSON = [
"req_id": requestId, "req_id": requestId,
"error": "UNKNOWN_ERROR" "error": "KEY_INVALID"
] ]
self?.webView?.sendEvent(name: "device_storage_failed", data: data.string) self.webView?.sendEvent(name: "device_storage_failed", data: data.string)
}, completed: { [weak self] in }
let data: JSON = [
"req_id": requestId
]
self?.webView?.sendEvent(name: "device_storage_key_saved", data: data.string)
})
} }
case "web_app_device_storage_get_key": case "web_app_device_storage_get_key":
if let json, let requestId = json["req_id"] as? String { if let json, let requestId = json["req_id"] as? String {
@ -1607,6 +1628,78 @@ public final class WebAppController: ViewController, AttachmentContainable {
self?.webView?.sendEvent(name: "device_storage_cleared", data: data.string) self?.webView?.sendEvent(name: "device_storage_cleared", data: data.string)
}) })
} }
case "web_app_secure_storage_save_key":
if let json, let requestId = json["req_id"] as? String {
if let key = json["key"] as? String {
let value = json["value"]
var effectiveValue: String?
if let stringValue = value as? String {
effectiveValue = stringValue
} else if value is NSNull {
effectiveValue = nil
} else {
let data: JSON = [
"req_id": requestId,
"error": "VALUE_INVALID"
]
self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
return
}
let _ = (WebAppSecureStorage.setValue(userId: self.context.account.peerId, botId: controller.botId, key: key, value: effectiveValue)
|> deliverOnMainQueue).start(error: { [weak self] error in
var errorValue = "UNKNOWN_ERROR"
if case .quotaExceeded = error {
errorValue = "QUOTA_EXCEEDED"
}
let data: JSON = [
"req_id": requestId,
"error": errorValue
]
self?.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
}, completed: { [weak self] in
let data: JSON = [
"req_id": requestId
]
self?.webView?.sendEvent(name: "secure_storage_key_saved", data: data.string)
})
} else {
let data: JSON = [
"req_id": requestId,
"error": "KEY_INVALID"
]
self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
}
}
case "web_app_secure_storage_get_key":
if let json, let requestId = json["req_id"] as? String {
if let key = json["key"] as? String {
let _ = (WebAppSecureStorage.getValue(userId: self.context.account.peerId, botId: controller.botId, key: key)
|> deliverOnMainQueue).start(next: { [weak self] value in
let data: JSON = [
"req_id": requestId,
"value": value ?? NSNull()
]
self?.webView?.sendEvent(name: "secure_storage_key_received", data: data.string)
})
} else {
let data: JSON = [
"req_id": requestId,
"error": "KEY_INVALID"
]
self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
}
}
case "web_app_secure_storage_clear":
if let json, let requestId = json["req_id"] as? String {
let _ = (WebAppSecureStorage.clearStorage(userId: self.context.account.peerId, botId: controller.botId)
|> deliverOnMainQueue).start(completed: { [weak self] in
let data: JSON = [
"req_id": requestId
]
self?.webView?.sendEvent(name: "secure_storage_cleared", data: data.string)
})
}
default: default:
break break
} }

View File

@ -0,0 +1,147 @@
import Foundation
import Security
import SwiftSignalKit
import TelegramCore
final class WebAppSecureStorage {
enum Error {
case quotaExceeded
case unknown
}
static private let maxKeyCount = 10
private init() {
}
static private func keyPrefix(userId: EnginePeer.Id, botId: EnginePeer.Id) -> String {
return "A\(UInt64(bitPattern: userId.toInt64()))WebBot\(UInt64(bitPattern: botId.toInt64()))Key_"
}
static private func makeQuery(userId: EnginePeer.Id, botId: EnginePeer.Id, key: String) -> [String: Any] {
let identifier = self.keyPrefix(userId: userId, botId: botId) + key
return [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: identifier,
kSecAttrService as String: "TMASecureStorage"
]
}
static private func countKeys(userId: EnginePeer.Id, botId: EnginePeer.Id) -> Int {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "TMASecureStorage",
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: true
]
var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecSuccess, let items = result as? [[String: Any]] {
let relevantPrefix = self.keyPrefix(userId: userId, botId: botId)
let count = items.filter {
if let account = $0[kSecAttrAccount as String] as? String {
return account.hasPrefix(relevantPrefix)
}
return false
}.count
return count
}
return 0
}
static func setValue(userId: EnginePeer.Id, botId: EnginePeer.Id, key: String, value: String?) -> Signal<Never, WebAppSecureStorage.Error> {
var query = makeQuery(userId: userId, botId: botId, key: key)
if value == nil {
let status = SecItemDelete(query as CFDictionary)
if status == errSecSuccess || status == errSecItemNotFound {
return .complete()
} else {
return .fail(.unknown)
}
}
guard let valueData = value?.data(using: .utf8) else {
return .fail(.unknown)
}
query[kSecAttrAccessible as String] = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
let status = SecItemCopyMatching(query as CFDictionary, nil)
if status == errSecSuccess {
let updateQuery: [String: Any] = [
kSecValueData as String: valueData
]
let updateStatus = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
if updateStatus == errSecSuccess {
return .complete()
} else {
return .fail(.unknown)
}
} else if status == errSecItemNotFound {
let currentCount = countKeys(userId: userId, botId: botId)
if currentCount >= maxKeyCount {
return .fail(.quotaExceeded)
}
query[kSecValueData as String] = valueData
let createStatus = SecItemAdd(query as CFDictionary, nil)
if createStatus == errSecSuccess {
return .complete()
} else {
return .fail(.unknown)
}
} else {
return .fail(.unknown)
}
}
static func getValue(userId: EnginePeer.Id, botId: EnginePeer.Id, key: String) -> Signal<String?, WebAppSecureStorage.Error> {
var query = makeQuery(userId: userId, botId: botId, key: key)
query[kSecReturnData as String] = true
query[kSecMatchLimit as String] = kSecMatchLimitOne
var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecSuccess, let data = result as? Data, let value = String(data: data, encoding: .utf8) {
return .single(value)
} else if status == errSecItemNotFound {
return .single(nil)
} else {
return .fail(.unknown)
}
}
static func clearStorage(userId: EnginePeer.Id, botId: EnginePeer.Id) -> Signal<Never, WebAppSecureStorage.Error> {
let serviceQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "TMASecureStorage",
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: true
]
var result: CFTypeRef?
let status = SecItemCopyMatching(serviceQuery as CFDictionary, &result)
if status == errSecSuccess, let items = result as? [[String: Any]] {
let relevantPrefix = self.keyPrefix(userId: userId, botId: botId)
for item in items {
if let account = item[kSecAttrAccount as String] as? String, account.hasPrefix(relevantPrefix) {
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecAttrService as String: "TMASecureStorage"
]
SecItemDelete(deleteQuery as CFDictionary)
}
}
}
return .complete()
}
}