Swiftgram/TelegramUI/SettingsController.swift
2017-03-08 01:03:03 +03:00

407 lines
16 KiB
Swift

import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
private struct SettingsItemArguments {
let account: Account
let accountManager: AccountManager
let pushController: (ViewController) -> Void
let presentController: (ViewController) -> Void
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
let saveEditingState: () -> Void
let logout: () -> Void
}
private enum SettingsSection: Int32 {
case info
case generalSettings
case accountSettings
case help
case logOut
}
private enum SettingsEntry: ItemListNodeEntry {
case userInfo(Peer?, CachedPeerData?, ItemListAvatarAndNameInfoItemState)
case setProfilePhoto
case notificationsAndSounds
case privacyAndSecurity
case dataAndStorage
case stickers
case phoneNumber(String)
case username(String)
case askAQuestion
case faq
case debug
case logOut
var section: ItemListSectionId {
switch self {
case .userInfo, .setProfilePhoto:
return SettingsSection.info.rawValue
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .stickers:
return SettingsSection.generalSettings.rawValue
case .phoneNumber, .username:
return SettingsSection.accountSettings.rawValue
case .askAQuestion, .faq, .debug:
return SettingsSection.help.rawValue
case .logOut:
return SettingsSection.logOut.rawValue
}
}
var stableId: Int32 {
switch self {
case .userInfo:
return 0
case .setProfilePhoto:
return 1
case .notificationsAndSounds:
return 2
case .privacyAndSecurity:
return 3
case .dataAndStorage:
return 4
case .stickers:
return 5
case .phoneNumber:
return 6
case .username:
return 7
case .askAQuestion:
return 8
case .faq:
return 9
case .debug:
return 10
case .logOut:
return 11
}
}
static func ==(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool {
switch lhs {
case let .userInfo(lhsPeer, lhsCachedData, lhsEditingState):
if case let .userInfo(rhsPeer, rhsCachedData, rhsEditingState) = rhs {
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
if !lhsPeer.isEqual(rhsPeer) {
return false
}
} else if (lhsPeer != nil) != (rhsPeer != nil) {
return false
}
if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData {
if !lhsCachedData.isEqual(to: rhsCachedData) {
return false
}
} else if (lhsCachedData != nil) != (rhsCachedData != nil) {
return false
}
if lhsEditingState != rhsEditingState {
return false
}
return true
} else {
return false
}
case .setProfilePhoto:
if case .setProfilePhoto = rhs {
return true
} else {
return false
}
case .notificationsAndSounds:
if case .notificationsAndSounds = rhs {
return true
} else {
return false
}
case .privacyAndSecurity:
if case .privacyAndSecurity = rhs {
return true
} else {
return false
}
case .dataAndStorage:
if case .dataAndStorage = rhs {
return true
} else {
return false
}
case .stickers:
if case .stickers = rhs {
return true
} else {
return false
}
case let .phoneNumber(number):
if case .phoneNumber(number) = rhs {
return true
} else {
return false
}
case let .username(address):
if case .username(address) = rhs {
return true
} else {
return false
}
case .askAQuestion:
if case .askAQuestion = rhs {
return true
} else {
return false
}
case .faq:
if case .faq = rhs {
return true
} else {
return false
}
case .debug:
if case .debug = rhs {
return true
} else {
return false
}
case .logOut:
if case .logOut = rhs {
return true
} else {
return false
}
}
}
static func <(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(_ arguments: SettingsItemArguments) -> ListViewItem {
switch self {
case let .userInfo(peer, cachedData, state):
return ItemListAvatarAndNameInfoItem(account: arguments.account, peer: peer, presence: TelegramUserPresence(status: .present(until: Int32.max)), cachedData: cachedData, state: state, sectionId: ItemListSectionId(self.section), style: .blocks, editingNameUpdated: { editingName in
arguments.updateEditingName(editingName)
})
case .setProfilePhoto:
return ItemListActionItem(title: "Set Profile Photo", kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.presentController(standardTextAlertController(title: "Verification Failed", text: "Your Apple ID or password is incorrect.", actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {
}),
TextAlertAction(type: .defaultAction, title: "OK", action: {
})
]))
})
case .notificationsAndSounds:
return ItemListDisclosureItem(title: "Notifications and Sounds", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.pushController(notificationsAndSoundsController(account: arguments.account))
})
case .privacyAndSecurity:
return ItemListDisclosureItem(title: "Privacy and Security", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.pushController(privacyAndSecurityController(account: arguments.account))
})
case .dataAndStorage:
return ItemListDisclosureItem(title: "Data and Storage", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
})
case .stickers:
return ItemListDisclosureItem(title: "Stickers", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
})
case let .phoneNumber(number):
return ItemListDisclosureItem(title: "Phone Number", label: number, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
})
case let .username(address):
return ItemListDisclosureItem(title: "Username", label: address, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.presentController(usernameSetupController(account: arguments.account))
})
case .askAQuestion:
return ItemListDisclosureItem(title: "Ask a Question", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
})
case .faq:
return ItemListDisclosureItem(title: "Telegram FAQ", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
})
case .debug:
return ItemListDisclosureItem(title: "Debug", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.pushController(debugController(account: arguments.account, accountManager: arguments.accountManager))
})
case .logOut:
return ItemListActionItem(title: "Log Out", kind: .destructive, alignment: .center, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.logout()
})
}
}
}
private struct SettingsEditingState: Equatable {
let editingName: ItemListAvatarAndNameInfoItemName
static func ==(lhs: SettingsEditingState, rhs: SettingsEditingState) -> Bool {
if lhs.editingName != rhs.editingName {
return false
}
return true
}
}
private struct SettingsState: Equatable {
let editingState: SettingsEditingState?
let updatingName: ItemListAvatarAndNameInfoItemName?
func withUpdatedEditingState(_ editingState: SettingsEditingState?) -> SettingsState {
return SettingsState(editingState: editingState, updatingName: self.updatingName)
}
func withUpdatedUpdatingName(_ updatingName: ItemListAvatarAndNameInfoItemName?) -> SettingsState {
return SettingsState(editingState: self.editingState, updatingName: updatingName)
}
static func ==(lhs: SettingsState, rhs: SettingsState) -> Bool {
if lhs.editingState != rhs.editingState {
return false
}
if lhs.updatingName != rhs.updatingName {
return false
}
return true
}
}
private func settingsEntries(state: SettingsState, view: PeerView) -> [SettingsEntry] {
var entries: [SettingsEntry] = []
if let peer = peerViewMainPeer(view) as? TelegramUser {
let userInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingState?.editingName, updatingName: state.updatingName)
entries.append(.userInfo(peer, view.cachedData, userInfoState))
entries.append(.setProfilePhoto)
entries.append(.notificationsAndSounds)
entries.append(.privacyAndSecurity)
entries.append(.dataAndStorage)
entries.append(.stickers)
if let phone = peer.phone {
entries.append(.phoneNumber(formatPhoneNumber(phone)))
}
entries.append(.username(peer.addressName == nil ? "" : ("@" + peer.addressName!)))
entries.append(.askAQuestion)
entries.append(.faq)
entries.append(.debug)
if let _ = state.editingState {
entries.append(.logOut)
}
}
return entries
}
public func settingsController(account: Account, accountManager: AccountManager) -> ViewController {
let statePromise = ValuePromise(SettingsState(editingState: nil, updatingName: nil), ignoreRepeated: true)
let stateValue = Atomic(value: SettingsState(editingState: nil, updatingName: nil))
let updateState: ((SettingsState) -> SettingsState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController) -> Void)?
let actionsDisposable = DisposableSet()
let updatePeerNameDisposable = MetaDisposable()
actionsDisposable.add(updatePeerNameDisposable)
let arguments = SettingsItemArguments(account: account, accountManager: accountManager, pushController: { controller in
pushControllerImpl?(controller)
}, presentController: { controller in
presentControllerImpl?(controller)
}, updateEditingName: { editingName in
updateState { state in
if let _ = state.editingState {
return state.withUpdatedEditingState(SettingsEditingState(editingName: editingName))
} else {
return state
}
}
}, saveEditingState: {
var updateName: ItemListAvatarAndNameInfoItemName?
updateState { state in
if let editingState = state.editingState {
updateName = editingState.editingName
return state.withUpdatedEditingState(nil).withUpdatedUpdatingName(editingState.editingName)
} else {
return state
}
}
if let updateName = updateName, case let .personName(firstName, lastName) = updateName {
updatePeerNameDisposable.set((updateAccountPeerName(account: account, firstName: firstName, lastName: lastName) |> afterDisposed {
Queue.mainQueue().async {
updateState { state in
return state.withUpdatedUpdatingName(nil)
}
}
}).start())
}
}, logout: {
let alertController = standardTextAlertController(title: NSLocalizedString("Settings.LogoutConfirmationTitle", comment: ""), text: NSLocalizedString("Settings.LogoutConfirmationText", comment: ""), actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {
}),
TextAlertAction(type: .defaultAction, title: "OK", action: {
let _ = logoutFromAccount(id: account.id, accountManager: accountManager).start()
})
])
presentControllerImpl?(alertController)
})
let peerView = account.viewTracker.peerView(account.peerId)
let signal = combineLatest(statePromise.get(), peerView)
|> map { state, view -> (ItemListControllerState, (ItemListNodeState<SettingsEntry>, SettingsEntry.ItemGenerationArguments)) in
let peer = peerViewMainPeer(view)
let rightNavigationButton: ItemListNavigationButton
if let _ = state.editingState {
rightNavigationButton = ItemListNavigationButton(title: "Done", style: .bold, enabled: true, action: {
arguments.saveEditingState()
})
} else {
rightNavigationButton = ItemListNavigationButton(title: "Edit", style: .regular, enabled: true, action: {
if let peer = peer as? TelegramUser {
updateState { state in
return state.withUpdatedEditingState(SettingsEditingState(editingName: ItemListAvatarAndNameInfoItemName(peer.indexName)))
}
}
})
}
let controllerState = ItemListControllerState(title: "Settings", leftNavigationButton: nil, rightNavigationButton: rightNavigationButton)
let listState = ItemListNodeState(entries: settingsEntries(state: state, view: view), style: .blocks)
return (controllerState, (listState, arguments))
} |> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(signal)
controller.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
controller.tabBarItem.title = "Settings"
controller.tabBarItem.image = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")?.precomposed()
controller.tabBarItem.selectedImage = UIImage(bundleImageName: "Chat List/Tabs/IconSettingsSelected")?.precomposed()
pushControllerImpl = { [weak controller] value in
(controller?.navigationController as? NavigationController)?.pushViewController(value)
}
presentControllerImpl = { [weak controller] value in
controller?.present(value, in: .window, with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
return controller
}