mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
## Summary Replace the community switch sheet with a two-column picker. Selection enlarges the avatar, waits with the voice-note loader for destination channel content and layout to settle, then follows a curved path into the header. Cache raster icons at a maximum 512-pixel size and accept only resource-free emoji SVGs. Cancel timed-out or disposed downloads without delaying content readiness. Animate Edit badges in place, and open Add Community on the pairing welcome page. Community removal lives only in the switcher’s Edit mode. Keep the flow shared across Android/iOS, with native iOS controls and removal confirmation, and clear navigation backgrounds at rest. ### Related issue Related to #8070 (community icon refresh/caching). ### Testing - 122 focused Flutter regression tests and seven native iOS tests passed after merging the latest navigation changes from `main`. Earlier feature build installed and launched on a physical iPhone. - Full mobile pre-push checks passed; regression coverage includes gradient rendering, rejected/oversized icons, bounded persisted raster decoding, SVG resource rejection and cache migration, timeout/disposal cancellation, disconnected destination channels, degraded unread-history settlement, and switching while icons refresh. Full `just ci` stopped during Rust checks for low disk space. Hosted CI remains required. - [Grid and Edit previews](https://github.com/block/buzz/pull/8076#issuecomment-5977838957). --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
78 lines
3.2 KiB
Swift
78 lines
3.2 KiB
Swift
import Flutter
|
|
import UIKit
|
|
|
|
/// Owns a destructive UIKit alert until dismissal and replies exactly once.
|
|
final class NativeConfirmationDialogCoordinator: NSObject {
|
|
private let channel: FlutterMethodChannel
|
|
private weak var parent: UIViewController?
|
|
private weak var alert: UIAlertController?
|
|
private var pendingResult: FlutterResult?
|
|
private var isFinishing = false
|
|
|
|
init(messenger: FlutterBinaryMessenger, parentViewController: UIViewController?) {
|
|
channel = FlutterMethodChannel(name: "buzz/confirmation_dialog", binaryMessenger: messenger)
|
|
parent = parentViewController
|
|
super.init()
|
|
channel.setMethodCallHandler { [weak self] call, result in
|
|
guard let self else { result(false); return }
|
|
self.handle(call, result: result)
|
|
}
|
|
}
|
|
|
|
private func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
guard call.method == "present" else { result(FlutterMethodNotImplemented); return }
|
|
guard let data = call.arguments as? [String: Any],
|
|
let title = data["title"] as? String,
|
|
let message = data["message"] as? String,
|
|
let confirmLabel = data["confirmLabel"] as? String,
|
|
let cancelLabel = data["cancelLabel"] as? String else {
|
|
result(FlutterError(code: "invalid_arguments", message: "Expected confirmation labels.", details: nil))
|
|
return
|
|
}
|
|
// Repeated taps must not create stacked alerts or multiple removals.
|
|
guard pendingResult == nil else { result(false); return }
|
|
var presenter = parent ?? UIApplication.shared.connectedScenes
|
|
.compactMap { $0 as? UIWindowScene }
|
|
.filter { $0.activationState == .foregroundActive }
|
|
.flatMap(\.windows).first(where: \.isKeyWindow)?.rootViewController
|
|
while let presented = presenter?.presentedViewController { presenter = presented }
|
|
guard let presenter, presenter.view.window != nil,
|
|
!presenter.isBeingDismissed, !(presenter is UIAlertController) else {
|
|
result(FlutterError(code: "presentation_failed", message: "Cannot present confirmation right now.", details: nil))
|
|
return
|
|
}
|
|
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
|
alert.overrideUserInterfaceStyle = data["dark"] as? Bool == true ? .dark : .light
|
|
alert.addAction(UIAlertAction(title: cancelLabel, style: .cancel) { [weak self] _ in
|
|
self?.cancel()
|
|
})
|
|
alert.addAction(UIAlertAction(title: confirmLabel, style: .destructive) { [weak self] _ in
|
|
self?.finish(true)
|
|
})
|
|
pendingResult = result
|
|
self.alert = alert
|
|
presenter.present(alert, animated: !UIAccessibility.isReduceMotionEnabled)
|
|
}
|
|
|
|
/// Dismisses an outstanding confirmation without authorizing the action.
|
|
func cancel() { finish(false) }
|
|
|
|
private func finish(_ confirmed: Bool) {
|
|
guard pendingResult != nil, !isFinishing else { return }
|
|
isFinishing = true
|
|
guard let alert else { resolve(confirmed); return }
|
|
// Finish native dismissal before Flutter removes the underlying route.
|
|
alert.dismiss(animated: !UIAccessibility.isReduceMotionEnabled) { [weak self] in
|
|
self?.resolve(confirmed)
|
|
}
|
|
}
|
|
|
|
private func resolve(_ confirmed: Bool) {
|
|
let result = pendingResult
|
|
pendingResult = nil
|
|
isFinishing = false
|
|
alert = nil
|
|
result?(confirmed)
|
|
}
|
|
}
|