mirror of
https://github.com/block/buzz.git
synced 2026-10-28 13:01:10 +00:00
Refine iOS search glass and use standard modal motion
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -154,7 +154,7 @@ final class ConcentricSheetSurfacePlatformView: NSObject, FlutterPlatformView {
|
||||
glassView.effect = effect
|
||||
}
|
||||
glassView.backgroundColor = .clear
|
||||
glassView.contentView.backgroundColor = surfaceColor.withAlphaComponent(0.12)
|
||||
glassView.contentView.backgroundColor = surfaceColor.withAlphaComponent(min(surfaceColor.cgColor.alpha, 0.12))
|
||||
} else {
|
||||
surfaceView.backgroundColor = surfaceColor
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -110,13 +111,24 @@ class HomePage extends HookConsumerWidget {
|
||||
allowSnapshotting: false,
|
||||
transitionDuration: reducedMotion
|
||||
? Duration.zero
|
||||
: defaultTargetPlatform == TargetPlatform.iOS
|
||||
? CupertinoRouteTransitionMixin.kTransitionDuration
|
||||
: const Duration(milliseconds: 260),
|
||||
reverseTransitionDuration: reducedMotion
|
||||
? Duration.zero
|
||||
: defaultTargetPlatform == TargetPlatform.iOS
|
||||
? CupertinoRouteTransitionMixin.kTransitionDuration
|
||||
: const Duration(milliseconds: 220),
|
||||
transitionsBuilder: (_, animation, secondaryAnimation, child) =>
|
||||
reducedMotion
|
||||
? child
|
||||
: defaultTargetPlatform == TargetPlatform.iOS
|
||||
? CupertinoFullscreenDialogTransition(
|
||||
primaryRouteAnimation: animation,
|
||||
secondaryRouteAnimation: secondaryAnimation,
|
||||
linearTransition: false,
|
||||
child: child,
|
||||
)
|
||||
: SlideTransition(
|
||||
position: animation.drive(
|
||||
Tween(
|
||||
|
||||
@@ -86,7 +86,13 @@ class SearchPage extends HookConsumerWidget {
|
||||
final searchPrimaryColor = navigationPrimaryForeground(context);
|
||||
final searchPlaceholderColor = navigationSecondaryForeground(context);
|
||||
final fieldHeight = _idleSearchFieldHeight(context);
|
||||
final filtersHeight = _searchHeaderFiltersHeight(context);
|
||||
final filtersHeight = nativeIos
|
||||
? (MediaQuery.textScalerOf(context).scale(16) * 1.4 + 16).clamp(
|
||||
48.0,
|
||||
double.infinity,
|
||||
) +
|
||||
Grid.xxs * 2
|
||||
: _searchHeaderFiltersHeight(context);
|
||||
|
||||
void activateSearch() => focusNode.requestFocus();
|
||||
|
||||
@@ -208,6 +214,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
key: const ValueKey('search-header-filters'),
|
||||
height: filtersHeight,
|
||||
child: FilterChipBar<_SearchFilter>(
|
||||
useNativeGlass: nativeIos,
|
||||
expandItems: true,
|
||||
visualDensity: const VisualDensity(horizontal: -2),
|
||||
chipVerticalPadding: _searchFilterChipVerticalPadding,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'ios_glass_navigation_button.dart';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
|
||||
@@ -37,6 +39,9 @@ class FilterChipBar<T> extends StatelessWidget {
|
||||
/// When enabled, every chip shares the width rather than scrolling.
|
||||
final bool expandItems;
|
||||
|
||||
/// Uses native glass capsules on iOS for navigation-style filters.
|
||||
final bool useNativeGlass;
|
||||
|
||||
/// Material density applied to each chip.
|
||||
final VisualDensity visualDensity;
|
||||
|
||||
@@ -52,6 +57,7 @@ class FilterChipBar<T> extends StatelessWidget {
|
||||
required this.selected,
|
||||
required this.onSelected,
|
||||
this.expandItems = false,
|
||||
this.useNativeGlass = false,
|
||||
this.visualDensity = VisualDensity.compact,
|
||||
this.chipVerticalPadding = Grid.quarter,
|
||||
this.barVerticalPadding = Grid.xxs,
|
||||
@@ -59,6 +65,60 @@ class FilterChipBar<T> extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (useNativeGlass && defaultTargetPlatform == TargetPlatform.iOS) {
|
||||
final textStyle = filterChipTextStyle.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
final height = (MediaQuery.textScalerOf(context).scale(16) * 1.4 + 16)
|
||||
.clamp(48.0, double.infinity);
|
||||
final widths = [
|
||||
for (final item in items)
|
||||
(TextPainter(
|
||||
text: TextSpan(text: item.label, style: textStyle),
|
||||
textScaler: MediaQuery.textScalerOf(context),
|
||||
textDirection: Directionality.of(context),
|
||||
)..layout()).width +
|
||||
32,
|
||||
];
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final available = constraints.maxWidth - Grid.gutter * 2;
|
||||
final required =
|
||||
widths.fold(0.0, (sum, width) => sum + width) +
|
||||
Grid.xxs * (items.length - 1);
|
||||
final extra = expandItems && available > required
|
||||
? (available - required) / items.length
|
||||
: 0.0;
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
clipBehavior: Clip.none,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Grid.gutter,
|
||||
vertical: barVerticalPadding,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
for (var index = 0; index < items.length; index++) ...[
|
||||
if (index > 0) const SizedBox(width: Grid.xxs),
|
||||
IosGlassNavigationButton(
|
||||
icon: IosGlassNavigationIcon.more,
|
||||
label: items[index].label,
|
||||
semanticLabel: items[index].label,
|
||||
onPressed: () => onSelected(items[index].id),
|
||||
isSelected: selected == items[index].id,
|
||||
width: widths[index] + extra,
|
||||
height: height,
|
||||
controlSize: height - 8,
|
||||
fillWidth: true,
|
||||
foregroundColor: context.colors.primary,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
final chips = [
|
||||
for (var index = 0; index < items.length; index++) ...[
|
||||
if (index > 0) const SizedBox(width: Grid.xxs),
|
||||
|
||||
@@ -148,10 +148,10 @@ void main() {
|
||||
final searchRoute = ModalRoute.of(tester.element(find.byType(SearchPage)))!;
|
||||
expect(searchRoute.allowSnapshotting, isFalse);
|
||||
expect(searchRoute.opaque, isTrue);
|
||||
expect(searchRoute.transitionDuration, const Duration(milliseconds: 260));
|
||||
expect(searchRoute.transitionDuration, const Duration(milliseconds: 500));
|
||||
expect(
|
||||
searchRoute.reverseTransitionDuration,
|
||||
const Duration(milliseconds: 220),
|
||||
const Duration(milliseconds: 500),
|
||||
);
|
||||
Navigator.of(tester.element(find.byType(SearchPage))).pop();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@@ -63,7 +63,12 @@ void main() {
|
||||
expect(tester.getRect(close).left, greaterThan(fieldRect.right));
|
||||
expect(tester.getSize(close), const Size(48, 48));
|
||||
for (final label in ['All', 'Messages', 'Channels', 'People']) {
|
||||
expect(find.text(label).hitTestable(), findsOneWidget);
|
||||
expect(
|
||||
platform == TargetPlatform.iOS
|
||||
? find.byTooltip(label)
|
||||
: find.text(label).hitTestable(),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
||||
final input = tester.element(find.byType(TextField));
|
||||
await tester.tap(find.byType(TextField));
|
||||
@@ -76,7 +81,19 @@ void main() {
|
||||
isTrue,
|
||||
);
|
||||
await tester.enterText(find.byType(TextField), 'design');
|
||||
await tester.tap(find.text('Channels'));
|
||||
if (platform == TargetPlatform.iOS) {
|
||||
tester
|
||||
.widget<IosGlassNavigationButton>(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is IosGlassNavigationButton &&
|
||||
widget.label == 'Channels',
|
||||
),
|
||||
)
|
||||
.onPressed!();
|
||||
} else {
|
||||
await tester.tap(find.text('Channels'));
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getRect(field), fieldRect);
|
||||
expect(tester.getRect(filters), filtersRect);
|
||||
@@ -90,7 +107,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
find.byType(IosGlassNavigationButton),
|
||||
platform == TargetPlatform.iOS ? findsOneWidget : findsNothing,
|
||||
platform == TargetPlatform.iOS ? findsNWidgets(5) : findsNothing,
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
|
||||
Reference in New Issue
Block a user