mirror of
https://github.com/block/buzz.git
synced 2026-11-12 20:31:12 +00:00
Long-pressing the Buzz icon on iOS now offers three quick actions: - **New chat** opens a new solo chat (the same draft as New › New chat). - **New DM** opens the New DM picker. - **Message <Bestie>** opens the DM with the current community's Bestie, using the agent's display name (for example "Message astra"). It's hidden when the community has no Bestie, and it updates when the Bestie, its name or the community changes. | Before | After | | --- | --- | | <img alt="before" src="https://github.com/user-attachments/assets/393c3fb6-fe64-4505-a7a4-12714d8bf1df" width="300"> | <img alt="after" src="https://github.com/user-attachments/assets/1c57df92-6fed-4fee-9ce8-e9ea0ec70294" width="300"> | ### How it works Home publishes the items through a small native bridge (`buzz/home_screen_shortcuts`), which sets `UIApplication.shortcutItems`. A tap goes through the scene delegate. The bridge keeps the latest one until Dart has handled it, the same pattern push-notification taps use, so cold launches work too. Home runs it only once the app is active and the destination is ready. When a tap arrives, Home pops back to the first route once, so a shortcut never opens beneath a Flutter sheet or a conversation that was already open. A screen that refuses to close (for example a New chat whose first message is still sending) stays, and the tap is dropped. The one exception is the native iOS New channel form, which isn't a Flutter route: a shortcut tapped with it open runs underneath it. If a screen is opened while a tap is still waiting for its destination, the tap is dropped. A New DM tap while the New DM picker is open replaces the picker. Opening the Bestie DM now goes through one shared function, so the Home launcher button and the shortcut run identical code. A second open while the DM is still being looked up does nothing. Signing out clears the items. The shortcut wiring lives in its own leaf widget, so Home doesn't rebuild for it. Behavior change for the existing Home Bestie launcher: if you open another screen while it's still looking up the DM, the DM no longer opens over that screen. Behavior change for the native member picker (New DM, and Add members in channels): a picker reopened while the previous one is still closing now opens once the dismissal finishes, instead of failing with "Cannot open the member picker". ### Validation - Added tests: a Home journey covers the published titles (including the Bestie's name), a cold tap that waits for the app to resume and for New chat to be available, a Bestie tap made with another route open, a New chat tap while a draft is open, a New DM tap replacing an open picker, a waiting tap dropped once another screen opens, no Home rebuilds on lifecycle changes, and clearing the items when Home goes away. A launcher test covers a slow Bestie lookup, and a New chat test covers a shortcut tapped while the first message is still sending. I also broke the code that runs the shortcut and confirmed the journey fails. - Full mobile suite and `flutter analyze` pass. CI doesn't compile Swift on drafts, so `NativeAddMembersSheet.swift` was compiled with a local `flutter build ios --simulator` build. - iPhone 17 Pro simulator (iOS 27), signed in to a Builderlab community: from a cold launch, New chat opened the new-chat draft. Then, with the app warm, New DM opened the picker and Message <Bestie> replaced it with the Bestie DM. The recording stays in Buzz because it shows real account data. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.1 KiB
Swift
65 lines
2.1 KiB
Swift
import Flutter
|
|
import UIKit
|
|
import app_links
|
|
|
|
class SceneDelegate: FlutterSceneDelegate {
|
|
override func scene(
|
|
_ scene: UIScene,
|
|
willConnectTo session: UISceneSession,
|
|
options connectionOptions: UIScene.ConnectionOptions
|
|
) {
|
|
super.scene(scene, willConnectTo: session, options: connectionOptions)
|
|
|
|
// app_links 6 uses UIApplicationDelegate callbacks. With UIScene, launch
|
|
// URLs arrive here instead; warm links still use Flutter's forwarding.
|
|
// Save the launch URL in the plugin before Dart subscribes to its stream.
|
|
if let url = connectionOptions.urlContexts.first?.url {
|
|
AppLinks.shared.handleLink(url: url)
|
|
} else if let url = connectionOptions.userActivities
|
|
.first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb })?.webpageURL {
|
|
AppLinks.shared.handleLink(url: url)
|
|
}
|
|
if let item = connectionOptions.shortcutItem {
|
|
HomeScreenShortcuts.shared.handle(item)
|
|
}
|
|
}
|
|
|
|
override func windowScene(
|
|
_ windowScene: UIWindowScene,
|
|
performActionFor shortcutItem: UIApplicationShortcutItem,
|
|
completionHandler: @escaping (Bool) -> Void
|
|
) {
|
|
if HomeScreenShortcuts.shared.handle(shortcutItem) {
|
|
completionHandler(true)
|
|
} else {
|
|
super.windowScene(windowScene, performActionFor: shortcutItem, completionHandler: completionHandler)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Owns scanner visibility without replacing Flutter's normal status-bar policy.
|
|
@objc(BuzzFlutterViewController)
|
|
class BuzzFlutterViewController: FlutterViewController {
|
|
private var scannerHidesStatusBar = false
|
|
|
|
override var prefersStatusBarHidden: Bool {
|
|
scannerHidesStatusBar || super.prefersStatusBarHidden
|
|
}
|
|
|
|
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
|
UIAccessibility.isReduceMotionEnabled ? .none : .fade
|
|
}
|
|
|
|
func setScannerStatusBarHidden(_ hidden: Bool) {
|
|
guard scannerHidesStatusBar != hidden else { return }
|
|
scannerHidesStatusBar = hidden
|
|
UIView.animate(
|
|
withDuration: UIAccessibility.isReduceMotionEnabled ? 0 : 0.23,
|
|
delay: 0,
|
|
options: [.beginFromCurrentState, .curveEaseInOut]
|
|
) {
|
|
self.setNeedsStatusBarAppearanceUpdate()
|
|
}
|
|
}
|
|
}
|