mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
343 lines
14 KiB
Swift
343 lines
14 KiB
Swift
import Foundation
|
|
import Display
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
|
|
private final class RecentSessionsControllerArguments {
|
|
let account: Account
|
|
|
|
let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void
|
|
let removeSession: (Int64) -> Void
|
|
|
|
init(account: Account, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void) {
|
|
self.account = account
|
|
self.setSessionIdWithRevealedOptions = setSessionIdWithRevealedOptions
|
|
self.removeSession = removeSession
|
|
}
|
|
}
|
|
|
|
private enum RecentSessionsSection: Int32 {
|
|
case currentSession
|
|
case otherSessions
|
|
}
|
|
|
|
private enum RecentSessionsEntryStableId: Hashable {
|
|
case session(Int64)
|
|
case index(Int32)
|
|
|
|
var hashValue: Int {
|
|
switch self {
|
|
case let .session(hash):
|
|
return hash.hashValue
|
|
case let .index(index):
|
|
return index.hashValue
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: RecentSessionsEntryStableId, rhs: RecentSessionsEntryStableId) -> Bool {
|
|
switch lhs {
|
|
case let .session(hash):
|
|
if case .session(hash) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .index(index):
|
|
if case .index(index) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum RecentSessionsEntry: ItemListNodeEntry {
|
|
case currentSessionHeader
|
|
case currentSession(RecentAccountSession)
|
|
case terminateOtherSessions
|
|
case currentSessionInfo
|
|
|
|
case otherSessionsHeader
|
|
case session(index: Int32, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
|
|
|
|
var section: ItemListSectionId {
|
|
switch self {
|
|
case .currentSessionHeader, .currentSession, .terminateOtherSessions, .currentSessionInfo:
|
|
return RecentSessionsSection.currentSession.rawValue
|
|
case .otherSessionsHeader, .session:
|
|
return RecentSessionsSection.otherSessions.rawValue
|
|
}
|
|
}
|
|
|
|
var stableId: RecentSessionsEntryStableId {
|
|
switch self {
|
|
case .currentSessionHeader:
|
|
return .index(0)
|
|
case .currentSession:
|
|
return .index(1)
|
|
case .terminateOtherSessions:
|
|
return .index(2)
|
|
case .currentSessionInfo:
|
|
return .index(3)
|
|
case .otherSessionsHeader:
|
|
return .index(4)
|
|
case let .session(_, session, _, _, _):
|
|
return .session(session.hash)
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool {
|
|
switch lhs {
|
|
case .currentSessionHeader, .terminateOtherSessions, .currentSessionInfo, .otherSessionsHeader:
|
|
return lhs.stableId == rhs.stableId
|
|
case let .currentSession(session):
|
|
if case .currentSession(session) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .session(index, session, enabled, editing, revealed):
|
|
if case .session(index, session, enabled, editing, revealed) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
static func <(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool {
|
|
switch lhs.stableId {
|
|
case let .index(lhsIndex):
|
|
if case let .index(rhsIndex) = rhs.stableId {
|
|
return lhsIndex <= rhsIndex
|
|
} else {
|
|
return true
|
|
}
|
|
case .session:
|
|
switch lhs {
|
|
case let .session(lhsIndex, _, _, _, _):
|
|
if case let .session(rhsIndex, _, _, _, _) = rhs {
|
|
return lhsIndex <= rhsIndex
|
|
} else {
|
|
return false
|
|
}
|
|
default:
|
|
preconditionFailure()
|
|
}
|
|
}
|
|
}
|
|
|
|
func item(_ arguments: RecentSessionsControllerArguments) -> ListViewItem {
|
|
switch self {
|
|
case .currentSessionHeader:
|
|
return ItemListSectionHeaderItem(text: "CURRENT SESSION", sectionId: self.section)
|
|
case let .currentSession(session):
|
|
return ItemListRecentSessionItem(session: session, enabled: true, editable: false, editing: false, revealed: false, sectionId: self.section, setSessionIdWithRevealedOptions: { _, _ in
|
|
}, removeSession: { _ in
|
|
})
|
|
case .terminateOtherSessions:
|
|
return ItemListActionItem(title: "Terminate all other sessions", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
|
|
|
})
|
|
case .currentSessionInfo:
|
|
return ItemListTextItem(text: "Logs out all devices except for this one.", sectionId: self.section)
|
|
case .otherSessionsHeader:
|
|
return ItemListSectionHeaderItem(text: "ACTIVE SESSIONS", sectionId: self.section)
|
|
case let .session(_, session, enabled, editing, revealed):
|
|
return ItemListRecentSessionItem(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)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct RecentSessionsControllerState: Equatable {
|
|
let editing: Bool
|
|
let sessionIdWithRevealedOptions: Int64?
|
|
let removingSessionId: Int64?
|
|
|
|
init() {
|
|
self.editing = false
|
|
self.sessionIdWithRevealedOptions = nil
|
|
self.removingSessionId = nil
|
|
}
|
|
|
|
init(editing: Bool, sessionIdWithRevealedOptions: Int64?, removingSessionId: Int64?) {
|
|
self.editing = editing
|
|
self.sessionIdWithRevealedOptions = sessionIdWithRevealedOptions
|
|
self.removingSessionId = removingSessionId
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func withUpdatedEditing(_ editing: Bool) -> RecentSessionsControllerState {
|
|
return RecentSessionsControllerState(editing: editing, sessionIdWithRevealedOptions: self.sessionIdWithRevealedOptions, removingSessionId: self.removingSessionId)
|
|
}
|
|
|
|
func withUpdatedSessionIdWithRevealedOptions(_ sessionIdWithRevealedOptions: Int64?) -> RecentSessionsControllerState {
|
|
return RecentSessionsControllerState(editing: self.editing, sessionIdWithRevealedOptions: sessionIdWithRevealedOptions, removingSessionId: self.removingSessionId)
|
|
}
|
|
|
|
func withUpdatedRemovingSessionId(_ removingSessionId: Int64?) -> RecentSessionsControllerState {
|
|
return RecentSessionsControllerState(editing: self.editing, sessionIdWithRevealedOptions: self.sessionIdWithRevealedOptions, removingSessionId: removingSessionId)
|
|
}
|
|
}
|
|
|
|
private func recentSessionsControllerEntries(state: RecentSessionsControllerState, sessions: [RecentAccountSession]?) -> [RecentSessionsEntry] {
|
|
var entries: [RecentSessionsEntry] = []
|
|
|
|
if let sessions = sessions {
|
|
var existingSessionIds = Set<Int64>()
|
|
entries.append(.currentSessionHeader)
|
|
if let index = sessions.index(where: { $0.hash == 0 }) {
|
|
existingSessionIds.insert(sessions[index].hash)
|
|
entries.append(.currentSession(sessions[index]))
|
|
}
|
|
entries.append(.terminateOtherSessions)
|
|
entries.append(.currentSessionInfo)
|
|
|
|
if sessions.count > 1 {
|
|
entries.append(.otherSessionsHeader)
|
|
|
|
let filteredSessions: [RecentAccountSession] = sessions.sorted(by: { lhs, rhs in
|
|
return lhs.activityDate > rhs.activityDate
|
|
})
|
|
|
|
for i in 0 ..< filteredSessions.count {
|
|
if !existingSessionIds.contains(sessions[i].hash) {
|
|
existingSessionIds.insert(sessions[i].hash)
|
|
entries.append(.session(index: Int32(i), session: sessions[i], enabled: state.removingSessionId != sessions[i].hash, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == sessions[i].hash))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
public func recentSessionsController(account: Account) -> ViewController {
|
|
let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true)
|
|
let stateValue = Atomic(value: RecentSessionsControllerState())
|
|
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
|
statePromise.set(stateValue.modify { f($0) })
|
|
}
|
|
|
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
|
|
|
let actionsDisposable = DisposableSet()
|
|
|
|
let removeSessionDisposable = MetaDisposable()
|
|
actionsDisposable.add(removeSessionDisposable)
|
|
|
|
let sessionsPromise = Promise<[RecentAccountSession]?>(nil)
|
|
|
|
let arguments = RecentSessionsControllerArguments(account: account, 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
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(sessionId)
|
|
}
|
|
|
|
let applySessions: Signal<Void, NoError> = sessionsPromise.get()
|
|
|> filter { $0 != nil }
|
|
|> take(1)
|
|
|> deliverOnMainQueue
|
|
|> mapToSignal { sessions -> Signal<Void, NoError> in
|
|
if let sessions = sessions {
|
|
var updatedSessions = sessions
|
|
for i in 0 ..< updatedSessions.count {
|
|
if updatedSessions[i].hash == sessionId {
|
|
updatedSessions.remove(at: i)
|
|
break
|
|
}
|
|
}
|
|
sessionsPromise.set(.single(updatedSessions))
|
|
}
|
|
|
|
return .complete()
|
|
}
|
|
|
|
removeSessionDisposable.set((terminateAccountSession(account: account, hash: sessionId) |> then(applySessions) |> deliverOnMainQueue).start(error: { _ in
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(nil)
|
|
}
|
|
}, completed: {
|
|
updateState {
|
|
return $0.withUpdatedRemovingSessionId(nil)
|
|
}
|
|
}))
|
|
})
|
|
|
|
let sessionsSignal: Signal<[RecentAccountSession]?, NoError> = .single(nil) |> then(requestRecentAccountSessions(account: account) |> map { Optional($0) })
|
|
|
|
sessionsPromise.set(sessionsSignal)
|
|
|
|
var previousSessions: [RecentAccountSession]?
|
|
|
|
let signal = combineLatest(statePromise.get(), sessionsPromise.get())
|
|
|> deliverOnMainQueue
|
|
|> map { state, sessions -> (ItemListControllerState, (ItemListNodeState<RecentSessionsEntry>, RecentSessionsEntry.ItemGenerationArguments)) in
|
|
var rightNavigationButton: ItemListNavigationButton?
|
|
if let sessions = sessions, !sessions.isEmpty {
|
|
if state.editing {
|
|
rightNavigationButton = ItemListNavigationButton(title: "Done", style: .bold, enabled: true, action: {
|
|
updateState { state in
|
|
return state.withUpdatedEditing(false)
|
|
}
|
|
})
|
|
} else {
|
|
rightNavigationButton = ItemListNavigationButton(title: "Edit", style: .regular, enabled: true, action: {
|
|
updateState { state in
|
|
return state.withUpdatedEditing(true)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var emptyStateItem: ItemListControllerEmptyStateItem?
|
|
if sessions == nil {
|
|
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem()
|
|
}
|
|
|
|
let previous = previousSessions
|
|
previousSessions = sessions
|
|
|
|
let controllerState = ItemListControllerState(title: "Active Sessions", leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, animateChanges: true)
|
|
let listState = ItemListNodeState(entries: recentSessionsControllerEntries(state: state, sessions: sessions), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: previous != nil && sessions != nil && previous!.count >= sessions!.count)
|
|
|
|
return (controllerState, (listState, arguments))
|
|
} |> afterDisposed {
|
|
actionsDisposable.dispose()
|
|
}
|
|
|
|
let controller = ItemListController(signal)
|
|
presentControllerImpl = { [weak controller] c, p in
|
|
if let controller = controller {
|
|
controller.present(c, in: .window, with: p)
|
|
}
|
|
}
|
|
return controller
|
|
}
|