Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,34 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ForumSettingsScreen",
module_name = "ForumSettingsScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/PresentationDataUtils",
"//submodules/Markdown",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/TelegramUI/Components/LottieComponent",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,267 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import AccountContext
import TelegramPresentationData
import PlainButtonComponent
import MultilineTextComponent
import LottieComponent
final class ForumModeComponent: Component {
enum Mode: Equatable {
case tabs
case list
}
let theme: PresentationTheme
let strings: PresentationStrings
let mode: Mode?
let modeUpdated: (Mode) -> Void
init(
theme: PresentationTheme,
strings: PresentationStrings,
mode: Mode?,
modeUpdated: @escaping (Mode) -> Void
) {
self.theme = theme
self.strings = strings
self.mode = mode
self.modeUpdated = modeUpdated
}
static func ==(lhs: ForumModeComponent, rhs: ForumModeComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.mode != rhs.mode {
return false
}
return true
}
final class View: UIView {
private let tabs = ComponentView<Empty>()
private let list = ComponentView<Empty>()
private var component: ForumModeComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ForumModeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let size = CGSize(width: availableSize.width, height: 224.0)
let sideInset = (size.width - 160.0 * 2.0) / 2.0
let tabsSize = self.tabs.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
ItemComponent(
theme: component.theme,
animation: "ForumTabs",
title: component.strings.PeerInfo_Topics_Tabs,
isSelected: component.mode == .tabs
)
),
effectAlignment: .center,
action: {
component.modeUpdated(.tabs)
},
animateScale: false
)
),
environment: {},
containerSize: CGSize(width: 160.0, height: size.height)
)
if let tabsView = self.tabs.view {
if tabsView.superview == nil {
self.addSubview(tabsView)
}
transition.setFrame(view: tabsView, frame: CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: tabsSize))
}
let listSize = self.list.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
ItemComponent(
theme: component.theme,
animation: "ForumList",
title: component.strings.PeerInfo_Topics_List,
isSelected: component.mode == .list
)
),
effectAlignment: .center,
action: {
component.modeUpdated(.list)
},
animateScale: false
)
),
environment: {},
containerSize: CGSize(width: 160.0, height: size.height)
)
if let listView = self.list.view {
if listView.superview == nil {
self.addSubview(listView)
}
transition.setFrame(view: listView, frame: CGRect(origin: CGPoint(x: sideInset + tabsSize.width, y: 0.0), size: listSize))
}
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class ItemComponent: Component {
let theme: PresentationTheme
let animation: String
let title: String
let isSelected: Bool
init(
theme: PresentationTheme,
animation: String,
title: String,
isSelected: Bool
) {
self.theme = theme
self.animation = animation
self.title = title
self.isSelected = isSelected
}
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.animation != rhs.animation {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.isSelected != rhs.isSelected {
return false
}
return true
}
final class View: UIView {
private let animation = ComponentView<Empty>()
private let selection = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private var component: ItemComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let previousComponent = self.component
self.component = component
self.state = state
let size = CGSize(width: availableSize.width, height: 224.0)
let animationSize = self.animation.update(
transition: transition,
component: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(name: component.animation),
color: component.isSelected ? component.theme.list.itemCheckColors.fillColor : component.theme.list.itemSecondaryTextColor,
loop: false
)
),
environment: {},
containerSize: CGSize(width: 170.0, height: 170.0)
)
if let animationView = self.animation.view {
if animationView.superview == nil {
self.addSubview(animationView)
}
transition.setFrame(view: animationView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - animationSize.width) / 2.0), y: 9.0), size: animationSize))
}
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: component.title, font: Font.medium(13.0), textColor: component.isSelected ? component.theme.list.itemCheckColors.foregroundColor : component.theme.list.itemPrimaryTextColor)))
),
environment: {},
containerSize: size
)
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 23.0), size: titleSize)
let selectionFrame = titleFrame.insetBy(dx: -10.0, dy: -6.0)
let _ = self.selection.update(
transition: transition,
component: AnyComponent(RoundedRectangle(color: component.theme.list.itemCheckColors.fillColor, cornerRadius: selectionFrame.size.height / 2.0)),
environment: {},
containerSize: selectionFrame.size
)
if let selectionView = self.selection.view {
if selectionView.superview == nil {
self.addSubview(selectionView)
}
transition.setFrame(view: selectionView, frame: selectionFrame)
selectionView.alpha = component.isSelected ? 1.0 : 0.0
}
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: titleFrame)
}
if previousComponent?.isSelected != component.isSelected && component.isSelected {
if let animationView = self.animation.view as? LottieComponent.View {
animationView.playOnce()
}
}
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
@@ -0,0 +1,627 @@
import Foundation
import UIKit
import Photos
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import PresentationDataUtils
import AccountContext
import ComponentFlow
import ViewControllerComponent
import MultilineTextComponent
import BalancedTextComponent
import ListSectionComponent
import ListActionItemComponent
import BundleIconComponent
import LottieComponent
import PlainButtonComponent
import TelegramStringFormatting
import Markdown
final class ForumSettingsScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let peerId: EnginePeer.Id
init(
context: AccountContext,
peerId: EnginePeer.Id
) {
self.context = context
self.peerId = peerId
}
static func ==(lhs: ForumSettingsScreenComponent, rhs: ForumSettingsScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
private final class ScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
final class View: UIView, UIScrollViewDelegate {
private let topOverscrollLayer = SimpleLayer()
private let scrollView: ScrollView
private let navigationTitle = ComponentView<Empty>()
private let icon = ComponentView<Empty>()
private let subtitle = ComponentView<Empty>()
private let generalSection = ComponentView<Empty>()
private let modeSection = ComponentView<Empty>()
private var ignoreScrolling: Bool = false
private var isUpdating: Bool = false
private var component: ForumSettingsScreenComponent?
private(set) weak var state: EmptyComponentState?
private var environment: EnvironmentType?
private var peer: EnginePeer?
private var peerDisposable: Disposable?
private var peerIdPromise = ValuePromise<EnginePeer.Id>()
private var isOn = false
private var mode: ForumModeComponent.Mode = .tabs
private var initialOnMode: ForumModeComponent.Mode?
override init(frame: CGRect) {
self.scrollView = ScrollView()
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.scrollsToTop = false
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.contentInsetAdjustmentBehavior = .never
if #available(iOS 13.0, *) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
}
self.scrollView.alwaysBounceVertical = true
super.init(frame: frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.peerDisposable?.dispose()
}
func scrollToTop() {
self.scrollView.setContentOffset(CGPoint(), animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
}
}
var scrolledUp = true
private func updateScrolling(transition: ComponentTransition) {
let navigationRevealOffsetY: CGFloat = 0.0
let navigationAlphaDistance: CGFloat = 16.0
let navigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentOffset.y - navigationRevealOffsetY) / navigationAlphaDistance))
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
}
var scrolledUp = false
if navigationAlpha < 0.5 {
scrolledUp = true
} else if navigationAlpha > 0.5 {
scrolledUp = false
}
if self.scrolledUp != scrolledUp {
self.scrolledUp = scrolledUp
if !self.isUpdating {
self.state?.updated()
}
}
if let navigationTitleView = self.navigationTitle.view {
transition.setAlpha(view: navigationTitleView, alpha: 1.0)
}
}
func toggleTopicsEnabled(_ enabled: Bool) {
self.isOn = enabled
self.state?.updated(transition: .spring(duration: 0.4))
}
func attemptNavigation(complete: @escaping () -> Void) -> Bool {
guard let component = self.component, let peer = self.peer else {
return true
}
var isUpdated = false
if self.isOn {
if let initialOnMode = self.initialOnMode {
if initialOnMode != self.mode {
isUpdated = true
}
} else {
isUpdated = true
}
} else {
if self.initialOnMode != nil {
isUpdated = true
}
}
if isUpdated {
if let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
if case .legacyGroup = peer {
} else {
if self.isOn && self.mode == .list {
for i in 0 ..< viewControllers.count {
if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId {
let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: component.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
viewControllers[i] = chatListController
}
}
navigationController.setViewControllers(viewControllers, animated: false)
} else {
for i in (0 ..< viewControllers.count).reversed() {
if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) {
viewControllers.remove(at: i)
}
}
navigationController.setViewControllers(viewControllers, animated: false)
if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController {
chatListController.resetForumStackIfOpen()
}
}
}
}
if self.isOn {
if case .legacyGroup = peer {
let context = component.context
let mode = self.mode
let signal: Signal<EnginePeer.Id?, NoError> = context.engine.peers.convertGroupToSupergroup(peerId: peer.id, additionalProcessing: { upgradedPeerId -> Signal<Never, NoError> in
return context.engine.peers.setChannelForumMode(id: upgradedPeerId, isForum: true, displayForumAsTabs: mode == .tabs)
})
|> map(Optional.init)
|> `catch` { [weak self] error -> Signal<PeerId?, NoError> in
guard let self, let controller = self.environment?.controller() else {
return .single(nil)
}
switch error {
case .tooManyChannels:
Queue.mainQueue().async {
let oldChannelsController = context.sharedContext.makeOldChannelsController(context: context, updatedPresentationData: nil, intent: .upgrade, completed: { result in
})
controller.push(oldChannelsController)
}
default:
break
}
return .single(nil)
}
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
guard let upgradedPeerId = upgradedPeerId else {
return .single(nil)
}
return .single(upgradedPeerId)
}
|> deliverOnMainQueue
let _ = signal.startStandalone(next: { [weak self] resultPeerId in
guard let self else {
return
}
if let resultPeerId {
self.peerIdPromise.set(resultPeerId)
let _ = component.context.engine.peers.setChannelForumMode(id: resultPeerId, isForum: true, displayForumAsTabs: self.mode == .tabs).startStandalone()
if let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
if self.mode == .list {
for i in 0 ..< viewControllers.count {
if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId {
let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: resultPeerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
viewControllers[i] = chatListController
}
}
navigationController.setViewControllers(viewControllers, animated: false)
} else {
for i in (0 ..< viewControllers.count).reversed() {
if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) {
viewControllers.remove(at: i)
} else if let peerInfoScreen = viewControllers[i] as? PeerInfoScreen, peerInfoScreen.peerId == component.peerId {
viewControllers.remove(at: i)
}
}
navigationController.setViewControllers(viewControllers, animated: false)
if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController {
chatListController.resetForumStackIfOpen()
}
}
}
} else {
self.isOn = false
self.state?.updated(transition: .easeInOut(duration: 0.2))
}
self.environment?.controller()?.dismiss()
})
return false
} else {
let _ = component.context.engine.peers.setChannelForumMode(id: component.peerId, isForum: true, displayForumAsTabs: self.mode == .tabs).startStandalone()
}
} else {
let _ = component.context.engine.peers.setChannelForumMode(id: component.peerId, isForum: false, displayForumAsTabs: false).startStandalone()
}
}
return true
}
func update(component: ForumSettingsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
if self.component == nil {
self.peerIdPromise.set(component.peerId)
self.peerDisposable = (self.peerIdPromise.get()
|> mapToSignal { peerId in
component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
}
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self else {
return
}
self.peer = peer
if case let .channel(channel) = peer {
self.isOn = channel.flags.contains(.isForum)
if self.isOn {
self.mode = channel.flags.contains(.displayForumAsTabs) ? .tabs : .list
self.initialOnMode = self.mode
} else {
self.initialOnMode = nil
}
}
self.state?.updated()
})
}
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
self.component = component
self.state = state
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? transition : transition.withAnimation(.curve(duration: 0.25, curve: .easeInOut))
if themeUpdated {
self.backgroundColor = environment.theme.list.blocksBackgroundColor
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let navigationTitleSize = self.navigationTitle.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.PeerInfo_Topics_Title, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
horizontalAlignment: .center
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
)
let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize)
if let navigationTitleView = self.navigationTitle.view {
if navigationTitleView.superview == nil {
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
navigationBar.view.addSubview(navigationTitleView)
}
}
transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame)
}
let bottomContentInset: CGFloat = 24.0
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let sectionSpacing: CGFloat = 32.0
var contentHeight: CGFloat = 0.0
contentHeight += environment.navigationHeight
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "Topics"),
loop: false
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: contentHeight + 8.0), size: iconSize)
if let iconView = self.icon.view as? LottieComponent.View {
if iconView.superview == nil {
self.scrollView.addSubview(iconView)
iconView.playOnce()
}
transition.setPosition(view: iconView, position: iconFrame.center)
iconView.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
}
contentHeight += 124.0
let subtitleString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.PeerInfo_Topics_EnableTopicsInfo, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor),
link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemAccentColor),
linkAttribute: { attributes in
return ("URL", "")
}), textAlignment: .center
))
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(subtitleString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.25,
highlightColor: environment.theme.list.itemAccentColor.withMultipliedAlpha(0.1),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
}
},
tapAction: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
let _ = component
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: contentHeight), size: subtitleSize)
if let subtitleView = self.subtitle.view {
if subtitleView.superview == nil {
self.scrollView.addSubview(subtitleView)
}
transition.setPosition(view: subtitleView, position: subtitleFrame.center)
subtitleView.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
}
contentHeight += subtitleSize.height
contentHeight += 27.0
var generalSectionItems: [AnyComponentWithIdentity<Empty>] = []
generalSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
style: .glass,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.PeerInfo_Topics_EnableTopics,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 1
))),
], alignment: .left, spacing: 2.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isOn, action: { [weak self] value in
guard let self else {
return
}
self.toggleTopicsEnabled(value)
})),
action: nil
))))
let generalSectionSize = self.generalSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
style: .glass,
header: nil,
footer: nil,
items: generalSectionItems
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let generalSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: generalSectionSize)
if let generalSectionView = self.generalSection.view {
if generalSectionView.superview == nil {
self.scrollView.addSubview(generalSectionView)
}
transition.setFrame(view: generalSectionView, frame: generalSectionFrame)
}
contentHeight += generalSectionSize.height
contentHeight += sectionSpacing
var otherSectionsHeight: CGFloat = 0.0
let modeSectionSize = self.modeSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.PeerInfo_Topics_DisplayAs,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
footer: AnyComponent(MultilineTextComponent(
text: .markdown(
text: environment.strings.PeerInfo_Topics_DisplayAsInfo,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor),
link: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.itemAccentColor),
linkAttribute: { _ in
return nil
}
)
),
maximumNumberOfLines: 0
)),
items: [
AnyComponentWithIdentity(
id: 0,
component: AnyComponent(
ForumModeComponent(
theme: environment.theme,
strings: environment.strings,
mode: self.isOn ? self.mode : nil,
modeUpdated: { [weak self] mode in
guard let self else {
return
}
self.mode = mode
self.state?.updated(transition: .spring(duration: 0.4))
}
)
)
)
]
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let modeSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + otherSectionsHeight), size: modeSectionSize)
if let modeSectionView = self.modeSection.view {
if modeSectionView.superview == nil {
modeSectionView.layer.allowsGroupOpacity = true
self.scrollView.addSubview(modeSectionView)
}
transition.setFrame(view: modeSectionView, frame: modeSectionFrame)
alphaTransition.setAlpha(view: modeSectionView, alpha: self.isOn ? 1.0 : 0.0)
}
otherSectionsHeight += modeSectionSize.height
otherSectionsHeight += sectionSpacing
if self.isOn {
contentHeight += otherSectionsHeight
}
contentHeight += bottomContentInset
contentHeight += environment.safeInsets.bottom
let previousBounds = self.scrollView.bounds
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
self.ignoreScrolling = true
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
}
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize
}
let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
if self.scrollView.verticalScrollIndicatorInsets != scrollInsets {
self.scrollView.verticalScrollIndicatorInsets = scrollInsets
}
self.ignoreScrolling = false
if !previousBounds.isEmpty, !transition.animation.isImmediate {
let bounds = self.scrollView.bounds
if bounds.maxY != previousBounds.maxY {
let offsetY = previousBounds.maxY - bounds.maxY
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
}
}
self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0))
self.updateScrolling(transition: transition)
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class ForumSettingsScreen: ViewControllerComponentContainer {
private let context: AccountContext
public init(context: AccountContext, peerId: EnginePeer.Id) {
self.context = context
super.init(context: context, component: ForumSettingsScreenComponent(
context: context,
peerId: peerId
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.title = ""
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.scrollToTop = { [weak self] in
guard let self, let componentView = self.node.hostView.componentView as? ForumSettingsScreenComponent.View else {
return
}
componentView.scrollToTop()
}
self.attemptNavigation = { [weak self] complete in
guard let self, let componentView = self.node.hostView.componentView as? ForumSettingsScreenComponent.View else {
return true
}
return componentView.attemptNavigation(complete: complete)
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
@objc private func cancelPressed() {
self.dismiss()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
}
}