1278 lines
69 KiB
Swift
1278 lines
69 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import ItemListUI
|
|
import ItemListPeerActionItem
|
|
import PresentationDataUtils
|
|
import AccountContext
|
|
import PromptUI
|
|
import UndoUI
|
|
|
|
private final class WinterGramSettingsArguments {
|
|
let updateSettings: (@escaping (WinterGramSettings) -> WinterGramSettings) -> Void
|
|
let toggleDropdown: (WinterGramDropdown) -> Void
|
|
let selectDropdownOption: (WinterGramDropdown, Int) -> Void
|
|
let toggleGhostExpanded: () -> Void
|
|
let editSpoofDevice: () -> Void
|
|
let addSpoofTemplate: () -> Void
|
|
let toggleSpoofTemplateSelected: (Int) -> Void
|
|
let deleteSelectedSpoofTemplates: () -> Void
|
|
let editApiId: () -> Void
|
|
let editApiHash: () -> Void
|
|
let openStash: () -> Void
|
|
let editStashPasscode: () -> Void
|
|
let editDeletedMark: () -> Void
|
|
let context: AccountContext
|
|
let openUrl: (String) -> Void
|
|
let clearDeleted: () -> Void
|
|
|
|
init(
|
|
context: AccountContext,
|
|
updateSettings: @escaping (@escaping (WinterGramSettings) -> WinterGramSettings) -> Void,
|
|
toggleDropdown: @escaping (WinterGramDropdown) -> Void,
|
|
selectDropdownOption: @escaping (WinterGramDropdown, Int) -> Void,
|
|
toggleGhostExpanded: @escaping () -> Void,
|
|
editSpoofDevice: @escaping () -> Void,
|
|
addSpoofTemplate: @escaping () -> Void,
|
|
toggleSpoofTemplateSelected: @escaping (Int) -> Void,
|
|
deleteSelectedSpoofTemplates: @escaping () -> Void,
|
|
editApiId: @escaping () -> Void,
|
|
editApiHash: @escaping () -> Void,
|
|
openStash: @escaping () -> Void,
|
|
editStashPasscode: @escaping () -> Void,
|
|
editDeletedMark: @escaping () -> Void,
|
|
openUrl: @escaping (String) -> Void,
|
|
clearDeleted: @escaping () -> Void
|
|
) {
|
|
self.updateSettings = updateSettings
|
|
self.toggleDropdown = toggleDropdown
|
|
self.selectDropdownOption = selectDropdownOption
|
|
self.toggleGhostExpanded = toggleGhostExpanded
|
|
self.editSpoofDevice = editSpoofDevice
|
|
self.addSpoofTemplate = addSpoofTemplate
|
|
self.toggleSpoofTemplateSelected = toggleSpoofTemplateSelected
|
|
self.deleteSelectedSpoofTemplates = deleteSelectedSpoofTemplates
|
|
self.editApiId = editApiId
|
|
self.editApiHash = editApiHash
|
|
self.openStash = openStash
|
|
self.editStashPasscode = editStashPasscode
|
|
self.editDeletedMark = editDeletedMark
|
|
self.context = context
|
|
self.openUrl = openUrl
|
|
self.clearDeleted = clearDeleted
|
|
}
|
|
}
|
|
|
|
public enum WinterGramSettingsSection: Int32, CaseIterable {
|
|
case banner
|
|
case ghost
|
|
case history
|
|
case stash
|
|
case antiFeatures
|
|
case chat
|
|
case liquidGlass
|
|
case spoofing
|
|
case ayugram
|
|
case other
|
|
|
|
public var title: String {
|
|
switch self {
|
|
case .banner:
|
|
return ""
|
|
case .ghost:
|
|
return "Ghost Mode"
|
|
case .history:
|
|
return "History"
|
|
case .stash:
|
|
return "Hidden Archive"
|
|
case .antiFeatures:
|
|
return "Features"
|
|
case .chat:
|
|
return "Chat"
|
|
case .liquidGlass:
|
|
return "Liquid Glass"
|
|
case .spoofing:
|
|
return "Spoofing"
|
|
case .ayugram:
|
|
return "Core"
|
|
case .other:
|
|
return "Other"
|
|
}
|
|
}
|
|
|
|
public var iconName: String {
|
|
switch self {
|
|
case .banner:
|
|
return ""
|
|
case .ghost:
|
|
return "eye.slash"
|
|
case .history:
|
|
return "clock.arrow.circlepath"
|
|
case .stash:
|
|
return "archivebox"
|
|
case .antiFeatures:
|
|
return "sparkles"
|
|
case .chat:
|
|
return "message"
|
|
case .liquidGlass:
|
|
return "drop"
|
|
case .spoofing:
|
|
return "theatermasks"
|
|
case .ayugram:
|
|
return "shield.fill"
|
|
case .other:
|
|
return "ellipsis.circle"
|
|
}
|
|
}
|
|
|
|
public init?(deepLinkName: String) {
|
|
switch deepLinkName.lowercased() {
|
|
case "ghost", "ghostmode": self = .ghost
|
|
case "history": self = .history
|
|
case "stash", "archive", "hiddenarchive": self = .stash
|
|
case "features", "antifeatures", "anti": self = .antiFeatures
|
|
case "chat": self = .chat
|
|
case "glass", "liquidglass": self = .liquidGlass
|
|
case "spoofing", "spoof": self = .spoofing
|
|
case "ayugram", "ayu": self = .ayugram
|
|
case "other", "misc": self = .other
|
|
default: return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum WinterGramDropdown: Equatable {
|
|
case sendWithoutSound
|
|
case peerId
|
|
case translationProvider
|
|
case webviewPlatform
|
|
case stashPrivacy
|
|
}
|
|
|
|
private func winterGramDropdownOptions(_ dropdown: WinterGramDropdown, settings: WinterGramSettings) -> [(title: String, selected: Bool, apply: (WinterGramSettings) -> WinterGramSettings)] {
|
|
switch dropdown {
|
|
case .stashPrivacy:
|
|
let p = settings.stashPrivacy
|
|
return [
|
|
("Profile Photo", p.profilePhoto, { s in var s = s; s.stashPrivacy.profilePhoto = !s.stashPrivacy.profilePhoto; return s }),
|
|
("Phone Number", p.phoneNumber, { s in var s = s; s.stashPrivacy.phoneNumber = !s.stashPrivacy.phoneNumber; return s }),
|
|
("Last Seen", p.presence, { s in var s = s; s.stashPrivacy.presence = !s.stashPrivacy.presence; return s }),
|
|
("Forwards", p.forwards, { s in var s = s; s.stashPrivacy.forwards = !s.stashPrivacy.forwards; return s }),
|
|
("Voice Calls", p.voiceCalls, { s in var s = s; s.stashPrivacy.voiceCalls = !s.stashPrivacy.voiceCalls; return s }),
|
|
("Birthday", p.birthday, { s in var s = s; s.stashPrivacy.birthday = !s.stashPrivacy.birthday; return s }),
|
|
("Gifts Auto-Save", p.giftsAutoSave, { s in var s = s; s.stashPrivacy.giftsAutoSave = !s.stashPrivacy.giftsAutoSave; return s }),
|
|
("Bio", p.bio, { s in var s = s; s.stashPrivacy.bio = !s.stashPrivacy.bio; return s }),
|
|
("Saved Music", p.savedMusic, { s in var s = s; s.stashPrivacy.savedMusic = !s.stashPrivacy.savedMusic; return s }),
|
|
("Group Invitations", p.groupInvitations, { s in var s = s; s.stashPrivacy.groupInvitations = !s.stashPrivacy.groupInvitations; return s })
|
|
]
|
|
case .sendWithoutSound:
|
|
let v = settings.sendWithoutSound
|
|
return [
|
|
("Never", v == .never, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.sendWithoutSound = .never; return s }),
|
|
("In Ghost Mode", v == .inGhostMode, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.sendWithoutSound = .inGhostMode; return s }),
|
|
("Always", v == .always, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.sendWithoutSound = .always; return s })
|
|
]
|
|
case .peerId:
|
|
let v = settings.showPeerId
|
|
return [
|
|
("Hidden", v == .hidden, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.showPeerId = .hidden; return s }),
|
|
("Telegram API", v == .telegramApi, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.showPeerId = .telegramApi; return s }),
|
|
("Bot API", v == .botApi, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.showPeerId = .botApi; return s })
|
|
]
|
|
case .translationProvider:
|
|
let v = settings.translationProvider
|
|
return [
|
|
("Disabled", !settings.translateMessages, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.translateMessages = false; return s }),
|
|
("Telegram", settings.translateMessages && v == .telegram, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.translateMessages = true; s.translationProvider = .telegram; return s }),
|
|
("Google", settings.translateMessages && v == .google, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.translateMessages = true; s.translationProvider = .google; return s }),
|
|
("Yandex", settings.translateMessages && v == .yandex, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.translateMessages = true; s.translationProvider = .yandex; return s }),
|
|
("System", settings.translateMessages && v == .system, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.translateMessages = true; s.translationProvider = .system; return s })
|
|
]
|
|
case .webviewPlatform:
|
|
let v = settings.webviewSpoofPlatform
|
|
return [
|
|
("Automatic", v == .auto, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.webviewSpoofPlatform = .auto; return s }),
|
|
("iOS", v == .ios, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.webviewSpoofPlatform = .ios; return s }),
|
|
("Android", v == .android, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.webviewSpoofPlatform = .android; return s }),
|
|
("macOS", v == .macos, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.webviewSpoofPlatform = .macos; return s }),
|
|
("Desktop", v == .desktop, { (s: WinterGramSettings) -> WinterGramSettings in var s = s; s.webviewSpoofPlatform = .desktop; return s })
|
|
]
|
|
}
|
|
}
|
|
|
|
private let winterGramDevicePresets: [(name: String, subtitle: String, model: String?)] = [
|
|
("Real device", "Report this device's real model", nil),
|
|
("iPhone 16 Pro Max", "iPhone17,2 · A18 Pro", "iPhone 16 Pro Max"),
|
|
("iPhone 15 Pro Max", "iPhone16,2 · A17 Pro", "iPhone 15 Pro Max"),
|
|
("iPhone 15 Pro", "iPhone16,1 · A17 Pro", "iPhone 15 Pro"),
|
|
("iPhone 15", "iPhone15,4 · A16", "iPhone 15"),
|
|
("iPhone 14 Pro Max", "iPhone15,3 · A16", "iPhone 14 Pro Max"),
|
|
("iPhone 14", "iPhone14,7 · A15", "iPhone 14"),
|
|
("iPhone 13 Pro", "iPhone14,2 · A15", "iPhone 13 Pro"),
|
|
("iPhone 13 mini", "iPhone14,4 · A15", "iPhone 13 mini"),
|
|
("iPhone 12", "iPhone13,2 · A14", "iPhone 12"),
|
|
("iPhone SE (3rd gen)", "iPhone14,6 · A15", "iPhone SE (3rd generation)"),
|
|
("iPhone X", "iPhone10,3 · A11", "iPhone X"),
|
|
("iPad Pro M4", "iPad16,3 · Apple M4", "iPad Pro M4"),
|
|
("Google Pixel 10 Pro", "Pixel 10 Pro · Tensor G5", "Pixel 10 Pro"),
|
|
("Samsung Galaxy S25 Ultra", "SM-S938U · Snapdragon 8 Elite", "Samsung Galaxy S25 Ultra"),
|
|
("OnePlus 13", "CPH2649 · Snapdragon 8 Elite", "OnePlus 13"),
|
|
("Windows 11", "Windows NT 10.0 · x64", "Windows 11"),
|
|
("Windows 10", "Windows NT 10.0 · x64", "Windows 10"),
|
|
("Linux Desktop", "Linux · x86_64", "Linux Desktop"),
|
|
("macOS Sequoia", "Mac15,9 · Apple M3 Max", "macOS Sequoia")
|
|
]
|
|
|
|
private enum WinterGramSettingsEntry: ItemListNodeEntry {
|
|
case banner
|
|
|
|
case ghostHeader
|
|
case ghostExpandable(Bool, Bool, [ItemListExpandableSwitchItem.SubItem])
|
|
case ghostSendWithoutSound(String)
|
|
case ghostUseScheduledMessages(Bool)
|
|
case ghostConfirmStory(Bool)
|
|
case ghostMarkReadAfterAction(Bool)
|
|
case ghostFooter
|
|
|
|
case historyHeader
|
|
case historyHeaderSpy
|
|
case historySaveDeleted(Bool)
|
|
case historySaveEdits(Bool)
|
|
case historySaveForBots(Bool)
|
|
case historySaveSelfDestruct(Bool)
|
|
case historySemiTransparent(Bool)
|
|
case historyDeletedMark(String)
|
|
case historyShowDeletedTime(Bool)
|
|
case historyFooter
|
|
|
|
case stashHeader
|
|
case stashList(Int)
|
|
case stashMute(Bool)
|
|
case stashAutoRead(Bool)
|
|
case stashPasscodeRow(String)
|
|
case stashFooter
|
|
|
|
case antiHeader
|
|
case antiDisableAds(Bool)
|
|
case antiLocalPremium(Bool)
|
|
case antiDisableStories(Bool)
|
|
case antiHidePremiumStatuses(Bool)
|
|
case antiDisableLinkWarning(Bool)
|
|
case antiDisableCopyProtection(Bool)
|
|
case antiAllowScreenshots(Bool)
|
|
case antiFooter
|
|
|
|
case confirmStickers(Bool)
|
|
case confirmGif(Bool)
|
|
case confirmVoice(Bool)
|
|
|
|
case chatHeader
|
|
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
|
|
case chatShowPeerId(String)
|
|
case chatShowRegistrationDate(Bool)
|
|
case chatHideEditedMark(Bool)
|
|
case chatTranslateProvider(String)
|
|
case chatWebviewHeight(Bool)
|
|
case chatOnlyAddedEmoji(Bool)
|
|
case chatForwardWithoutAuthor(Bool)
|
|
case chatFooter
|
|
|
|
case glassHeader
|
|
case glassEnabled(Bool)
|
|
case glassVibrancy(Bool)
|
|
case glassChatList(Bool)
|
|
case glassNavBars(Bool)
|
|
case glassTabBar(Bool)
|
|
case glassBubbles(Bool)
|
|
case glassFooter
|
|
|
|
case spoofingHeader
|
|
case spoofPresetsHeader
|
|
case spoofPreset(Int, String, String, Bool, Bool)
|
|
case spoofAddTemplate
|
|
case spoofDeleteSelected
|
|
case spoofingDevice(String)
|
|
case spoofingDevicePreset(Int, Bool)
|
|
case spoofingWebviewPlatform(String)
|
|
case spoofingApiId(String)
|
|
case spoofingApiHash(String)
|
|
case spoofingFooter
|
|
|
|
case expandableSelection(WinterGramDropdown, String, Bool, [ItemListExpandableSelectionItem.Option])
|
|
|
|
var section: ItemListSectionId {
|
|
switch self {
|
|
case .banner:
|
|
return WinterGramSettingsSection.banner.rawValue
|
|
case .ghostHeader, .ghostExpandable, .ghostSendWithoutSound, .ghostUseScheduledMessages, .ghostConfirmStory, .ghostMarkReadAfterAction, .ghostFooter:
|
|
return WinterGramSettingsSection.ghost.rawValue
|
|
case .expandableSelection(let dropdown, _, _, _):
|
|
switch dropdown {
|
|
case .sendWithoutSound:
|
|
return WinterGramSettingsSection.ghost.rawValue
|
|
case .peerId:
|
|
return WinterGramSettingsSection.chat.rawValue
|
|
case .translationProvider:
|
|
return WinterGramSettingsSection.chat.rawValue
|
|
case .webviewPlatform:
|
|
return WinterGramSettingsSection.spoofing.rawValue
|
|
case .stashPrivacy:
|
|
return WinterGramSettingsSection.stash.rawValue
|
|
}
|
|
case .historyHeader, .historyHeaderSpy, .historySaveDeleted, .historySaveEdits, .historySaveForBots, .historySaveSelfDestruct, .historySemiTransparent, .historyDeletedMark, .historyShowDeletedTime, .historyFooter:
|
|
return WinterGramSettingsSection.history.rawValue
|
|
case .stashHeader, .stashList, .stashMute, .stashAutoRead, .stashPasscodeRow, .stashFooter:
|
|
return WinterGramSettingsSection.stash.rawValue
|
|
case .antiHeader, .antiDisableAds, .antiLocalPremium, .antiDisableStories, .antiHidePremiumStatuses, .antiDisableLinkWarning, .antiDisableCopyProtection, .antiAllowScreenshots, .antiFooter:
|
|
return WinterGramSettingsSection.antiFeatures.rawValue
|
|
case .confirmStickers, .confirmGif, .confirmVoice:
|
|
return WinterGramSettingsSection.chat.rawValue
|
|
case .chatHeader, .chatPreview, .chatShowPeerId, .chatShowRegistrationDate, .chatHideEditedMark, .chatTranslateProvider, .chatWebviewHeight, .chatOnlyAddedEmoji, .chatForwardWithoutAuthor, .chatFooter:
|
|
return WinterGramSettingsSection.chat.rawValue
|
|
case .glassHeader, .glassEnabled, .glassVibrancy, .glassChatList, .glassNavBars, .glassTabBar, .glassBubbles, .glassFooter:
|
|
return WinterGramSettingsSection.liquidGlass.rawValue
|
|
case .spoofingHeader, .spoofingDevice, .spoofingDevicePreset, .spoofingWebviewPlatform, .spoofingApiId, .spoofingApiHash, .spoofPresetsHeader, .spoofPreset, .spoofAddTemplate, .spoofDeleteSelected, .spoofingFooter:
|
|
return WinterGramSettingsSection.spoofing.rawValue
|
|
}
|
|
}
|
|
|
|
var stableId: Int32 {
|
|
if case let .spoofingDevicePreset(index, _) = self {
|
|
return 30100 + Int32(index)
|
|
}
|
|
return self.baseStableId * 100
|
|
}
|
|
|
|
private var baseStableId: Int32 {
|
|
switch self {
|
|
case .banner: return -1
|
|
case .ghostHeader: return 0
|
|
case .ghostExpandable: return 2
|
|
case .ghostSendWithoutSound: return 3
|
|
case .ghostUseScheduledMessages: return 4
|
|
case .ghostConfirmStory: return 5
|
|
case .ghostMarkReadAfterAction: return 6
|
|
case .ghostFooter: return 7
|
|
case .historyHeader: return 10
|
|
case .historyHeaderSpy: return 11
|
|
case .historySaveDeleted: return 12
|
|
case .historySaveEdits: return 13
|
|
case .historySaveForBots: return 14
|
|
case .historySaveSelfDestruct: return 15
|
|
case .historySemiTransparent: return 16
|
|
case .historyDeletedMark: return 17
|
|
case .historyShowDeletedTime: return 18
|
|
case .historyFooter: return 19
|
|
case .stashHeader: return 20
|
|
case .stashList: return 21
|
|
case .stashMute: return 23
|
|
case .stashAutoRead: return 24
|
|
case .stashPasscodeRow: return 25
|
|
case .stashFooter: return 37
|
|
case .antiHeader: return 30
|
|
case .antiDisableAds: return 31
|
|
case .antiLocalPremium: return 32
|
|
case .antiDisableStories: return 33
|
|
case .antiHidePremiumStatuses: return 34
|
|
case .antiDisableLinkWarning: return 35
|
|
case .antiDisableCopyProtection: return 36
|
|
case .antiAllowScreenshots: return 37
|
|
case .antiFooter: return 38
|
|
case .confirmStickers: return 64
|
|
case .confirmGif: return 65
|
|
case .confirmVoice: return 66
|
|
case .chatHeader: return 48
|
|
case .chatPreview: return 49
|
|
case .chatShowPeerId: return 52
|
|
case .chatShowRegistrationDate: return 53
|
|
case .chatHideEditedMark: return 54
|
|
case .chatTranslateProvider: return 56
|
|
case .chatWebviewHeight: return 58
|
|
case .chatOnlyAddedEmoji: return 59
|
|
case .chatForwardWithoutAuthor: return 60
|
|
case .chatFooter: return 67
|
|
case .glassHeader: return 80
|
|
case .glassEnabled: return 81
|
|
case .glassVibrancy: return 82
|
|
case .glassChatList: return 83
|
|
case .glassNavBars: return 84
|
|
case .glassTabBar: return 85
|
|
case .glassBubbles: return 86
|
|
case .glassFooter: return 87
|
|
case .spoofingHeader: return 100
|
|
case .spoofPresetsHeader: return 101
|
|
case let .spoofPreset(index, _, _, _, _): return 102 + Int32(index)
|
|
case .spoofAddTemplate: return 203
|
|
case .spoofDeleteSelected: return 204
|
|
case .spoofingDevice: return 300
|
|
case .spoofingDevicePreset: return 301
|
|
case .spoofingWebviewPlatform: return 400
|
|
case .spoofingApiId: return 401
|
|
case .spoofingApiHash: return 402
|
|
case .spoofingFooter: return 403
|
|
case .expandableSelection(.sendWithoutSound, _, _, _): return 3
|
|
case .expandableSelection(.peerId, _, _, _): return 52
|
|
case .expandableSelection(.translationProvider, _, _, _): return 56
|
|
case .expandableSelection(.webviewPlatform, _, _, _): return 400
|
|
case .expandableSelection(.stashPrivacy, _, _, _): return 26
|
|
}
|
|
}
|
|
|
|
static func <(lhs: WinterGramSettingsEntry, rhs: WinterGramSettingsEntry) -> Bool {
|
|
return lhs.stableId < rhs.stableId
|
|
}
|
|
|
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
|
let arguments = arguments as! WinterGramSettingsArguments
|
|
let lang = presentationData.strings
|
|
switch self {
|
|
case .banner:
|
|
return WinterGramBannerItem(theme: presentationData.theme, title: "WinterGram", subtitle: "", iconImage: UIImage(bundleImageName: "WinterGramDark"), sectionId: self.section)
|
|
case .ghostHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_GHOSTMODE, sectionId: self.section)
|
|
case let .ghostExpandable(value, isExpanded, subItems):
|
|
return ItemListExpandableSwitchItem(presentationData: presentationData, systemStyle: .glass, title: lang.WinterGram_GhostMode, value: value, isExpanded: isExpanded, subItems: subItems, sectionId: self.section, style: .blocks, updated: { newValue in
|
|
arguments.updateSettings { var s = $0; s.ghostModeEnabled = newValue; return s }
|
|
}, selectAction: {
|
|
arguments.toggleGhostExpanded()
|
|
}, subAction: { subItem in
|
|
guard let id = subItem.id as? String else {
|
|
return
|
|
}
|
|
arguments.updateSettings { settings in
|
|
var s = settings
|
|
switch id {
|
|
case "readMessages":
|
|
s.sendReadReceipts = !subItem.isSelected
|
|
case "readStories":
|
|
s.sendReadStories = !subItem.isSelected
|
|
case "online":
|
|
s.sendOnlineStatus = !subItem.isSelected
|
|
case "typing":
|
|
s.sendUploadProgress = !subItem.isSelected
|
|
case "autoOffline":
|
|
s.sendOfflineAfterOnline = subItem.isSelected
|
|
default:
|
|
break
|
|
}
|
|
s.ghostModeEnabled = true
|
|
return s
|
|
}
|
|
})
|
|
case let .ghostSendWithoutSound(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_SendWithoutSound, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.toggleDropdown(.sendWithoutSound)
|
|
})
|
|
case let .ghostUseScheduledMessages(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_UseScheduledMessages, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.useScheduledMessages = value; return s }
|
|
})
|
|
case let .ghostConfirmStory(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ConfirmStoryView, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.confirmStoryView = value; return s }
|
|
})
|
|
case let .ghostMarkReadAfterAction(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ReadAfterAction, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.markReadAfterAction = value; return s }
|
|
})
|
|
|
|
case .ghostFooter:
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(lang.WinterGram_WhenGhostModeIsOnWinterGramStopsSendingReadReceiptsOnlineStatusAndTypingActivity), sectionId: self.section)
|
|
|
|
case .historyHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_HISTORY, sectionId: self.section)
|
|
case .historyHeaderSpy:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_SPYMODE, sectionId: self.section)
|
|
case let .historySaveDeleted(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_SaveDeletedMessages, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.saveDeletedMessages = value; return s }
|
|
})
|
|
case let .historySaveEdits(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_SaveEditHistory, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.saveMessagesHistory = value; return s }
|
|
})
|
|
case let .historySaveForBots(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_SaveDeletedFromBots, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.saveForBots = value; return s }
|
|
})
|
|
case let .historySaveSelfDestruct(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_SaveSelfDestructMessages, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.saveSelfDestructMessages = value; return s }
|
|
})
|
|
case let .historySemiTransparent(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_DimDeletedMessages, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.semiTransparentDeletedMessages = value; return s }
|
|
})
|
|
case let .historyDeletedMark(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_DeletedMark, label: value.isEmpty ? "🧹" : value, sectionId: self.section, style: .blocks, action: {
|
|
arguments.editDeletedMark()
|
|
})
|
|
case let .historyShowDeletedTime(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ShowDeletionTime, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.showDeletedTime = value; return s }
|
|
})
|
|
case .historyFooter:
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(lang.WinterGram_DeletedAndEditedMessagesAreKeptLocallyOnThisDeviceOnly), sectionId: self.section)
|
|
|
|
case .stashHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_HIDDENARCHIVE, sectionId: self.section)
|
|
case let .stashList(count):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_StashedChats, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: {
|
|
arguments.openStash()
|
|
})
|
|
case let .stashMute(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_MuteNotifications, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.stashMuteNotifications = value; return s }
|
|
})
|
|
case let .stashAutoRead(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_AutoMarkAsRead, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.stashAutoMarkRead = value; return s }
|
|
})
|
|
case let .stashPasscodeRow(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_StashPasscode, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.editStashPasscode()
|
|
})
|
|
case .stashFooter:
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(lang.WinterGram_StashedChatsAreHiddenFromTheMainListAndAccessibleOnlyHere), sectionId: self.section)
|
|
|
|
case .antiHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_FEATURES, sectionId: self.section)
|
|
case let .antiDisableAds(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_DisableAds, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.disableAds = value; return s }
|
|
})
|
|
case let .antiLocalPremium(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_LocalPremium, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.localPremium = value; return s }
|
|
})
|
|
case let .antiDisableStories(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_HideStories, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.disableStories = value; return s }
|
|
})
|
|
case let .antiHidePremiumStatuses(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_HidePremiumStatuses, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.hidePremiumStatuses = value; return s }
|
|
})
|
|
case let .antiDisableLinkWarning(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_DisableOpenLinkWarning, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.disableOpenLinkWarning = value; return s }
|
|
})
|
|
case let .antiDisableCopyProtection(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_AllowSavingRestrictedContent, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.disableCopyProtection = value; return s }
|
|
})
|
|
case let .antiAllowScreenshots(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_AllowScreenshotsEverywhere, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.allowScreenshots = value; return s }
|
|
})
|
|
case .antiFooter:
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(lang.WinterGram_LocalPremiumUnlocksPremiumOnlyUIOnThisDeviceItDoesNotGrantServerSidePremium), sectionId: self.section)
|
|
|
|
case let .confirmStickers(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ConfirmStickers, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.stickerConfirmation = value; return s }
|
|
})
|
|
case let .confirmGif(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ConfirmGIFs, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.gifConfirmation = value; return s }
|
|
})
|
|
case let .confirmVoice(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ConfirmVoiceMessages, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.voiceConfirmation = value; return s }
|
|
})
|
|
|
|
case .chatHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_CHAT, sectionId: self.section)
|
|
case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, messageItems):
|
|
return ThemeSettingsChatPreviewItem(context: arguments.context, systemStyle: .glass, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: messageItems)
|
|
case let .chatShowPeerId(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_ShowPeerID, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.toggleDropdown(.peerId)
|
|
})
|
|
case let .chatShowRegistrationDate(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ShowRegistrationDate, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.showRegistrationDate = value; return s }
|
|
})
|
|
case let .chatHideEditedMark(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_HideEditedMark, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.hideEditedMark = value; return s }
|
|
})
|
|
case let .chatTranslateProvider(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_TranslationProvider, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.toggleDropdown(.translationProvider)
|
|
})
|
|
case let .chatWebviewHeight(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_IncreaseWebViewHeight, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.increaseWebviewHeight = value; return s }
|
|
})
|
|
case let .chatOnlyAddedEmoji(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_OnlyAddedEmojiStickers, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.showOnlyAddedEmojisAndStickers = value; return s }
|
|
})
|
|
case let .chatForwardWithoutAuthor(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ForwardWithoutAuthor, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.forwardWithoutAuthor = value; return s }
|
|
})
|
|
case .chatFooter:
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(lang.WinterGram_TheseOptionsChangeHowMessageMetadataAndActionsAreShownInChats), sectionId: self.section)
|
|
|
|
case .glassHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_LIQUIDGLASS, sectionId: self.section)
|
|
case let .glassEnabled(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_LiquidGlass, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.liquidGlass.enabled = value; return s }
|
|
})
|
|
case let .glassVibrancy(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_Vibrancy, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.liquidGlass.vibrancy = value; return s }
|
|
})
|
|
case let .glassChatList(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ApplyToChatList, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.liquidGlass.applyToChatList = value; return s }
|
|
})
|
|
case let .glassNavBars(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ApplyToNavigationBars, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.liquidGlass.applyToNavigationBars = value; return s }
|
|
})
|
|
case let .glassTabBar(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ApplyToTabBar, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.liquidGlass.applyToTabBar = value; return s }
|
|
})
|
|
case let .glassBubbles(value):
|
|
return ItemListSwitchItem(presentationData: presentationData, title: lang.WinterGram_ApplyToBubbles, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
|
arguments.updateSettings { var s = $0; s.liquidGlass.applyToBubbles = value; return s }
|
|
})
|
|
case .glassFooter:
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(lang.WinterGram_TransparencyBlurAndTintCanBeFineTunedPerSurfaceTurnLiquidGlassOffForTheStandardOpaqueLook), sectionId: self.section)
|
|
|
|
case .spoofingHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_SPOOFING, sectionId: self.section)
|
|
case let .spoofingDevice(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_SpoofDeviceModel, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.editSpoofDevice()
|
|
})
|
|
case let .spoofingDevicePreset(index, selected):
|
|
let preset = winterGramDevicePresets[index]
|
|
return ItemListCheckboxItem(presentationData: presentationData, title: preset.name, subtitle: preset.subtitle, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
|
arguments.updateSettings { var s = $0; s.spoofDeviceModel = preset.model; return s }
|
|
})
|
|
case let .spoofingWebviewPlatform(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_WebViewPlatform, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.toggleDropdown(.webviewPlatform)
|
|
})
|
|
case let .spoofingApiId(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_APIID, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.editApiId()
|
|
})
|
|
case let .spoofingApiHash(value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: lang.WinterGram_APIHash, label: wntOption(value, lang), sectionId: self.section, style: .blocks, action: {
|
|
arguments.editApiHash()
|
|
})
|
|
case .spoofPresetsHeader:
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: lang.WinterGram_Templates, sectionId: self.section)
|
|
case let .spoofPreset(index, name, subtitle, editing, selected):
|
|
// Always an ItemListCheckboxItem so the row never swaps node *type* between display and
|
|
// editing. ItemList updates a row in place by stableId and cannot re-type a node (the cast
|
|
// in updateNode fails silently), which previously left the disclosure row in place in edit
|
|
// mode and made selection unresponsive. In display mode tapping applies the template; in
|
|
// edit mode it toggles selection. Bonus: templates now match the device-preset rows below.
|
|
return ItemListCheckboxItem(presentationData: presentationData, title: name, subtitle: subtitle, style: .left, checked: editing ? selected : false, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
|
if editing {
|
|
arguments.toggleSpoofTemplateSelected(index)
|
|
} else {
|
|
arguments.updateSettings { settings in
|
|
var settings = settings
|
|
if index < settings.spoofPresets.count {
|
|
let preset = settings.spoofPresets[index]
|
|
settings.spoofDeviceModel = preset.deviceModel.isEmpty ? nil : preset.deviceModel
|
|
settings.spoofAppVersion = preset.appVersion.isEmpty ? nil : preset.appVersion
|
|
}
|
|
return settings
|
|
}
|
|
}
|
|
})
|
|
case .spoofAddTemplate:
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: lang.WinterGram_AddTemplate, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
|
arguments.addSpoofTemplate()
|
|
})
|
|
case .spoofDeleteSelected:
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(presentationData.theme), title: lang.WinterGram_DeleteSelected, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
|
arguments.deleteSelectedSpoofTemplates()
|
|
})
|
|
case .spoofingFooter:
|
|
return ItemListTextItem(presentationData: presentationData, text: .plain(lang.WinterGram_SpoofTheDeviceModelAppVersionAndWebViewPlatformReportedToTelegramAndMiniAppsAPIIDHashUseYourOwnCredentialsFromMyTelegramOrgChangingThemRequiresReLogin), sectionId: self.section)
|
|
case let .expandableSelection(dropdown, title, isExpanded, options):
|
|
let mode: ItemListExpandableSelectionItem.SelectionMode = dropdown == .stashPrivacy ? .multiple : .single
|
|
return ItemListExpandableSelectionItem(presentationData: presentationData, systemStyle: .glass, title: title, options: options, mode: mode, isExpanded: isExpanded, sectionId: self.section, style: .blocks, updated: { option in
|
|
arguments.selectDropdownOption(dropdown, option.index)
|
|
}, toggleExpanded: {
|
|
arguments.toggleDropdown(dropdown)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
private func sendWithoutSoundLabel(_ value: WinterGramSendWithoutSound) -> String {
|
|
switch value {
|
|
case .never: return "Never"
|
|
case .inGhostMode: return "In Ghost Mode"
|
|
case .always: return "Always"
|
|
}
|
|
}
|
|
|
|
private func peerIdLabel(_ value: WinterGramPeerIdDisplay) -> String {
|
|
switch value {
|
|
case .hidden: return "Hidden"
|
|
case .telegramApi: return "Telegram API"
|
|
case .botApi: return "Bot API"
|
|
}
|
|
}
|
|
|
|
private func translationProviderLabel(_ value: WinterGramTranslationProvider) -> String {
|
|
switch value {
|
|
case .telegram: return "Telegram"
|
|
case .google: return "Google"
|
|
case .yandex: return "Yandex"
|
|
case .system: return "System"
|
|
}
|
|
}
|
|
|
|
private func webviewPlatformLabel(_ value: WinterGramWebviewPlatform) -> String {
|
|
switch value {
|
|
case .auto: return "Automatic"
|
|
case .ios: return "iOS"
|
|
case .android: return "Android"
|
|
case .macos: return "macOS"
|
|
case .desktop: return "Desktop"
|
|
}
|
|
}
|
|
|
|
private func avatarRadiusLabel(_ value: Int32) -> String {
|
|
switch value {
|
|
case 50: return "Round"
|
|
case 30: return "Squircle"
|
|
case 15: return "Rounded"
|
|
case 0: return "Square"
|
|
default: return "\(value)"
|
|
}
|
|
}
|
|
|
|
private func spoofLabel(_ value: String?) -> String {
|
|
if let value = value, !value.isEmpty {
|
|
return value
|
|
}
|
|
return "Default"
|
|
}
|
|
|
|
private func iconPackLabel(_ value: WinterGramIconPack) -> String {
|
|
switch value {
|
|
case .wintergram: return "WinterGram"
|
|
case .ayugram: return "Ayu"
|
|
case .exteragram: return "exteraGram"
|
|
case .telegram: return "Telegram"
|
|
}
|
|
}
|
|
|
|
private func winterGramSettingsEntries(presentationData: PresentationData, settings: WinterGramSettings, deletedCount: Int, expandedDropdown: WinterGramDropdown?, ghostExpanded: Bool, spoofTemplatesEditing: Bool, selectedTemplates: Set<Int>, category: WinterGramSettingsSection? = nil) -> [WinterGramSettingsEntry] {
|
|
let lang = presentationData.strings
|
|
var entries: [WinterGramSettingsEntry] = []
|
|
|
|
func appendDropdown(_ dropdown: WinterGramDropdown, _ title: String, _ section: WinterGramSettingsSection) {
|
|
let options = winterGramDropdownOptions(dropdown, settings: settings).enumerated().map { index, option in
|
|
ItemListExpandableSelectionItem.Option(id: "\(dropdown)_\(index)", title: wntOption(option.title, lang), isSelected: option.selected, index: index)
|
|
}
|
|
entries.append(.expandableSelection(dropdown, title, expandedDropdown == dropdown, options))
|
|
}
|
|
|
|
func appendGhost() {
|
|
entries.append(.ghostHeader)
|
|
let subItems: [ItemListExpandableSwitchItem.SubItem] = [
|
|
.init(id: "readMessages", title: lang.WinterGram_DontReadMessages, isSelected: settings.suppressesReadReceipts, isEnabled: true),
|
|
.init(id: "readStories", title: lang.WinterGram_DontReadStories, isSelected: settings.suppressesStoryViews, isEnabled: true),
|
|
.init(id: "online", title: lang.WinterGram_DontSendOnline, isSelected: settings.suppressesOnlinePresence, isEnabled: true),
|
|
.init(id: "typing", title: lang.WinterGram_DontSendTyping, isSelected: settings.suppressesTypingStatus, isEnabled: true),
|
|
.init(id: "autoOffline", title: lang.WinterGram_AutoOffline, isSelected: settings.sendOfflineAfterOnline, isEnabled: true)
|
|
]
|
|
entries.append(.ghostExpandable(settings.ghostModeEnabled, ghostExpanded, subItems))
|
|
appendDropdown(.sendWithoutSound, lang.WinterGram_SendWithoutSound, .ghost)
|
|
entries.append(.ghostUseScheduledMessages(settings.useScheduledMessages))
|
|
entries.append(.ghostConfirmStory(settings.confirmStoryView))
|
|
entries.append(.ghostMarkReadAfterAction(settings.markReadAfterAction))
|
|
entries.append(.ghostFooter)
|
|
}
|
|
|
|
func appendHistory() {
|
|
if category == .ayugram {
|
|
entries.append(.historyHeaderSpy)
|
|
entries.append(.historySaveDeleted(settings.saveDeletedMessages))
|
|
entries.append(.historySaveEdits(settings.saveMessagesHistory))
|
|
entries.append(.historyDeletedMark(settings.deletedMark))
|
|
entries.append(.historyShowDeletedTime(settings.showDeletedTime))
|
|
} else {
|
|
entries.append(.historyHeader)
|
|
entries.append(.historySaveDeleted(settings.saveDeletedMessages))
|
|
entries.append(.historySaveEdits(settings.saveMessagesHistory))
|
|
entries.append(.historySaveForBots(settings.saveForBots))
|
|
entries.append(.historySaveSelfDestruct(settings.saveSelfDestructMessages))
|
|
entries.append(.historySemiTransparent(settings.semiTransparentDeletedMessages))
|
|
entries.append(.historyDeletedMark(settings.deletedMark))
|
|
entries.append(.historyShowDeletedTime(settings.showDeletedTime))
|
|
entries.append(.historyFooter)
|
|
}
|
|
}
|
|
|
|
func appendStash() {
|
|
entries.append(.stashHeader)
|
|
entries.append(.stashList(Set(settings.stashedPeerIds).count))
|
|
entries.append(.stashMute(settings.stashMuteNotifications))
|
|
entries.append(.stashAutoRead(settings.stashAutoMarkRead))
|
|
entries.append(.stashPasscodeRow(settings.stashPasscode.isEmpty ? "None" : "••••"))
|
|
appendDropdown(.stashPrivacy, lang.WinterGram_AutoPrivacy, .stash)
|
|
entries.append(.stashFooter)
|
|
}
|
|
|
|
func appendAntiFeatures() {
|
|
entries.append(.antiHeader)
|
|
entries.append(.antiDisableAds(settings.disableAds))
|
|
entries.append(.antiLocalPremium(settings.localPremium))
|
|
entries.append(.antiDisableStories(settings.disableStories))
|
|
entries.append(.antiHidePremiumStatuses(settings.hidePremiumStatuses))
|
|
entries.append(.antiDisableLinkWarning(settings.disableOpenLinkWarning))
|
|
entries.append(.antiDisableCopyProtection(settings.disableCopyProtection))
|
|
entries.append(.antiAllowScreenshots(settings.allowScreenshots))
|
|
entries.append(.antiFooter)
|
|
}
|
|
|
|
func appendChat() {
|
|
entries.append(.chatHeader)
|
|
appendDropdown(.peerId, lang.WinterGram_ShowPeerID, .chat)
|
|
entries.append(.chatShowRegistrationDate(settings.showRegistrationDate))
|
|
entries.append(.chatHideEditedMark(settings.hideEditedMark))
|
|
appendDropdown(.translationProvider, lang.WinterGram_MessageTranslation, .chat)
|
|
entries.append(.chatWebviewHeight(settings.increaseWebviewHeight))
|
|
entries.append(.chatOnlyAddedEmoji(settings.showOnlyAddedEmojisAndStickers))
|
|
entries.append(.chatForwardWithoutAuthor(settings.forwardWithoutAuthor))
|
|
entries.append(.confirmStickers(settings.stickerConfirmation))
|
|
entries.append(.confirmGif(settings.gifConfirmation))
|
|
entries.append(.confirmVoice(settings.voiceConfirmation))
|
|
entries.append(.chatFooter)
|
|
}
|
|
|
|
func appendLiquidGlass() {
|
|
entries.append(.glassHeader)
|
|
entries.append(.glassEnabled(settings.liquidGlass.enabled))
|
|
entries.append(.glassVibrancy(settings.liquidGlass.vibrancy))
|
|
entries.append(.glassChatList(settings.liquidGlass.applyToChatList))
|
|
entries.append(.glassNavBars(settings.liquidGlass.applyToNavigationBars))
|
|
entries.append(.glassTabBar(settings.liquidGlass.applyToTabBar))
|
|
entries.append(.glassBubbles(settings.liquidGlass.applyToBubbles))
|
|
entries.append(.glassFooter)
|
|
}
|
|
|
|
func appendSpoofing() {
|
|
entries.append(.spoofingHeader)
|
|
entries.append(.spoofPresetsHeader)
|
|
for (index, preset) in settings.spoofPresets.enumerated() {
|
|
let subtitle = [preset.deviceModel, preset.appVersion].filter { !$0.isEmpty }.joined(separator: " · ")
|
|
entries.append(.spoofPreset(index, preset.name, subtitle.isEmpty ? "Default" : subtitle, spoofTemplatesEditing, selectedTemplates.contains(index)))
|
|
}
|
|
entries.append(.spoofAddTemplate)
|
|
if spoofTemplatesEditing && !selectedTemplates.isEmpty {
|
|
entries.append(.spoofDeleteSelected)
|
|
}
|
|
for (i, preset) in winterGramDevicePresets.enumerated() {
|
|
entries.append(.spoofingDevicePreset(i, preset.model == settings.spoofDeviceModel))
|
|
}
|
|
appendDropdown(.webviewPlatform, lang.WinterGram_WebViewPlatform, .spoofing)
|
|
entries.append(.spoofingApiId(settings.customApiId.flatMap { $0 == 0 ? nil : "\($0)" } ?? "Default"))
|
|
entries.append(.spoofingApiHash(spoofLabel(settings.customApiHash)))
|
|
entries.append(.spoofingFooter)
|
|
}
|
|
|
|
if let category = category {
|
|
switch category {
|
|
case .banner:
|
|
break
|
|
case .ghost:
|
|
appendGhost()
|
|
case .history:
|
|
appendHistory()
|
|
case .stash:
|
|
appendStash()
|
|
case .antiFeatures:
|
|
appendAntiFeatures()
|
|
case .chat:
|
|
appendChat()
|
|
case .liquidGlass:
|
|
appendLiquidGlass()
|
|
case .spoofing:
|
|
appendSpoofing()
|
|
case .ayugram:
|
|
appendGhost()
|
|
appendHistory()
|
|
case .other:
|
|
appendChat()
|
|
}
|
|
} else {
|
|
entries.append(.banner)
|
|
appendGhost()
|
|
appendHistory()
|
|
appendStash()
|
|
appendAntiFeatures()
|
|
appendChat()
|
|
appendLiquidGlass()
|
|
appendSpoofing()
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
public func winterGramSettingsController(context: AccountContext, category: WinterGramSettingsSection? = nil) -> ViewController {
|
|
let accountManager = context.sharedContext.accountManager
|
|
|
|
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
|
var pushControllerImpl: ((ViewController) -> Void)?
|
|
var refreshDeletedCount: (() -> Void)?
|
|
|
|
let sectionTabs: [WinterGramSettingsSection] = WinterGramSettingsSection.allCases.filter { $0 != .ayugram && $0 != .other }
|
|
let selectedCategoryPromise = ValuePromise<WinterGramSettingsSection>(category ?? .ghost, ignoreRepeated: true)
|
|
|
|
let initialSettings = currentWinterGramSettings
|
|
let requiresRestartPromise = ValuePromise<Bool>(false)
|
|
|
|
let updateSettings: (@escaping (WinterGramSettings) -> WinterGramSettings) -> Void = { f in
|
|
let _ = (updateWinterGramSettingsInteractively(accountManager: accountManager, f)
|
|
|> deliverOnMainQueue).startStandalone(next: { _ in
|
|
let newSettings = currentWinterGramSettings
|
|
let needsRestart = newSettings.spoofDeviceModel != initialSettings.spoofDeviceModel ||
|
|
newSettings.spoofAppVersion != initialSettings.spoofAppVersion ||
|
|
newSettings.customApiId != initialSettings.customApiId ||
|
|
newSettings.customApiHash != initialSettings.customApiHash ||
|
|
newSettings.materialDesign != initialSettings.materialDesign ||
|
|
newSettings.customFont != initialSettings.customFont ||
|
|
newSettings.monoFont != initialSettings.monoFont ||
|
|
newSettings.liquidGlass != initialSettings.liquidGlass
|
|
requiresRestartPromise.set(needsRestart)
|
|
})
|
|
}
|
|
|
|
let updateStashPrivacy: (@escaping (WinterGramStashPrivacySettings) -> WinterGramStashPrivacySettings) -> Void = { f in
|
|
let previous = currentWinterGramSettings.stashPrivacy
|
|
updateSettings { settings in
|
|
var settings = settings
|
|
settings.stashPrivacy = f(settings.stashPrivacy)
|
|
return settings
|
|
}
|
|
let updated = f(previous)
|
|
if updated != previous {
|
|
for rawPeerId in Set(currentWinterGramSettings.stashedPeerIds) {
|
|
let peerId = EnginePeer.Id(rawPeerId)
|
|
let _ = winterGramApplyStashPrivacy(engine: context.engine, peerId: peerId, stashed: true, privacySettings: updated).startStandalone()
|
|
}
|
|
}
|
|
}
|
|
|
|
let expandedDropdownValue = Atomic<WinterGramDropdown?>(value: nil)
|
|
let expandedDropdownPromise = ValuePromise<WinterGramDropdown?>(nil, ignoreRepeated: true)
|
|
|
|
let ghostExpandedValue = Atomic<Bool>(value: false)
|
|
let ghostExpandedPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
|
let spoofTemplatesEditingValue = Atomic<Bool>(value: false)
|
|
let spoofTemplatesEditingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
|
let selectedTemplatesValue = Atomic<Set<Int>>(value: Set())
|
|
let selectedTemplatesPromise = ValuePromise<Set<Int>>(Set(), ignoreRepeated: true)
|
|
|
|
let arguments = WinterGramSettingsArguments(
|
|
context: context,
|
|
updateSettings: updateSettings,
|
|
toggleDropdown: { dropdown in
|
|
let newValue: WinterGramDropdown? = expandedDropdownValue.with { $0 == dropdown ? nil : dropdown }
|
|
let _ = expandedDropdownValue.swap(newValue)
|
|
expandedDropdownPromise.set(newValue)
|
|
},
|
|
selectDropdownOption: { dropdown, index in
|
|
let options = winterGramDropdownOptions(dropdown, settings: currentWinterGramSettings)
|
|
if index >= 0, index < options.count {
|
|
if dropdown == .stashPrivacy {
|
|
updateStashPrivacy { privacy in
|
|
var settings = currentWinterGramSettings
|
|
settings.stashPrivacy = privacy
|
|
return options[index].apply(settings).stashPrivacy
|
|
}
|
|
} else {
|
|
updateSettings { options[index].apply($0) }
|
|
}
|
|
}
|
|
if dropdown != .stashPrivacy {
|
|
let _ = expandedDropdownValue.swap(nil)
|
|
expandedDropdownPromise.set(nil)
|
|
}
|
|
},
|
|
toggleGhostExpanded: {
|
|
let newValue = !ghostExpandedValue.with { $0 }
|
|
let _ = ghostExpandedValue.swap(newValue)
|
|
ghostExpandedPromise.set(newValue)
|
|
},
|
|
editSpoofDevice: {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let lang = presentationData.strings
|
|
let current = currentWinterGramSettings.spoofDeviceModel
|
|
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
var items: [ActionSheetItem] = []
|
|
items.append(ActionSheetTextItem(title: lang.WinterGram_SpoofDeviceModel))
|
|
|
|
let templates = [
|
|
("iPhone 16 Pro Max", "iPhone17,2", "11.1"),
|
|
("iPhone 15 Pro", "iPhone16,1", "11.1"),
|
|
("iPad Pro M4", "iPad16,3", "11.1"),
|
|
("Pixel 9 Pro", "Pixel 9 Pro", "11.1")
|
|
]
|
|
|
|
for (name, model, version) in templates {
|
|
items.append(ActionSheetButtonItem(title: name, action: { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
updateSettings { var s = $0; s.spoofDeviceModel = model; s.spoofAppVersion = version; return s }
|
|
}))
|
|
}
|
|
|
|
for preset in currentWinterGramSettings.spoofPresets {
|
|
items.append(ActionSheetButtonItem(title: preset.name, action: { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
updateSettings { var s = $0; s.spoofDeviceModel = preset.deviceModel; s.spoofAppVersion = preset.appVersion; return s }
|
|
}))
|
|
}
|
|
|
|
items.append(ActionSheetButtonItem(title: lang.WinterGram_Custom, action: { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
let prompt = promptController(context: context, text: lang.WinterGram_SpoofDeviceModel, value: current, placeholder: current ?? "iPhone17,2", characterLimit: 64, apply: { value in
|
|
updateSettings { var s = $0; s.spoofDeviceModel = (value?.isEmpty ?? true) ? nil : value; return s }
|
|
})
|
|
presentControllerImpl?(prompt, nil)
|
|
}))
|
|
|
|
items.append(ActionSheetButtonItem(title: lang.WinterGram_SaveCurrentAsProfile, color: .accent, action: { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
let prompt = promptController(context: context, text: lang.WinterGram_ProfileName, value: nil, placeholder: "My Profile", characterLimit: 32, apply: { name in
|
|
if let name = name, !name.isEmpty {
|
|
updateSettings { var s = $0
|
|
let preset = WinterGramSpoofPreset(name: name, deviceModel: s.spoofDeviceModel ?? "", appVersion: s.spoofAppVersion ?? "")
|
|
s.spoofPresets.append(preset)
|
|
return s
|
|
}
|
|
}
|
|
})
|
|
presentControllerImpl?(prompt, nil)
|
|
}))
|
|
|
|
items.append(ActionSheetButtonItem(title: lang.WinterGram_DefaultRealDevice, color: .destructive, action: { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
updateSettings { var s = $0; s.spoofDeviceModel = nil; s.spoofAppVersion = nil; return s }
|
|
}))
|
|
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: items),
|
|
ActionSheetItemGroup(items: [
|
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
})
|
|
])
|
|
])
|
|
presentControllerImpl?(controller, nil)
|
|
},
|
|
addSpoofTemplate: {
|
|
let lang = context.sharedContext.currentPresentationData.with { $0 }.strings
|
|
let controller = promptController(context: context, text: lang.WinterGram_ProfileName, value: nil, placeholder: "My Profile", characterLimit: 32, apply: { name in
|
|
if let name = name?.trimmingCharacters(in: .whitespaces), !name.isEmpty {
|
|
updateSettings { settings in
|
|
var settings = settings
|
|
settings.spoofPresets.append(WinterGramSpoofPreset(name: name, deviceModel: settings.spoofDeviceModel ?? "", appVersion: settings.spoofAppVersion ?? ""))
|
|
return settings
|
|
}
|
|
}
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
},
|
|
toggleSpoofTemplateSelected: { index in
|
|
let updated = selectedTemplatesValue.modify { selected in
|
|
var selected = selected
|
|
if selected.contains(index) {
|
|
selected.remove(index)
|
|
} else {
|
|
selected.insert(index)
|
|
}
|
|
return selected
|
|
}
|
|
selectedTemplatesPromise.set(updated)
|
|
},
|
|
deleteSelectedSpoofTemplates: {
|
|
let selected = selectedTemplatesValue.with { $0 }
|
|
guard !selected.isEmpty else { return }
|
|
updateSettings { settings in
|
|
var settings = settings
|
|
let sorted = selected.sorted(by: >)
|
|
for index in sorted {
|
|
if index >= 0 && index < settings.spoofPresets.count {
|
|
settings.spoofPresets.remove(at: index)
|
|
}
|
|
}
|
|
return settings
|
|
}
|
|
let _ = selectedTemplatesValue.swap(Set())
|
|
selectedTemplatesPromise.set(Set())
|
|
},
|
|
editApiId: {
|
|
let lang = context.sharedContext.currentPresentationData.with { $0 }.strings
|
|
let current = currentWinterGramSettings.customApiId.flatMap { $0 == 0 ? nil : "\($0)" }
|
|
let controller = promptController(context: context, text: lang.WinterGram_APIID, value: current, placeholder: "1234567", characterLimit: 16, apply: { value in
|
|
updateSettings { var s = $0
|
|
if let value = value, let parsed = Int32(value.trimmingCharacters(in: .whitespaces)), parsed != 0 {
|
|
s.customApiId = parsed
|
|
} else {
|
|
s.customApiId = nil
|
|
}
|
|
return s
|
|
}
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
},
|
|
editApiHash: {
|
|
let lang = context.sharedContext.currentPresentationData.with { $0 }.strings
|
|
let current = currentWinterGramSettings.customApiHash
|
|
let controller = promptController(context: context, text: lang.WinterGram_APIHash, value: current, placeholder: "0123456789abcdef0123456789abcdef", characterLimit: 64, apply: { value in
|
|
updateSettings { var s = $0
|
|
let trimmed = value?.trimmingCharacters(in: .whitespaces)
|
|
s.customApiHash = (trimmed?.isEmpty ?? true) ? nil : trimmed
|
|
return s
|
|
}
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
},
|
|
openStash: {
|
|
let passcode = currentWinterGramSettings.stashPasscode
|
|
if passcode.isEmpty {
|
|
pushControllerImpl?(winterGramStashController(context: context))
|
|
} else {
|
|
let lang = context.sharedContext.currentPresentationData.with { $0 }.strings
|
|
let controller = promptController(context: context, text: lang.WinterGram_EnterPasscode, value: nil, placeholder: "••••", characterLimit: 16, apply: { value in
|
|
if (value ?? "") == passcode {
|
|
pushControllerImpl?(winterGramStashController(context: context))
|
|
}
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
}
|
|
},
|
|
editStashPasscode: {
|
|
let lang = context.sharedContext.currentPresentationData.with { $0 }.strings
|
|
let current = currentWinterGramSettings.stashPasscode
|
|
let controller = promptController(context: context, text: lang.WinterGram_StashPasscode, value: current.isEmpty ? nil : current, placeholder: lang.WinterGram_EmptyNoPasscode, characterLimit: 16, apply: { value in
|
|
updateSettings { var s = $0; s.stashPasscode = (value ?? "").trimmingCharacters(in: .whitespaces); return s }
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
},
|
|
editDeletedMark: {
|
|
let lang = context.sharedContext.currentPresentationData.with { $0 }.strings
|
|
let current = currentWinterGramSettings.deletedMark
|
|
let controller = promptController(context: context, text: lang.WinterGram_DeletedMark, value: current.isEmpty ? nil : current, placeholder: "🧹", characterLimit: 8, apply: { value in
|
|
updateSettings { var s = $0; s.deletedMark = (value ?? "").trimmingCharacters(in: .whitespaces); return s }
|
|
})
|
|
presentControllerImpl?(controller, nil)
|
|
},
|
|
openUrl: { url in
|
|
context.sharedContext.applicationBindings.openUrl(url)
|
|
},
|
|
clearDeleted: {
|
|
let _ = (winterGramClearDeletedMessages(postbox: context.account.postbox)
|
|
|> deliverOnMainQueue).startStandalone(next: { _ in
|
|
refreshDeletedCount?()
|
|
})
|
|
}
|
|
)
|
|
|
|
let deletedCountPromise = Promise<Int>()
|
|
deletedCountPromise.set(winterGramDeletedMessagesCount(postbox: context.account.postbox))
|
|
refreshDeletedCount = {
|
|
deletedCountPromise.set(winterGramDeletedMessagesCount(postbox: context.account.postbox))
|
|
}
|
|
|
|
let signal = combineLatest(queue: .mainQueue(),
|
|
context.sharedContext.presentationData,
|
|
winterGramSettings(accountManager: accountManager),
|
|
deletedCountPromise.get(),
|
|
selectedCategoryPromise.get(),
|
|
expandedDropdownPromise.get(),
|
|
ghostExpandedPromise.get(),
|
|
spoofTemplatesEditingPromise.get(),
|
|
selectedTemplatesPromise.get()
|
|
)
|
|
|> deliverOnMainQueue
|
|
|> map { presentationData, settings, deletedCount, selectedCategory, expandedDropdown, ghostExpanded, spoofTemplatesEditing, selectedTemplates -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|
let lang = presentationData.strings
|
|
let controllerState: ItemListControllerState
|
|
if let category = category {
|
|
let controllerStateTitle = wntOption(category.title, presentationData.strings)
|
|
let rightNavigationButton: ItemListNavigationButton?
|
|
if category == .spoofing, !settings.spoofPresets.isEmpty {
|
|
rightNavigationButton = ItemListNavigationButton(content: .text(spoofTemplatesEditing ? presentationData.strings.Common_Done : presentationData.strings.Common_Edit), style: spoofTemplatesEditing ? .bold : .regular, enabled: true, action: {
|
|
let nextValue = !spoofTemplatesEditingValue.with { $0 }
|
|
let _ = spoofTemplatesEditingValue.swap(nextValue)
|
|
if !nextValue {
|
|
let _ = selectedTemplatesValue.swap(Set())
|
|
selectedTemplatesPromise.set(Set())
|
|
}
|
|
spoofTemplatesEditingPromise.set(nextValue)
|
|
})
|
|
} else {
|
|
rightNavigationButton = nil
|
|
}
|
|
controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(controllerStateTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
|
} else {
|
|
let titles = sectionTabs.map { wntOption($0.title, lang) }
|
|
let selectedIndex = sectionTabs.firstIndex(of: selectedCategory) ?? 0
|
|
controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl(titles, selectedIndex), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
|
}
|
|
let activeCategory = category ?? selectedCategory
|
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: winterGramSettingsEntries(presentationData: presentationData, settings: settings, deletedCount: deletedCount, expandedDropdown: expandedDropdown, ghostExpanded: ghostExpanded, spoofTemplatesEditing: spoofTemplatesEditing, selectedTemplates: selectedTemplates, category: activeCategory), style: .blocks, animateChanges: true)
|
|
return (controllerState, (listState, arguments))
|
|
}
|
|
|
|
let controller = ItemListController(context: context, state: signal)
|
|
controller.titleControlValueChanged = { index in
|
|
selectedCategoryPromise.set(sectionTabs[index])
|
|
}
|
|
|
|
var bannerController: UndoOverlayController?
|
|
let restartBannerDisposable = (requiresRestartPromise.get()
|
|
|> deliverOnMainQueue).start(next: { [weak controller] needsRestart in
|
|
guard let controller = controller else { return }
|
|
if needsRestart {
|
|
if bannerController == nil {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let banner = UndoOverlayController(presentationData: presentationData, content: .info(title: presentationData.strings.WinterGram_RestartRequired, text: presentationData.strings.WinterGram_SomeSettingsWillTakeEffectAfterRestart, timeout: nil, customUndoText: presentationData.strings.WinterGram_Restart), elevatedLayout: false, action: { action in
|
|
if action == .undo {
|
|
exit(0)
|
|
}
|
|
return true
|
|
})
|
|
bannerController = banner
|
|
controller.present(banner, in: .window(.root))
|
|
}
|
|
} else {
|
|
bannerController?.dismiss()
|
|
bannerController = nil
|
|
}
|
|
})
|
|
|
|
presentControllerImpl = { [weak controller] c, a in
|
|
controller?.present(c, in: .window(.root), with: a)
|
|
}
|
|
pushControllerImpl = { [weak controller] c in
|
|
let _ = restartBannerDisposable
|
|
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
|
}
|
|
return controller
|
|
}
|