mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add recent actions settings sheet
This commit is contained in:
parent
4286199f6a
commit
7f7d0f1c74
@ -0,0 +1,990 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import ButtonComponent
|
||||
import PresentationDataUtils
|
||||
import Markdown
|
||||
import UndoUI
|
||||
import TelegramStringFormatting
|
||||
import ListSectionComponent
|
||||
import ListActionItemComponent
|
||||
import PlainButtonComponent
|
||||
|
||||
private enum ActionTypeSection: Hashable, CaseIterable {
|
||||
case members
|
||||
case settings
|
||||
case messages
|
||||
}
|
||||
|
||||
private enum MembersActionType: Hashable, CaseIterable {
|
||||
case newAdminRights
|
||||
case newExceptions
|
||||
case newMembers
|
||||
case leftMembers
|
||||
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case .newAdminRights:
|
||||
return "New Admin Rights"
|
||||
case .newExceptions:
|
||||
return "New Exceptions"
|
||||
case .newMembers:
|
||||
return "New Members"
|
||||
case .leftMembers:
|
||||
return "Members left the Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SettingsActionType: Hashable, CaseIterable {
|
||||
case groupInfo
|
||||
case inviteLinks
|
||||
case videoChats
|
||||
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case .groupInfo:
|
||||
return "Group Info"
|
||||
case .inviteLinks:
|
||||
return "Invite Links"
|
||||
case .videoChats:
|
||||
return "Video Chats"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum MessagesActionType: Hashable, CaseIterable {
|
||||
case deletedMessages
|
||||
case editedMessages
|
||||
case pinnedMessages
|
||||
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case .deletedMessages:
|
||||
return "Deleted Messages"
|
||||
case .editedMessages:
|
||||
return "Edited Messages"
|
||||
case .pinnedMessages:
|
||||
return "Pinned Messages"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ActionType: Hashable {
|
||||
case members(MembersActionType)
|
||||
case settings(SettingsActionType)
|
||||
case messages(MessagesActionType)
|
||||
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case let .members(value):
|
||||
return value.title(strings: strings)
|
||||
case let .settings(value):
|
||||
return value.title(strings: strings)
|
||||
case let .messages(value):
|
||||
return value.title(strings: strings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class RecentActionsSettingsSheetComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let adminPeers: [EnginePeer]
|
||||
let completion: (RecentActionsSettingsSheet.Result) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
adminPeers: [EnginePeer],
|
||||
completion: @escaping (RecentActionsSettingsSheet.Result) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.adminPeers = adminPeers
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
static func ==(lhs: RecentActionsSettingsSheetComponent, rhs: RecentActionsSettingsSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.adminPeers != rhs.adminPeers {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private struct ItemLayout: Equatable {
|
||||
var containerSize: CGSize
|
||||
var containerInset: CGFloat
|
||||
var bottomInset: CGFloat
|
||||
var topInset: CGFloat
|
||||
|
||||
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) {
|
||||
self.containerSize = containerSize
|
||||
self.containerInset = containerInset
|
||||
self.bottomInset = bottomInset
|
||||
self.topInset = topInset
|
||||
}
|
||||
}
|
||||
|
||||
private final class ScrollView: UIScrollView {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let dimView: UIView
|
||||
private let backgroundLayer: SimpleLayer
|
||||
private let navigationBarContainer: SparseContainerView
|
||||
private let navigationBackgroundView: BlurredBackgroundView
|
||||
private let navigationBarSeparator: SimpleLayer
|
||||
private let scrollView: ScrollView
|
||||
private let scrollContentClippingView: SparseContainerView
|
||||
private let scrollContentView: UIView
|
||||
|
||||
private let leftButton = ComponentView<Empty>()
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
|
||||
private let optionsSection = ComponentView<Empty>()
|
||||
private let adminsSection = ComponentView<Empty>()
|
||||
|
||||
private let bottomOverscrollLimit: CGFloat
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var component: RecentActionsSettingsSheetComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
private var topOffsetDistance: CGFloat?
|
||||
|
||||
private var expandedSections = Set<ActionTypeSection>()
|
||||
private var selectedMembersActions = Set<MembersActionType>()
|
||||
private var selectedSettingsActions = Set<SettingsActionType>()
|
||||
private var selectedMessagesActions = Set<MessagesActionType>()
|
||||
private var selectedAdmins = Set<EnginePeer.Id>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.bottomOverscrollLimit = 200.0
|
||||
|
||||
self.dimView = UIView()
|
||||
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.backgroundLayer.cornerRadius = 10.0
|
||||
|
||||
self.navigationBarContainer = SparseContainerView()
|
||||
|
||||
self.navigationBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
||||
self.navigationBarSeparator = SimpleLayer()
|
||||
|
||||
self.scrollView = ScrollView()
|
||||
|
||||
self.scrollContentClippingView = SparseContainerView()
|
||||
self.scrollContentClippingView.clipsToBounds = true
|
||||
|
||||
self.scrollContentView = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dimView)
|
||||
self.layer.addSublayer(self.backgroundLayer)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.alwaysBounceVertical = true
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = true
|
||||
|
||||
self.addSubview(self.scrollContentClippingView)
|
||||
self.scrollContentClippingView.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.scrollContentView)
|
||||
|
||||
self.addSubview(self.navigationBarContainer)
|
||||
|
||||
self.navigationBarContainer.addSubview(self.navigationBackgroundView)
|
||||
self.navigationBarContainer.layer.addSublayer(self.navigationBarSeparator)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if !self.backgroundLayer.frame.contains(point) {
|
||||
return self.dimView
|
||||
}
|
||||
|
||||
if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
let result = super.hitTest(point, with: event)
|
||||
return result
|
||||
}
|
||||
|
||||
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
guard let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateResult() -> RecentActionsSettingsSheet.Result {
|
||||
return RecentActionsSettingsSheet.Result(
|
||||
)
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: Transition) {
|
||||
guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||
|
||||
let navigationAlpha: CGFloat = 1.0 - max(0.0, min(1.0, (topOffset + 20.0) / 20.0))
|
||||
transition.setAlpha(view: self.navigationBackgroundView, alpha: navigationAlpha)
|
||||
transition.setAlpha(layer: self.navigationBarSeparator, alpha: navigationAlpha)
|
||||
|
||||
topOffset = max(0.0, topOffset)
|
||||
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
||||
|
||||
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
||||
|
||||
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
||||
self.topOffsetDistance = topOffsetDistance
|
||||
var topOffsetFraction = topOffset / topOffsetDistance
|
||||
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
||||
|
||||
let transitionFactor: CGFloat = 1.0 - topOffsetFraction
|
||||
if self.isUpdating {
|
||||
DispatchQueue.main.async { [weak controller] in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
|
||||
}
|
||||
} else {
|
||||
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
|
||||
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
if let environment = self.environment, let controller = environment.controller() {
|
||||
controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: RecentActionsSettingsSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let themeUpdated = self.environment?.theme !== environment.theme
|
||||
|
||||
let resetScrolling = self.scrollView.bounds.width != availableSize.width
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
if self.component == nil {
|
||||
self.selectedMembersActions = Set(MembersActionType.allCases)
|
||||
self.selectedSettingsActions = Set(SettingsActionType.allCases)
|
||||
self.selectedMessagesActions = Set(MessagesActionType.allCases)
|
||||
self.selectedAdmins = Set(component.adminPeers.map(\.id))
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
self.environment = environment
|
||||
|
||||
if themeUpdated {
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor
|
||||
|
||||
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||
self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
contentHeight += 54.0
|
||||
contentHeight += 16.0
|
||||
|
||||
let leftButtonSize = self.leftButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)),
|
||||
action: { [weak self] in
|
||||
guard let self, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
).minSize(CGSize(width: 44.0, height: 56.0))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 120.0, height: 100.0)
|
||||
)
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: leftButtonSize)
|
||||
if let leftButtonView = self.leftButton.view {
|
||||
if leftButtonView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(leftButtonView)
|
||||
}
|
||||
transition.setFrame(view: leftButtonView, frame: leftButtonFrame)
|
||||
}
|
||||
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
|
||||
let clippingY: CGFloat
|
||||
|
||||
let actionTypeSectionItem: (ActionTypeSection) -> AnyComponentWithIdentity<Empty> = { actionTypeSection in
|
||||
let sectionId: AnyHashable
|
||||
let totalCount: Int
|
||||
let selectedCount: Int
|
||||
let isExpanded: Bool
|
||||
let title: String
|
||||
|
||||
sectionId = actionTypeSection
|
||||
isExpanded = self.expandedSections.contains(actionTypeSection)
|
||||
|
||||
switch actionTypeSection {
|
||||
case .members:
|
||||
totalCount = MembersActionType.allCases.count
|
||||
selectedCount = self.selectedMembersActions.count
|
||||
title = "Members and Admins"
|
||||
case .settings:
|
||||
totalCount = SettingsActionType.allCases.count
|
||||
selectedCount = self.selectedSettingsActions.count
|
||||
title = "Group Settings"
|
||||
case .messages:
|
||||
totalCount = MessagesActionType.allCases.count
|
||||
selectedCount = self.selectedMessagesActions.count
|
||||
title = "Messages"
|
||||
}
|
||||
|
||||
let itemTitle: AnyComponent<Empty> = AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: title,
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent(
|
||||
theme: environment.theme,
|
||||
title: "\(selectedCount)/\(totalCount)",
|
||||
isExpanded: isExpanded
|
||||
)))
|
||||
], spacing: 7.0))
|
||||
|
||||
let toggleAction: () -> Void = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
switch actionTypeSection {
|
||||
case .members:
|
||||
if self.selectedMembersActions.isEmpty {
|
||||
self.selectedMembersActions = Set(MembersActionType.allCases)
|
||||
} else {
|
||||
self.selectedMembersActions.removeAll()
|
||||
}
|
||||
case .settings:
|
||||
if self.selectedSettingsActions.isEmpty {
|
||||
self.selectedSettingsActions = Set(SettingsActionType.allCases)
|
||||
} else {
|
||||
self.selectedSettingsActions.removeAll()
|
||||
}
|
||||
case .messages:
|
||||
if self.selectedMessagesActions.isEmpty {
|
||||
self.selectedMessagesActions = Set(MessagesActionType.allCases)
|
||||
} else {
|
||||
self.selectedMessagesActions.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
self.state?.updated(transition: .spring(duration: 0.35))
|
||||
}
|
||||
|
||||
return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
title: itemTitle,
|
||||
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
|
||||
isSelected: selectedCount != 0,
|
||||
toggle: {
|
||||
toggleAction()
|
||||
}
|
||||
)),
|
||||
icon: .none,
|
||||
accessory: nil,
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.expandedSections.contains(actionTypeSection) {
|
||||
self.expandedSections.remove(actionTypeSection)
|
||||
} else {
|
||||
self.expandedSections.insert(actionTypeSection)
|
||||
}
|
||||
|
||||
self.state?.updated(transition: .spring(duration: 0.35))
|
||||
},
|
||||
highlighting: .disabled
|
||||
)))
|
||||
}
|
||||
|
||||
let expandedActionTypeSectionItem: (ActionTypeSection) -> AnyComponentWithIdentity<Empty> = { actionTypeSection in
|
||||
let sectionId: AnyHashable
|
||||
let selectedActionTypes: Set<ActionType>
|
||||
let actionTypes: [ActionType]
|
||||
switch actionTypeSection {
|
||||
case .members:
|
||||
sectionId = "members-sub"
|
||||
actionTypes = MembersActionType.allCases.map(ActionType.members)
|
||||
selectedActionTypes = Set(self.selectedMembersActions.map(ActionType.members))
|
||||
case .settings:
|
||||
sectionId = "settings-sub"
|
||||
actionTypes = SettingsActionType.allCases.map(ActionType.settings)
|
||||
selectedActionTypes = Set(self.selectedSettingsActions.map(ActionType.settings))
|
||||
case .messages:
|
||||
sectionId = "messages-sub"
|
||||
actionTypes = MessagesActionType.allCases.map(ActionType.messages)
|
||||
selectedActionTypes = Set(self.selectedMessagesActions.map(ActionType.messages))
|
||||
}
|
||||
|
||||
var subItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
for actionType in actionTypes {
|
||||
let actionItemTitle: String = actionType.title(strings: environment.strings)
|
||||
|
||||
let subItemToggleAction: () -> Void = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
switch actionType {
|
||||
case let .members(value):
|
||||
if self.selectedMembersActions.contains(value) {
|
||||
self.selectedMembersActions.remove(value)
|
||||
} else {
|
||||
self.selectedMembersActions.insert(value)
|
||||
}
|
||||
case let .settings(value):
|
||||
if self.selectedSettingsActions.contains(value) {
|
||||
self.selectedSettingsActions.remove(value)
|
||||
} else {
|
||||
self.selectedSettingsActions.insert(value)
|
||||
}
|
||||
case let .messages(value):
|
||||
if self.selectedMessagesActions.contains(value) {
|
||||
self.selectedMessagesActions.remove(value)
|
||||
} else {
|
||||
self.selectedMessagesActions.insert(value)
|
||||
}
|
||||
}
|
||||
|
||||
self.state?.updated(transition: .spring(duration: 0.35))
|
||||
}
|
||||
|
||||
subItems.append(AnyComponentWithIdentity(id: actionType, component: AnyComponent(ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: actionItemTitle,
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
|
||||
isSelected: selectedActionTypes.contains(actionType),
|
||||
toggle: {
|
||||
subItemToggleAction()
|
||||
}
|
||||
)),
|
||||
icon: .none,
|
||||
accessory: .none,
|
||||
action: { _ in
|
||||
subItemToggleAction()
|
||||
},
|
||||
highlighting: .disabled
|
||||
))))
|
||||
}
|
||||
|
||||
return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListSubSectionComponent(
|
||||
theme: environment.theme,
|
||||
leftInset: 62.0,
|
||||
items: subItems
|
||||
)))
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let titleString: String = "Recent Actions"
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((54.0 - titleSize.height) * 0.5)), size: titleSize)
|
||||
if let titleView = title.view {
|
||||
if titleView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
|
||||
let navigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: 54.0))
|
||||
transition.setFrame(view: self.navigationBackgroundView, frame: navigationBackgroundFrame)
|
||||
self.navigationBackgroundView.update(size: navigationBackgroundFrame.size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(layer: self.navigationBarSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
||||
var optionsSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
for actionTypeSection in ActionTypeSection.allCases {
|
||||
optionsSectionItems.append(actionTypeSectionItem(actionTypeSection))
|
||||
if self.expandedSections.contains(actionTypeSection) {
|
||||
optionsSectionItems.append(expandedActionTypeSectionItem(actionTypeSection))
|
||||
}
|
||||
}
|
||||
|
||||
let optionsSectionTransition = transition
|
||||
let optionsSectionSize = self.optionsSection.update(
|
||||
transition: optionsSectionTransition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "FILTER ACTIONS BY TYPE",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
footer: nil,
|
||||
items: optionsSectionItems
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0)
|
||||
)
|
||||
let optionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: optionsSectionSize)
|
||||
if let optionsSectionView = self.optionsSection.view {
|
||||
if optionsSectionView.superview == nil {
|
||||
self.scrollContentView.addSubview(optionsSectionView)
|
||||
self.optionsSection.parentState = state
|
||||
}
|
||||
transition.setFrame(view: optionsSectionView, frame: optionsSectionFrame)
|
||||
}
|
||||
contentHeight += optionsSectionSize.height
|
||||
contentHeight += 24.0
|
||||
|
||||
var peerItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
for peer in component.adminPeers {
|
||||
peerItems.append(AnyComponentWithIdentity(id: peer.id, component: AnyComponent(AdminUserActionsPeerComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
sideInset: 0.0,
|
||||
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||
peer: peer,
|
||||
selectionState: .editing(isSelected: self.selectedAdmins.contains(peer.id)),
|
||||
action: { [weak self] peer in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.selectedAdmins.contains(peer.id) {
|
||||
self.selectedAdmins.remove(peer.id)
|
||||
} else {
|
||||
self.selectedAdmins.insert(peer.id)
|
||||
}
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .easeInOut)))
|
||||
}
|
||||
))))
|
||||
}
|
||||
|
||||
var adminsSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
let allAdminsToggleAction: () -> Void = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.selectedAdmins.isEmpty {
|
||||
self.selectedAdmins = Set(component.adminPeers.map(\.id))
|
||||
} else {
|
||||
self.selectedAdmins.removeAll()
|
||||
}
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .easeInOut)))
|
||||
}
|
||||
adminsSectionItems.append(AnyComponentWithIdentity(id: adminsSectionItems.count, component: AnyComponent(ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Show Actions by All Admins",
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
|
||||
isSelected: !self.selectedAdmins.isEmpty,
|
||||
toggle: {
|
||||
allAdminsToggleAction()
|
||||
}
|
||||
)),
|
||||
icon: .none,
|
||||
accessory: .none,
|
||||
action: { _ in
|
||||
allAdminsToggleAction()
|
||||
},
|
||||
highlighting: .disabled
|
||||
))))
|
||||
adminsSectionItems.append(AnyComponentWithIdentity(id: adminsSectionItems.count, component: AnyComponent(ListSubSectionComponent(
|
||||
theme: environment.theme,
|
||||
leftInset: 62.0,
|
||||
items: peerItems
|
||||
))))
|
||||
let adminsSectionSize = self.adminsSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "FILTER ACTIONS BY ADMINS",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
footer: nil,
|
||||
items: adminsSectionItems
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0)
|
||||
)
|
||||
let adminsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: adminsSectionSize)
|
||||
if let adminsSectionView = self.adminsSection.view {
|
||||
if adminsSectionView.superview == nil {
|
||||
self.scrollContentView.addSubview(adminsSectionView)
|
||||
self.adminsSection.parentState = state
|
||||
}
|
||||
transition.setFrame(view: adminsSectionView, frame: adminsSectionFrame)
|
||||
}
|
||||
contentHeight += adminsSectionSize.height
|
||||
|
||||
contentHeight += 30.0
|
||||
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: environment.theme.list.itemCheckColors.fillColor,
|
||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(ButtonTextContentComponent(
|
||||
text: "Apply Filter",
|
||||
badge: 0,
|
||||
textColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: environment.theme.list.itemCheckColors.fillColor
|
||||
))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.dismiss()
|
||||
component.completion(self.calculateResult())
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
||||
)
|
||||
let bottomPanelHeight = 8.0 + environment.safeInsets.bottom + actionButtonSize.height
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
|
||||
if let actionButtonView = actionButton.view {
|
||||
if actionButtonView.superview == nil {
|
||||
self.addSubview(actionButtonView)
|
||||
}
|
||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
||||
}
|
||||
|
||||
contentHeight += bottomPanelHeight
|
||||
|
||||
clippingY = actionButtonFrame.minY - 24.0
|
||||
|
||||
let topInset: CGFloat = max(0.0, availableSize.height - containerInset - contentHeight)
|
||||
|
||||
let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset)
|
||||
|
||||
self.scrollContentClippingView.layer.cornerRadius = 10.0
|
||||
|
||||
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset)
|
||||
|
||||
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
||||
|
||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: clippingY - containerInset))
|
||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
||||
|
||||
self.ignoreScrolling = true
|
||||
let previousBounds = self.scrollView.bounds
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||
let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight)
|
||||
if contentSize != self.scrollView.contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
if resetScrolling {
|
||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize)
|
||||
} else {
|
||||
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.ignoreScrolling = false
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public class RecentActionsSettingsSheet: ViewControllerComponentContainer {
|
||||
public final class Result {
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
public init(context: AccountContext, adminPeers: [EnginePeer], completion: @escaping (Result) -> Void) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: RecentActionsSettingsSheetComponent(context: context, adminPeers: adminPeers, completion: completion), navigationBarAppearance: .none)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.navigationPresentation = .flatModal
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? RecentActionsSettingsSheetComponent.View {
|
||||
componentView.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? RecentActionsSettingsSheetComponent.View {
|
||||
componentView.animateOut(completion: { [weak self] in
|
||||
completion?()
|
||||
self?.dismiss(animated: false)
|
||||
})
|
||||
} else {
|
||||
self.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MediaSectionExpandIndicatorComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let isExpanded: Bool
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
isExpanded: Bool
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.isExpanded = isExpanded
|
||||
}
|
||||
|
||||
static func ==(lhs: MediaSectionExpandIndicatorComponent, rhs: MediaSectionExpandIndicatorComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let arrowView: UIImageView
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.arrowView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.arrowView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: MediaSectionExpandIndicatorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let titleArrowSpacing: CGFloat = 1.0
|
||||
|
||||
if self.arrowView.image == nil {
|
||||
self.arrowView.image = PresentationResourcesItemList.expandDownArrowImage(component.theme)
|
||||
}
|
||||
self.arrowView.tintColor = component.theme.list.itemPrimaryTextColor
|
||||
let arrowSize = self.arrowView.image?.size ?? CGSize(width: 1.0, height: 1.0)
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(13.0), textColor: component.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
|
||||
let size = CGSize(width: titleSize.width + titleArrowSpacing + arrowSize.width, height: titleSize.height)
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - titleSize.height) * 0.5)), size: titleSize)
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + titleArrowSpacing, y: floor((size.height - arrowSize.height) * 0.5) + 2.0), size: arrowSize)
|
||||
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
self.arrowView.center = arrowFrame.center
|
||||
self.arrowView.bounds = CGRect(origin: CGPoint(), size: arrowFrame.size)
|
||||
transition.setTransform(view: self.arrowView, transform: CATransform3DTranslate(CATransform3DMakeRotation(component.isExpanded ? CGFloat.pi : 0.0, 0.0, 0.0, 1.0), 0.0, component.isExpanded ? 1.0 : -1.0, 0.0))
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -131,6 +131,11 @@ extension ChatControllerImpl {
|
||||
return
|
||||
}
|
||||
|
||||
/*if "".isEmpty {
|
||||
self.push(RecentActionsSettingsSheet(context: self.context, adminPeers: authors.map(EnginePeer.init), completion: { _ in }))
|
||||
return
|
||||
}*/
|
||||
|
||||
self.navigationActionDisposable.set((combineLatest(authors.map { author in
|
||||
self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id)
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user