mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-04-24 16:46:02 +02:00
351 lines
15 KiB
Swift
351 lines
15 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import ItemListUI
|
|
import AccountContext
|
|
|
|
// MARK: - Entry Definition
|
|
|
|
private enum MiscSection: Int32 {
|
|
case master
|
|
case features
|
|
}
|
|
|
|
private enum MiscEntry: ItemListNodeEntry {
|
|
case masterHeader(PresentationTheme, String)
|
|
case masterToggle(PresentationTheme, String, Bool, Int, Int)
|
|
case masterInfo(PresentationTheme, String)
|
|
case featuresHeader(PresentationTheme, String)
|
|
case bypassCopyProtection(PresentationTheme, String, Bool)
|
|
case disableViewOnceAutoDelete(PresentationTheme, String, Bool)
|
|
case bypassScreenshotProtection(PresentationTheme, String, Bool)
|
|
case blockAds(PresentationTheme, String, Bool)
|
|
case alwaysOnline(PresentationTheme, String, Bool)
|
|
|
|
var section: ItemListSectionId {
|
|
switch self {
|
|
case .masterHeader, .masterToggle, .masterInfo:
|
|
return MiscSection.master.rawValue
|
|
case .featuresHeader, .bypassCopyProtection, .disableViewOnceAutoDelete, .bypassScreenshotProtection, .blockAds, .alwaysOnline:
|
|
return MiscSection.features.rawValue
|
|
}
|
|
}
|
|
|
|
var stableId: Int32 {
|
|
switch self {
|
|
case .masterHeader: return 0
|
|
case .masterToggle: return 1
|
|
case .masterInfo: return 2
|
|
case .featuresHeader: return 3
|
|
case .bypassCopyProtection: return 4
|
|
case .disableViewOnceAutoDelete: return 5
|
|
case .bypassScreenshotProtection: return 6
|
|
case .blockAds: return 7
|
|
case .alwaysOnline: return 8
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: MiscEntry, rhs: MiscEntry) -> Bool {
|
|
switch lhs {
|
|
case let .masterHeader(lhsTheme, lhsText):
|
|
if case let .masterHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
}
|
|
return false
|
|
case let .masterToggle(lhsTheme, lhsText, lhsValue, lhsActive, lhsTotal):
|
|
if case let .masterToggle(rhsTheme, rhsText, rhsValue, rhsActive, rhsTotal) = rhs,
|
|
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsActive == rhsActive, lhsTotal == rhsTotal {
|
|
return true
|
|
}
|
|
return false
|
|
case let .masterInfo(lhsTheme, lhsText):
|
|
if case let .masterInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
}
|
|
return false
|
|
case let .featuresHeader(lhsTheme, lhsText):
|
|
if case let .featuresHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
}
|
|
return false
|
|
case let .bypassCopyProtection(lhsTheme, lhsText, lhsValue):
|
|
if case let .bypassCopyProtection(rhsTheme, rhsText, rhsValue) = rhs,
|
|
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
}
|
|
return false
|
|
case let .disableViewOnceAutoDelete(lhsTheme, lhsText, lhsValue):
|
|
if case let .disableViewOnceAutoDelete(rhsTheme, rhsText, rhsValue) = rhs,
|
|
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
}
|
|
return false
|
|
case let .bypassScreenshotProtection(lhsTheme, lhsText, lhsValue):
|
|
if case let .bypassScreenshotProtection(rhsTheme, rhsText, rhsValue) = rhs,
|
|
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
}
|
|
return false
|
|
case let .blockAds(lhsTheme, lhsText, lhsValue):
|
|
if case let .blockAds(rhsTheme, rhsText, rhsValue) = rhs,
|
|
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
}
|
|
return false
|
|
case let .alwaysOnline(lhsTheme, lhsText, lhsValue):
|
|
if case let .alwaysOnline(rhsTheme, rhsText, rhsValue) = rhs,
|
|
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
static func <(lhs: MiscEntry, rhs: MiscEntry) -> Bool {
|
|
return lhs.stableId < rhs.stableId
|
|
}
|
|
|
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
|
let arguments = arguments as! MiscControllerArguments
|
|
switch self {
|
|
case let .masterHeader(_, text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .masterToggle(_, text, value, activeCount, totalCount):
|
|
let title = "\(text) \(activeCount)/\(totalCount)"
|
|
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.toggleMaster(value)
|
|
})
|
|
case let .masterInfo(_, text):
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
|
case let .featuresHeader(_, text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .bypassCopyProtection(_, text, value):
|
|
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
|
arguments.toggleBypassCopyProtection()
|
|
})
|
|
case let .disableViewOnceAutoDelete(_, text, value):
|
|
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
|
arguments.toggleDisableViewOnceAutoDelete()
|
|
})
|
|
case let .bypassScreenshotProtection(_, text, value):
|
|
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
|
arguments.toggleBypassScreenshotProtection()
|
|
})
|
|
case let .blockAds(_, text, value):
|
|
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
|
arguments.toggleBlockAds()
|
|
})
|
|
case let .alwaysOnline(_, text, value):
|
|
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
|
arguments.toggleAlwaysOnline()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Arguments
|
|
|
|
private final class MiscControllerArguments {
|
|
let toggleMaster: (Bool) -> Void
|
|
let toggleBypassCopyProtection: () -> Void
|
|
let toggleDisableViewOnceAutoDelete: () -> Void
|
|
let toggleBypassScreenshotProtection: () -> Void
|
|
let toggleBlockAds: () -> Void
|
|
let toggleAlwaysOnline: () -> Void
|
|
|
|
init(
|
|
toggleMaster: @escaping (Bool) -> Void,
|
|
toggleBypassCopyProtection: @escaping () -> Void,
|
|
toggleDisableViewOnceAutoDelete: @escaping () -> Void,
|
|
toggleBypassScreenshotProtection: @escaping () -> Void,
|
|
toggleBlockAds: @escaping () -> Void,
|
|
toggleAlwaysOnline: @escaping () -> Void
|
|
) {
|
|
self.toggleMaster = toggleMaster
|
|
self.toggleBypassCopyProtection = toggleBypassCopyProtection
|
|
self.toggleDisableViewOnceAutoDelete = toggleDisableViewOnceAutoDelete
|
|
self.toggleBypassScreenshotProtection = toggleBypassScreenshotProtection
|
|
self.toggleBlockAds = toggleBlockAds
|
|
self.toggleAlwaysOnline = toggleAlwaysOnline
|
|
}
|
|
}
|
|
|
|
// MARK: - State
|
|
|
|
private struct MiscControllerState: Equatable {
|
|
var isEnabled: Bool
|
|
var bypassCopyProtection: Bool
|
|
var disableViewOnceAutoDelete: Bool
|
|
var bypassScreenshotProtection: Bool
|
|
var blockAds: Bool
|
|
var alwaysOnline: Bool
|
|
|
|
static func ==(lhs: MiscControllerState, rhs: MiscControllerState) -> Bool {
|
|
return lhs.isEnabled == rhs.isEnabled &&
|
|
lhs.bypassCopyProtection == rhs.bypassCopyProtection &&
|
|
lhs.disableViewOnceAutoDelete == rhs.disableViewOnceAutoDelete &&
|
|
lhs.bypassScreenshotProtection == rhs.bypassScreenshotProtection &&
|
|
lhs.blockAds == rhs.blockAds &&
|
|
lhs.alwaysOnline == rhs.alwaysOnline
|
|
}
|
|
}
|
|
|
|
// MARK: - Entries Builder
|
|
|
|
private func miscControllerEntries(presentationData: PresentationData, state: MiscControllerState) -> [MiscEntry] {
|
|
var entries: [MiscEntry] = []
|
|
|
|
let theme = presentationData.theme
|
|
|
|
var activeCount = 0
|
|
if state.bypassCopyProtection { activeCount += 1 }
|
|
if state.disableViewOnceAutoDelete { activeCount += 1 }
|
|
if state.bypassScreenshotProtection { activeCount += 1 }
|
|
if state.blockAds { activeCount += 1 }
|
|
if state.alwaysOnline { activeCount += 1 }
|
|
|
|
entries.append(.masterHeader(theme, "РАСШИРЕННЫЕ ВОЗМОЖНОСТИ"))
|
|
entries.append(.masterToggle(theme, "Misc", state.isEnabled, activeCount, 5))
|
|
entries.append(.masterInfo(theme, "Когда включено, выбранные функции обхода ограничений будут активны."))
|
|
|
|
entries.append(.featuresHeader(theme, "ФУНКЦИИ"))
|
|
entries.append(.bypassCopyProtection(theme, "Разрешить пересылку", state.bypassCopyProtection))
|
|
entries.append(.disableViewOnceAutoDelete(theme, "Сохранять View Once", state.disableViewOnceAutoDelete))
|
|
entries.append(.bypassScreenshotProtection(theme, "Разрешить скриншоты", state.bypassScreenshotProtection))
|
|
entries.append(.blockAds(theme, "Блокировать рекламу", state.blockAds))
|
|
entries.append(.alwaysOnline(theme, "Вечный онлайн", state.alwaysOnline))
|
|
|
|
return entries
|
|
}
|
|
|
|
// MARK: - Controller
|
|
|
|
public func miscController(context: AccountContext) -> ViewController {
|
|
let statePromise = ValuePromise(
|
|
MiscControllerState(
|
|
isEnabled: MiscSettingsManager.shared.isEnabled,
|
|
bypassCopyProtection: MiscSettingsManager.shared.bypassCopyProtection,
|
|
disableViewOnceAutoDelete: MiscSettingsManager.shared.disableViewOnceAutoDelete,
|
|
bypassScreenshotProtection: MiscSettingsManager.shared.bypassScreenshotProtection,
|
|
blockAds: MiscSettingsManager.shared.blockAds,
|
|
alwaysOnline: MiscSettingsManager.shared.alwaysOnline
|
|
),
|
|
ignoreRepeated: true
|
|
)
|
|
let stateValue = Atomic(value: MiscControllerState(
|
|
isEnabled: MiscSettingsManager.shared.isEnabled,
|
|
bypassCopyProtection: MiscSettingsManager.shared.bypassCopyProtection,
|
|
disableViewOnceAutoDelete: MiscSettingsManager.shared.disableViewOnceAutoDelete,
|
|
bypassScreenshotProtection: MiscSettingsManager.shared.bypassScreenshotProtection,
|
|
blockAds: MiscSettingsManager.shared.blockAds,
|
|
alwaysOnline: MiscSettingsManager.shared.alwaysOnline
|
|
))
|
|
|
|
let updateState: ((inout MiscControllerState) -> Void) -> Void = { f in
|
|
let result = stateValue.modify { state in
|
|
var state = state
|
|
f(&state)
|
|
return state
|
|
}
|
|
statePromise.set(result)
|
|
}
|
|
|
|
let arguments = MiscControllerArguments(
|
|
toggleMaster: { value in
|
|
MiscSettingsManager.shared.isEnabled = value
|
|
updateState { state in
|
|
state.isEnabled = value
|
|
}
|
|
},
|
|
toggleBypassCopyProtection: {
|
|
let newValue = !MiscSettingsManager.shared.bypassCopyProtection
|
|
MiscSettingsManager.shared.bypassCopyProtection = newValue
|
|
updateState { state in
|
|
state.bypassCopyProtection = newValue
|
|
}
|
|
},
|
|
toggleDisableViewOnceAutoDelete: {
|
|
let newValue = !MiscSettingsManager.shared.disableViewOnceAutoDelete
|
|
MiscSettingsManager.shared.disableViewOnceAutoDelete = newValue
|
|
updateState { state in
|
|
state.disableViewOnceAutoDelete = newValue
|
|
}
|
|
},
|
|
toggleBypassScreenshotProtection: {
|
|
let newValue = !MiscSettingsManager.shared.bypassScreenshotProtection
|
|
MiscSettingsManager.shared.bypassScreenshotProtection = newValue
|
|
updateState { state in
|
|
state.bypassScreenshotProtection = newValue
|
|
}
|
|
},
|
|
toggleBlockAds: {
|
|
let newValue = !MiscSettingsManager.shared.blockAds
|
|
MiscSettingsManager.shared.blockAds = newValue
|
|
updateState { state in
|
|
state.blockAds = newValue
|
|
}
|
|
},
|
|
toggleAlwaysOnline: {
|
|
let newValue = !MiscSettingsManager.shared.alwaysOnline
|
|
MiscSettingsManager.shared.alwaysOnline = newValue
|
|
// State will be refreshed via notification if Ghost Mode got auto-disabled
|
|
updateState { state in
|
|
state.alwaysOnline = newValue
|
|
}
|
|
}
|
|
)
|
|
|
|
// Refresh UI when Ghost Mode is auto-disabled by mutual exclusion —
|
|
// the toggle flip happens externally, so we must pull fresh values from the managers.
|
|
let ghostModeChangedSignal: Signal<Void, NoError> = Signal { subscriber in
|
|
let observer = NotificationCenter.default.addObserver(
|
|
forName: GhostModeManager.settingsChangedNotification,
|
|
object: nil,
|
|
queue: .main
|
|
) { _ in
|
|
updateState { state in
|
|
state.isEnabled = MiscSettingsManager.shared.isEnabled
|
|
state.bypassCopyProtection = MiscSettingsManager.shared.bypassCopyProtection
|
|
state.disableViewOnceAutoDelete = MiscSettingsManager.shared.disableViewOnceAutoDelete
|
|
state.bypassScreenshotProtection = MiscSettingsManager.shared.bypassScreenshotProtection
|
|
state.blockAds = MiscSettingsManager.shared.blockAds
|
|
state.alwaysOnline = MiscSettingsManager.shared.alwaysOnline
|
|
}
|
|
}
|
|
return ActionDisposable { NotificationCenter.default.removeObserver(observer) }
|
|
}
|
|
let _ = ghostModeChangedSignal.start()
|
|
|
|
let signal = combineLatest(
|
|
context.sharedContext.presentationData,
|
|
statePromise.get()
|
|
)
|
|
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|
let entries = miscControllerEntries(presentationData: presentationData, state: state)
|
|
|
|
let controllerState = ItemListControllerState(
|
|
presentationData: ItemListPresentationData(presentationData),
|
|
title: .text("Прочее"),
|
|
leftNavigationButton: nil,
|
|
rightNavigationButton: nil,
|
|
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
|
animateChanges: false
|
|
)
|
|
|
|
let listState = ItemListNodeState(
|
|
presentationData: ItemListPresentationData(presentationData),
|
|
entries: entries,
|
|
style: .blocks,
|
|
animateChanges: true
|
|
)
|
|
|
|
return (controllerState, (listState, arguments))
|
|
}
|
|
|
|
let controller = ItemListController(context: context, state: signal)
|
|
return controller
|
|
}
|