Skip to content

Commit f0ee4d8

Browse files
committed
Adapt download and updater controls to floating tab bar
1 parent e0b428e commit f0ee4d8

3 files changed

Lines changed: 91 additions & 82 deletions

File tree

‎Telegram-Mac/app/MainViewController.swift‎

Lines changed: 77 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ final class UpdateTabView : Control {
2222
let imageView: ImageView = ImageView()
2323
let shimmer = ShimmerEffectView()
2424
let progressView: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 24, 24))
25+
private let glassView = LiquidGlassView(frame: .zero, cornerRadius: 20)
26+
private var updateState: UpdateButtonState = .common
2527

2628
var bottomOverlayChanged: (() -> Void)?
2729

@@ -37,96 +39,107 @@ final class UpdateTabView : Control {
3739

3840
var isInstalling: Bool = false {
3941
didSet {
40-
shimmer.change(opacity: isInstalling ? 0 : 1)
41-
textView.isHidden = isInstalling || layoutState == .minimisize
42-
progressView.isHidden = !isInstalling
43-
imageView.isHidden = isInstalling || layoutState != .minimisize
44-
45-
if layoutState != .minimisize, isInstalling, let superview = self.superview {
46-
self.layer?.cornerRadius = frame.height / 2
47-
change(size: NSMakeSize(60, frame.height), animated: true, timingFunction: .spring)
48-
change(pos: NSMakePoint(superview.bounds.focus(self.frame.size).minX, self.frame.minY), animated: true, timingFunction: .spring)
49-
progressView.change(pos: self.bounds.focus(progressView.frame.size).origin, animated: true, timingFunction: .spring)
50-
} else {
51-
if let superview = self.superview, isChatList {
52-
change(size: NSMakeSize(self.textView.frame.width + 40, frame.height), animated: true, timingFunction: .spring)
53-
change(pos: NSMakePoint(superview.bounds.focus(self.frame.size).minX, self.frame.minY), animated: true, timingFunction: .spring)
54-
imageView.change(pos: self.bounds.focus(imageView.frame.size).origin, animated: true, timingFunction: .spring)
55-
}
56-
}
57-
42+
updateContent(animated: isInstalling != oldValue)
5843
}
5944
}
6045

6146
var layoutState: SplitViewState = .dual {
6247
didSet {
63-
let installing = self.isInstalling
64-
self.isInstalling = installing
48+
updateContent(animated: false)
6549
}
6650
}
67-
68-
69-
51+
52+
var preferredWidth: CGFloat {
53+
if layoutState == .minimisize {
54+
return 40
55+
}
56+
return isInstalling ? 60 : textView.frame.width + 40
57+
}
58+
59+
private func updateContent(animated: Bool) {
60+
shimmer.change(opacity: isInstalling ? 0 : 1, animated: animated)
61+
textView.isHidden = isInstalling || layoutState == .minimisize
62+
progressView.isHidden = !isInstalling
63+
imageView.isHidden = isInstalling || layoutState != .minimisize
64+
if isChatList, let superview, frame.width != preferredWidth {
65+
change(size: NSMakeSize(preferredWidth, frame.height), animated: animated, timingFunction: .spring)
66+
change(pos: NSMakePoint(superview.bounds.focus(frame.size).minX, frame.minY), animated: animated, timingFunction: .spring)
67+
}
68+
needsLayout = true
69+
}
70+
7071
required init(frame frameRect: NSRect) {
7172
super.init(frame: frameRect)
73+
backgroundColor = .clear
74+
layer?.masksToBounds = false
75+
glassView.isEventLess = true
76+
addSubview(glassView)
77+
interactiveGlassView = glassView
78+
scaleOnClick = true
7279
textView.userInteractionEnabled = false
7380
textView.isSelectable = false
81+
textView.disableBackgroundDrawing = true
82+
textView.isAccessibilityDecorative = true
7483
addSubview(textView)
84+
progressView.isEventLess = true
85+
progressView.isAccessibilityDecorative = true
7586
addSubview(progressView)
87+
imageView.isEventLess = true
88+
imageView.isAccessibilityDecorative = true
7689
addSubview(imageView)
90+
shimmer.isEventLess = true
91+
shimmer.layer?.masksToBounds = true
7792
addSubview(shimmer)
78-
7993
shimmer.isStatic = true
80-
81-
progressView.progressColor = theme.colors.underSelectedColor
82-
isInstalling = false
83-
84-
let layout = TextViewLayout(.initialize(string: strings().updateUpdateTelegram, color: theme.colors.underSelectedColor, font: .medium(.title)))
85-
layout.measure(width: max(280, frame.width))
86-
textView.update(layout)
87-
imageView.isAccessibilityDecorative = true
88-
setAccessibilityLabel(strings().updateUpdateTelegram)
89-
90-
let shadow = NSShadow()
91-
shadow.shadowBlurRadius = 5
92-
shadow.shadowColor = NSColor.black.withAlphaComponent(0.3)
93-
shadow.shadowOffset = NSMakeSize(0, 2)
94-
self.shadow = shadow
95-
94+
updateLocalizationAndTheme(theme: theme)
9695
}
9796

9897
override func cursorUpdate(with event: NSEvent) {
9998
NSCursor.pointingHand.set()
10099
}
101100

102-
override var backgroundColor: NSColor {
103-
didSet {
104-
textView.backgroundColor = backgroundColor
105-
}
101+
func update(_ state: UpdateButtonState) {
102+
updateState = state
103+
updateLocalizationAndTheme(theme: theme)
106104
}
107-
105+
108106
override func updateLocalizationAndTheme(theme: PresentationTheme) {
109107
super.updateLocalizationAndTheme(theme: theme)
110-
imageView.image = (theme as! TelegramPresentationTheme).icons.appUpdate
108+
let theme = theme as! TelegramPresentationTheme
109+
let tint: NSColor
110+
switch updateState {
111+
case .common:
112+
tint = theme.colors.accent
113+
case .important:
114+
tint = theme.colors.greenUI
115+
case .critical:
116+
tint = theme.colors.redUI
117+
}
118+
glassView.customTint = tint
119+
let foreground = tint.lightness > 0.8 ? NSColor(0x000000) : NSColor(0xffffff)
120+
let layout = TextViewLayout(.initialize(string: strings().updateUpdateTelegram, color: foreground, font: .medium(.title)), maximumNumberOfLines: 1)
121+
layout.measure(width: max(280, frame.width))
122+
textView.update(layout)
123+
imageView.image = generateTintedImage(image: theme.icons.appUpdate, color: foreground)
111124
imageView.sizeToFit()
112-
needsLayout = true
113-
shimmer.updateAbsoluteRect(bounds, within: bounds.size)
114-
shimmer.update(backgroundColor: .clear, foregroundColor: .clear, shimmeringColor: NSColor.white.withAlphaComponent(0.3), shapes: [.roundedRect(rect: bounds, cornerRadius: bounds.height / 2)], horizontal: true, size: bounds.size)
115-
progressView.progressColor = theme.colors.underSelectedColor
116-
}
117-
118-
override func setFrameOrigin(_ newOrigin: NSPoint) {
119-
super.setFrameOrigin(newOrigin)
125+
progressView.progressColor = foreground
126+
setAccessibilityLabel(strings().updateUpdateTelegram)
127+
updateContent(animated: false)
120128
}
121-
122-
129+
123130
override func layout() {
124131
super.layout()
125-
126-
127-
132+
let cornerRadius: CGFloat = isChatList ? bounds.height / 2 : 0
133+
layer?.cornerRadius = cornerRadius
134+
glassView.cornerRadius = cornerRadius
135+
glassView.frame = bounds
136+
glassView.updateLayout(size: bounds.size, transition: .immediate)
128137
shimmer.frame = bounds
129-
shimmer.layer?.cornerRadius = bounds.height / 2
138+
shimmer.layer?.cornerRadius = cornerRadius
139+
if bounds.width > 0, bounds.height > 0 {
140+
shimmer.updateAbsoluteRect(bounds, within: bounds.size)
141+
shimmer.update(backgroundColor: .clear, foregroundColor: .clear, shimmeringColor: NSColor.white.withAlphaComponent(0.3), shapes: [.roundedRect(rect: bounds, cornerRadius: cornerRadius)], horizontal: true, size: bounds.size)
142+
}
130143
textView.center()
131144
progressView.center()
132145
imageView.center()
@@ -145,14 +158,7 @@ final class UpdateTabController: GenericViewController<UpdateTabView> {
145158
private let context: SharedAccountContext
146159
private var state: UpdateButtonState = .common {
147160
didSet {
148-
switch state {
149-
case .common:
150-
genericView.backgroundColor = theme.colors.accent
151-
case .important:
152-
genericView.backgroundColor = theme.colors.greenUI
153-
case .critical:
154-
genericView.backgroundColor = theme.colors.redUI
155-
}
161+
genericView.update(state)
156162
}
157163
}
158164
private var parentSize: NSSize = .zero
@@ -204,7 +210,7 @@ final class UpdateTabController: GenericViewController<UpdateTabView> {
204210
let context = self.context
205211

206212

207-
genericView.set(background: theme.colors.grayForeground, for: .Normal)
213+
genericView.set(background: .clear, for: .Normal)
208214
genericView.isHidden = true
209215

210216
#if APP_STORE
@@ -254,20 +260,17 @@ final class UpdateTabController: GenericViewController<UpdateTabView> {
254260
}
255261

256262
func updateLayout(_ layout: SplitViewState, parentSize: NSSize, isChatList: Bool) {
263+
genericView.isChatList = isChatList
257264
genericView.layoutState = layout
258265
self.parentSize = parentSize
259-
genericView.isChatList = isChatList
260266
if isChatList && layout == .minimisize {
261-
// Icon-only circular button in single-column minimisize mode —
262-
// there's no room for the text pill, so render as a 40×40 circle
263-
// centered horizontally above the bottom edge.
264267
let side: CGFloat = 40
265268
genericView.setFrameSize(NSMakeSize(side, side))
266269
genericView.layer?.cornerRadius = side / 2
267270
genericView.centerX(y: parentSize.height - side - 10)
268271
shakeDisposable.set(nil)
269272
} else if isChatList {
270-
genericView.setFrameSize(NSMakeSize(genericView.textView.frame.width + 40, 40))
273+
genericView.setFrameSize(NSMakeSize(genericView.preferredWidth, 40))
271274
genericView.layer?.cornerRadius = genericView.frame.height / 2
272275
let bottom = parentSize.height - genericView.frame.height
273276
genericView.centerX(y: bottom - 60)

‎packages/ChatListUI/Sources/ChatListUI/DownloadsControl.swift‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public final class DownloadsControl : Control {
4040

4141
self.layer?.cornerRadius = frameRect.height / 2
4242

43-
let glass = LiquidGlassView(frame: bounds, cornerRadius: frameRect.height / 2, hasShadow: true, hasBorder: true, tintAlpha: 0.3)
43+
let glass = LiquidGlassView(frame: bounds, cornerRadius: frameRect.height / 2, hasShadow: true, hasBorder: true, tintAlpha: 0.15)
44+
glass.isEventLess = true
4445
self.glassView = glass
4546
addSubview(glass)
4647
self.stretchReferenceView = glass
@@ -69,7 +70,6 @@ public final class DownloadsControl : Control {
6970
let theme = theme as! TelegramPresentationTheme
7071
self.backgroundColor = .clear
7172
self.border = []
72-
glassView.customTint = theme.colors.background
7373

7474
progressView.style = ControlStyle(foregroundColor: theme.colors.accent, backgroundColor: .clear, highlightColor: .clear)
7575

@@ -105,9 +105,10 @@ public final class DownloadsControl : Control {
105105
let progressInset = frame.height / 2
106106
progressView.frame = NSMakeRect(progressInset, frame.height - progressView.frame.height, frame.width - progressInset * 2, 2)
107107

108-
titleView.resize(frame.width - 20)
108+
let textWidth = max(0, frame.width - 30 - nextView.frame.width)
109+
titleView.resize(textWidth)
109110
titleView.setFrameOrigin(.zero)
110-
textContainer.setFrameSize(NSMakeSize(frame.width - 10 - 10, titleView.frame.height))
111+
textContainer.setFrameSize(NSMakeSize(textWidth, titleView.frame.height))
111112
textContainer.centerY(x: 10)
112113
nextView.centerY(x: frame.width - 10 - nextView.frame.width)
113114

‎packages/ChatListUI/Sources/ChatListUI/PeersListController.swift‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,7 +1898,8 @@ public class PeerListContainerView : Control {
18981898
var bottomControlsY = size.height - contentBottomInset
18991899
for view in [downloads as NSView?, removalUndo as NSView?].compactMap({ $0 }) {
19001900
let edge: CGFloat = 12
1901-
let rect = NSMakeRect(edge, bottomControlsY - view.frame.height - edge, size.width - edge * 2, view.frame.height)
1901+
let y = bottomControlsY - view.frame.height - edge
1902+
let rect = view is DownloadsControl ? downloadsFrame(in: size, y: y) : NSMakeRect(edge, y, size.width - edge * 2, view.frame.height)
19021903
transition.updateFrame(view: view, frame: rect)
19031904
bottomControlsY = rect.minY
19041905
}
@@ -1918,6 +1919,11 @@ public class PeerListContainerView : Control {
19181919
}
19191920
}
19201921

1922+
private func downloadsFrame(in size: NSSize, y: CGFloat) -> NSRect {
1923+
let width = min(500, max(0, size.width - 40))
1924+
return NSMakeRect(floorToScreenPixels((size.width - width) / 2), y, width, 30)
1925+
}
1926+
19211927
private var pendingRemovalUndo: (toast: PendingChatRemovalToast, undo: () -> Void)?
19221928

19231929
public func updateRemovalUndo(_ toast: PendingChatRemovalToast?, undo: @escaping () -> Void, animated: Bool) {
@@ -1969,7 +1975,6 @@ public class PeerListContainerView : Control {
19691975

19701976
fileprivate func applyDownloadsVisibility(animated: Bool) {
19711977
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
1972-
let edge: CGFloat = 12
19731978
let allowed: Bool
19741979
if let st = self.state {
19751980
let hideContent = st.splitState == .minimisize || st.appear == .short
@@ -1982,7 +1987,7 @@ public class PeerListContainerView : Control {
19821987
if let view = self.downloads {
19831988
current = view
19841989
} else {
1985-
current = DownloadsControl(frame: NSMakeRect(edge, frame.height, frame.width - edge * 2, 30))
1990+
current = DownloadsControl(frame: downloadsFrame(in: frame.size, y: frame.height))
19861991
self.downloads = current
19871992
addSubview(current, positioned: .above, relativeTo: self.tableView)
19881993
if animated {
@@ -1995,7 +2000,7 @@ public class PeerListContainerView : Control {
19952000
}, for: .Click)
19962001
} else if let view = self.downloads {
19972002
self.downloads = nil
1998-
transition.updateFrame(view: view, frame: NSRect(origin: NSMakePoint(edge, frame.height), size: view.frame.size), completion: { _ in
2003+
transition.updateFrame(view: view, frame: downloadsFrame(in: frame.size, y: frame.height), completion: { _ in
19992004
view.removeFromSuperview()
20002005
})
20012006
}

0 commit comments

Comments
 (0)