mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
896 lines
45 KiB
Swift
896 lines
45 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import ItemListUI
|
|
import PresentationDataUtils
|
|
import AccountContext
|
|
import AuthTransferUI
|
|
import ItemListPeerActionItem
|
|
import DeviceAccess
|
|
|
|
private final class RecentSessionsControllerArguments {
|
|
let context: AccountContext
|
|
|
|
let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void
|
|
let removeSession: (Int64) -> Void
|
|
let terminateOtherSessions: () -> Void
|
|
|
|
let openSession: (RecentAccountSession) -> Void
|
|
let openWebSession: (WebAuthorization, Peer?) -> Void
|
|
|
|
let removeWebSession: (Int64) -> Void
|
|
let terminateAllWebSessions: () -> Void
|
|
|
|
let addDevice: () -> Void
|
|
|
|
let openOtherAppsUrl: () -> Void
|
|
let setupAuthorizationTTL: () -> Void
|
|
|
|
let openDesktopLink: () -> Void
|
|
let openWebLink: () -> Void
|
|
|
|
init(context: AccountContext, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, terminateOtherSessions: @escaping () -> Void, openSession: @escaping (RecentAccountSession) -> Void, openWebSession: @escaping (WebAuthorization, Peer?) -> Void, removeWebSession: @escaping (Int64) -> Void, terminateAllWebSessions: @escaping () -> Void, addDevice: @escaping () -> Void, openOtherAppsUrl: @escaping () -> Void, setupAuthorizationTTL: @escaping () -> Void, openDesktopLink: @escaping () -> Void, openWebLink: @escaping () -> Void) {
|
|
self.context = context
|
|
self.setSessionIdWithRevealedOptions = setSessionIdWithRevealedOptions
|
|
self.removeSession = removeSession
|
|
self.terminateOtherSessions = terminateOtherSessions
|
|
|
|
self.openSession = openSession
|
|
self.openWebSession = openWebSession
|
|
|
|
self.removeWebSession = removeWebSession
|
|
self.terminateAllWebSessions = terminateAllWebSessions
|
|
|
|
self.addDevice = addDevice
|
|
|
|
self.openOtherAppsUrl = openOtherAppsUrl
|
|
|
|
self.setupAuthorizationTTL = setupAuthorizationTTL
|
|
|
|
self.openDesktopLink = openDesktopLink
|
|
self.openWebLink = openWebLink
|
|
}
|
|
}
|
|
|
|
private enum RecentSessionsMode: Int {
|
|
case sessions
|
|
case websites
|
|
}
|
|
|
|
private enum RecentSessionsSection: Int32 {
|
|
case header
|
|
case currentSession
|
|
case pendingSessions
|
|
case otherSessions
|
|
case ttl
|
|
}
|
|
|
|
private enum RecentSessionsEntryStableId: Hashable {
|
|
case session(Int64)
|
|
case index(Int32)
|
|
case devicesInfo
|
|
case ttl(Int32)
|
|
}
|
|
|
|
private struct SortIndex: Comparable {
|
|
var section: Int
|
|
var item: Int
|
|
|
|
static func <(lhs: SortIndex, rhs: SortIndex) -> Bool {
|
|
if lhs.section != rhs.section {
|
|
return lhs.section < rhs.section
|
|
}
|
|
return lhs.item < rhs.item
|
|
}
|
|
}
|
|
|
|
private enum RecentSessionsEntry: ItemListNodeEntry {
|
|
case header(SortIndex, String)
|
|
case currentSessionHeader(SortIndex, String)
|
|
case currentSession(SortIndex, PresentationStrings, PresentationDateTimeFormat, RecentAccountSession)
|
|
case terminateOtherSessions(SortIndex, String)
|
|
case terminateAllWebSessions(SortIndex, String)
|
|
case currentAddDevice(SortIndex, String)
|
|
case currentSessionInfo(SortIndex, String)
|
|
case pendingSessionsHeader(SortIndex, String)
|
|
case pendingSession(index: Int32, sortIndex: SortIndex, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
|
|
case pendingSessionsInfo(SortIndex, String)
|
|
case otherSessionsHeader(SortIndex, String)
|
|
case addDevice(SortIndex, String)
|
|
case session(index: Int32, sortIndex: SortIndex, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
|
|
case website(index: Int32, sortIndex: SortIndex, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool)
|
|
case devicesInfo(SortIndex, String)
|
|
case ttlHeader(SortIndex, String)
|
|
case ttlTimeout(SortIndex, String, String)
|
|
|
|
var section: ItemListSectionId {
|
|
switch self {
|
|
case .header:
|
|
return RecentSessionsSection.header.rawValue
|
|
case .currentSessionHeader, .currentSession, .terminateOtherSessions, .terminateAllWebSessions, .currentAddDevice, .currentSessionInfo:
|
|
return RecentSessionsSection.currentSession.rawValue
|
|
case .pendingSessionsHeader, .pendingSession, .pendingSessionsInfo:
|
|
return RecentSessionsSection.pendingSessions.rawValue
|
|
case .otherSessionsHeader, .addDevice, .session, .website, .devicesInfo:
|
|
return RecentSessionsSection.otherSessions.rawValue
|
|
case .ttlHeader, .ttlTimeout:
|
|
return RecentSessionsSection.ttl.rawValue
|
|
}
|
|
}
|
|
|
|
var stableId: RecentSessionsEntryStableId {
|
|
switch self {
|
|
case .header:
|
|
return .index(0)
|
|
case .currentSessionHeader:
|
|
return .index(1)
|
|
case .currentSession:
|
|
return .index(2)
|
|
case .terminateOtherSessions:
|
|
return .index(3)
|
|
case .terminateAllWebSessions:
|
|
return .index(4)
|
|
case .currentAddDevice:
|
|
return .index(5)
|
|
case .currentSessionInfo:
|
|
return .index(6)
|
|
case .pendingSessionsHeader:
|
|
return .index(7)
|
|
case let .pendingSession(_, _, _, _, session, _, _, _):
|
|
return .session(session.hash)
|
|
case .pendingSessionsInfo:
|
|
return .index(8)
|
|
case .otherSessionsHeader:
|
|
return .index(9)
|
|
case .addDevice:
|
|
return .index(10)
|
|
case let .session(_, _, _, _, session, _, _, _):
|
|
return .session(session.hash)
|
|
case let .website(_, _, _, _, _, website, _, _, _, _):
|
|
return .session(website.hash)
|
|
case .devicesInfo:
|
|
return .devicesInfo
|
|
case .ttlHeader:
|
|
return .index(11)
|
|
case .ttlTimeout:
|
|
return .index(12)
|
|
}
|
|
}
|
|
|
|
var sortIndex: SortIndex {
|
|
switch self {
|
|
case let .header(index, _):
|
|
return index
|
|
case let .currentSessionHeader(index, _):
|
|
return index
|
|
case let .currentSession(index, _, _, _):
|
|
return index
|
|
case let .terminateOtherSessions(index, _):
|
|
return index
|
|
case let .terminateAllWebSessions(index, _):
|
|
return index
|
|
case let .currentAddDevice(index, _):
|
|
return index
|
|
case let .currentSessionInfo(index, _):
|
|
return index
|
|
case let .pendingSessionsHeader(index, _):
|
|
return index
|
|
case let .pendingSession(_, index, _, _, _, _, _, _):
|
|
return index
|
|
case let .pendingSessionsInfo(index, _):
|
|
return index
|
|
case let .otherSessionsHeader(index, _):
|
|
return index
|
|
case let .addDevice(index, _):
|
|
return index
|
|
case let .session(_, index, _, _, _, _, _, _):
|
|
return index
|
|
case let .website(_, index, _, _, _, _, _, _, _, _):
|
|
return index
|
|
case let .devicesInfo(index, _):
|
|
return index
|
|
case let .ttlHeader(index, _):
|
|
return index
|
|
case let .ttlTimeout(index, _, _):
|
|
return index
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool {
|
|
switch lhs {
|
|
case let .header(lhsSortIndex, lhsText):
|
|
if case let .header(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .currentSessionHeader(lhsSortIndex, lhsText):
|
|
if case let .currentSessionHeader(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .terminateOtherSessions(lhsSortIndex, lhsText):
|
|
if case let .terminateOtherSessions(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .terminateAllWebSessions(lhsSortIndex, lhsText):
|
|
if case let .terminateAllWebSessions(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .currentAddDevice(lhsSortIndex, lhsText):
|
|
if case let .currentAddDevice(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .currentSessionInfo(lhsSortIndex, lhsText):
|
|
if case let .currentSessionInfo(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .pendingSessionsHeader(lhsSortIndex, lhsText):
|
|
if case let .pendingSessionsHeader(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .pendingSession(lhsIndex, lhsSortIndex, lhsStrings, lhsDateTimeFormat, lhsSession, lhsEnabled, lhsEditing, lhsRevealed):
|
|
if case let .pendingSession(rhsIndex, rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsSession, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .pendingSessionsInfo(lhsSortIndex, lhsText):
|
|
if case let .pendingSessionsInfo(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .otherSessionsHeader(lhsSortIndex, lhsText):
|
|
if case let .otherSessionsHeader(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .addDevice(lhsSortIndex, lhsText):
|
|
if case let .addDevice(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .currentSession(lhsSortIndex, lhsStrings, lhsDateTimeFormat, lhsSession):
|
|
if case let .currentSession(rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsSession) = rhs, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .session(lhsIndex, lhsSortIndex, lhsStrings, lhsDateTimeFormat, lhsSession, lhsEnabled, lhsEditing, lhsRevealed):
|
|
if case let .session(rhsIndex, rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsSession, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .website(lhsIndex, lhsSortIndex, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsWebsite, lhsPeer, lhsEnabled, lhsEditing, lhsRevealed):
|
|
if case let .website(rhsIndex, rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsWebsite, rhsPeer, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameOrder == rhsNameOrder, lhsWebsite == rhsWebsite, arePeersEqual(lhsPeer, rhsPeer), lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .devicesInfo(lhsSortIndex, lhsText):
|
|
if case let .devicesInfo(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .ttlHeader(lhsSortIndex, lhsText):
|
|
if case let .ttlHeader(rhsSortIndex, rhsText) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .ttlTimeout(lhsSortIndex, lhsText, lhsValue):
|
|
if case let .ttlTimeout(rhsSortIndex, rhsText, rhsValue) = rhs, lhsSortIndex == rhsSortIndex, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
static func <(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool {
|
|
return lhs.sortIndex < rhs.sortIndex
|
|
}
|
|
|
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
|
let arguments = arguments as! RecentSessionsControllerArguments
|
|
switch self {
|
|
case let .header(_, text):
|
|
return RecentSessionsHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animationName: "Devices", sectionId: self.section, buttonAction: {
|
|
arguments.addDevice()
|
|
}, linkAction: { action in
|
|
if case let .tap(link) = action {
|
|
switch link {
|
|
case "desktop":
|
|
arguments.openDesktopLink()
|
|
case "web":
|
|
arguments.openWebLink()
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
})
|
|
case let .currentSessionHeader(_, text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .currentSession(_, _, dateTimeFormat, session):
|
|
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: true, editable: false, editing: false, revealed: false, sectionId: self.section, setSessionIdWithRevealedOptions: { _, _ in
|
|
}, removeSession: { _ in
|
|
}, action: {
|
|
arguments.openSession(session)
|
|
})
|
|
case let .terminateOtherSessions(_, text):
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
|
arguments.terminateOtherSessions()
|
|
})
|
|
case let .terminateAllWebSessions(_, text):
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
|
arguments.terminateAllWebSessions()
|
|
})
|
|
case let .currentAddDevice(_, text):
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
|
arguments.addDevice()
|
|
})
|
|
case let .currentSessionInfo(_, text):
|
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { action in
|
|
switch action {
|
|
case .tap:
|
|
arguments.openOtherAppsUrl()
|
|
}
|
|
})
|
|
case let .pendingSessionsHeader(_, text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .pendingSession(_, _, _, dateTimeFormat, session, enabled, editing, revealed):
|
|
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
|
|
arguments.setSessionIdWithRevealedOptions(previousId, id)
|
|
}, removeSession: { id in
|
|
arguments.removeSession(id)
|
|
}, action: {
|
|
arguments.openSession(session)
|
|
})
|
|
case let .pendingSessionsInfo(_, text):
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
|
case let .otherSessionsHeader(_, text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .addDevice(_, text):
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
|
arguments.addDevice()
|
|
})
|
|
case let .session(_, _, _, dateTimeFormat, session, enabled, editing, revealed):
|
|
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
|
|
arguments.setSessionIdWithRevealedOptions(previousId, id)
|
|
}, removeSession: { id in
|
|
arguments.removeSession(id)
|
|
}, action: {
|
|
arguments.openSession(session)
|
|
})
|
|
case let .website(_, _, _, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed):
|
|
return ItemListWebsiteItem(context: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
|
|
arguments.setSessionIdWithRevealedOptions(previousId, id)
|
|
}, removeSession: { id in
|
|
arguments.removeWebSession(id)
|
|
}, action: {
|
|
arguments.openWebSession(website, peer)
|
|
})
|
|
case let .devicesInfo(_, text):
|
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { action in
|
|
switch action {
|
|
case .tap:
|
|
arguments.openOtherAppsUrl()
|
|
}
|
|
})
|
|
case let .ttlHeader(_, text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .ttlTimeout(_, text, value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
|
arguments.setupAuthorizationTTL()
|
|
}, tag: PrivacyAndSecurityEntryTag.accountTimeout)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct RecentSessionsControllerState: Equatable {
|
|
let editing: Bool
|
|
let sessionIdWithRevealedOptions: Int64?
|
|
let removingSessionId: Int64?
|
|
let terminatingOtherSessions: Bool
|
|
|
|
init() {
|
|
self.editing = false
|
|
self.sessionIdWithRevealedOptions = nil
|
|
self.removingSessionId = nil
|
|
self.terminatingOtherSessions = false
|
|
}
|
|
|
|
init(editing: Bool, sessionIdWithRevealedOptions: Int64?, removingSessionId: Int64?, terminatingOtherSessions: Bool) {
|
|
self.editing = editing
|
|
self.sessionIdWithRevealedOptions = sessionIdWithRevealedOptions
|
|
self.removingSessionId = removingSessionId
|
|
self.terminatingOtherSessions = terminatingOtherSessions
|
|
}
|
|
|
|
static func ==(lhs: RecentSessionsControllerState, rhs: RecentSessionsControllerState) -> Bool {
|
|
if lhs.editing != rhs.editing {
|
|
return false
|
|
}
|
|
if lhs.sessionIdWithRevealedOptions != rhs.sessionIdWithRevealedOptions {
|
|
return false
|
|
}
|
|
if lhs.removingSessionId != rhs.removingSessionId {
|
|
return false
|
|
}
|
|
if lhs.terminatingOtherSessions != rhs.terminatingOtherSessions {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func withUpdatedEditing(_ editing: Bool) -> RecentSessionsControllerState {
|
|
return RecentSessionsControllerState(editing: editing, sessionIdWithRevealedOptions: self.sessionIdWithRevealedOptions, removingSessionId: self.removingSessionId, terminatingOtherSessions: self.terminatingOtherSessions)
|
|
}
|
|
|
|
func withUpdatedSessionIdWithRevealedOptions(_ sessionIdWithRevealedOptions: Int64?) -> RecentSessionsControllerState {
|
|
return RecentSessionsControllerState(editing: self.editing, sessionIdWithRevealedOptions: sessionIdWithRevealedOptions, removingSessionId: self.removingSessionId, terminatingOtherSessions: self.terminatingOtherSessions)
|
|
}
|
|
|
|
func withUpdatedRemovingSessionId(_ removingSessionId: Int64?) -> RecentSessionsControllerState {
|
|
return RecentSessionsControllerState(editing: self.editing, sessionIdWithRevealedOptions: self.sessionIdWithRevealedOptions, removingSessionId: removingSessionId, terminatingOtherSessions: self.terminatingOtherSessions)
|
|
}
|
|
|
|
func withUpdatedTerminatingOtherSessions(_ terminatingOtherSessions: Bool) -> RecentSessionsControllerState {
|
|
return RecentSessionsControllerState(editing: self.editing, sessionIdWithRevealedOptions: self.sessionIdWithRevealedOptions, removingSessionId: self.removingSessionId, terminatingOtherSessions: terminatingOtherSessions)
|
|
}
|
|
}
|
|
|
|
private func recentSessionsControllerEntries(presentationData: PresentationData, state: RecentSessionsControllerState, sessionsState: ActiveSessionsContextState, enableQRLogin: Bool) -> [RecentSessionsEntry] {
|
|
var entries: [RecentSessionsEntry] = []
|
|
|
|
entries.append(.header(SortIndex(section: 0, item: 0), presentationData.strings.AuthSessions_HeaderInfo))
|
|
|
|
if !sessionsState.sessions.isEmpty {
|
|
var existingSessionIds = Set<Int64>()
|
|
entries.append(.currentSessionHeader(SortIndex(section: 1, item: 0), presentationData.strings.AuthSessions_CurrentSession))
|
|
if let index = sessionsState.sessions.firstIndex(where: { $0.hash == 0 }) {
|
|
existingSessionIds.insert(sessionsState.sessions[index].hash)
|
|
entries.append(.currentSession(SortIndex(section: 1, item: 1), presentationData.strings, presentationData.dateTimeFormat, sessionsState.sessions[index]))
|
|
}
|
|
|
|
var hasAddDevice = false
|
|
if sessionsState.sessions.count > 1 || enableQRLogin {
|
|
if sessionsState.sessions.count > 1 {
|
|
entries.append(.terminateOtherSessions(SortIndex(section: 1, item: 2), presentationData.strings.AuthSessions_TerminateOtherSessions))
|
|
entries.append(.currentSessionInfo(SortIndex(section: 1, item: 3), presentationData.strings.AuthSessions_TerminateOtherSessionsHelp))
|
|
} else if enableQRLogin {
|
|
hasAddDevice = true
|
|
// entries.append(.currentAddDevice(SortIndex(section: 1, item: 4), presentationData.strings.AuthSessions_AddDevice))
|
|
entries.append(.currentSessionInfo(SortIndex(section: 1, item: 5), presentationData.strings.AuthSessions_OtherDevices))
|
|
}
|
|
|
|
let filteredPendingSessions: [RecentAccountSession] = sessionsState.sessions.filter({ $0.flags.contains(.passwordPending) })
|
|
if !filteredPendingSessions.isEmpty {
|
|
entries.append(.pendingSessionsHeader(SortIndex(section: 1, item: 6), presentationData.strings.AuthSessions_IncompleteAttempts))
|
|
for i in 0 ..< filteredPendingSessions.count {
|
|
if !existingSessionIds.contains(filteredPendingSessions[i].hash) {
|
|
existingSessionIds.insert(filteredPendingSessions[i].hash)
|
|
entries.append(.pendingSession(index: Int32(i), sortIndex: SortIndex(section: 2, item: i), strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredPendingSessions[i], enabled: state.removingSessionId != filteredPendingSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredPendingSessions[i].hash))
|
|
}
|
|
}
|
|
entries.append(.pendingSessionsInfo(SortIndex(section: 3, item: 0), presentationData.strings.AuthSessions_IncompleteAttemptsInfo))
|
|
}
|
|
|
|
if sessionsState.sessions.count > 1 {
|
|
entries.append(.otherSessionsHeader(SortIndex(section: 4, item: 0), presentationData.strings.AuthSessions_OtherSessions))
|
|
}
|
|
|
|
// if enableQRLogin && !hasAddDevice {
|
|
// entries.append(.addDevice(SortIndex(section: 4, item: 1), presentationData.strings.AuthSessions_AddDevice))
|
|
// }
|
|
|
|
let filteredSessions: [RecentAccountSession] = sessionsState.sessions.sorted(by: { lhs, rhs in
|
|
return lhs.activityDate > rhs.activityDate
|
|
})
|
|
|
|
for i in 0 ..< filteredSessions.count {
|
|
if !existingSessionIds.contains(filteredSessions[i].hash) {
|
|
existingSessionIds.insert(filteredSessions[i].hash)
|
|
entries.append(.session(index: Int32(i), sortIndex: SortIndex(section: 5, item: i), strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredSessions[i], enabled: state.removingSessionId != filteredSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredSessions[i].hash))
|
|
}
|
|
}
|
|
|
|
if enableQRLogin && !hasAddDevice {
|
|
entries.append(.devicesInfo(SortIndex(section: 6, item: 0), presentationData.strings.AuthSessions_OtherDevices))
|
|
}
|
|
}
|
|
|
|
entries.append(.ttlHeader(SortIndex(section: 7, item: 0), presentationData.strings.AuthSessions_TerminateIfAwayTitle.uppercased()))
|
|
entries.append(.ttlTimeout(SortIndex(section: 7, item: 1), presentationData.strings.AuthSessions_TerminateIfAwayFor, timeIntervalString(strings: presentationData.strings, value: sessionsState.ttlDays * 24 * 60 * 60)))
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
private func recentSessionsControllerEntries(presentationData: PresentationData, state: RecentSessionsControllerState, websites: [WebAuthorization]?, peers: [PeerId : Peer]?) -> [RecentSessionsEntry] {
|
|
var entries: [RecentSessionsEntry] = []
|
|
|
|
if let websites = websites, let peers = peers {
|
|
var existingSessionIds = Set<Int64>()
|
|
if websites.count > 0 {
|
|
entries.append(.terminateAllWebSessions(SortIndex(section: 0, item: 0), presentationData.strings.AuthSessions_LogOutApplications))
|
|
entries.append(.currentSessionInfo(SortIndex(section: 0, item: 1), presentationData.strings.AuthSessions_LogOutApplicationsHelp))
|
|
|
|
entries.append(.otherSessionsHeader(SortIndex(section: 0, item: 2), presentationData.strings.AuthSessions_LoggedInWithTelegram))
|
|
|
|
let filteredWebsites: [WebAuthorization] = websites.sorted(by: { lhs, rhs in
|
|
return lhs.dateActive > rhs.dateActive
|
|
})
|
|
|
|
for i in 0 ..< filteredWebsites.count {
|
|
let website = websites[i]
|
|
if !existingSessionIds.contains(website.hash) {
|
|
existingSessionIds.insert(website.hash)
|
|
entries.append(.website(index: Int32(i), sortIndex: SortIndex(section: 1, item: i), strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, website: website, peer: peers[website.botId], enabled: state.removingSessionId != website.hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == website.hash))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
private final class RecentSessionsControllerImpl: ItemListController, RecentSessionsController {
|
|
}
|
|
|
|
public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext, webSessionsContext: WebSessionsContext, websitesOnly: Bool) -> ViewController & RecentSessionsController {
|
|
let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true)
|
|
let stateValue = Atomic(value: RecentSessionsControllerState())
|
|
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
|
statePromise.set(stateValue.modify { f($0) })
|
|
}
|
|
|
|
activeSessionsContext.loadMore()
|
|
webSessionsContext.loadMore()
|
|
|
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
|
var pushControllerImpl: ((ViewController) -> Void)?
|
|
var dismissImpl: (() -> Void)?
|
|
|
|
let actionsDisposable = DisposableSet()
|
|
|
|
let removeSessionDisposable = MetaDisposable()
|
|
actionsDisposable.add(removeSessionDisposable)
|
|
|
|
let terminateOtherSessionsDisposable = MetaDisposable()
|
|
actionsDisposable.add(terminateOtherSessionsDisposable)
|
|
|
|
let didAppearValue = ValuePromise<Bool>(false)
|
|
|
|
if websitesOnly {
|
|
let autoDismissDisposable = (webSessionsContext.state
|
|
|> filter { !$0.isLoadingMore && $0.sessions.isEmpty }
|
|
|> take(1)
|
|
|> mapToSignal { _ in
|
|
return didAppearValue.get()
|
|
|> filter { $0 }
|
|
|> take(1)
|
|
}
|
|
|> deliverOnMainQueue).start(next: { _ in
|
|
dismissImpl?()
|
|
})
|
|
actionsDisposable.add(autoDismissDisposable)
|
|
}
|
|
|
|
let mode = ValuePromise<RecentSessionsMode>(websitesOnly ? .websites : .sessions)
|
|
|
|
let removeSessionImpl: (Int64, @escaping () -> Void) -> Void = { sessionId, completion in
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: [
|
|
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateSessionText),
|
|
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateSession, color: .destructive, action: {
|
|
dismissAction()
|
|
completion()
|
|
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(sessionId)
|
|
}
|
|
|
|
removeSessionDisposable.set((activeSessionsContext.remove(hash: sessionId)
|
|
|> deliverOnMainQueue).start(error: { _ in
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(nil)
|
|
}
|
|
}, completed: {
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(nil)
|
|
}
|
|
context.sharedContext.updateNotificationTokensRegistration()
|
|
}))
|
|
})
|
|
]),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}
|
|
|
|
let removeWebSessionImpl: (Int64) -> Void = { sessionId in
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(sessionId)
|
|
}
|
|
|
|
removeSessionDisposable.set(((webSessionsContext.remove(hash: sessionId)
|
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
})
|
|
|> deliverOnMainQueue).start(error: { _ in
|
|
}, completed: {
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(nil)
|
|
}
|
|
}))
|
|
}
|
|
|
|
let updateAuthorizationTTLDisposable = MetaDisposable()
|
|
actionsDisposable.add(updateAuthorizationTTLDisposable)
|
|
|
|
let updateSessionDisposable = MetaDisposable()
|
|
actionsDisposable.add(updateSessionDisposable)
|
|
|
|
let arguments = RecentSessionsControllerArguments(context: context, setSessionIdWithRevealedOptions: { sessionId, fromSessionId in
|
|
updateState { state in
|
|
if (sessionId == nil && fromSessionId == state.sessionIdWithRevealedOptions) || (sessionId != nil && fromSessionId == nil) {
|
|
return state.withUpdatedSessionIdWithRevealedOptions(sessionId)
|
|
} else {
|
|
return state
|
|
}
|
|
}
|
|
}, removeSession: { sessionId in
|
|
removeSessionImpl(sessionId, {})
|
|
}, terminateOtherSessions: {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: [
|
|
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateOtherSessionsText),
|
|
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateOtherSessions, color: .destructive, action: {
|
|
dismissAction()
|
|
|
|
updateState {
|
|
return $0.withUpdatedTerminatingOtherSessions(true)
|
|
}
|
|
|
|
terminateOtherSessionsDisposable.set((activeSessionsContext.removeOther()
|
|
|> deliverOnMainQueue).start(error: { _ in
|
|
updateState {
|
|
return $0.withUpdatedTerminatingOtherSessions(false)
|
|
}
|
|
}, completed: {
|
|
updateState {
|
|
return $0.withUpdatedTerminatingOtherSessions(false)
|
|
}
|
|
context.sharedContext.updateNotificationTokensRegistration()
|
|
}))
|
|
})
|
|
]),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}, openSession: { session in
|
|
let controller = RecentSessionScreen(context: context, subject: .session(session), updateAcceptSecretChats: { value in
|
|
updateSessionDisposable.set(activeSessionsContext.updateSessionAcceptsSecretChats(session, accepts: value).start())
|
|
}, updateAcceptIncomingCalls: { value in
|
|
updateSessionDisposable.set(activeSessionsContext.updateSessionAcceptsIncomingCalls(session, accepts: value).start())
|
|
}, remove: { completion in
|
|
removeSessionImpl(session.hash, {
|
|
completion()
|
|
})
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
}, openWebSession: { session, peer in
|
|
let controller = RecentSessionScreen(context: context, subject: .website(session, peer), updateAcceptSecretChats: { _ in }, updateAcceptIncomingCalls: { _ in }, remove: { completion in
|
|
removeWebSessionImpl(session.hash)
|
|
completion()
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
}, removeWebSession: { sessionId in
|
|
removeWebSessionImpl(sessionId)
|
|
}, terminateAllWebSessions: {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: [
|
|
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_LogOutApplications, color: .destructive, action: {
|
|
dismissAction()
|
|
|
|
updateState {
|
|
return $0.withUpdatedTerminatingOtherSessions(true)
|
|
}
|
|
|
|
terminateOtherSessionsDisposable.set((webSessionsContext.removeAll()
|
|
|> deliverOnMainQueue).start(error: { _ in
|
|
}, completed: {
|
|
updateState {
|
|
return $0.withUpdatedTerminatingOtherSessions(false)
|
|
}
|
|
mode.set(.sessions)
|
|
}))
|
|
})
|
|
]),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}, addDevice: {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
DeviceAccess.authorizeAccess(to: .camera(.video), presentationData: presentationData, present: { c, a in
|
|
c.presentationArguments = a
|
|
context.sharedContext.mainWindow?.present(c, on: .root)
|
|
}, openSettings: {
|
|
context.sharedContext.applicationBindings.openSettings()
|
|
}, { granted in
|
|
guard granted else {
|
|
return
|
|
}
|
|
pushControllerImpl?(AuthTransferScanScreen(context: context, activeSessionsContext: activeSessionsContext))
|
|
})
|
|
}, openOtherAppsUrl: {
|
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/apps", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
|
}, setupAuthorizationTTL: {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
let ttlAction: (Int32) -> Void = { ttl in
|
|
updateAuthorizationTTLDisposable.set(activeSessionsContext.updateAuthorizationTTL(days: ttl).start())
|
|
}
|
|
let timeoutValues: [Int32] = [
|
|
7,
|
|
30,
|
|
90,
|
|
180
|
|
]
|
|
let timeoutItems: [ActionSheetItem] = timeoutValues.map { value in
|
|
return ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: value * 24 * 60 * 60), action: {
|
|
dismissAction()
|
|
ttlAction(value)
|
|
})
|
|
}
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: timeoutItems),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, nil)
|
|
}, openDesktopLink: {
|
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://getdesktop.telegram.org", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
|
}, openWebLink: {
|
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://web.telegram.org", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
|
})
|
|
|
|
let previousMode = Atomic<RecentSessionsMode>(value: .sessions)
|
|
|
|
let enableQRLogin = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
|
|> map { view -> Bool in
|
|
guard let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else {
|
|
return false
|
|
}
|
|
guard let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR else {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|> distinctUntilChanged
|
|
|
|
let signal = combineLatest(context.sharedContext.presentationData, mode.get(), statePromise.get(), activeSessionsContext.state, webSessionsContext.state, enableQRLogin)
|
|
|> deliverOnMainQueue
|
|
|> map { presentationData, mode, state, sessionsState, websitesAndPeers, enableQRLogin -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|
var rightNavigationButton: ItemListNavigationButton?
|
|
let websites = websitesAndPeers.sessions
|
|
let peers = websitesAndPeers.peers
|
|
|
|
if sessionsState.sessions.count > 1 {
|
|
if state.terminatingOtherSessions {
|
|
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
|
} else if state.editing {
|
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
|
updateState { state in
|
|
return state.withUpdatedEditing(false)
|
|
}
|
|
})
|
|
} else {
|
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
|
updateState { state in
|
|
return state.withUpdatedEditing(true)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
let emptyStateItem: ItemListControllerEmptyStateItem? = nil
|
|
// if sessionsState.sessions.count == 1 && mode == .sessions {
|
|
// emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
|
|
// } else {
|
|
// emptyStateItem = nil
|
|
// }
|
|
|
|
let title: ItemListControllerTitle
|
|
let entries: [RecentSessionsEntry]
|
|
if websitesOnly {
|
|
title = .text(presentationData.strings.AuthSessions_LoggedIn)
|
|
} else {
|
|
title = .text(presentationData.strings.AuthSessions_DevicesTitle)
|
|
}
|
|
|
|
var animateChanges = true
|
|
switch (mode, websites, peers) {
|
|
case (.websites, let websites, let peers):
|
|
entries = recentSessionsControllerEntries(presentationData: presentationData, state: state, websites: websites, peers: peers)
|
|
default:
|
|
entries = recentSessionsControllerEntries(presentationData: presentationData, state: state, sessionsState: sessionsState, enableQRLogin: enableQRLogin)
|
|
}
|
|
|
|
let previousMode = previousMode.swap(mode)
|
|
var crossfadeState = false
|
|
|
|
if previousMode != mode {
|
|
crossfadeState = true
|
|
animateChanges = false
|
|
}
|
|
|
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: crossfadeState, animateChanges: animateChanges, scrollEnabled: emptyStateItem == nil)
|
|
|
|
return (controllerState, (listState, arguments))
|
|
} |> afterDisposed {
|
|
actionsDisposable.dispose()
|
|
}
|
|
|
|
let controller = RecentSessionsControllerImpl(context: context, state: signal)
|
|
controller.titleControlValueChanged = { [weak mode] index in
|
|
mode?.set(index == 0 ? .sessions : .websites)
|
|
}
|
|
controller.didAppear = { _ in
|
|
didAppearValue.set(true)
|
|
}
|
|
presentControllerImpl = { [weak controller] c, p in
|
|
if let controller = controller {
|
|
controller.present(c, in: .window(.root), with: p)
|
|
}
|
|
}
|
|
pushControllerImpl = { [weak controller] c in
|
|
controller?.push(c)
|
|
}
|
|
dismissImpl = { [weak controller] in
|
|
controller?.dismiss()
|
|
}
|
|
|
|
return controller
|
|
}
|