mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add manual account deletion
This commit is contained in:
parent
ecc9279281
commit
25a09c5451
BIN
Telegram/Telegram-iOS/Resources/Delete1.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Delete1.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Delete2.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Delete2.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Delete3.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Delete3.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Delete4.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Delete4.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Delete5.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Delete5.tgs
Normal file
Binary file not shown.
Binary file not shown.
@ -7770,6 +7770,8 @@ Sorry for the inconvenience.";
|
||||
"DeleteAccount.DeleteMyAccountTitle" = "Delete My Account";
|
||||
"DeleteAccount.DeleteMyAccount" = "Delete My Account";
|
||||
|
||||
"DeleteAccount.SavedMessages" = "Saved";
|
||||
|
||||
"DeleteAccount.ComeBackLater" = "Come Back Later";
|
||||
"DeleteAccount.Continue" = "Continue";
|
||||
|
||||
@ -7778,10 +7780,18 @@ Sorry for the inconvenience.";
|
||||
|
||||
"DeleteAccount.GroupsAndChannelsTitle" = "Your Groups and Channels";
|
||||
"DeleteAccount.GroupsAndChannelsText" = "The groups and channels you created will either get new admins or become orphaned.";
|
||||
"DeleteAccount.GroupsAndChannelsInfo" = "You can transfer group and channel ownership to other users via Chat Info > Edit > Admins. [More info]()";
|
||||
"DeleteAccount.GroupsAndChannelsInfo" = "You can transfer group and channel ownership to other users via Chat Info > Edit > Admins.";
|
||||
|
||||
"DeleteAccount.MessageHistoryTitle" = "Your Message History";
|
||||
"DeleteAccount.MessageHistoryText" = "Your chat partners will keep their message history with you, including the messages you shared in secret chats.\n\nYou can remove any messages for both sides at any time, but this will not be possible if you delete your account. [More info]()";
|
||||
"DeleteAccount.MessageHistoryText" = "Your chat partners will keep their message history with you, including the messages you shared in secret chats.\n\nYou can remove any messages for both sides at any time, but this will not be possible if you delete your account.";
|
||||
|
||||
"DeleteAccount.DeleteMessagesURL" = "https://telegram.org/faq#q-can-i-delete-my-messages";
|
||||
|
||||
"DeleteAccount.EnterPhoneNumber" = "Enter Your Phone Number";
|
||||
"DeleteAccount.InvalidPhoneNumberError" = "Invalid phone number. Please try again.";
|
||||
|
||||
"DeleteAccount.EnterPassword" = "Enter Your Password";
|
||||
"DeleteAccount.InvalidPasswordError" = "Invalid password. Please try again.";
|
||||
|
||||
"DeleteAccount.ConfirmationAlertTitle" = "Proceed to Delete Your Account?";
|
||||
"DeleteAccount.ConfirmationAlertText" = "Deleting your account will permanently delete your data!\n\nIt is imposible to reverse this action!";
|
||||
@ -7801,3 +7811,8 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Premium.Gift.Years_1" = "%@ Year";
|
||||
"Premium.Gift.Years_any" = "%@ Years";
|
||||
|
||||
"Premium.GiftedTitle.3Month" = "[%@]() has gifted you a 3-month subscription for Telegram Premium";
|
||||
"Premium.GiftedTitle.6Month" = "[%@]() has gifted you a 6-month subscription for Telegram Premium";
|
||||
"Premium.GiftedTitle.12Month" = "[%@]() has gifted you a 12-month subscription for Telegram Premium";
|
||||
"Premium.GiftedDescription" = "You now have access to additional features.";
|
||||
|
@ -120,7 +120,7 @@ class ChatListRecentPeersListItemNode: ListViewItemNode {
|
||||
peersNode = currentPeersNode
|
||||
peersNode.updateThemeAndStrings(theme: item.theme, strings: item.strings)
|
||||
} else {
|
||||
peersNode = ChatListSearchRecentPeersNode(context: item.context, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in
|
||||
peersNode = ChatListSearchRecentPeersNode(context: item.context, theme: item.theme, mode: .list(compact: false), strings: item.strings, peerSelected: { peer in
|
||||
self?.item?.peerSelected(peer)
|
||||
}, peerContextAction: { peer, node, gesture in
|
||||
self?.item?.peerContextAction(peer, node, gesture)
|
||||
|
@ -1271,7 +1271,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
let badgeNode = ASImageNode()
|
||||
badgeNode.displaysAsynchronously = false
|
||||
badgeNode.image = UIImage(bundleImageName: "Components/BadgeTest")
|
||||
badgeNode.image = UIImage(bundleImageName: "Components/AppBadge")
|
||||
self.badgeNode = badgeNode
|
||||
self.displayNode.addSubnode(badgeNode)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import ContextUI
|
||||
import AccountContext
|
||||
|
||||
public enum HorizontalPeerItemMode {
|
||||
case list
|
||||
case list(compact: Bool)
|
||||
case actionSheet
|
||||
}
|
||||
|
||||
@ -25,13 +25,13 @@ public final class HorizontalPeerItem: ListViewItem {
|
||||
let context: AccountContext
|
||||
public let peer: EnginePeer
|
||||
let action: (EnginePeer) -> Void
|
||||
let contextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void
|
||||
let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
let isPeerSelected: (EnginePeer.Id) -> Bool
|
||||
let customWidth: CGFloat?
|
||||
let presence: EnginePeer.Presence?
|
||||
let unreadBadge: (Int32, Bool)?
|
||||
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, context: AccountContext, peer: EnginePeer, presence: EnginePeer.Presence?, unreadBadge: (Int32, Bool)?, action: @escaping (EnginePeer) -> Void, contextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, customWidth: CGFloat?) {
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, context: AccountContext, peer: EnginePeer, presence: EnginePeer.Presence?, unreadBadge: (Int32, Bool)?, action: @escaping (EnginePeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, customWidth: CGFloat?) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.mode = mode
|
||||
@ -111,11 +111,6 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
item.action(item.peer)
|
||||
}
|
||||
}
|
||||
self.peerNode.contextAction = { [weak self] node, gesture in
|
||||
if let item = self?.item {
|
||||
item.contextAction(item.peer, node, gesture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
@ -186,10 +181,25 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.peerNode.theme = itemTheme
|
||||
if case let .list(compact) = item.mode {
|
||||
strongSelf.peerNode.compact = compact
|
||||
} else {
|
||||
strongSelf.peerNode.compact = false
|
||||
}
|
||||
strongSelf.peerNode.setup(context: item.context, theme: item.theme, strings: item.strings, peer: EngineRenderedPeer(peer: item.peer), numberOfLines: 1, synchronousLoad: synchronousLoads)
|
||||
strongSelf.peerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.size)
|
||||
strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false)
|
||||
|
||||
if let contextAction = item.contextAction {
|
||||
strongSelf.peerNode.contextAction = { [weak item] node, gesture in
|
||||
if let item = item {
|
||||
contextAction(item.peer, node, gesture)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strongSelf.peerNode.contextAction = nil
|
||||
}
|
||||
|
||||
let badgeBackgroundWidth: CGFloat
|
||||
if let currentBadgeBackgroundImage = currentBadgeBackgroundImage {
|
||||
strongSelf.badgeBackgroundNode.image = currentBadgeBackgroundImage
|
||||
|
@ -113,8 +113,9 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 28.0 + params.leftInset
|
||||
let topInset: CGFloat = 124.0
|
||||
let leftInset: CGFloat = 24.0 + params.leftInset
|
||||
let iconSize = CGSize(width: 140.0, height: 140.0)
|
||||
let topInset: CGFloat = iconSize.height - 4.0
|
||||
let spacing: CGFloat = 5.0
|
||||
|
||||
let attributedTitle = NSAttributedString(string: item.title ?? "", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
|
||||
@ -123,9 +124,9 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}))
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var contentSize = CGSize(width: params.width, height: topInset + textLayout.size.height)
|
||||
if let _ = item.title {
|
||||
@ -138,13 +139,12 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.item == nil {
|
||||
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.animationName), width: 192, height: 192, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.animationName), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||
strongSelf.animationNode.visibility = true
|
||||
}
|
||||
strongSelf.item = item
|
||||
strongSelf.accessibilityLabel = attributedText.string
|
||||
|
||||
let iconSize = CGSize(width: 128.0, height: 128.0)
|
||||
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
|
||||
strongSelf.animationNode.updateLayout(size: iconSize)
|
||||
|
||||
|
@ -275,9 +275,8 @@ class GiftAvatarComponent: Component {
|
||||
}
|
||||
|
||||
self.hasIdleAnimations = component.hasIdleAnimations
|
||||
|
||||
let avatarSize = CGSize(width: 100.0, height: 100.0)
|
||||
self.avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: component.peer, size: avatarSize, font: avatarPlaceholderFont(size: 78.0), fullSize: true))
|
||||
self.avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: component.peer, size: avatarSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true))
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarSize.width) / 2.0), y: 63.0), size: avatarSize)
|
||||
|
||||
return availableSize
|
||||
|
@ -84,6 +84,8 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
|
||||
private var peer: EngineRenderedPeer?
|
||||
|
||||
public var compact = false
|
||||
|
||||
public var theme: SelectablePeerNodeTheme = SelectablePeerNodeTheme(textColor: .black, secretTextColor: .green, selectedTextColor: .blue, checkBackgroundColor: .white, checkFillColor: .blue, checkColor: .white, avatarPlaceholderColor: .white) {
|
||||
didSet {
|
||||
if !self.theme.isEqual(to: oldValue) {
|
||||
@ -147,7 +149,7 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
let text: String
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if peer.peerId == context.account.peerId {
|
||||
text = strings.DialogList_SavedMessages
|
||||
text = self.compact ? strings.DeleteAccount_SavedMessages : strings.DialogList_SavedMessages
|
||||
overrideImage = .savedMessagesIcon
|
||||
} else if peer.peerId.isReplies {
|
||||
text = strings.DialogList_Replies
|
||||
|
@ -102,6 +102,7 @@ swift_library(
|
||||
"//submodules/PaymentMethodUI:PaymentMethodUI",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/InviteLinksUI:InviteLinksUI",
|
||||
"//submodules/HorizontalPeerItem:HorizontalPeerItem",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -286,5 +286,4 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode {
|
||||
@objc func countryPressed() {
|
||||
self.selectCountryCode?()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,25 +13,47 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UrlHandling
|
||||
import InviteLinksUI
|
||||
import CountrySelectionUI
|
||||
import PhoneInputNode
|
||||
|
||||
private struct DeleteAccountDataArguments {
|
||||
let context: AccountContext
|
||||
let openLink: (String) -> Void
|
||||
let selectCountryCode: () -> Void
|
||||
let updatePassword: (String) -> Void
|
||||
let proceed: () -> Void
|
||||
}
|
||||
|
||||
private enum DeleteAccountDataSection: Int32 {
|
||||
case header
|
||||
case main
|
||||
}
|
||||
|
||||
private enum DeleteAccountEntryTag: Equatable, ItemListItemTag {
|
||||
case password
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? DeleteAccountEntryTag {
|
||||
return self == other
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
|
||||
case header(PresentationTheme, String, String, String)
|
||||
|
||||
case peers(PresentationTheme, [Peer])
|
||||
case peers(PresentationTheme, [EnginePeer])
|
||||
case phone(PresentationTheme, PresentationStrings)
|
||||
case password(PresentationTheme, String)
|
||||
case info(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .header, .peers, .info:
|
||||
case .header:
|
||||
return DeleteAccountDataSection.header.rawValue
|
||||
case .peers, .info, .phone, .password:
|
||||
return DeleteAccountDataSection.main.rawValue
|
||||
}
|
||||
}
|
||||
@ -43,7 +65,11 @@ private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
|
||||
case .peers:
|
||||
return 1
|
||||
case .info:
|
||||
return 2
|
||||
case .phone:
|
||||
return 3
|
||||
case .password:
|
||||
return 4
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +82,7 @@ private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
|
||||
return false
|
||||
}
|
||||
case let .peers(lhsTheme, lhsPeers):
|
||||
if case let .peers(rhsTheme, rhsPeers) = rhs, lhsTheme === rhsTheme, arePeerArraysEqual(lhsPeers, rhsPeers) {
|
||||
if case let .peers(rhsTheme, rhsPeers) = rhs, lhsTheme === rhsTheme, lhsPeers == rhsPeers {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -67,6 +93,19 @@ private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .phone(lhsTheme, lhsStrings):
|
||||
if case let .phone(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .password(lhsTheme, lhsPlaceholder):
|
||||
if case let .password(rhsTheme, rhsPlaceholder) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,14 +119,26 @@ private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
|
||||
case let .header(theme, animation, title, text):
|
||||
return InviteLinkHeaderItem(context: arguments.context, theme: theme, title: title, text: text, animationName: animation, sectionId: self.section, linkAction: nil)
|
||||
case let .peers(_, peers):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(peers.first?.debugDisplayTitle ?? ""), sectionId: self.section)
|
||||
return DeleteAccountPeersItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, peers: peers, sectionId: self.section)
|
||||
case let .info(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case .phone:
|
||||
return DeleteAccountPhoneItem(theme: presentationData.theme, strings: presentationData.strings, value: (nil, nil, ""), sectionId: self.section, selectCountryCode: {
|
||||
arguments.selectCountryCode()
|
||||
}, updated: { _ in
|
||||
|
||||
})
|
||||
case let .password(_, placeholder):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: "", placeholder: placeholder, type: .password, returnKeyType: .done, tag: DeleteAccountEntryTag.password, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updatePassword(value)
|
||||
}, action: {
|
||||
arguments.proceed()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteAccountDataEntries(presentationData: PresentationData, mode: DeleteAccountDataMode, peers: [Peer]) -> [DeleteAccountDataEntry] {
|
||||
private func deleteAccountDataEntries(presentationData: PresentationData, mode: DeleteAccountDataMode, peers: [EnginePeer]) -> [DeleteAccountDataEntry] {
|
||||
var entries: [DeleteAccountDataEntry] = []
|
||||
|
||||
let headerTitle: String
|
||||
@ -96,24 +147,45 @@ private func deleteAccountDataEntries(presentationData: PresentationData, mode:
|
||||
|
||||
switch mode {
|
||||
case .peers:
|
||||
headerAnimation = ""
|
||||
headerAnimation = "Delete1"
|
||||
headerTitle = presentationData.strings.DeleteAccount_CloudStorageTitle
|
||||
headerText = presentationData.strings.DeleteAccount_CloudStorageText
|
||||
case .groups:
|
||||
headerAnimation = ""
|
||||
headerAnimation = "Delete2"
|
||||
headerTitle = presentationData.strings.DeleteAccount_GroupsAndChannelsTitle
|
||||
headerText = presentationData.strings.DeleteAccount_GroupsAndChannelsText
|
||||
case .messages:
|
||||
headerAnimation = ""
|
||||
headerAnimation = "Delete3"
|
||||
headerTitle = presentationData.strings.DeleteAccount_MessageHistoryTitle
|
||||
headerText = presentationData.strings.DeleteAccount_MessageHistoryText
|
||||
case .phone:
|
||||
headerAnimation = "Delete4"
|
||||
headerTitle = presentationData.strings.DeleteAccount_EnterPhoneNumber
|
||||
headerText = ""
|
||||
case .password:
|
||||
headerAnimation = "Delete5"
|
||||
headerTitle = presentationData.strings.DeleteAccount_EnterPassword
|
||||
headerText = ""
|
||||
}
|
||||
|
||||
entries.append(.header(presentationData.theme, headerAnimation, headerTitle, headerText))
|
||||
entries.append(.peers(presentationData.theme, peers))
|
||||
|
||||
if case .groups = mode {
|
||||
entries.append(.info(presentationData.theme, presentationData.strings.DeleteAccount_GroupsAndChannelsInfo))
|
||||
switch mode {
|
||||
case .peers:
|
||||
if !peers.isEmpty {
|
||||
entries.append(.peers(presentationData.theme, peers))
|
||||
}
|
||||
case .groups:
|
||||
if !peers.isEmpty {
|
||||
entries.append(.peers(presentationData.theme, peers))
|
||||
entries.append(.info(presentationData.theme, presentationData.strings.DeleteAccount_GroupsAndChannelsInfo))
|
||||
}
|
||||
case .messages:
|
||||
break
|
||||
case .phone:
|
||||
entries.append(.phone(presentationData.theme, presentationData.strings))
|
||||
case .password:
|
||||
entries.append(.password(presentationData.theme, presentationData.strings.LoginPassword_PasswordPlaceholder))
|
||||
}
|
||||
|
||||
return entries
|
||||
@ -121,55 +193,150 @@ private func deleteAccountDataEntries(presentationData: PresentationData, mode:
|
||||
|
||||
enum DeleteAccountDataMode {
|
||||
case peers
|
||||
case groups
|
||||
case groups([EnginePeer])
|
||||
case messages
|
||||
case phone
|
||||
case password
|
||||
}
|
||||
|
||||
func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDataMode) -> ViewController {
|
||||
private struct DeleteAccountDataState: Equatable {
|
||||
var password: String
|
||||
var isLoading: Bool
|
||||
|
||||
static func == (lhs: DeleteAccountDataState, rhs: DeleteAccountDataState) -> Bool {
|
||||
return lhs.password == rhs.password && lhs.isLoading == rhs.isLoading
|
||||
}
|
||||
}
|
||||
|
||||
func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDataMode, twoStepAuthData: TwoStepVerificationAccessConfiguration?) -> ViewController {
|
||||
let initialState = DeleteAccountDataState(password: "", isLoading: false)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((DeleteAccountDataState) -> DeleteAccountDataState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var replaceTopControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var updateCodeImpl: (() -> Void)?
|
||||
|
||||
var activateInputImpl: (() -> Void)?
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
|
||||
if case .phone = mode {
|
||||
loadServerCountryCodes(accountManager: context.sharedContext.accountManager, engine: context.engine, completion: {
|
||||
updateCodeImpl?()
|
||||
})
|
||||
}
|
||||
|
||||
var updateCountryCodeImpl: ((Int32, String) -> Void)?
|
||||
var proceedImpl: (() -> Void)?
|
||||
|
||||
let arguments = DeleteAccountDataArguments(context: context, openLink: { _ in
|
||||
|
||||
}, selectCountryCode: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = AuthorizationSequenceCountrySelectionController(strings: presentationData.strings, theme: presentationData.theme)
|
||||
controller.completeWithCountryCode = { code, name in
|
||||
updateCountryCodeImpl?(Int32(code), name)
|
||||
activateInputImpl?()
|
||||
}
|
||||
dismissInputImpl?()
|
||||
pushControllerImpl?(controller)
|
||||
}, updatePassword: { password in
|
||||
updateState { current in
|
||||
var updated = current
|
||||
updated.password = password
|
||||
return updated
|
||||
}
|
||||
}, proceed: {
|
||||
proceedImpl?()
|
||||
})
|
||||
|
||||
let peers: Signal<[Peer], NoError> = .single([])
|
||||
|
||||
let preloadedGroupPeers = Promise<[EnginePeer]>([])
|
||||
|
||||
let peers: Signal<[EnginePeer], NoError>
|
||||
switch mode {
|
||||
case .peers:
|
||||
peers = combineLatest(
|
||||
context.engine.peers.recentPeers()
|
||||
|> map { recentPeers -> [EnginePeer] in
|
||||
if case let .peers(peers) = recentPeers {
|
||||
return peers.map { EnginePeer($0) }
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
) |> map { recentPeers, accountPeer -> [EnginePeer] in
|
||||
var peers: [EnginePeer] = []
|
||||
if let accountPeer = accountPeer {
|
||||
peers.append(accountPeer)
|
||||
}
|
||||
peers.append(contentsOf: recentPeers.prefix(9))
|
||||
return peers
|
||||
}
|
||||
|
||||
preloadedGroupPeers.set(context.engine.peers.adminedPublicChannels(scope: .all)
|
||||
|> map { peers -> [EnginePeer] in
|
||||
return peers.map { EnginePeer($0) }
|
||||
})
|
||||
case let .groups(preloadedPeers):
|
||||
peers = .single(preloadedPeers.shuffled())
|
||||
default:
|
||||
peers = .single([])
|
||||
}
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
peers
|
||||
peers,
|
||||
statePromise.get()
|
||||
)
|
||||
|> map { presentationData, peers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { presentationData, peers, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
let footerItem = DeleteAccountFooterItem(theme: presentationData.theme, title: presentationData.strings.DeleteAccount_ComeBackLater, secondaryTitle: presentationData.strings.DeleteAccount_Continue, action: {
|
||||
var focusItemTag: DeleteAccountEntryTag?
|
||||
var buttonTitle: String
|
||||
switch mode {
|
||||
case .phone:
|
||||
buttonTitle = ""
|
||||
case .password:
|
||||
buttonTitle = ""
|
||||
focusItemTag = .password
|
||||
default:
|
||||
buttonTitle = presentationData.strings.DeleteAccount_ComeBackLater
|
||||
}
|
||||
|
||||
let rightNavigationButton: ItemListNavigationButton?
|
||||
if state.isLoading {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||
} else {
|
||||
rightNavigationButton = nil
|
||||
}
|
||||
|
||||
let footerItem = DeleteAccountFooterItem(theme: presentationData.theme, title: buttonTitle, secondaryTitle: presentationData.strings.DeleteAccount_Continue, action: {
|
||||
dismissImpl?()
|
||||
}, secondaryAction: {
|
||||
let nextMode: DeleteAccountDataMode?
|
||||
switch mode {
|
||||
case .peers:
|
||||
nextMode = .groups
|
||||
case .groups:
|
||||
nextMode = .messages
|
||||
case .messages:
|
||||
nextMode = nil
|
||||
}
|
||||
|
||||
if let nextMode = nextMode {
|
||||
let controller = deleteAccountDataController(context: context, mode: nextMode)
|
||||
replaceTopControllerImpl?(controller)
|
||||
}
|
||||
proceedImpl?()
|
||||
})
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.DeleteAccount_DeleteMyAccountTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deleteAccountDataEntries(presentationData: presentationData, mode: mode, peers: peers), style: .blocks, footerItem: footerItem)
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.DeleteAccount_DeleteMyAccountTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deleteAccountDataEntries(presentationData: presentationData, mode: mode, peers: peers), style: .blocks, focusItemTag: focusItemTag, footerItem: footerItem)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal, tabBarItem: nil)
|
||||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root))
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
replaceTopControllerImpl = { [weak controller] c in
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
navigationController.pushViewController(c, completion: { [weak navigationController, weak controller, weak c] in
|
||||
@ -184,7 +351,172 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat
|
||||
dismissImpl = { [weak controller] in
|
||||
let _ = controller?.dismiss()
|
||||
}
|
||||
|
||||
updateCodeImpl = { [weak controller] in
|
||||
controller?.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? DeleteAccountPhoneItemNode {
|
||||
itemNode.updateCountryCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activateInputImpl = { [weak controller] in
|
||||
controller?.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? DeleteAccountPhoneItemNode {
|
||||
itemNode.activateInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
dismissInputImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
}
|
||||
controller.didAppear = { firstTime in
|
||||
if !firstTime {
|
||||
return
|
||||
}
|
||||
activateInputImpl?()
|
||||
}
|
||||
|
||||
updateCountryCodeImpl = { [weak controller] code, name in
|
||||
controller?.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? DeleteAccountPhoneItemNode {
|
||||
itemNode.updateCountryCode(code: code, name: name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proceedImpl = { [weak controller] in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let action: ([EnginePeer]) -> Void = { preloadedPeers in
|
||||
let nextMode: DeleteAccountDataMode?
|
||||
switch mode {
|
||||
case .peers:
|
||||
if !preloadedPeers.isEmpty {
|
||||
nextMode = .groups(preloadedPeers)
|
||||
} else {
|
||||
nextMode = .messages
|
||||
}
|
||||
case .groups:
|
||||
nextMode = .messages
|
||||
case .messages:
|
||||
nextMode = .phone
|
||||
case .phone:
|
||||
if let twoStepAuthData = twoStepAuthData, case .set = twoStepAuthData {
|
||||
nextMode = .password
|
||||
} else {
|
||||
nextMode = nil
|
||||
}
|
||||
case .password:
|
||||
nextMode = nil
|
||||
}
|
||||
|
||||
if let nextMode = nextMode {
|
||||
let controller = deleteAccountDataController(context: context, mode: nextMode, twoStepAuthData: twoStepAuthData)
|
||||
replaceTopControllerImpl?(controller)
|
||||
} else {
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.DeleteAccount_ConfirmationAlertTitle, text: presentationData.strings.DeleteAccount_ConfirmationAlertText, actions: [TextAlertAction(type: .destructiveAction, title: presentationData.strings.DeleteAccount_ConfirmationAlertDelete, action: {
|
||||
updateState { current in
|
||||
var updated = current
|
||||
updated.isLoading = true
|
||||
return updated
|
||||
}
|
||||
|
||||
let accountId = context.account.id
|
||||
let accountManager = context.sharedContext.accountManager
|
||||
let _ = (context.engine.auth.deleteAccount(reason: "Manual")
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
updateState { current in
|
||||
var updated = current
|
||||
updated.isLoading = false
|
||||
return updated
|
||||
}
|
||||
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
|
||||
}, completed: {
|
||||
dismissImpl?()
|
||||
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: true).start()
|
||||
})
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
})]))
|
||||
}
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .peers:
|
||||
let _ = (preloadedGroupPeers.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peers in
|
||||
action(peers)
|
||||
})
|
||||
case .phone:
|
||||
var phoneNumber: String?
|
||||
controller?.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? DeleteAccountPhoneItemNode {
|
||||
phoneNumber = itemNode.phoneNumber
|
||||
}
|
||||
}
|
||||
|
||||
if let phoneNumber = phoneNumber, phoneNumber.count > 4 {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue)
|
||||
.start(next: { accountPeer in
|
||||
if let accountPeer = accountPeer, case let .user(user) = accountPeer, var phone = user.phone {
|
||||
if !phone.hasPrefix("+") {
|
||||
phone = "+\(phone)"
|
||||
}
|
||||
if phone != phoneNumber {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.DeleteAccount_InvalidPhoneNumberError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
|
||||
return
|
||||
}
|
||||
action([])
|
||||
}
|
||||
})
|
||||
}
|
||||
case .password:
|
||||
let state = stateValue.with { $0 }
|
||||
if !state.password.isEmpty {
|
||||
updateState { current in
|
||||
var updated = current
|
||||
updated.isLoading = true
|
||||
return updated
|
||||
}
|
||||
|
||||
let _ = (context.engine.auth.requestTwoStepVerifiationSettings(password: state.password)
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
updateState { current in
|
||||
var updated = current
|
||||
updated.isLoading = false
|
||||
return updated
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.LoginPassword_FloodError
|
||||
case .invalidPassword:
|
||||
text = presentationData.strings.DeleteAccount_InvalidPasswordError
|
||||
default:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
|
||||
}, completed: {
|
||||
updateState { current in
|
||||
var updated = current
|
||||
updated.isLoading = false
|
||||
return updated
|
||||
}
|
||||
|
||||
action([])
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
action([])
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,9 @@ final class DeleteAccountFooterItem: ItemListControllerFooterItem {
|
||||
final class DeleteAccountFooterItemNode: ItemListControllerFooterItemNode {
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let clipNode: ASDisplayNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
private let secondaryButtonNode: HighlightTrackingButtonNode
|
||||
private let secondaryButtonNode: HighlightableButtonNode
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
@ -64,16 +65,20 @@ final class DeleteAccountFooterItemNode: ItemListControllerFooterItemNode {
|
||||
self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.tabBar.backgroundColor)
|
||||
self.separatorNode = ASDisplayNode()
|
||||
|
||||
self.clipNode = ASDisplayNode()
|
||||
self.clipNode.clipsToBounds = true
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true)
|
||||
|
||||
self.secondaryButtonNode = HighlightTrackingButtonNode()
|
||||
self.secondaryButtonNode = HighlightableButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.secondaryButtonNode)
|
||||
self.addSubnode(self.clipNode)
|
||||
self.clipNode.addSubnode(self.buttonNode)
|
||||
self.clipNode.addSubnode(self.secondaryButtonNode)
|
||||
|
||||
self.secondaryButtonNode.addTarget(self, action: #selector(self.secondaryButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
@ -121,8 +126,19 @@ final class DeleteAccountFooterItemNode: ItemListControllerFooterItemNode {
|
||||
let secondaryButtonSize = self.secondaryButtonNode.measure(CGSize(width: buttonWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
var panelHeight: CGFloat = buttonHeight + topInset + spacing + secondaryButtonSize.height + bottomInset
|
||||
|
||||
var buttonOffset: CGFloat = 0.0
|
||||
let totalPanelHeight: CGFloat
|
||||
|
||||
if (self.buttonNode.title?.isEmpty ?? false) {
|
||||
buttonOffset = -buttonHeight - topInset
|
||||
self.buttonNode.alpha = 0.0
|
||||
} else {
|
||||
self.buttonNode.alpha = 1.0
|
||||
}
|
||||
|
||||
if let inputHeight = layout.inputHeight, inputHeight > 0.0 {
|
||||
panelHeight += buttonOffset
|
||||
totalPanelHeight = panelHeight + insets.bottom
|
||||
} else {
|
||||
panelHeight += insets.bottom
|
||||
@ -130,13 +146,15 @@ final class DeleteAccountFooterItemNode: ItemListControllerFooterItemNode {
|
||||
}
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - totalPanelHeight), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: panelFrame.minY + topInset), size: CGSize(width: buttonWidth, height: buttonHeight)))
|
||||
|
||||
transition.updateFrame(node: self.secondaryButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - secondaryButtonSize.width) / 2.0), y: panelFrame.minY + topInset + buttonHeight + spacing), size: secondaryButtonSize))
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: panelFrame)
|
||||
self.backgroundNode.update(size: panelFrame.size, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.clipNode, frame: panelFrame)
|
||||
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: topInset + buttonOffset), size: CGSize(width: buttonWidth, height: buttonHeight)))
|
||||
transition.updateFrame(node: self.secondaryButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - secondaryButtonSize.width) / 2.0), y: topInset + buttonHeight + spacing + buttonOffset), size: secondaryButtonSize))
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: UIScreenPixel)))
|
||||
|
||||
return panelHeight
|
||||
|
@ -15,14 +15,17 @@ import PresentationDataUtils
|
||||
import UrlHandling
|
||||
import AccountUtils
|
||||
import PremiumUI
|
||||
import PasswordSetupUI
|
||||
|
||||
private struct DeleteAccountOptionsArguments {
|
||||
let changePhoneNumber: () -> Void
|
||||
let addAccount: () -> Void
|
||||
let setupPrivacy: () -> Void
|
||||
let setTwoStepAuth: () -> Void
|
||||
let setupTwoStepAuth: () -> Void
|
||||
let setPasscode: () -> Void
|
||||
let clearCache: () -> Void
|
||||
let clearSyncedContacts: () -> Void
|
||||
let deleteChats: () -> Void
|
||||
let contactSupport: () -> Void
|
||||
let deleteAccount: () -> Void
|
||||
}
|
||||
@ -111,8 +114,8 @@ private enum DeleteAccountOptionsEntry: ItemListNodeEntry, Equatable {
|
||||
arguments.setupPrivacy()
|
||||
})
|
||||
case let .setTwoStepAuth(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.setPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.setTwoStepAuth()
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetTwoStepAuth, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.setupTwoStepAuth()
|
||||
})
|
||||
case let .setPasscode(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
@ -124,11 +127,11 @@ private enum DeleteAccountOptionsEntry: ItemListNodeEntry, Equatable {
|
||||
})
|
||||
case let .clearSyncedContacts(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.clearSynced, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.clearCache()
|
||||
arguments.clearSyncedContacts()
|
||||
})
|
||||
case let .deleteChats(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteChats, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.clearCache()
|
||||
arguments.deleteChats()
|
||||
})
|
||||
case let .contactSupport(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
@ -168,14 +171,14 @@ private func deleteAccountOptionsEntries(presentationData: PresentationData, can
|
||||
return entries
|
||||
}
|
||||
|
||||
public func deleteAccountOptionsController(context: AccountContext, navigationController: NavigationController, hasTwoStepAuth: Bool) -> ViewController {
|
||||
public func deleteAccountOptionsController(context: AccountContext, navigationController: NavigationController, hasTwoStepAuth: Bool, twoStepAuthData: TwoStepVerificationAccessConfiguration?) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var replaceTopControllerImpl: ((ViewController, Bool) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
let supportPeerDisposable = MetaDisposable()
|
||||
|
||||
|
||||
let arguments = DeleteAccountOptionsArguments(changePhoneNumber: {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.engine.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { accountPeer in
|
||||
@ -224,9 +227,30 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo
|
||||
}
|
||||
})
|
||||
}, setupPrivacy: {
|
||||
replaceTopControllerImpl?(makePrivacyAndSecurityController(context: context), false)
|
||||
}, setupTwoStepAuth: {
|
||||
if let data = twoStepAuthData {
|
||||
switch data {
|
||||
case .set:
|
||||
break
|
||||
case let .notSet(pendingEmail):
|
||||
if pendingEmail == nil {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = TwoFactorAuthSplashScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .intro(.init(
|
||||
title: presentationData.strings.TwoFactorSetup_Intro_Title,
|
||||
text: presentationData.strings.TwoFactorSetup_Intro_Text,
|
||||
actionText: presentationData.strings.TwoFactorSetup_Intro_Action,
|
||||
doneText: presentationData.strings.TwoFactorSetup_Done_Action
|
||||
)))
|
||||
|
||||
}, setTwoStepAuth: {
|
||||
replaceTopControllerImpl?(controller, false)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controller = twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: twoStepAuthData.flatMap({ Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>.single(.access(configuration: $0)) })))
|
||||
replaceTopControllerImpl?(controller, false)
|
||||
}, setPasscode: {
|
||||
let _ = passcodeOptionsAccessController(context: context, pushController: { controller in
|
||||
replaceTopControllerImpl?(controller, false)
|
||||
@ -241,11 +265,44 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo
|
||||
}, clearCache: {
|
||||
pushControllerImpl?(storageUsageController(context: context))
|
||||
dismissImpl?()
|
||||
}, clearSyncedContacts: {
|
||||
replaceTopControllerImpl?(dataPrivacyController(context: context), false)
|
||||
}, deleteChats: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var faqUrl = presentationData.strings.DeleteAccount_DeleteMessagesURL
|
||||
if faqUrl == "DeleteAccount.DeleteMessagesURL" || faqUrl.isEmpty {
|
||||
faqUrl = "https://telegram.org/faq#q-can-i-delete-my-messages"
|
||||
}
|
||||
let resolvedUrl = resolveInstantViewUrl(account: context.account, url: faqUrl)
|
||||
|
||||
let resolvedUrlPromise = Promise<ResolvedUrl>()
|
||||
resolvedUrlPromise.set(resolvedUrl)
|
||||
|
||||
let openFaq: (Promise<ResolvedUrl>) -> Void = { resolvedUrl in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
presentControllerImpl?(controller, nil)
|
||||
let _ = (resolvedUrl.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] resolvedUrl in
|
||||
controller?.dismiss()
|
||||
dismissImpl?()
|
||||
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in
|
||||
pushControllerImpl?(controller)
|
||||
}, dismissInput: {}, contentContext: nil)
|
||||
})
|
||||
}
|
||||
|
||||
openFaq(resolvedUrlPromise)
|
||||
}, contactSupport: { [weak navigationController] in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let supportPeer = Promise<PeerId?>()
|
||||
supportPeer.set(context.engine.peers.supportPeerId())
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
|
||||
var faqUrl = presentationData.strings.Settings_FAQ_URL
|
||||
if faqUrl == "Settings.FAQ_URL" || faqUrl.isEmpty {
|
||||
faqUrl = "https://telegram.org/faq#general"
|
||||
@ -289,7 +346,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo
|
||||
})
|
||||
]), nil)
|
||||
}, deleteAccount: {
|
||||
let controller = deleteAccountDataController(context: context, mode: .peers)
|
||||
let controller = deleteAccountDataController(context: context, mode: .peers, twoStepAuthData: twoStepAuthData)
|
||||
replaceTopControllerImpl?(controller, true)
|
||||
})
|
||||
|
||||
@ -337,7 +394,18 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo
|
||||
}
|
||||
})
|
||||
} else {
|
||||
navigationController?.replaceTopController(c, animated: true)
|
||||
if c is PrivacyAndSecurityControllerImpl {
|
||||
if let navigationController = navigationController {
|
||||
if let existing = navigationController.viewControllers.first(where: { $0 is PrivacyAndSecurityControllerImpl }) as? ViewController {
|
||||
existing.scrollToTop?()
|
||||
dismissImpl?()
|
||||
} else {
|
||||
navigationController.replaceTopController(c, animated: true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
navigationController?.replaceTopController(c, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
|
296
submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift
Normal file
296
submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift
Normal file
@ -0,0 +1,296 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import HorizontalPeerItem
|
||||
import AccountContext
|
||||
import MergeLists
|
||||
|
||||
private struct PeersEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let peer: EnginePeer
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
|
||||
var stableId: EnginePeer.Id {
|
||||
return self.peer.id
|
||||
}
|
||||
|
||||
static func ==(lhs: PeersEntry, rhs: PeersEntry) -> Bool {
|
||||
if lhs.index != rhs.index {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static func <(lhs: PeersEntry, rhs: PeersEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext) -> ListViewItem {
|
||||
return HorizontalPeerItem(theme: self.theme, strings: self.strings, mode: .list(compact: true), context: context, peer: self.peer, presence: nil, unreadBadge: nil, action: { _ in }, contextAction: nil, isPeerSelected: { _ in return false }, customWidth: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private struct DeleteAccountPeersItemNodeTransition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let firstTime: Bool
|
||||
let animated: Bool
|
||||
}
|
||||
|
||||
private func preparedPeersTransition(context: AccountContext, from fromEntries: [PeersEntry], to toEntries: [PeersEntry], firstTime: Bool, animated: Bool) -> DeleteAccountPeersItemNodeTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context), directionHint: .Down) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context), directionHint: nil) }
|
||||
|
||||
return DeleteAccountPeersItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, animated: animated)
|
||||
}
|
||||
|
||||
class DeleteAccountPeersItem: ListViewItem, ItemListItem {
|
||||
var sectionId: ItemListSectionId
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peers: [EnginePeer]
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peers: [EnginePeer], sectionId: ItemListSectionId) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.peers = peers
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = DeleteAccountPeersItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? DeleteAccountPeersItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteAccountPeersItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let dataPromise = Promise<(AccountContext, [EnginePeer], PresentationTheme, PresentationStrings)>()
|
||||
private var disposable: Disposable?
|
||||
|
||||
private var item: DeleteAccountPeersItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
private let listView: ListView
|
||||
private var queuedTransitions: [DeleteAccountPeersItemNodeTransition] = []
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.isUserInteractionEnabled = false
|
||||
|
||||
self.listView = ListView()
|
||||
self.listView.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.listView)
|
||||
|
||||
let previous: Atomic<[PeersEntry]> = Atomic(value: [])
|
||||
let firstTime:Atomic<Bool> = Atomic(value: true)
|
||||
|
||||
self.disposable = (self.dataPromise.get() |> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
if let strongSelf = self {
|
||||
let (context, peers, theme, strings) = data
|
||||
|
||||
var entries: [PeersEntry] = []
|
||||
for peer in peers {
|
||||
entries.append(PeersEntry(index: entries.count, peer: peer, theme: theme, strings: strings))
|
||||
}
|
||||
|
||||
let animated = !firstTime.swap(false)
|
||||
|
||||
let transition = preparedPeersTransition(context: context, from: previous.swap(entries), to: entries, firstTime: !animated, animated: animated)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: DeleteAccountPeersItemNodeTransition) {
|
||||
self.queuedTransitions.append(transition)
|
||||
self.dequeueTransitions()
|
||||
}
|
||||
|
||||
private func dequeueTransitions() {
|
||||
while !self.queuedTransitions.isEmpty {
|
||||
let transition = self.queuedTransitions.removeFirst()
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if transition.firstTime {
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.LowLatency)
|
||||
} else if transition.animated {
|
||||
options.insert(.AnimateInsertion)
|
||||
}
|
||||
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: DeleteAccountPeersItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
var themeUpdated = false
|
||||
if currentItem?.theme !== item.theme {
|
||||
themeUpdated = true
|
||||
}
|
||||
print(themeUpdated)
|
||||
|
||||
let contentSize: CGSize
|
||||
var insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
contentSize = CGSize(width: params.width, height: 109.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutParams = params
|
||||
|
||||
strongSelf.dataPromise.set(.single((item.context, item.peers, item.theme, item.strings)))
|
||||
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.maskNode)
|
||||
}
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
let listInsets = UIEdgeInsets(top: params.leftInset, left: 0.0, bottom: params.rightInset, right: 0.0)
|
||||
strongSelf.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 92.0, height: params.width)
|
||||
strongSelf.listView.position = CGPoint(x: params.width / 2.0, y: contentSize.height / 2.0)
|
||||
strongSelf.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: 92.0, height: params.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
strongSelf.dequeueTransitions()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
413
submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift
Normal file
413
submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift
Normal file
@ -0,0 +1,413 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import PhoneInputNode
|
||||
import CountrySelectionUI
|
||||
import CoreTelephony
|
||||
|
||||
private func generateCountryButtonBackground(color: UIColor, strokeColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 56, height: 44.0 + 6.0), rotatedContext: { size, context in
|
||||
let arrowSize: CGFloat = 6.0
|
||||
let lineWidth = UIScreenPixel
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize)))
|
||||
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize))
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
|
||||
context.setStrokeColor(strokeColor.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
|
||||
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: 15.0, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 0.0, y: lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0))
|
||||
context.strokePath()
|
||||
})?.stretchableImage(withLeftCapWidth: 55, topCapHeight: 1)
|
||||
}
|
||||
|
||||
private func generateCountryButtonHighlightedBackground(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 56.0, height: 44.0 + 6.0), rotatedContext: { size, context in
|
||||
let arrowSize: CGFloat = 6.0
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize)))
|
||||
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize))
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 55, topCapHeight: 2)
|
||||
}
|
||||
|
||||
private func generatePhoneInputBackground(color: UIColor, strokeColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 82.0, height: 44.0), rotatedContext: { size, context in
|
||||
let lineWidth = UIScreenPixel
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(strokeColor.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.move(to: CGPoint(x: 0.0, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width, y: size.height - lineWidth / 2.0))
|
||||
context.strokePath()
|
||||
context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 0.0))
|
||||
context.strokePath()
|
||||
})?.stretchableImage(withLeftCapWidth: 81, topCapHeight: 2)
|
||||
}
|
||||
|
||||
|
||||
class DeleteAccountPhoneItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let value: (Int32?, String?, String)
|
||||
let sectionId: ItemListSectionId
|
||||
let selectCountryCode: () -> Void
|
||||
let updated: (Int) -> Void
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, value: (Int32?, String?, String), sectionId: ItemListSectionId, selectCountryCode: @escaping () -> Void, updated: @escaping (Int) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.value = value
|
||||
self.sectionId = sectionId
|
||||
self.selectCountryCode = selectCountryCode
|
||||
self.updated = updated
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = DeleteAccountPhoneItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? DeleteAccountPhoneItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteAccountPhoneItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let countryButton: ASButtonNode
|
||||
private let phoneBackground: ASImageNode
|
||||
private let phoneInputNode: PhoneInputNode
|
||||
|
||||
private var item: DeleteAccountPhoneItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
var preferredCountryIdForCode: [String: String] = [:]
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.isUserInteractionEnabled = false
|
||||
|
||||
self.countryButton = ASButtonNode()
|
||||
|
||||
self.phoneBackground = ASImageNode()
|
||||
self.phoneBackground.displaysAsynchronously = false
|
||||
self.phoneBackground.displayWithoutProcessing = true
|
||||
self.phoneBackground.isLayerBacked = true
|
||||
|
||||
self.phoneInputNode = PhoneInputNode(fontSize: 17.0)
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.phoneBackground)
|
||||
self.addSubnode(self.countryButton)
|
||||
self.addSubnode(self.phoneInputNode)
|
||||
|
||||
self.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 4.0, right: 0.0)
|
||||
self.countryButton.contentHorizontalAlignment = .left
|
||||
|
||||
self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
let processNumberChange: (String) -> Bool = { [weak self] number in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return false
|
||||
}
|
||||
if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode) {
|
||||
let flagString = emojiFlagForISOCountryCode(country.id)
|
||||
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: item.strings) ?? country.name
|
||||
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: [])
|
||||
|
||||
let maskFont = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers])
|
||||
if let mask = AuthorizationSequenceCountrySelectionController.lookupPatternByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode).flatMap({ NSAttributedString(string: $0, font: maskFont, textColor: item.theme.list.itemPlaceholderTextColor) }) {
|
||||
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = nil
|
||||
strongSelf.phoneInputNode.mask = mask
|
||||
} else {
|
||||
strongSelf.phoneInputNode.mask = nil
|
||||
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: item.strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: item.theme.list.itemPlaceholderTextColor)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.phoneInputNode.numberTextUpdated = { [weak self] number in
|
||||
if let strongSelf = self {
|
||||
let _ = processNumberChange(strongSelf.phoneInputNode.number)
|
||||
}
|
||||
}
|
||||
|
||||
self.phoneInputNode.countryCodeUpdated = { [weak self] code, name in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
if let name = name {
|
||||
strongSelf.preferredCountryIdForCode[code] = name
|
||||
}
|
||||
|
||||
if processNumberChange(strongSelf.phoneInputNode.number) {
|
||||
} else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] {
|
||||
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: item.strings) ?? countryName
|
||||
strongSelf.countryButton.setTitle(localizedName, with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: [])
|
||||
} else if let code = Int(code), let (_, countryName) = countryCodeToIdAndName[code] {
|
||||
strongSelf.countryButton.setTitle(countryName, with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: [])
|
||||
} else {
|
||||
strongSelf.countryButton.setTitle(item.strings.Login_CountryCode, with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.phoneInputNode.customFormatter = { number in
|
||||
if let (_, code) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: [:]) {
|
||||
return code.code
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var countryId: String? = nil
|
||||
let networkInfo = CTTelephonyNetworkInfo()
|
||||
if let carrier = networkInfo.subscriberCellularProvider {
|
||||
countryId = carrier.isoCountryCode
|
||||
}
|
||||
|
||||
if countryId == nil {
|
||||
countryId = (Locale.current as NSLocale).object(forKey: .countryCode) as? String
|
||||
}
|
||||
|
||||
var countryCodeAndId: (Int32, String) = (1, "US")
|
||||
|
||||
if let countryId = countryId {
|
||||
let normalizedId = countryId.uppercased()
|
||||
for (code, idAndName) in countryCodeToIdAndName {
|
||||
if idAndName.0 == normalizedId {
|
||||
countryCodeAndId = (Int32(code), idAndName.0.uppercased())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.phoneInputNode.number = "+\(countryCodeAndId.0)"
|
||||
}
|
||||
|
||||
@objc private func countryPressed() {
|
||||
if let item = self.item {
|
||||
item.selectCountryCode()
|
||||
}
|
||||
}
|
||||
|
||||
var phoneNumber: String {
|
||||
return self.phoneInputNode.number
|
||||
}
|
||||
|
||||
func updateCountryCode() {
|
||||
self.phoneInputNode.codeAndNumber = self.phoneInputNode.codeAndNumber
|
||||
}
|
||||
|
||||
func updateCountryCode(code: Int32, name: String) {
|
||||
self.phoneInputNode.codeAndNumber = (code, name, self.phoneInputNode.codeAndNumber.2)
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.phoneInputNode.numberField.textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
self.phoneInputNode.countryCodeField.layer.addShakeAnimation()
|
||||
self.phoneInputNode.numberField.layer.addShakeAnimation()
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: DeleteAccountPhoneItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
var updatedCountryButtonBackground: UIImage?
|
||||
var updatedCountryButtonHighlightedBackground: UIImage?
|
||||
var updatedPhoneBackground: UIImage?
|
||||
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedCountryButtonBackground = generateCountryButtonBackground(color: item.theme.list.itemBlocksBackgroundColor, strokeColor: item.theme.list.itemBlocksSeparatorColor)
|
||||
updatedCountryButtonHighlightedBackground = generateCountryButtonHighlightedBackground(color: item.theme.list.itemHighlightedBackgroundColor)
|
||||
updatedPhoneBackground = generatePhoneInputBackground(color: item.theme.list.itemBlocksBackgroundColor, strokeColor: item.theme.list.itemBlocksSeparatorColor)
|
||||
}
|
||||
|
||||
let contentSize: CGSize
|
||||
var insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let countryButtonHeight: CGFloat = 44.0
|
||||
let inputFieldsHeight: CGFloat = 44.0
|
||||
|
||||
contentSize = CGSize(width: params.width, height: countryButtonHeight + inputFieldsHeight)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutParams = params
|
||||
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.maskNode)
|
||||
}
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
if let updatedCountryButtonBackground = updatedCountryButtonBackground {
|
||||
strongSelf.countryButton.setBackgroundImage(updatedCountryButtonBackground, for: [])
|
||||
}
|
||||
if let updatedCountryButtonHighlightedBackground = updatedCountryButtonHighlightedBackground {
|
||||
strongSelf.countryButton.setBackgroundImage(updatedCountryButtonHighlightedBackground, for: .highlighted)
|
||||
}
|
||||
if let updatedPhoneBackground = updatedPhoneBackground {
|
||||
strongSelf.phoneBackground.image = updatedPhoneBackground
|
||||
}
|
||||
|
||||
strongSelf.phoneInputNode.countryCodeField.textField.textColor = item.theme.list.itemPrimaryTextColor
|
||||
strongSelf.phoneInputNode.countryCodeField.textField.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
strongSelf.phoneInputNode.countryCodeField.textField.tintColor = item.theme.list.itemAccentColor
|
||||
strongSelf.phoneInputNode.numberField.textField.textColor = item.theme.list.itemPrimaryTextColor
|
||||
strongSelf.phoneInputNode.numberField.textField.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
strongSelf.phoneInputNode.numberField.textField.tintColor = item.theme.list.itemAccentColor
|
||||
|
||||
strongSelf.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: params.leftInset + 15.0, bottom: 4.0, right: 0.0)
|
||||
|
||||
strongSelf.countryButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: 44.0 + 6.0))
|
||||
strongSelf.phoneBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: 44.0), size: CGSize(width: params.width, height: 44.0))
|
||||
|
||||
let countryCodeFrame = CGRect(origin: CGPoint(x: 11.0, y: 44.0), size: CGSize(width: 67.0, height: 44.0))
|
||||
let numberFrame = CGRect(origin: CGPoint(x: 92.0, y: 44.0), size: CGSize(width: layout.size.width - 70.0 - 8.0, height: 44.0))
|
||||
let placeholderFrame = numberFrame.offsetBy(dx: 0.0, dy: 8.0)
|
||||
|
||||
let phoneInputFrame = countryCodeFrame.union(numberFrame)
|
||||
|
||||
strongSelf.phoneInputNode.frame = phoneInputFrame
|
||||
strongSelf.phoneInputNode.countryCodeField.frame = countryCodeFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY)
|
||||
strongSelf.phoneInputNode.numberField.frame = numberFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY)
|
||||
strongSelf.phoneInputNode.placeholderNode.frame = placeholderFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -482,6 +482,10 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
|
||||
return entries
|
||||
}
|
||||
|
||||
class PrivacyAndSecurityControllerImpl: ItemListController {
|
||||
|
||||
}
|
||||
|
||||
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, updatedBlockedPeers: ((BlockedPeersContext?) -> Void)? = nil, updatedHasTwoStepAuth: ((Bool) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil, blockedPeersContext: BlockedPeersContext? = nil, hasTwoStepAuth: Bool? = nil) -> ViewController {
|
||||
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
|
||||
@ -492,6 +496,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
var pushControllerImpl: ((ViewController, Bool) -> Void)?
|
||||
var replaceTopControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
var getNavigationControllerImpl: (() -> NavigationController?)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
@ -822,12 +827,26 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
6 * 30 * 24 * 60 * 60,
|
||||
365 * 24 * 60 * 60
|
||||
]
|
||||
let timeoutItems: [ActionSheetItem] = timeoutValues.map { value in
|
||||
var timeoutItems: [ActionSheetItem] = timeoutValues.map { value in
|
||||
return ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: value), action: {
|
||||
dismissAction()
|
||||
timeoutAction(value)
|
||||
})
|
||||
}
|
||||
timeoutItems.append(ActionSheetButtonItem(title: presentationData.strings.PrivacySettings_DeleteAccountNow, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
guard let navigationController = getNavigationControllerImpl?() else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get())
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { hasTwoStepAuth, twoStepAuthData in
|
||||
let optionsController = deleteAccountOptionsController(context: context, navigationController: navigationController, hasTwoStepAuth: hasTwoStepAuth ?? false, twoStepAuthData: twoStepAuthData)
|
||||
pushControllerImpl?(optionsController, true)
|
||||
})
|
||||
}))
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: timeoutItems),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
@ -886,7 +905,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
let controller = PrivacyAndSecurityControllerImpl(context: context, state: signal)
|
||||
pushControllerImpl = { [weak controller] c, animated in
|
||||
(controller?.navigationController as? NavigationController)?.pushViewController(c, animated: animated)
|
||||
}
|
||||
@ -896,7 +915,10 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
|
||||
getNavigationControllerImpl = { [weak controller] in
|
||||
return (controller?.navigationController as? NavigationController)
|
||||
}
|
||||
|
||||
controller.didAppear = { _ in
|
||||
updateHasTwoStepAuth()
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData
|
||||
if let pendingEmail = pendingEmail {
|
||||
entries.append(.pendingEmailConfirmInfo(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPendingEmail(pendingEmail.email.pattern).string))
|
||||
entries.append(.pendingEmailConfirmCode(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_RecoveryCode, state.emailCode))
|
||||
entries.append(.pendingEmailInfo(presentationData.theme, "[" + presentationData.strings.TwoStepAuth_ConfirmationAbort + "]()"))
|
||||
entries.append(.pendingEmailInfo(presentationData.theme, "[" + presentationData.strings.TwoStepAuth_ConfirmationAbort + "]()"))
|
||||
|
||||
/*entries.append(.pendingEmailInfo(presentationData.theme, presentationData.strings.TwoStepAuth_ConfirmationText + "\n\n\(pendingEmailAndValue.pendingEmail.pattern)\n\n[" + presentationData.strings.TwoStepAuth_ConfirmationAbort + "]()"))*/
|
||||
} else {
|
||||
|
@ -91,6 +91,7 @@ public struct PresentationResourcesSettings {
|
||||
public static let changePhoneNumber = renderIcon(name: "Settings/Menu/ChangePhoneNumber")
|
||||
|
||||
public static let deleteAddAccount = renderIcon(name: "Settings/Menu/DeleteAddAccount")
|
||||
public static let deleteSetTwoStepAuth = renderIcon(name: "Settings/Menu/DeleteTwoStepAuth")
|
||||
public static let deleteSetPasscode = renderIcon(name: "Settings/Menu/FaceId")
|
||||
public static let deleteChats = renderIcon(name: "Settings/Menu/DeleteChats")
|
||||
public static let clearSynced = renderIcon(name: "Settings/Menu/ClearSynced")
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/DeleteTwoStepAuth.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/DeleteTwoStepAuth.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
114
submodules/TelegramUI/Images.xcassets/Settings/Menu/DeleteTwoStepAuth.imageset/Icon.pdf
vendored
Normal file
114
submodules/TelegramUI/Images.xcassets/Settings/Menu/DeleteTwoStepAuth.imageset/Icon.pdf
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.345098 0.337255 0.839216 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
-1.000000 -0.000000 -0.000000 1.000000 25.000000 6.000000 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
6.650000 19.000000 m
|
||||
2.945000 19.000000 0.000000 16.055000 0.000000 12.350000 c
|
||||
0.000000 8.645000 2.945000 5.700001 6.650000 5.700001 c
|
||||
7.500165 5.700001 8.310955 5.861581 9.054688 6.145313 c
|
||||
10.450001 4.750000 l
|
||||
12.350000 4.750000 l
|
||||
12.350000 2.850000 l
|
||||
14.250000 2.850000 l
|
||||
14.250000 0.950001 l
|
||||
14.903126 0.296875 l
|
||||
15.093125 0.106874 15.300938 0.000000 15.585938 0.000000 c
|
||||
18.049999 0.000000 l
|
||||
18.619999 0.000000 19.000000 0.380001 19.000000 0.950001 c
|
||||
19.000000 3.414062 l
|
||||
19.000000 3.699062 18.893126 3.906875 18.703125 4.096874 c
|
||||
12.854687 9.945312 l
|
||||
13.138419 10.689045 13.299999 11.499835 13.299999 12.350000 c
|
||||
13.299999 16.055000 10.355000 19.000000 6.650000 19.000000 c
|
||||
h
|
||||
5.225000 16.150000 m
|
||||
6.555000 16.150000 7.600000 15.105000 7.600000 13.775000 c
|
||||
7.600000 12.445000 6.555000 11.400000 5.225000 11.400000 c
|
||||
3.895000 11.400000 2.850000 12.445000 2.850000 13.775000 c
|
||||
2.850000 15.105000 3.895000 16.150000 5.225000 16.150000 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1987
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002077 00000 n
|
||||
0000002100 00000 n
|
||||
0000002273 00000 n
|
||||
0000002347 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2406
|
||||
%%EOF
|
Loading…
x
Reference in New Issue
Block a user