mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
## Summary - add sticky date headers that crossfade with in-timeline dates - replace the Latest pill with a centered down-arrow control - animate the control out from its bottom-center anchor <img width="630" height="1368" alt="2AF1BB3F-B295-4084-8A65-90AED60B58B5" src="https://github.com/user-attachments/assets/505de450-66f5-4628-8c59-34b5511ffb5c" /> ## Validation - `bin/just mobile-check` - `bin/just mobile-test` (1,359 tests) - Android debug and signed iOS release builds --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
132 lines
3.6 KiB
Swift
132 lines
3.6 KiB
Swift
import Flutter
|
|
import UIKit
|
|
|
|
private final class StickyDateGlassView: UIVisualEffectView {
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
layer.cornerRadius = bounds.height / 2
|
|
}
|
|
}
|
|
|
|
final class StickyDateGlassHeaderFactory: NSObject, FlutterPlatformViewFactory {
|
|
private let messenger: FlutterBinaryMessenger
|
|
|
|
init(messenger: FlutterBinaryMessenger) {
|
|
self.messenger = messenger
|
|
super.init()
|
|
}
|
|
|
|
func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
|
|
FlutterStandardMessageCodec.sharedInstance()
|
|
}
|
|
|
|
func create(
|
|
withFrame frame: CGRect,
|
|
viewIdentifier viewId: Int64,
|
|
arguments args: Any?
|
|
) -> FlutterPlatformView {
|
|
StickyDateGlassHeaderPlatformView(
|
|
frame: frame,
|
|
viewIdentifier: viewId,
|
|
arguments: args,
|
|
messenger: messenger
|
|
)
|
|
}
|
|
}
|
|
|
|
final class StickyDateGlassHeaderPlatformView: NSObject, FlutterPlatformView {
|
|
private let glassView: StickyDateGlassView
|
|
private let channel: FlutterMethodChannel
|
|
private let dateLabel = UILabel()
|
|
|
|
init(
|
|
frame: CGRect,
|
|
viewIdentifier viewId: Int64,
|
|
arguments args: Any?,
|
|
messenger: FlutterBinaryMessenger
|
|
) {
|
|
let arguments = args as? [String: Any]
|
|
let text = arguments?["label"] as? String ?? ""
|
|
channel = FlutterMethodChannel(
|
|
name: "buzz/sticky_date_glass/\(viewId)",
|
|
binaryMessenger: messenger
|
|
)
|
|
|
|
if #available(iOS 26.0, *) {
|
|
let glassEffect = UIGlassEffect(style: .regular)
|
|
glassEffect.isInteractive = false
|
|
glassView = StickyDateGlassView(effect: glassEffect)
|
|
} else {
|
|
glassView = StickyDateGlassView(
|
|
effect: UIBlurEffect(style: .systemMaterial)
|
|
)
|
|
}
|
|
|
|
super.init()
|
|
|
|
glassView.frame = frame
|
|
glassView.isOpaque = false
|
|
glassView.isUserInteractionEnabled = false
|
|
glassView.clipsToBounds = true
|
|
glassView.layer.cornerCurve = .continuous
|
|
applyBrightness(from: arguments?["brightness"])
|
|
|
|
dateLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
dateLabel.text = text
|
|
dateLabel.textAlignment = .center
|
|
dateLabel.textColor = .secondaryLabel
|
|
dateLabel.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(
|
|
for: UIFont.systemFont(ofSize: 14, weight: .medium)
|
|
)
|
|
dateLabel.adjustsFontForContentSizeCategory = true
|
|
dateLabel.numberOfLines = 1
|
|
dateLabel.lineBreakMode = .byTruncatingTail
|
|
dateLabel.isAccessibilityElement = false
|
|
|
|
glassView.contentView.addSubview(dateLabel)
|
|
NSLayoutConstraint.activate([
|
|
dateLabel.leadingAnchor.constraint(
|
|
equalTo: glassView.contentView.leadingAnchor,
|
|
constant: 12
|
|
),
|
|
dateLabel.trailingAnchor.constraint(
|
|
equalTo: glassView.contentView.trailingAnchor,
|
|
constant: -12
|
|
),
|
|
dateLabel.centerYAnchor.constraint(
|
|
equalTo: glassView.contentView.centerYAnchor
|
|
),
|
|
])
|
|
|
|
channel.setMethodCallHandler { [weak self] call, result in
|
|
switch call.method {
|
|
case "setLabel":
|
|
guard let text = call.arguments as? String else {
|
|
result(FlutterMethodNotImplemented)
|
|
return
|
|
}
|
|
self?.dateLabel.text = text
|
|
result(nil)
|
|
case "setBrightness":
|
|
self?.applyBrightness(from: call.arguments)
|
|
result(nil)
|
|
default:
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
}
|
|
}
|
|
|
|
func view() -> UIView {
|
|
glassView
|
|
}
|
|
|
|
private func applyBrightness(from value: Any?) {
|
|
let interfaceStyle: UIUserInterfaceStyle = value as? String == "dark" ? .dark : .light
|
|
glassView.overrideUserInterfaceStyle = interfaceStyle
|
|
}
|
|
|
|
deinit {
|
|
channel.setMethodCallHandler(nil)
|
|
}
|
|
}
|