Files
buzz/mobile/ios/Runner/ConcentricSheetSurface.swift
klopez4212 b4509dee8b feat(mobile): add Buzzies and refresh agent avatar picker (#8921)
## Summary

Adds Buzzies and refreshes the agent avatar picker with larger previews,
rounded option strips, and clearer category choices. Also moves Agents
above Appearance in the home menu and adds a custom agent icon.

### Related issue

None found.

### Testing

Analysis, 4,138 app tests, package tests, and 121 configured-auth tests
pass. Earlier visual build exercised on Pixel and iOS simulator, with
iPhone review; snapshots below show that build.

| Android · Pixel | iOS · simulator preview |
| --- | --- |
| ![Buzzies in the Android avatar
picker](https://github.com/user-attachments/assets/bf56120c-0128-4683-a0ad-189587a13c8d)
| ![Buzzies in the iOS avatar
picker](https://github.com/user-attachments/assets/63c692f5-822c-4490-883f-36bec8d2be15)
|

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
2026-11-02 17:00:12 +00:00

219 lines
6.7 KiB
Swift

import Flutter
import UIKit
final class ConcentricSheetSurfaceFactory: 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 {
ConcentricSheetSurfacePlatformView(
frame: frame,
viewIdentifier: viewId,
messenger: messenger,
arguments: args
)
}
}
final class ConcentricSheetSurfacePlatformView: NSObject, FlutterPlatformView {
private let rootView: UIView
private let surfaceView: UIView
private let channel: FlutterMethodChannel
private let corners: String
private let usesGlass: Bool
private let topRadius: Double?
init(
frame: CGRect,
viewIdentifier viewId: Int64,
messenger: FlutterBinaryMessenger,
arguments args: Any?
) {
let arguments = args as? [String: Any]
let colorValue = (arguments?["color"] as? NSNumber)?.uint32Value ?? 0xFFFF_FFFF
let backdropColorValue = (arguments?["backdropColor"] as? NSNumber)?.uint32Value
let minimumRadius = (arguments?["minimumRadius"] as? NSNumber)?.doubleValue ?? 24
corners = arguments?["corners"] as? String ?? "all"
topRadius = (arguments?["topRadius"] as? NSNumber)?.doubleValue
usesGlass = (arguments?["usesGlass"] as? NSNumber)?.boolValue == true
rootView = UIView(frame: frame)
rootView.isUserInteractionEnabled = false
rootView.isOpaque = !usesGlass && backdropColorValue != nil
rootView.backgroundColor = backdropColorValue.map { Self.color(from: $0) } ?? .clear
if #available(iOS 26.0, *), usesGlass {
let effect = UIGlassEffect(style: .regular)
effect.isInteractive = false
surfaceView = UIVisualEffectView(effect: effect)
} else {
surfaceView = UIView()
}
surfaceView.frame = rootView.bounds
surfaceView.isUserInteractionEnabled = false
surfaceView.isOpaque = !usesGlass
surfaceView.clipsToBounds = true
surfaceView.layer.cornerCurve = .continuous
surfaceView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
rootView.addSubview(surfaceView)
channel = FlutterMethodChannel(
name: "buzz/concentric_sheet_surface/\(viewId)",
binaryMessenger: messenger
)
super.init()
updateColors(
colorValue: colorValue,
backdropColorValue: backdropColorValue,
glassTintColorValue: (arguments?["glassTintColor"] as? NSNumber)?.uint32Value
)
applyInterfaceStyle(from: arguments?["brightness"])
applyCornerConfiguration(minimumRadius: minimumRadius, capsule: arguments?["capsule"] as? Bool == true)
channel.setMethodCallHandler { [weak self] call, result in
guard let self else {
result(nil)
return
}
switch call.method {
case "updateColors":
guard
let arguments = call.arguments as? [String: Any],
let colorValue = arguments["color"] as? NSNumber
else {
result(
FlutterError(
code: "invalid_arguments",
message: "Expected a surface color.",
details: nil
)
)
return
}
self.updateColors(
colorValue: colorValue.uint32Value,
backdropColorValue: (arguments["backdropColor"] as? NSNumber)?.uint32Value,
glassTintColorValue: (arguments["glassTintColor"] as? NSNumber)?.uint32Value
)
result(nil)
case "updateGeometry":
guard
let arguments = call.arguments as? [String: Any],
let minimumRadius = arguments["minimumRadius"] as? NSNumber
else {
result(
FlutterError(
code: "invalid_arguments",
message: "Expected a minimum corner radius.",
details: nil
)
)
return
}
self.applyInterfaceStyle(from: arguments["brightness"])
self.applyCornerConfiguration(
minimumRadius: minimumRadius.doubleValue,
capsule: arguments["capsule"] as? Bool == true
)
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
}
deinit {
channel.setMethodCallHandler(nil)
}
func view() -> UIView {
rootView
}
private func updateColors(
colorValue: UInt32,
backdropColorValue: UInt32?,
glassTintColorValue: UInt32?
) {
let surfaceColor = Self.color(from: colorValue)
if let glassView = surfaceView as? UIVisualEffectView {
if #available(iOS 26.0, *), let effect = glassView.effect as? UIGlassEffect {
effect.tintColor = glassTintColorValue.map { Self.color(from: $0) }
glassView.effect = effect
}
glassView.backgroundColor = .clear
glassView.contentView.backgroundColor = surfaceColor.withAlphaComponent(min(surfaceColor.cgColor.alpha, 0.12))
} else {
surfaceView.backgroundColor = surfaceColor
}
rootView.isOpaque = !usesGlass && backdropColorValue != nil
rootView.backgroundColor = backdropColorValue.map { Self.color(from: $0) } ?? .clear
}
private func applyCornerConfiguration(minimumRadius: CGFloat, capsule: Bool) {
if #available(iOS 26.0, *) {
if capsule {
surfaceView.cornerConfiguration = .capsule()
return
}
let radius = UICornerRadius.containerConcentric(minimum: minimumRadius)
surfaceView.cornerConfiguration =
corners == "bottom"
? .uniformBottomRadius(
radius,
topLeftRadius: nil,
topRightRadius: nil
)
: topRadius.map {
.uniformBottomRadius(
radius,
topLeftRadius: .fixed($0),
topRightRadius: .fixed($0)
)
} ?? .uniformCorners(radius: radius)
} else {
surfaceView.layer.cornerRadius = minimumRadius
if corners == "bottom" {
surfaceView.layer.maskedCorners = [
.layerMinXMaxYCorner,
.layerMaxXMaxYCorner,
]
}
}
}
private func applyInterfaceStyle(from value: Any?) {
switch value as? String {
case "dark":
rootView.overrideUserInterfaceStyle = .dark
case "light":
rootView.overrideUserInterfaceStyle = .light
default:
rootView.overrideUserInterfaceStyle = .unspecified
}
}
private static func color(from value: UInt32) -> UIColor {
let alpha = CGFloat((value >> 24) & 0xFF) / 255
let red = CGFloat((value >> 16) & 0xFF) / 255
let green = CGFloat((value >> 8) & 0xFF) / 255
let blue = CGFloat(value & 0xFF) / 255
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}