import Foundation import UIKit import Display import SwiftSignalKit import TelegramCore import TelegramPresentationData import ItemListUI import AccountContext // MARK: - Entry Definition private enum GhostgramSettingsSection: Int32 { case features } private enum GhostgramSettingsEntry: ItemListNodeEntry { case deletedMessages(PresentationTheme, String, String) case ghostMode(PresentationTheme, String, String) case misc(PresentationTheme, String, String) case deviceSpoof(PresentationTheme, String, String) case voiceMorpher(PresentationTheme, String, String) case sendDelay(PresentationTheme, String, String) case info(PresentationTheme, String) var section: ItemListSectionId { return GhostgramSettingsSection.features.rawValue } var stableId: Int32 { switch self { case .deletedMessages: return 0 case .ghostMode: return 1 case .misc: return 2 case .deviceSpoof: return 3 case .voiceMorpher: return 4 case .sendDelay: return 5 case .info: return 6 } } static func ==(lhs: GhostgramSettingsEntry, rhs: GhostgramSettingsEntry) -> Bool { switch lhs { case let .deletedMessages(lhsTheme, lhsText, lhsValue): if case let .deletedMessages(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } return false case let .ghostMode(lhsTheme, lhsText, lhsValue): if case let .ghostMode(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } return false case let .misc(lhsTheme, lhsText, lhsValue): if case let .misc(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } return false case let .deviceSpoof(lhsTheme, lhsText, lhsValue): if case let .deviceSpoof(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } return false case let .voiceMorpher(lhsTheme, lhsText, lhsValue): if case let .voiceMorpher(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } return false case let .sendDelay(lhsTheme, lhsText, lhsValue): if case let .sendDelay(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } return false case let .info(lhsTheme, lhsText): if case let .info(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } return false } } static func <(lhs: GhostgramSettingsEntry, rhs: GhostgramSettingsEntry) -> Bool { return lhs.stableId < rhs.stableId } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! GhostgramSettingsControllerArguments switch self { case let .deletedMessages(_, text, value): return ItemListDisclosureItem( presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openDeletedMessages() } ) case let .ghostMode(_, text, value): return ItemListDisclosureItem( presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openGhostMode() } ) case let .misc(_, text, value): return ItemListDisclosureItem( presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openMisc() } ) case let .deviceSpoof(_, text, value): return ItemListDisclosureItem( presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openDeviceSpoof() } ) case let .voiceMorpher(_, text, value): return ItemListDisclosureItem( presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openVoiceMorpher() } ) case let .sendDelay(_, text, value): return ItemListDisclosureItem( presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSendDelay() } ) case let .info(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } } // MARK: - Arguments private final class GhostgramSettingsControllerArguments { let openDeletedMessages: () -> Void let openGhostMode: () -> Void let openMisc: () -> Void let openDeviceSpoof: () -> Void let openVoiceMorpher: () -> Void let openSendDelay: () -> Void init( openDeletedMessages: @escaping () -> Void, openGhostMode: @escaping () -> Void, openMisc: @escaping () -> Void, openDeviceSpoof: @escaping () -> Void, openVoiceMorpher: @escaping () -> Void, openSendDelay: @escaping () -> Void ) { self.openDeletedMessages = openDeletedMessages self.openGhostMode = openGhostMode self.openMisc = openMisc self.openDeviceSpoof = openDeviceSpoof self.openVoiceMorpher = openVoiceMorpher self.openSendDelay = openSendDelay } } // MARK: - State private struct GhostgramSettingsState: Equatable { var deletedMessagesEnabled: Bool var ghostModeEnabled: Bool var ghostModeActiveCount: Int var miscEnabled: Bool var miscActiveCount: Int var deviceSpoofEnabled: Bool var voiceMorpherEnabled: Bool var voiceMorpherPresetName: String var sendDelayEnabled: Bool static func current() -> GhostgramSettingsState { return GhostgramSettingsState( deletedMessagesEnabled: AntiDeleteManager.shared.isEnabled, ghostModeEnabled: GhostModeManager.shared.isEnabled, ghostModeActiveCount: GhostModeManager.shared.activeFeatureCount, miscEnabled: MiscSettingsManager.shared.isEnabled, miscActiveCount: MiscSettingsManager.shared.activeFeatureCount, deviceSpoofEnabled: DeviceSpoofManager.shared.isEnabled, voiceMorpherEnabled: VoiceMorpherManager.shared.isEnabled, voiceMorpherPresetName: VoiceMorpherManager.shared.selectedPreset.name, sendDelayEnabled: SendDelayManager.shared.isEnabled ) } } // MARK: - Entries builder private func ghostgramSettingsControllerEntries( presentationData: PresentationData, state: GhostgramSettingsState ) -> [GhostgramSettingsEntry] { var entries: [GhostgramSettingsEntry] = [] // Deleted Messages let deletedStatus = state.deletedMessagesEnabled ? "Вкл" : "Выкл" entries.append(.deletedMessages(presentationData.theme, "Удалённые сообщения", deletedStatus)) // Ghost Mode let ghostModeStatus = state.ghostModeEnabled ? "\(state.ghostModeActiveCount)/5" : "Выкл" entries.append(.ghostMode(presentationData.theme, "Режим призрака", ghostModeStatus)) // Misc let miscStatus = state.miscEnabled ? "\(state.miscActiveCount)/5" : "Выкл" entries.append(.misc(presentationData.theme, "Прочее", miscStatus)) // Device Spoofing let deviceSpoofStatus = state.deviceSpoofEnabled ? "Вкл" : "Выкл" entries.append(.deviceSpoof(presentationData.theme, "Подмена устройства", deviceSpoofStatus)) // Voice Morpher let voiceMorpherStatus = state.voiceMorpherEnabled ? state.voiceMorpherPresetName : "Выкл" entries.append(.voiceMorpher(presentationData.theme, "Голосовой двойник", voiceMorpherStatus)) // Send Delay let sendDelayStatus = state.sendDelayEnabled ? "Вкл" : "Выкл" entries.append(.sendDelay(presentationData.theme, "Отложка сообщений", sendDelayStatus)) // Info entries.append(.info(presentationData.theme, "Функции конфиденциальности Ghostgram. Скрытые отметки о прочтении, обход исчезающих сообщений, обход защиты от пересылки и другое.")) return entries } // MARK: - Controller public func ghostgramSettingsController(context: AccountContext) -> ViewController { var pushControllerImpl: ((ViewController, Bool) -> Void)? let stateValue = Atomic(value: GhostgramSettingsState.current()) let statePromise = ValuePromise(GhostgramSettingsState.current(), ignoreRepeated: true) let arguments = GhostgramSettingsControllerArguments( openDeletedMessages: { pushControllerImpl?(deletedMessagesController(context: context), true) }, openGhostMode: { pushControllerImpl?(ghostModeController(context: context), true) }, openMisc: { pushControllerImpl?(miscController(context: context), true) }, openDeviceSpoof: { pushControllerImpl?(deviceSpoofController(context: context), true) }, openVoiceMorpher: { pushControllerImpl?(voiceMorpherController(context: context), true) }, openSendDelay: { pushControllerImpl?(sendDelayController(context: context), true) } ) let signal = combineLatest( context.sharedContext.presentationData, statePromise.get() ) |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in let entries = ghostgramSettingsControllerEntries(presentationData: presentationData, state: state) let controllerState = ItemListControllerState( presentationData: ItemListPresentationData(presentationData), title: .text("Ghostgram"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true ) let listState = ItemListNodeState( presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, animateChanges: true ) return (controllerState, (listState, arguments)) } let controller = ItemListController(context: context, state: signal) // Refresh state when view appears controller.visibleBottomContentOffsetChanged = { _ in } controller.didAppear = { _ in let newState = GhostgramSettingsState.current() let _ = stateValue.modify { _ in newState } statePromise.set(newState) } pushControllerImpl = { [weak controller] c, animated in controller?.push(c) } return controller }