Swiftgram/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift
2023-04-19 23:47:38 +04:00

274 lines
12 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
import AppBundle
private func generateArrowImage(color: UIColor) -> UIImage? {
let smallRadius: CGFloat = 5.0
let largeRadius: CGFloat = 14.0
return generateImage(CGSize(width: smallRadius + largeRadius, height: smallRadius + largeRadius + 16.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/SettingsArrow"), color: color), let cgImage = image.cgImage {
context.setFillColor(color.cgColor)
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height - image.size.height - 16.0), size: CGSize(width: size.width, height: 16.0)))
context.draw(cgImage, in: CGRect(origin: CGPoint(x: size.width - image.size.width, y: size.height - image.size.height), size: image.size))
}
})
}
final class InstantPageSettingsNode: ASDisplayNode {
private var settings: InstantPagePresentationSettings
private var currentThemeType: (InstantPageThemeType, Bool)
private var theme: InstantPageSettingsItemTheme
private let applySettings: (InstantPagePresentationSettings) -> Void
private let openInSafari: () -> Void
private var sections: [[InstantPageSettingsItemNode]] = []
private let sansFamilyNode: InstantPageSettingsFontFamilyNode
private let serifFamilyNode: InstantPageSettingsFontFamilyNode
private let themeItemNode: InstantPageSettingsThemeItemNode
private let autoNightItemNode: InstantPageSettingsSwitchNode
private let openInItemNode: InstantPageSettingsButtonItemNode
private let arrowNode: ASImageNode
private let itemContainerNode: ASDisplayNode
init(strings: PresentationStrings, settings: InstantPagePresentationSettings, currentThemeType: (InstantPageThemeType, Bool), applySettings: @escaping (InstantPagePresentationSettings) -> Void, openInSafari: @escaping () -> Void) {
self.settings = settings
self.currentThemeType = currentThemeType
self.theme = InstantPageSettingsItemTheme.themeFor(currentThemeType.0)
self.applySettings = applySettings
self.openInSafari = openInSafari
self.arrowNode = ASImageNode()
self.arrowNode.displayWithoutProcessing = true
self.arrowNode.displaysAsynchronously = false
self.arrowNode.image = generateArrowImage(color: self.theme.itemBackgroundColor)
self.itemContainerNode = ASDisplayNode()
self.itemContainerNode.layer.masksToBounds = true
self.itemContainerNode.layer.cornerRadius = 16.0
self.itemContainerNode.backgroundColor = self.theme.listBackgroundColor
var updateSerifImpl: ((Bool) -> Void)?
var updateThemeTypeImpl: ((InstantPageThemeType) -> Void)?
var updateAutoNightImpl: ((Bool) -> Void)?
var openInSafariImpl: (() -> Void)?
self.sansFamilyNode = InstantPageSettingsFontFamilyNode(theme: self.theme, title: "San Francisco", family: nil, checked: !settings.forceSerif, tapped: {
updateSerifImpl?(false)
})
self.serifFamilyNode = InstantPageSettingsFontFamilyNode(theme: self.theme, title: "Georgia", family: "Georgia", checked: settings.forceSerif, tapped: {
updateSerifImpl?(true)
})
self.themeItemNode = InstantPageSettingsThemeItemNode(theme: theme, themeType: settings.themeType, update: { value in
updateThemeTypeImpl?(value)
})
self.autoNightItemNode = InstantPageSettingsSwitchNode(theme: theme, title: strings.InstantPage_AutoNightTheme, isOn: settings.autoNightMode, isEnabled: settings.themeType != .dark, toggled: { value in
updateAutoNightImpl?(value)
})
self.openInItemNode = InstantPageSettingsButtonItemNode(theme: theme, title: strings.Web_OpenExternal, tapped: {
openInSafariImpl?()
})
super.init()
self.addSubnode(self.arrowNode)
self.addSubnode(self.itemContainerNode)
self.sections = [
[
InstantPageSettingsBacklightItemNode(theme: self.theme)
],
[
InstantPageSettingsFontSizeItemNode(theme: self.theme, fontSizeVariant: Int(settings.fontSize.rawValue), updated: { [weak self] value in
if let strongSelf = self {
strongSelf.updateSettings {
let size: InstantPagePresentationFontSize = InstantPagePresentationFontSize(rawValue: Int32(value)) ?? .standard
return $0.withUpdatedFontSize(size)
}
}
}),
self.sansFamilyNode,
self.serifFamilyNode
],
[
self.themeItemNode,
self.autoNightItemNode
],
[
self.openInItemNode
]
]
for section in self.sections {
for item in section {
self.itemContainerNode.addSubnode(item)
}
}
updateSerifImpl = { [weak self] value in
if let strongSelf = self {
strongSelf.updateSettings {
return $0.withUpdatedForceSerif(value)
}
}
}
updateThemeTypeImpl = { [weak self] value in
if let strongSelf = self {
let disableAutoNightMode = strongSelf.currentThemeType.1
strongSelf.updateSettings {
if disableAutoNightMode {
let currentTime: Int32 = 0
return $0.withUpdatedThemeType(value).withUpdatedIgnoreAutoNightModeUntil(currentTime)
} else {
return $0.withUpdatedThemeType(value)
}
}
}
}
updateAutoNightImpl = { [weak self] value in
if let strongSelf = self {
strongSelf.updateSettings {
return $0.withUpdatedAutoNightMode(value).withUpdatedIgnoreAutoNightModeUntil(0)
}
}
}
openInSafariImpl = { [weak self] in
if let strongSelf = self {
strongSelf.openInSafari()
}
}
}
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let fixedWidth: CGFloat = 295.0
let sectionSpacing: CGFloat = 4.0
let sideInset: CGFloat = 11.0
let topInset: CGFloat = layout.insets(options: [.statusBar]).top + 44.0 + 6.0
var contentHeight: CGFloat = 0.0
var itemSizes: [[CGFloat]] = []
for sectionIndex in 0 ..< self.sections.count {
itemSizes.append([])
if sectionIndex != 0 {
contentHeight += sectionSpacing
}
for itemIndex in 0 ..< self.sections[sectionIndex].count {
let previousItem: InstantPageSettingsItemNodeStatus
var previousItemNode: InstantPageSettingsItemNode?
let nextItem: InstantPageSettingsItemNodeStatus
var nextItemNode: InstantPageSettingsItemNode?
if itemIndex == 0 {
if sectionIndex == 0 {
previousItem = .none
} else {
previousItem = .otherSection
}
} else {
previousItem = .sameSection
previousItemNode = self.sections[sectionIndex][itemIndex - 1]
}
if itemIndex == self.sections[sectionIndex].count - 1 {
if sectionIndex == self.sections.count - 1 {
nextItem = .none
} else {
nextItem = .otherSection
}
} else {
nextItem = .sameSection
nextItemNode = self.sections[sectionIndex][itemIndex + 1]
}
let itemHeight = self.sections[sectionIndex][itemIndex].updateLayout(width: fixedWidth, previousItem: (previousItem, previousItemNode), nextItem: (nextItem, nextItemNode))
itemSizes[sectionIndex].append(itemHeight)
contentHeight += itemHeight
}
}
if let image = self.arrowNode.image {
transition.updateFrame(node: self.arrowNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width, y: topInset - image.size.height + 16.0 + 8.0), size: image.size))
}
transition.updateFrame(node: self.itemContainerNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideInset - fixedWidth, y: topInset), size: CGSize(width: fixedWidth, height: contentHeight)))
var nextItemOffset: CGFloat = 0.0
for sectionIndex in 0 ..< self.sections.count {
if sectionIndex != 0 {
nextItemOffset += sectionSpacing
}
for itemIndex in 0 ..< self.sections[sectionIndex].count {
let itemHeight = itemSizes[sectionIndex][itemIndex]
transition.updateFrame(node: self.sections[sectionIndex][itemIndex], frame: CGRect(origin: CGPoint(x: 0.0, y: nextItemOffset), size: CGSize(width: fixedWidth, height: itemHeight)))
nextItemOffset += itemHeight
}
}
}
func animateIn() {
self.layer.allowsGroupOpacity = true
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { [weak self] _ in
self?.layer.allowsGroupOpacity = false
})
}
func animateOut(completion: @escaping () -> Void) {
self.layer.allowsGroupOpacity = true
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
self?.layer.allowsGroupOpacity = false
completion()
})
}
private func updateSettings(_ f: (InstantPagePresentationSettings) -> InstantPagePresentationSettings) {
let updated = f(self.settings)
if updated != self.settings {
self.settings = updated
self.applySettings(settings)
}
}
func updateSettingsAndCurrentThemeType(settings: InstantPagePresentationSettings, type: (InstantPageThemeType, Bool)) {
self.currentThemeType = type
self.sansFamilyNode.checked = !self.settings.forceSerif
self.serifFamilyNode.checked = self.settings.forceSerif
self.themeItemNode.themeType = self.settings.themeType
self.autoNightItemNode.isEnabled = self.settings.themeType != .dark
let theme = InstantPageSettingsItemTheme.themeFor(self.currentThemeType.0)
if theme != self.theme {
self.theme = theme
if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) {
self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
self.arrowNode.image = generateArrowImage(color: self.theme.itemBackgroundColor)
self.itemContainerNode.backgroundColor = self.theme.listBackgroundColor
for section in self.sections {
for item in section {
item.updateTheme(self.theme)
}
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.itemContainerNode.frame.contains(point) {
return super.hitTest(point, with: event)
} else {
return nil
}
}
}