Swiftgram/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift
2024-08-12 15:25:44 +02:00

998 lines
52 KiB
Swift

import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import LegacyComponents
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
import OpenInExternalAppUI
import ItemListPeerActionItem
import StorageUsageScreen
import PresentationDataUtils
public enum AutomaticSaveIncomingPeerType {
case privateChats
case groups
case channels
}
private final class DataAndStorageControllerArguments {
let openStorageUsage: () -> Void
let openNetworkUsage: () -> Void
let openProxy: () -> Void
let openAutomaticDownloadConnectionType: (AutomaticDownloadConnectionType) -> Void
let resetAutomaticDownload: () -> Void
let toggleVoiceUseLessData: (Bool) -> Void
let openSaveIncoming: (AutomaticSaveIncomingPeerType) -> Void
let toggleSaveEditedPhotos: (Bool) -> Void
let togglePauseMusicOnRecording: (Bool) -> Void
let toggleRaiseToListen: (Bool) -> Void
let toggleDownloadInBackground: (Bool) -> Void
let openBrowserSelection: () -> Void
let openIntents: () -> Void
let toggleSensitiveContent: (Bool) -> Void
init(
openStorageUsage: @escaping () -> Void,
openNetworkUsage: @escaping () -> Void,
openProxy: @escaping () -> Void,
openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void,
resetAutomaticDownload: @escaping () -> Void,
toggleVoiceUseLessData: @escaping (Bool) -> Void,
openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void,
toggleSaveEditedPhotos: @escaping (Bool) -> Void,
togglePauseMusicOnRecording: @escaping (Bool) -> Void,
toggleRaiseToListen: @escaping (Bool) -> Void,
toggleDownloadInBackground: @escaping (Bool) -> Void,
openBrowserSelection: @escaping () -> Void,
openIntents: @escaping () -> Void,
toggleSensitiveContent: @escaping (Bool) -> Void
) {
self.openStorageUsage = openStorageUsage
self.openNetworkUsage = openNetworkUsage
self.openProxy = openProxy
self.openAutomaticDownloadConnectionType = openAutomaticDownloadConnectionType
self.resetAutomaticDownload = resetAutomaticDownload
self.toggleVoiceUseLessData = toggleVoiceUseLessData
self.openSaveIncoming = openSaveIncoming
self.toggleSaveEditedPhotos = toggleSaveEditedPhotos
self.togglePauseMusicOnRecording = togglePauseMusicOnRecording
self.toggleRaiseToListen = toggleRaiseToListen
self.toggleDownloadInBackground = toggleDownloadInBackground
self.openBrowserSelection = openBrowserSelection
self.openIntents = openIntents
self.toggleSensitiveContent = toggleSensitiveContent
}
}
private enum DataAndStorageSection: Int32 {
case usage
case autoDownload
case autoSave
case backgroundDownload
case voiceCalls
case other
case connection
case sensitiveContent
}
public enum DataAndStorageEntryTag: ItemListItemTag, Equatable {
case automaticDownloadReset
case saveEditedPhotos
case downloadInBackground
case pauseMusicOnRecording
case raiseToListen
case autoSave(AutomaticSaveIncomingPeerType)
case sensitiveContent
public func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? DataAndStorageEntryTag, self == other {
return true
} else {
return false
}
}
}
private enum DataAndStorageEntry: ItemListNodeEntry {
case storageUsage(PresentationTheme, String, String)
case networkUsage(PresentationTheme, String, String)
case automaticDownloadHeader(PresentationTheme, String)
case automaticDownloadCellular(PresentationTheme, String, String)
case automaticDownloadWifi(PresentationTheme, String, String)
case automaticDownloadReset(PresentationTheme, String, Bool)
case autoSaveHeader(String)
case autoSaveItem(index: Int, type: AutomaticSaveIncomingPeerType, title: String, label: String, value: String)
case autoSaveInfo(String)
case downloadInBackground(PresentationTheme, String, Bool)
case downloadInBackgroundInfo(PresentationTheme, String)
case useLessVoiceData(PresentationTheme, String, Bool)
case useLessVoiceDataInfo(PresentationTheme, String)
case otherHeader(PresentationTheme, String)
case openLinksIn(PresentationTheme, String, String)
case shareSheet(PresentationTheme, String)
case saveEditedPhotos(PresentationTheme, String, Bool)
case pauseMusicOnRecording(PresentationTheme, String, Bool)
case raiseToListen(PresentationTheme, String, Bool)
case raiseToListenInfo(PresentationTheme, String)
case sensitiveContent(String, Bool)
case sensitiveContentInfo(String)
case connectionHeader(PresentationTheme, String)
case connectionProxy(PresentationTheme, String, String)
var section: ItemListSectionId {
switch self {
case .storageUsage, .networkUsage:
return DataAndStorageSection.usage.rawValue
case .automaticDownloadHeader, .automaticDownloadCellular, .automaticDownloadWifi, .automaticDownloadReset:
return DataAndStorageSection.autoDownload.rawValue
case .autoSaveHeader, .autoSaveItem, .autoSaveInfo:
return DataAndStorageSection.autoSave.rawValue
case .downloadInBackground, .downloadInBackgroundInfo:
return DataAndStorageSection.backgroundDownload.rawValue
case .useLessVoiceData, .useLessVoiceDataInfo:
return DataAndStorageSection.voiceCalls.rawValue
case .otherHeader, .openLinksIn, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo:
return DataAndStorageSection.other.rawValue
case .sensitiveContent, .sensitiveContentInfo:
return DataAndStorageSection.sensitiveContent.rawValue
case .connectionHeader, .connectionProxy:
return DataAndStorageSection.connection.rawValue
}
}
var stableId: Int32 {
switch self {
case .storageUsage:
return 0
case .networkUsage:
return 1
case .automaticDownloadHeader:
return 2
case .automaticDownloadCellular:
return 3
case .automaticDownloadWifi:
return 4
case .automaticDownloadReset:
return 5
case .autoSaveHeader:
return 6
case let .autoSaveItem(index, _, _, _, _):
return 7 + Int32(index)
case .autoSaveInfo:
return 20
case .downloadInBackground:
return 21
case .downloadInBackgroundInfo:
return 22
case .useLessVoiceData:
return 23
case .useLessVoiceDataInfo:
return 24
case .otherHeader:
return 29
case .openLinksIn:
return 30
case .shareSheet:
return 31
case .saveEditedPhotos:
return 32
case .pauseMusicOnRecording:
return 33
case .raiseToListen:
return 34
case .raiseToListenInfo:
return 35
case .sensitiveContent:
return 36
case .sensitiveContentInfo:
return 37
case .connectionHeader:
return 38
case .connectionProxy:
return 39
}
}
static func ==(lhs: DataAndStorageEntry, rhs: DataAndStorageEntry) -> Bool {
switch lhs {
case let .storageUsage(lhsTheme, lhsText, lhsValue):
if case let .storageUsage(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .networkUsage(lhsTheme, lhsText, lhsValue):
if case let .networkUsage(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .automaticDownloadHeader(lhsTheme, lhsText):
if case let .automaticDownloadHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .automaticDownloadCellular(lhsTheme, lhsText, lhsValue):
if case let .automaticDownloadCellular(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .automaticDownloadWifi(lhsTheme, lhsText, lhsEnabled):
if case let .automaticDownloadWifi(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
return true
} else {
return false
}
case let .automaticDownloadReset(lhsTheme, lhsText, lhsEnabled):
if case let .automaticDownloadReset(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
return true
} else {
return false
}
case let .autoSaveHeader(text):
if case .autoSaveHeader(text) = rhs {
return true
} else {
return false
}
case let .autoSaveItem(index, type, title, label, value):
if case .autoSaveItem(index, type, title, label, value) = rhs {
return true
} else {
return false
}
case let .autoSaveInfo(text):
if case .autoSaveInfo(text) = rhs {
return true
} else {
return false
}
case let .useLessVoiceData(lhsTheme, lhsText, lhsValue):
if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .useLessVoiceDataInfo(lhsTheme, lhsText):
if case let .useLessVoiceDataInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .otherHeader(lhsTheme, lhsText):
if case let .otherHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .openLinksIn(lhsTheme, lhsText, lhsValue):
if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .shareSheet(lhsTheme, lhsText):
if case let .shareSheet(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .saveEditedPhotos(lhsTheme, lhsText, lhsValue):
if case let .saveEditedPhotos(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .pauseMusicOnRecording(lhsTheme, lhsText, lhsValue):
if case let .pauseMusicOnRecording(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .raiseToListen(lhsTheme, lhsText, lhsValue):
if case let .raiseToListen(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .raiseToListenInfo(lhsTheme, lhsText):
if case let .raiseToListenInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .sensitiveContent(text, value):
if case .sensitiveContent(text, value) = rhs {
return true
} else {
return false
}
case let .sensitiveContentInfo(text):
if case .sensitiveContentInfo(text) = rhs {
return true
} else {
return false
}
case let .downloadInBackground(lhsTheme, lhsText, lhsValue):
if case let .downloadInBackground(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .downloadInBackgroundInfo(lhsTheme, lhsText):
if case let .downloadInBackgroundInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .connectionHeader(lhsTheme, lhsText):
if case let .connectionHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .connectionProxy(lhsTheme, lhsText, lhsValue):
if case let .connectionProxy(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
}
}
static func <(lhs: DataAndStorageEntry, rhs: DataAndStorageEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! DataAndStorageControllerArguments
switch self {
case let .storageUsage(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Storage")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openStorageUsage()
})
case let .networkUsage(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Network")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openNetworkUsage()
})
case let .automaticDownloadHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .automaticDownloadCellular(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.openAutomaticDownloadConnectionType(.cellular)
})
case let .automaticDownloadWifi(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.openAutomaticDownloadConnectionType(.wifi)
})
case let .automaticDownloadReset(theme, text, enabled):
var icon = PresentationResourcesItemList.resetIcon(theme)
if !enabled {
icon = generateTintedImage(image: icon, color: theme.list.itemDisabledTextColor)
}
return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: text, sectionId: self.section, height: .generic, color: enabled ? .accent : .disabled, editing: false, action: {
if enabled {
arguments.resetAutomaticDownload()
}
})
case let .autoSaveHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .autoSaveItem(_, type, title, label, value):
let iconName: String
switch type {
case .privateChats:
iconName = "Settings/Menu/EditProfile"
case .groups:
iconName = "Settings/Menu/GroupChats"
case .channels:
iconName = "Settings/Menu/Channels"
}
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: iconName)?.precomposed(), title: title, label: value, labelStyle: .text, additionalDetailLabel: label.isEmpty ? nil : label, sectionId: self.section, style: .blocks, action: {
arguments.openSaveIncoming(type)
})
case let .autoSaveInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .useLessVoiceData(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleVoiceUseLessData(value)
}, tag: nil)
case let .useLessVoiceDataInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .openLinksIn(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openBrowserSelection()
})
case let .shareSheet(_, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openIntents()
})
case let .saveEditedPhotos(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleSaveEditedPhotos(value)
}, tag: DataAndStorageEntryTag.saveEditedPhotos)
case let .pauseMusicOnRecording(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.togglePauseMusicOnRecording(value)
}, tag: DataAndStorageEntryTag.pauseMusicOnRecording)
case let .raiseToListen(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleRaiseToListen(value)
}, tag: DataAndStorageEntryTag.raiseToListen)
case let .raiseToListenInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .sensitiveContent(text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: false, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleSensitiveContent(value)
}, tag: DataAndStorageEntryTag.sensitiveContent)
case let .sensitiveContentInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .downloadInBackground(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleDownloadInBackground(value)
}, tag: DataAndStorageEntryTag.downloadInBackground)
case let .downloadInBackgroundInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .connectionHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .connectionProxy(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openProxy()
})
}
}
}
private struct DataAndStorageControllerState: Equatable {
static func ==(lhs: DataAndStorageControllerState, rhs: DataAndStorageControllerState) -> Bool {
return true
}
}
private struct DataAndStorageData: Equatable {
let automaticMediaDownloadSettings: MediaAutoDownloadSettings
let autodownloadSettings: AutodownloadSettings
let generatedMediaStoreSettings: GeneratedMediaStoreSettings
let mediaInputSettings: MediaInputSettings
let voiceCallSettings: VoiceCallSettings
let proxySettings: ProxySettings?
init(automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, generatedMediaStoreSettings: GeneratedMediaStoreSettings, mediaInputSettings: MediaInputSettings, voiceCallSettings: VoiceCallSettings, proxySettings: ProxySettings?) {
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
self.autodownloadSettings = autodownloadSettings
self.generatedMediaStoreSettings = generatedMediaStoreSettings
self.mediaInputSettings = mediaInputSettings
self.voiceCallSettings = voiceCallSettings
self.proxySettings = proxySettings
}
static func ==(lhs: DataAndStorageData, rhs: DataAndStorageData) -> Bool {
return lhs.automaticMediaDownloadSettings == rhs.automaticMediaDownloadSettings && lhs.generatedMediaStoreSettings == rhs.generatedMediaStoreSettings && lhs.mediaInputSettings == rhs.mediaInputSettings && lhs.voiceCallSettings == rhs.voiceCallSettings && lhs.proxySettings == rhs.proxySettings
}
}
private func stringForUseLessDataSetting(_ dataSaving: VoiceCallDataSaving, strings: PresentationStrings) -> String {
switch dataSaving {
case .never:
return strings.CallSettings_Never
case .cellular:
return strings.CallSettings_OnMobile
case .always:
return strings.CallSettings_Always
case .default:
return ""
}
}
private func stringForAutoDownloadTypes(strings: PresentationStrings, decimalSeparator: String, photo: Bool, videoSize: Int64?, fileSize: Int64?) -> String {
var types: [String] = []
if photo && videoSize == nil {
types.append(strings.ChatSettings_AutoDownloadSettings_TypePhoto)
}
if let videoSize = videoSize {
if photo {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeMedia(autodownloadDataSizeString(videoSize, decimalSeparator: decimalSeparator)).string)
} else {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeVideo(autodownloadDataSizeString(videoSize, decimalSeparator: decimalSeparator)).string)
}
}
if let fileSize = fileSize {
types.append(strings.ChatSettings_AutoDownloadSettings_TypeFile(autodownloadDataSizeString(fileSize, decimalSeparator: decimalSeparator)).string)
}
if types.isEmpty {
return strings.ChatSettings_AutoDownloadSettings_OffForAll
}
var string: String = ""
for i in 0 ..< types.count {
if !string.isEmpty {
string.append(strings.ChatSettings_AutoDownloadSettings_Delimeter)
}
string.append(types[i])
}
return string
}
private func stringForAutoDownloadSetting(strings: PresentationStrings, decimalSeparator: String, settings: MediaAutoDownloadSettings, connectionType: AutomaticDownloadConnectionType) -> String {
let connection: MediaAutoDownloadConnection
switch connectionType {
case .cellular:
connection = settings.cellular
case .wifi:
connection = settings.wifi
}
if !connection.enabled {
return strings.ChatSettings_AutoDownloadSettings_OffForAll
} else {
let categories = effectiveAutodownloadCategories(settings: settings, networkType: connectionType.automaticDownloadNetworkType)
let photo = isAutodownloadEnabledForAnyPeerType(category: categories.photo)
let video = isAutodownloadEnabledForAnyPeerType(category: categories.video)
let file = isAutodownloadEnabledForAnyPeerType(category: categories.file)
return stringForAutoDownloadTypes(strings: strings, decimalSeparator: decimalSeparator, photo: photo, videoSize: video ? categories.video.sizeLimit : nil, fileSize: file ? categories.file.sizeLimit : nil)
}
}
private func autosaveLabelAndValue(presentationData: PresentationData, settings: MediaAutoSaveSettings, peerType: AutomaticSaveIncomingPeerType, exceptionPeers: [EnginePeer.Id: EnginePeer?]) -> (label: String, value: String) {
var exceptionCount = 0
let configuration: MediaAutoSaveConfiguration
switch peerType {
case .privateChats:
configuration = settings.configurations[.users] ?? .default
case .groups:
configuration = settings.configurations[.groups] ?? .default
case .channels:
configuration = settings.configurations[.channels] ?? .default
}
for exception in settings.exceptions {
if let maybePeer = exceptionPeers[exception.id], let peer = maybePeer {
let peerTypeValue: AutomaticSaveIncomingPeerType
switch peer {
case .user, .secretChat:
peerTypeValue = .privateChats
case .legacyGroup:
peerTypeValue = .groups
case let .channel(channel):
if case .broadcast = channel.info {
peerTypeValue = .channels
} else {
peerTypeValue = .groups
}
}
if peerTypeValue == peerType {
exceptionCount += 1
}
}
}
let value: String
if configuration.photo || configuration.video {
value = presentationData.strings.Settings_AutosaveMediaOn
} else {
value = presentationData.strings.Settings_AutosaveMediaOff
}
var label = ""
if configuration.photo && configuration.video {
label.append(presentationData.strings.Settings_AutosaveMediaAllMedia(dataSizeString(Int(configuration.maximumVideoSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string)
} else {
if configuration.photo {
if !label.isEmpty {
label.append(", ")
}
label.append(presentationData.strings.Settings_AutosaveMediaPhoto)
} else if configuration.video {
if !label.isEmpty {
label.append(", ")
}
label.append(presentationData.strings.Settings_AutosaveMediaVideo(dataSizeString(Int(configuration.maximumVideoSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string)
}
}
if exceptionCount != 0 {
if !label.isEmpty {
label.append(", ")
}
label.append(presentationData.strings.Notifications_CategoryExceptions(Int32(exceptionCount)))
}
return (label, value)
}
private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings) -> [DataAndStorageEntry] {
var entries: [DataAndStorageEntry] = []
entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache, dataSizeString(storageUsage, formatting: DataSizeStringFormatting(presentationData: presentationData))))
entries.append(.networkUsage(presentationData.theme, presentationData.strings.NetworkUsageSettings_Title, dataSizeString(networkUsage, formatting: DataSizeStringFormatting(presentationData: presentationData))))
entries.append(.automaticDownloadHeader(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadTitle.uppercased()))
entries.append(.automaticDownloadCellular(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadUsingCellular, stringForAutoDownloadSetting(strings: presentationData.strings, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, settings: data.automaticMediaDownloadSettings, connectionType: .cellular)))
entries.append(.automaticDownloadWifi(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadUsingWiFi, stringForAutoDownloadSetting(strings: presentationData.strings, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, settings: data.automaticMediaDownloadSettings, connectionType: .wifi)))
let defaultSettings = MediaAutoDownloadSettings.defaultSettings
entries.append(.automaticDownloadReset(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadReset, data.automaticMediaDownloadSettings.cellular != defaultSettings.cellular || data.automaticMediaDownloadSettings.wifi != defaultSettings.wifi))
entries.append(.autoSaveHeader(presentationData.strings.Settings_SaveToCameraRollSection))
let privateLabelAndValue = autosaveLabelAndValue(presentationData: presentationData, settings: mediaAutoSaveSettings, peerType: .privateChats, exceptionPeers: autosaveExceptionPeers)
let groupsLabelAndValue = autosaveLabelAndValue(presentationData: presentationData, settings: mediaAutoSaveSettings, peerType: .groups, exceptionPeers: autosaveExceptionPeers)
let channelsLabelAndValue = autosaveLabelAndValue(presentationData: presentationData, settings: mediaAutoSaveSettings, peerType: .channels, exceptionPeers: autosaveExceptionPeers)
entries.append(.autoSaveItem(index: 0, type: .privateChats, title: presentationData.strings.Notifications_PrivateChats, label: privateLabelAndValue.label, value: privateLabelAndValue.value))
entries.append(.autoSaveItem(index: 1, type: .groups, title: presentationData.strings.Notifications_GroupChats, label: groupsLabelAndValue.label, value: groupsLabelAndValue.value))
entries.append(.autoSaveItem(index: 2, type: .channels, title: presentationData.strings.Notifications_Channels, label: channelsLabelAndValue.label, value: channelsLabelAndValue.value))
entries.append(.autoSaveInfo(presentationData.strings.Settings_SaveToCameraRollInfo))
let dataSaving = effectiveDataSaving(for: data.voiceCallSettings, autodownloadSettings: data.autodownloadSettings)
entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.ChatSettings_UseLessDataForCalls, dataSaving != .never))
entries.append(.useLessVoiceDataInfo(presentationData.theme, presentationData.strings.CallSettings_UseLessDataLongDescription))
entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
}
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
entries.append(.pauseMusicOnRecording(presentationData.theme, presentationData.strings.Settings_PauseMusicOnRecording, data.mediaInputSettings.pauseMusicOnRecording))
entries.append(.raiseToListen(presentationData.theme, presentationData.strings.Settings_RaiseToListen, data.mediaInputSettings.enableRaiseToSpeak))
entries.append(.raiseToListenInfo(presentationData.theme, presentationData.strings.Settings_RaiseToListenInfo))
if let contentSettingsConfiguration = contentSettingsConfiguration, contentSettingsConfiguration.canAdjustSensitiveContent {
entries.append(.sensitiveContent(presentationData.strings.Settings_SensitiveContent, contentSettingsConfiguration.sensitiveContentEnabled))
entries.append(.sensitiveContentInfo(presentationData.strings.Settings_SensitiveContentInfo))
}
let proxyValue: String
if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled {
switch activeServer.connection {
case .socks5:
proxyValue = presentationData.strings.ChatSettings_ConnectionType_UseSocks5
case .mtp:
proxyValue = presentationData.strings.SocksProxySetup_ProxyTelegram
}
} else {
proxyValue = presentationData.strings.GroupInfo_SharedMediaNone
}
entries.append(.connectionHeader(presentationData.theme, presentationData.strings.ChatSettings_ConnectionType_Title.uppercased()))
entries.append(.connectionProxy(presentationData.theme, presentationData.strings.SocksProxySetup_Title, proxyValue))
return entries
}
public func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndStorageEntryTag? = nil) -> ViewController {
let initialState = DataAndStorageControllerState()
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
let actionsDisposable = DisposableSet()
//let cacheUsagePromise = Promise<CacheUsageStatsResult?>()
//cacheUsagePromise.set(cacheUsageStats(context: context))
let updateSensitiveContentDisposable = MetaDisposable()
actionsDisposable.add(updateSensitiveContentDisposable)
let updatedContentSettingsConfiguration = contentSettingsConfiguration(network: context.account.network)
|> map(Optional.init)
let contentSettingsConfiguration = Promise<ContentSettingsConfiguration?>()
contentSettingsConfiguration.set(.single(nil)
|> then(updatedContentSettingsConfiguration))
struct UsageData: Equatable {
var network: Int64
var storage: Int64
}
let usageSignal: Signal<UsageData, NoError> = combineLatest(
context.account.postbox.mediaBox.storageBox.totalSize(),
context.account.postbox.mediaBox.cacheStorageBox.totalSize(),
accountNetworkUsageStats(account: context.account, reset: [])
)
|> map { disk1, disk2, networkStats -> UsageData in
var network: Int64 = 0
var keys: [KeyPath<NetworkUsageStats, Int64>] = []
keys.append(\.generic.cellular.outgoing)
keys.append(\.generic.cellular.incoming)
keys.append(\.generic.wifi.incoming)
keys.append(\.generic.wifi.outgoing)
keys.append(\.image.cellular.outgoing)
keys.append(\.image.cellular.incoming)
keys.append(\.image.wifi.incoming)
keys.append(\.image.wifi.outgoing)
keys.append(\.video.cellular.outgoing)
keys.append(\.video.cellular.incoming)
keys.append(\.video.wifi.incoming)
keys.append(\.video.wifi.outgoing)
keys.append(\.audio.cellular.outgoing)
keys.append(\.audio.cellular.incoming)
keys.append(\.audio.wifi.incoming)
keys.append(\.audio.wifi.outgoing)
keys.append(\.file.cellular.outgoing)
keys.append(\.file.cellular.incoming)
keys.append(\.file.wifi.incoming)
keys.append(\.file.wifi.outgoing)
keys.append(\.call.cellular.outgoing)
keys.append(\.call.cellular.incoming)
keys.append(\.call.wifi.incoming)
keys.append(\.call.wifi.outgoing)
keys.append(\.sticker.cellular.outgoing)
keys.append(\.sticker.cellular.incoming)
keys.append(\.sticker.wifi.incoming)
keys.append(\.sticker.wifi.outgoing)
keys.append(\.voiceMessage.cellular.outgoing)
keys.append(\.voiceMessage.cellular.incoming)
keys.append(\.voiceMessage.wifi.incoming)
keys.append(\.voiceMessage.wifi.outgoing)
for key in keys {
network += networkStats[keyPath: key]
}
return UsageData(network: network, storage: disk1 + disk2)
}
let dataAndStorageDataPromise = Promise<DataAndStorageData>()
dataAndStorageDataPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings, ApplicationSpecificSharedDataKeys.mediaInputSettings, SharedDataKeys.proxySettings])
|> map { sharedData -> DataAndStorageData in
var automaticMediaDownloadSettings: MediaAutoDownloadSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]?.get(MediaAutoDownloadSettings.self) {
automaticMediaDownloadSettings = value
} else {
automaticMediaDownloadSettings = .defaultSettings
}
var autodownloadSettings: AutodownloadSettings
if let value = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) {
autodownloadSettings = value
automaticMediaDownloadSettings = automaticMediaDownloadSettings.updatedWithAutodownloadSettings(autodownloadSettings)
} else {
autodownloadSettings = .defaultSettings
}
let generatedMediaStoreSettings: GeneratedMediaStoreSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings]?.get(GeneratedMediaStoreSettings.self) {
generatedMediaStoreSettings = value
} else {
generatedMediaStoreSettings = GeneratedMediaStoreSettings.defaultSettings
}
let mediaInputSettings: MediaInputSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaInputSettings]?.get(MediaInputSettings.self) {
mediaInputSettings = value
} else {
mediaInputSettings = MediaInputSettings.defaultSettings
}
let voiceCallSettings: VoiceCallSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.voiceCallSettings]?.get(VoiceCallSettings.self) {
voiceCallSettings = value
} else {
voiceCallSettings = VoiceCallSettings.defaultSettings
}
var proxySettings: ProxySettings?
if let value = sharedData.entries[SharedDataKeys.proxySettings]?.get(ProxySettings.self) {
proxySettings = value
}
return DataAndStorageData(automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, generatedMediaStoreSettings: generatedMediaStoreSettings, mediaInputSettings: mediaInputSettings, voiceCallSettings: voiceCallSettings, proxySettings: proxySettings)
})
let arguments = DataAndStorageControllerArguments(openStorageUsage: {
pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in
return storageUsageExceptionsScreen(context: context, category: category)
}))
}, openNetworkUsage: {
let mediaAutoDownloadSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings])
|> map { sharedData -> MediaAutoDownloadSettings in
var automaticMediaDownloadSettings: MediaAutoDownloadSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]?.get(MediaAutoDownloadSettings.self) {
automaticMediaDownloadSettings = value
} else {
automaticMediaDownloadSettings = .defaultSettings
}
return automaticMediaDownloadSettings
}
let _ = (combineLatest(
accountNetworkUsageStats(account: context.account, reset: []),
mediaAutoDownloadSettings
)
|> take(1)
|> deliverOnMainQueue).start(next: { stats, mediaAutoDownloadSettings in
var stats = stats
if stats.resetWifiTimestamp == 0 {
var value = stat()
if stat(context.account.basePath, &value) == 0 {
stats.resetWifiTimestamp = Int32(value.st_ctimespec.tv_sec)
}
}
pushControllerImpl?(DataUsageScreen(context: context, stats: stats, mediaAutoDownloadSettings: mediaAutoDownloadSettings, makeAutodownloadSettingsController: { isCellular in
return autodownloadMediaConnectionTypeController(context: context, connectionType: isCellular ? .cellular : .wifi)
}))
})
}, openProxy: {
pushControllerImpl?(proxySettingsController(context: context))
}, openAutomaticDownloadConnectionType: { connectionType in
pushControllerImpl?(autodownloadMediaConnectionTypeController(context: context, connectionType: connectionType))
}, resetAutomaticDownload: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.AutoDownloadSettings_ResetHelp),
ActionSheetButtonItem(title: presentationData.strings.AutoDownloadSettings_Reset, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
var settings = settings
let defaultSettings = MediaAutoDownloadSettings.defaultSettings
settings.cellular = defaultSettings.cellular
settings.wifi = defaultSettings.wifi
return settings
}).start()
})
]), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
}, toggleVoiceUseLessData: { value in
let _ = updateVoiceCallSettingsSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var current = current
current.dataSaving = value ? .always : .never
return current
}).start()
}, openSaveIncoming: { type in
pushControllerImpl?(saveIncomingMediaController(context: context, scope: .peerType(type)))
}, toggleSaveEditedPhotos: { value in
let _ = updateGeneratedMediaStoreSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedStoreEditedPhotos(value)
}).start()
}, togglePauseMusicOnRecording: { value in
let _ = updateMediaInputSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedPauseMusicOnRecording(value)
}).start()
}, toggleRaiseToListen: { value in
let _ = updateMediaInputSettingsInteractively(accountManager: context.sharedContext.accountManager, {
$0.withUpdatedEnableRaiseToSpeak(value)
}).start()
}, toggleDownloadInBackground: { value in
let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
var settings = settings
settings.downloadInBackground = value
return settings
}).start()
}, openBrowserSelection: {
let controller = webBrowserSettingsController(context: context)
pushControllerImpl?(controller)
}, openIntents: {
let controller = intentsSettingsController(context: context)
pushControllerImpl?(controller)
}, toggleSensitiveContent: { value in
let update = {
let _ = (contentSettingsConfiguration.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak contentSettingsConfiguration] settings in
if var settings = settings {
settings.sensitiveContentEnabled = value
contentSettingsConfiguration?.set(.single(settings))
}
})
updateSensitiveContentDisposable.set(updateRemoteContentSettingsConfiguration(postbox: context.account.postbox, network: context.account.network, sensitiveContentEnabled: value).start())
}
if value {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let alertController = textAlertController(context: context, title: presentationData.strings.SensitiveContent_Enable_Title, text: presentationData.strings.SensitiveContent_Enable_Text, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.SensitiveContent_Enable_Confirm, action: {
update()
})
])
presentControllerImpl?(alertController, nil)
} else {
update()
}
})
let preferencesKey: PostboxViewKey = .preferences(keys: Set([ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings]))
let preferences = context.account.postbox.combinedView(keys: [preferencesKey])
|> map { views -> MediaAutoSaveSettings in
guard let view = views.views[preferencesKey] as? PreferencesView else {
return .default
}
return view.values[ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings]?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default
}
let autosaveExceptionPeers: Signal<[EnginePeer.Id: EnginePeer?], NoError> = preferences
|> mapToSignal { mediaAutoSaveSettings -> Signal<[EnginePeer.Id: EnginePeer?], NoError> in
let peerIds = mediaAutoSaveSettings.exceptions.map(\.id)
return context.engine.data.get(EngineDataMap(
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))
))
}
let sensitiveContent = Atomic<Bool?>(value: nil)
let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData,
statePromise.get(),
dataAndStorageDataPromise.get(),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]),
contentSettingsConfiguration.get(),
preferences,
usageSignal,
autosaveExceptionPeers
)
|> map { presentationData, state, dataAndStorageData, sharedData, contentSettingsConfiguration, mediaAutoSaveSettings, usageSignal, autosaveExceptionPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in
let webBrowserSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings
let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org"))
let defaultWebBrowser: String
if let option = options.first(where: { $0.identifier == webBrowserSettings.defaultWebBrowser }) {
defaultWebBrowser = option.title
} else if webBrowserSettings.defaultWebBrowser == "inApp" {
defaultWebBrowser = presentationData.strings.WebBrowser_InAppSafari
} else {
defaultWebBrowser = presentationData.strings.WebBrowser_Telegram
}
let previousSensitiveContent = sensitiveContent.swap(contentSettingsConfiguration?.sensitiveContentEnabled)
var animateChanges = false
if previousSensitiveContent != contentSettingsConfiguration?.sensitiveContentEnabled {
animateChanges = true
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
} |> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
pushControllerImpl = { [weak controller] c in
if let controller = controller {
(controller.navigationController as? NavigationController)?.pushViewController(c)
}
}
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
return controller
}