mirror of
https://github.com/block/buzz.git
synced 2026-11-13 21:01:53 +00:00
Duo keeps conversations and Settings details open across closed, book and flat postures. Book mode splits at the fold; flattening restores the sidebar. Native rail controls, full-width navigation backdrops and sheet-local safe areas keep content aligned. Ordinary iPhones remain single-pane in portrait and landscape; tablet and Duo split layouts remain enabled. Includes the recovered preview's matching pane widths, Buzz empty-pane mark, stable Settings chevrons, Bestie pane routing and Duo-only upward New menu attachment. Current main's Activity redesign, Search content fade and unread-navigation button are retained. Menu selection checks sit beside their labels. Shared mobile changes remain in scope: the native iOS status editor, Appearance preview layout, avatar crop preservation, onboarding footer layout, profile/invite sheet geometry and nested conversation navigation. This PR is not exclusively Duo-gated. Nested navigator observers preserve recording cancellation and navigation callbacks; crop resizing preserves the selected image region. iOS 27.1 APIs remain compile-time and runtime gated. Validation on the reconciled source: Flutter analysis, Activity/Home journeys, the full mobile test suite, file-size checks and Swift package tests. The iPhone single-pane fix was also exercised on an iPhone 18 Pro simulator before reconciliation. Fresh hosted CI and physical-device confirmation of the reconciled build are separate and remain pending. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Tom Brow <tomb@block.xyz> Co-authored-by: Tom Brow <tomb@squareup.com>
138 lines
4.5 KiB
Dart
138 lines
4.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
|
|
/// UIKit's measured navigation geometry, with a first-frame bootstrap size.
|
|
class IosNavigationMetrics {
|
|
const IosNavigationMetrics({
|
|
this.compactHeight = 44,
|
|
this.largeTitleHeight = 52,
|
|
this.railCenterX,
|
|
this.railTopClearance,
|
|
this.windowSize,
|
|
this.bookDivision,
|
|
});
|
|
|
|
final double compactHeight;
|
|
final double largeTitleHeight;
|
|
final double? railCenterX;
|
|
final double? railTopClearance;
|
|
final Size? windowSize;
|
|
final Rect? bookDivision;
|
|
|
|
/// Aligns the shared rail with the status cluster in the reserved region.
|
|
double railCenter(Size size, double fallback) {
|
|
final center = windowSize == size ? railCenterX ?? fallback : fallback;
|
|
return center + (center < size.width / 2 ? 6 : -6);
|
|
}
|
|
|
|
static void updateRail(BuildContext context, Map<Object?, Object?> values) {
|
|
final notifier = _MetricsScope.of(context);
|
|
final center = (values['centerX'] as num?)?.toDouble();
|
|
final width = (values['windowWidth'] as num?)?.toDouble();
|
|
final height = (values['windowHeight'] as num?)?.toDouble();
|
|
if (notifier == null ||
|
|
center == null ||
|
|
width == null ||
|
|
height == null ||
|
|
!center.isFinite ||
|
|
!width.isFinite ||
|
|
!height.isFinite ||
|
|
center < 0 ||
|
|
center > width ||
|
|
width <= 0 ||
|
|
height <= 0) {
|
|
return;
|
|
}
|
|
final size = Size(width, height);
|
|
final rawTop = (values['topClearance'] as num?)?.toDouble();
|
|
final top =
|
|
rawTop != null && rawTop.isFinite && rawTop >= 0 && rawTop < height
|
|
? rawTop
|
|
: null;
|
|
final rawDivision = values['bookDivision'];
|
|
Rect? division;
|
|
if (rawDivision is List &&
|
|
rawDivision.length == 4 &&
|
|
rawDivision.every((value) => value is num && value.isFinite)) {
|
|
final bounds = rawDivision
|
|
.map((value) => (value as num).toDouble())
|
|
.toList();
|
|
final candidate = Rect.fromLTWH(
|
|
bounds[0],
|
|
bounds[1],
|
|
bounds[2],
|
|
bounds[3],
|
|
);
|
|
if (candidate.width >= 0 &&
|
|
candidate.left > 0 &&
|
|
candidate.right < width &&
|
|
candidate.top <= 1 &&
|
|
candidate.bottom >= height - 1) {
|
|
division = candidate;
|
|
}
|
|
}
|
|
final old = notifier.value;
|
|
if (old.railCenterX == center &&
|
|
old.railTopClearance == top &&
|
|
old.windowSize == size &&
|
|
old.bookDivision == division) {
|
|
return;
|
|
}
|
|
notifier.value = IosNavigationMetrics(
|
|
compactHeight: old.compactHeight,
|
|
largeTitleHeight: old.largeTitleHeight,
|
|
railCenterX: center,
|
|
railTopClearance: top,
|
|
windowSize: size,
|
|
bookDivision: division,
|
|
);
|
|
}
|
|
|
|
static IosNavigationMetrics of(BuildContext context) =>
|
|
_MetricsScope.of(context)?.value ?? const IosNavigationMetrics();
|
|
|
|
static void update(BuildContext context, Map<Object?, Object?> values) {
|
|
final notifier = _MetricsScope.of(context);
|
|
if (notifier == null) return;
|
|
final compact = (values['compactHeight'] as num?)?.toDouble();
|
|
final expanded = (values['expandedHeight'] as num?)?.toDouble();
|
|
if (compact == null || !compact.isFinite || compact <= 0) return;
|
|
final extra = expanded == null
|
|
? notifier.value.largeTitleHeight
|
|
: expanded - compact;
|
|
if (!extra.isFinite || extra < 0) return;
|
|
if ((notifier.value.compactHeight - compact).abs() < 0.1 &&
|
|
(notifier.value.largeTitleHeight - extra).abs() < 0.1) {
|
|
return;
|
|
}
|
|
notifier.value = IosNavigationMetrics(
|
|
compactHeight: compact,
|
|
largeTitleHeight: extra,
|
|
railCenterX: notifier.value.railCenterX,
|
|
railTopClearance: notifier.value.railTopClearance,
|
|
windowSize: notifier.value.windowSize,
|
|
bookDivision: notifier.value.bookDivision,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Shares measured UIKit spacing across routes and their body insets.
|
|
class IosNavigationMetricsHost extends HookWidget {
|
|
const IosNavigationMetricsHost({super.key, required this.child});
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final metrics = useValueNotifier(const IosNavigationMetrics());
|
|
return _MetricsScope(notifier: metrics, child: child);
|
|
}
|
|
}
|
|
|
|
class _MetricsScope
|
|
extends InheritedNotifier<ValueNotifier<IosNavigationMetrics>> {
|
|
const _MetricsScope({required super.notifier, required super.child});
|
|
|
|
static ValueNotifier<IosNavigationMetrics>? of(BuildContext context) =>
|
|
context.dependOnInheritedWidgetOfExactType<_MetricsScope>()?.notifier;
|
|
}
|