mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Folder improvements
This commit is contained in:
parent
f122288113
commit
dd215afc38
@ -401,7 +401,7 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|
||||
return account.postbox.transaction { transaction -> ([Api.InputPeer], Int) in
|
||||
var newChatCount = 0
|
||||
for peerId in peerIds {
|
||||
if transaction.getPeerChatListIndex(peerId) != nil {
|
||||
if transaction.getPeerChatListIndex(peerId) == nil {
|
||||
newChatCount += 1
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,7 @@ public final class AnimatedTextComponent: Component {
|
||||
if let characterComponentView = characterView.view {
|
||||
var animateIn = false
|
||||
if characterComponentView.superview == nil {
|
||||
characterComponentView.layer.rasterizationScale = UIScreenScale
|
||||
self.addSubview(characterComponentView)
|
||||
animateIn = true
|
||||
}
|
||||
|
20
submodules/TelegramUI/Components/ButtonComponent/BUILD
Normal file
20
submodules/TelegramUI/Components/ButtonComponent/BUILD
Normal file
@ -0,0 +1,20 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ButtonComponent",
|
||||
module_name = "ButtonComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,433 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import AnimatedTextComponent
|
||||
|
||||
public final class ButtonBadgeComponent: Component {
|
||||
let fillColor: UIColor
|
||||
let content: AnyComponent<Empty>
|
||||
|
||||
public init(
|
||||
fillColor: UIColor,
|
||||
content: AnyComponent<Empty>
|
||||
) {
|
||||
self.fillColor = fillColor
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public static func ==(lhs: ButtonBadgeComponent, rhs: ButtonBadgeComponent) -> Bool {
|
||||
if lhs.fillColor != rhs.fillColor {
|
||||
return false
|
||||
}
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let backgroundView: UIImageView
|
||||
private let content = ComponentView<Empty>()
|
||||
|
||||
private var component: ButtonBadgeComponent?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.backgroundView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(component: ButtonBadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let height: CGFloat = 20.0
|
||||
let contentInset: CGFloat = 10.0
|
||||
|
||||
let themeUpdated = self.component?.fillColor != component.fillColor
|
||||
self.component = component
|
||||
|
||||
let contentSize = self.content.update(
|
||||
transition: transition,
|
||||
component: component.content,
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let backgroundWidth: CGFloat = max(height, contentSize.width + contentInset)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: backgroundWidth, height: height))
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||
|
||||
if let contentView = self.content.view {
|
||||
if contentView.superview == nil {
|
||||
self.addSubview(contentView)
|
||||
}
|
||||
transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floor((backgroundFrame.width - contentSize.width) * 0.5), y: floor((backgroundFrame.height - contentSize.height) * 0.5)), size: contentSize))
|
||||
}
|
||||
|
||||
if themeUpdated || backgroundFrame.height != self.backgroundView.image?.size.height {
|
||||
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: backgroundFrame.height, color: component.fillColor)
|
||||
}
|
||||
|
||||
return backgroundFrame.size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ButtonTextContentComponent: Component {
|
||||
public let text: String
|
||||
public let badge: Int
|
||||
public let textColor: UIColor
|
||||
public let badgeBackground: UIColor
|
||||
public let badgeForeground: UIColor
|
||||
|
||||
public init(
|
||||
text: String,
|
||||
badge: Int,
|
||||
textColor: UIColor,
|
||||
badgeBackground: UIColor,
|
||||
badgeForeground: UIColor
|
||||
) {
|
||||
self.text = text
|
||||
self.badge = badge
|
||||
self.textColor = textColor
|
||||
self.badgeBackground = badgeBackground
|
||||
self.badgeForeground = badgeForeground
|
||||
}
|
||||
|
||||
public static func ==(lhs: ButtonTextContentComponent, rhs: ButtonTextContentComponent) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.badge != rhs.badge {
|
||||
return false
|
||||
}
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
if lhs.badgeBackground != rhs.badgeBackground {
|
||||
return false
|
||||
}
|
||||
if lhs.badgeForeground != rhs.badgeForeground {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var component: ButtonTextContentComponent?
|
||||
private weak var componentState: EmptyComponentState?
|
||||
|
||||
private let content = ComponentView<Empty>()
|
||||
private var badge: ComponentView<Empty>?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func update(component: ButtonTextContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let previousBadge = self.component?.badge
|
||||
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
let badgeSpacing: CGFloat = 6.0
|
||||
|
||||
let contentSize = self.content.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(
|
||||
text: component.text,
|
||||
font: Font.semibold(17.0),
|
||||
color: component.textColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
var badgeSize: CGSize?
|
||||
if component.badge > 0 {
|
||||
var badgeTransition = transition
|
||||
let badge: ComponentView<Empty>
|
||||
if let current = self.badge {
|
||||
badge = current
|
||||
} else {
|
||||
badgeTransition = .immediate
|
||||
badge = ComponentView()
|
||||
self.badge = badge
|
||||
}
|
||||
badgeSize = badge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(ButtonBadgeComponent(
|
||||
fillColor: component.badgeBackground,
|
||||
content: AnyComponent(AnimatedTextComponent(
|
||||
font: Font.semibold(15.0),
|
||||
color: component.badgeForeground,
|
||||
items: [
|
||||
AnimatedTextComponent.Item(id: AnyHashable(0), content: .number(component.badge))
|
||||
]
|
||||
))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
}
|
||||
|
||||
var size = contentSize
|
||||
if let badgeSize {
|
||||
//size.width += badgeSpacing
|
||||
//size.width += badgeSize.width
|
||||
size.height = max(size.height, badgeSize.height)
|
||||
}
|
||||
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) * 0.5), y: floor((size.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
|
||||
if let contentView = self.content.view {
|
||||
if contentView.superview == nil {
|
||||
self.addSubview(contentView)
|
||||
}
|
||||
transition.setFrame(view: contentView, frame: contentFrame)
|
||||
}
|
||||
|
||||
if let badgeSize, let badge = self.badge {
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: contentFrame.maxX + badgeSpacing, y: floor((size.height - badgeSize.height) * 0.5) + 1.0), size: badgeSize)
|
||||
|
||||
if let badgeView = badge.view {
|
||||
var animateIn = false
|
||||
if badgeView.superview == nil {
|
||||
animateIn = true
|
||||
self.addSubview(badgeView)
|
||||
}
|
||||
|
||||
if animateIn {
|
||||
badgeView.frame = badgeFrame
|
||||
} else {
|
||||
transition.setFrame(view: badgeView, frame: badgeFrame)
|
||||
|
||||
if !transition.animation.isImmediate, let previousBadge, previousBadge != component.badge {
|
||||
let middleScale: CGFloat = previousBadge < component.badge ? 1.1 : 0.9
|
||||
let values: [NSNumber] = [1.0, middleScale as NSNumber, 1.0]
|
||||
badgeView.layer.animateKeyframes(values: values, duration: 0.25, keyPath: "transform.scale")
|
||||
}
|
||||
}
|
||||
|
||||
if animateIn, !transition.animation.isImmediate {
|
||||
badgeView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
badgeView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let badge = self.badge {
|
||||
self.badge = nil
|
||||
if let badgeView = badge.view {
|
||||
if !transition.animation.isImmediate {
|
||||
badgeView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak badgeView] _ in
|
||||
badgeView?.removeFromSuperview()
|
||||
})
|
||||
badgeView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.25, removeOnCompletion: false)
|
||||
} else {
|
||||
badgeView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ButtonComponent: Component {
|
||||
public struct Background: Equatable {
|
||||
public var color: UIColor
|
||||
public var pressedColor: UIColor
|
||||
public var cornerRadius: CGFloat
|
||||
|
||||
public init(
|
||||
color: UIColor,
|
||||
pressedColor: UIColor,
|
||||
cornerRadius: CGFloat = 10.0
|
||||
) {
|
||||
self.color = color
|
||||
self.pressedColor = pressedColor
|
||||
self.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
public let background: Background
|
||||
public let content: AnyComponentWithIdentity<Empty>
|
||||
public let isEnabled: Bool
|
||||
public let action: () -> Void
|
||||
|
||||
public init(
|
||||
background: Background,
|
||||
content: AnyComponentWithIdentity<Empty>,
|
||||
isEnabled: Bool,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.background = background
|
||||
self.content = content
|
||||
self.isEnabled = isEnabled
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: ButtonComponent, rhs: ButtonComponent) -> Bool {
|
||||
if lhs.background != rhs.background {
|
||||
return false
|
||||
}
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
if lhs.isEnabled != rhs.isEnabled {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class ContentItem {
|
||||
let id: AnyHashable
|
||||
let view = ComponentView<Empty>()
|
||||
|
||||
init(id: AnyHashable) {
|
||||
self.id = id
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton {
|
||||
private var component: ButtonComponent?
|
||||
private weak var componentState: EmptyComponentState?
|
||||
|
||||
private var contentItem: ContentItem?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let self, let component = self.component, component.isEnabled {
|
||||
if highlighted {
|
||||
self.layer.removeAnimation(forKey: "opacity")
|
||||
self.alpha = 0.7
|
||||
} else {
|
||||
self.alpha = 1.0
|
||||
self.layer.animateAlpha(from: 7, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func update(component: ButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
self.isEnabled = component.isEnabled
|
||||
|
||||
transition.setBackgroundColor(view: self, color: component.background.color)
|
||||
transition.setCornerRadius(layer: self.layer, cornerRadius: component.background.cornerRadius)
|
||||
|
||||
var previousContentItem: ContentItem?
|
||||
let contentItem: ContentItem
|
||||
var contentItemTransition = transition
|
||||
if let current = self.contentItem, current.id == component.content.id {
|
||||
contentItem = current
|
||||
} else {
|
||||
contentItemTransition = .immediate
|
||||
previousContentItem = self.contentItem
|
||||
contentItem = ContentItem(id: component.content.id)
|
||||
self.contentItem = contentItem
|
||||
}
|
||||
|
||||
let contentSize = contentItem.view.update(
|
||||
transition: contentItemTransition,
|
||||
component: component.content.component,
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let contentView = contentItem.view.view {
|
||||
var animateIn = false
|
||||
var contentTransition = transition
|
||||
if contentView.superview == nil {
|
||||
contentTransition = .immediate
|
||||
animateIn = true
|
||||
contentView.isUserInteractionEnabled = false
|
||||
self.addSubview(contentView)
|
||||
}
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - contentSize.width) * 0.5), y: floor((availableSize.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
|
||||
contentTransition.setFrame(view: contentView, frame: contentFrame)
|
||||
contentTransition.setAlpha(view: contentView, alpha: component.isEnabled ? 1.0 : 0.7)
|
||||
|
||||
if animateIn && previousContentItem != nil && !transition.animation.isImmediate {
|
||||
contentView.layer.animateScale(from: 0.4, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
contentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
contentView.layer.animatePosition(from: CGPoint(x: 0.0, y: -availableSize.height * 0.15), to: CGPoint(), duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
if let previousContentItem, let previousContentView = previousContentItem.view.view {
|
||||
if !transition.animation.isImmediate {
|
||||
previousContentView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
previousContentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousContentView] _ in
|
||||
previousContentView?.removeFromSuperview()
|
||||
})
|
||||
previousContentView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: availableSize.height * 0.35), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
} else {
|
||||
previousContentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ swift_library(
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/Markdown",
|
||||
|
@ -16,6 +16,7 @@ import PresentationDataUtils
|
||||
import Markdown
|
||||
import UndoUI
|
||||
import PremiumUI
|
||||
import ButtonComponent
|
||||
|
||||
private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -733,20 +734,22 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(SolidRoundedButtonComponent(
|
||||
title: actionButtonTitle,
|
||||
badge: (self.selectedItems.isEmpty || allChatsAdded) ? nil : "\(self.selectedItems.count)",
|
||||
theme: SolidRoundedButtonComponent.Theme(theme: environment.theme),
|
||||
font: .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 11.0,
|
||||
gloss: false,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: environment.theme.list.itemCheckColors.fillColor,
|
||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: actionButtonTitle,
|
||||
component: AnyComponent(ButtonTextContentComponent(
|
||||
text: actionButtonTitle,
|
||||
badge: self.selectedItems.count,
|
||||
textColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: environment.theme.list.itemCheckColors.fillColor
|
||||
))
|
||||
),
|
||||
isEnabled: !self.selectedItems.isEmpty || component.linkContents?.localFilterId != nil,
|
||||
animationName: nil,
|
||||
iconPosition: .right,
|
||||
iconSpacing: 4.0,
|
||||
isLoading: self.inProgress,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||
return
|
||||
@ -865,6 +868,29 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
||||
)
|
||||
/*let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(SolidRoundedButtonComponent(
|
||||
title: actionButtonTitle,
|
||||
badge: (self.selectedItems.isEmpty || allChatsAdded) ? nil : "\(self.selectedItems.count)",
|
||||
theme: SolidRoundedButtonComponent.Theme(theme: environment.theme),
|
||||
font: .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 11.0,
|
||||
gloss: false,
|
||||
isEnabled: !self.selectedItems.isEmpty || component.linkContents?.localFilterId != nil,
|
||||
animationName: nil,
|
||||
iconPosition: .right,
|
||||
iconSpacing: 4.0,
|
||||
isLoading: self.inProgress,
|
||||
action: { [weak self] in
|
||||
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
||||
)*/
|
||||
let bottomPanelHeight = 14.0 + environment.safeInsets.bottom + actionButtonSize.height
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
|
Loading…
x
Reference in New Issue
Block a user