mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Sync iOS glass controls with app theme
Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -9,6 +9,10 @@ final class JumpToLatestGlassButtonFactory: NSObject, FlutterPlatformViewFactory
|
||||
super.init()
|
||||
}
|
||||
|
||||
func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
|
||||
FlutterStandardMessageCodec.sharedInstance()
|
||||
}
|
||||
|
||||
func create(
|
||||
withFrame frame: CGRect,
|
||||
viewIdentifier viewId: Int64,
|
||||
@@ -17,6 +21,7 @@ final class JumpToLatestGlassButtonFactory: NSObject, FlutterPlatformViewFactory
|
||||
JumpToLatestGlassButtonPlatformView(
|
||||
frame: frame,
|
||||
viewIdentifier: viewId,
|
||||
arguments: args,
|
||||
messenger: messenger
|
||||
)
|
||||
}
|
||||
@@ -43,6 +48,7 @@ final class JumpToLatestGlassButtonPlatformView: NSObject, FlutterPlatformView {
|
||||
init(
|
||||
frame: CGRect,
|
||||
viewIdentifier viewId: Int64,
|
||||
arguments args: Any?,
|
||||
messenger: FlutterBinaryMessenger
|
||||
) {
|
||||
containerView = UIView(frame: frame)
|
||||
@@ -54,6 +60,7 @@ final class JumpToLatestGlassButtonPlatformView: NSObject, FlutterPlatformView {
|
||||
|
||||
containerView.backgroundColor = .clear
|
||||
containerView.isOpaque = false
|
||||
applyBrightness(from: args)
|
||||
|
||||
var configuration: UIButton.Configuration
|
||||
if #available(iOS 26.0, *) {
|
||||
@@ -81,6 +88,15 @@ final class JumpToLatestGlassButtonPlatformView: NSObject, FlutterPlatformView {
|
||||
for: .touchUpInside
|
||||
)
|
||||
|
||||
channel.setMethodCallHandler { [weak self] call, result in
|
||||
guard call.method == "setBrightness" else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
return
|
||||
}
|
||||
self?.applyBrightness(from: call.arguments)
|
||||
result(nil)
|
||||
}
|
||||
|
||||
containerView.addSubview(button)
|
||||
NSLayoutConstraint.activate([
|
||||
button.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
|
||||
@@ -93,4 +109,17 @@ final class JumpToLatestGlassButtonPlatformView: NSObject, FlutterPlatformView {
|
||||
func view() -> UIView {
|
||||
containerView
|
||||
}
|
||||
|
||||
private func applyBrightness(from value: Any?) {
|
||||
let brightness = (value as? [String: Any])?["brightness"] as? String
|
||||
?? value as? String
|
||||
let interfaceStyle: UIUserInterfaceStyle = brightness == "dark" ? .dark : .light
|
||||
containerView.overrideUserInterfaceStyle = interfaceStyle
|
||||
button.overrideUserInterfaceStyle = interfaceStyle
|
||||
button.setNeedsUpdateConfiguration()
|
||||
}
|
||||
|
||||
deinit {
|
||||
channel.setMethodCallHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ final class StickyDateGlassHeaderPlatformView: NSObject, FlutterPlatformView {
|
||||
glassView.isUserInteractionEnabled = false
|
||||
glassView.clipsToBounds = true
|
||||
glassView.layer.cornerCurve = .continuous
|
||||
applyBrightness(from: arguments?["brightness"])
|
||||
|
||||
dateLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
dateLabel.text = text
|
||||
@@ -98,12 +99,20 @@ final class StickyDateGlassHeaderPlatformView: NSObject, FlutterPlatformView {
|
||||
])
|
||||
|
||||
channel.setMethodCallHandler { [weak self] call, result in
|
||||
guard call.method == "setLabel", let text = call.arguments as? String else {
|
||||
switch call.method {
|
||||
case "setLabel":
|
||||
guard let text = call.arguments as? String else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
return
|
||||
}
|
||||
self?.dateLabel.text = text
|
||||
result(nil)
|
||||
case "setBrightness":
|
||||
self?.applyBrightness(from: call.arguments)
|
||||
result(nil)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
return
|
||||
}
|
||||
self?.dateLabel.text = text
|
||||
result(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +120,11 @@ final class StickyDateGlassHeaderPlatformView: NSObject, FlutterPlatformView {
|
||||
glassView
|
||||
}
|
||||
|
||||
private func applyBrightness(from value: Any?) {
|
||||
let interfaceStyle: UIUserInterfaceStyle = value as? String == "dark" ? .dark : .light
|
||||
glassView.overrideUserInterfaceStyle = interfaceStyle
|
||||
}
|
||||
|
||||
deinit {
|
||||
channel.setMethodCallHandler(nil)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -21,6 +22,7 @@ class JumpToLatestButton extends HookWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final nativeChannel = useState<MethodChannel?>(null);
|
||||
final onPressedRef = useRef(onPressed)..value = onPressed;
|
||||
final brightness = context.theme.brightness.name;
|
||||
|
||||
useEffect(() {
|
||||
final channel = nativeChannel.value;
|
||||
@@ -31,6 +33,14 @@ class JumpToLatestButton extends HookWidget {
|
||||
return () => channel.setMethodCallHandler(null);
|
||||
}, [nativeChannel.value]);
|
||||
|
||||
useEffect(() {
|
||||
final channel = nativeChannel.value;
|
||||
if (channel != null) {
|
||||
unawaited(channel.invokeMethod<void>('setBrightness', brightness));
|
||||
}
|
||||
return null;
|
||||
}, [nativeChannel.value, brightness]);
|
||||
|
||||
final borderColor = context.colors.onSurface.withValues(alpha: 0.08);
|
||||
final usesNativeIosGlass = defaultTargetPlatform == TargetPlatform.iOS;
|
||||
|
||||
@@ -47,6 +57,8 @@ class JumpToLatestButton extends HookWidget {
|
||||
key: const ValueKey('channel-jump-to-latest-ios-glass'),
|
||||
viewType: _iosViewType,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
creationParams: <String, Object>{'brightness': brightness},
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
onPlatformViewCreated: (viewId) {
|
||||
nativeChannel.value = MethodChannel(
|
||||
'$_iosViewType/$viewId',
|
||||
|
||||
@@ -168,14 +168,16 @@ class _IosStickyDateGlass extends HookWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final nativeChannel = useState<MethodChannel?>(null);
|
||||
final brightness = context.theme.brightness.name;
|
||||
|
||||
useEffect(() {
|
||||
final channel = nativeChannel.value;
|
||||
if (channel != null) {
|
||||
unawaited(channel.invokeMethod<void>('setLabel', label));
|
||||
unawaited(channel.invokeMethod<void>('setBrightness', brightness));
|
||||
}
|
||||
return null;
|
||||
}, [nativeChannel.value, label]);
|
||||
}, [nativeChannel.value, label, brightness]);
|
||||
|
||||
return Semantics(
|
||||
header: true,
|
||||
@@ -189,7 +191,10 @@ class _IosStickyDateGlass extends HookWidget {
|
||||
key: const ValueKey('channel-sticky-date-header-ios-glass'),
|
||||
viewType: StickyDateHeader._iosViewType,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.transparent,
|
||||
creationParams: <String, Object>{'label': label},
|
||||
creationParams: <String, Object>{
|
||||
'label': label,
|
||||
'brightness': brightness,
|
||||
},
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
onPlatformViewCreated: (viewId) {
|
||||
nativeChannel.value = MethodChannel(
|
||||
|
||||
@@ -2,11 +2,22 @@ import 'package:buzz/features/channels/jump_to_latest_button.dart';
|
||||
import 'package:buzz/shared/theme/theme.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('uses the native glass control on iOS', (tester) async {
|
||||
testWidgets('keeps the native iOS glass in sync with the app theme', (
|
||||
tester,
|
||||
) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
const channel = MethodChannel('buzz/jump_to_latest_glass/41');
|
||||
final methodCalls = <MethodCall>[];
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
||||
call,
|
||||
) async {
|
||||
methodCalls.add(call);
|
||||
return null;
|
||||
});
|
||||
try {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@@ -17,11 +28,43 @@ void main() {
|
||||
|
||||
final nativeView = tester.widget<UiKitView>(find.byType(UiKitView));
|
||||
expect(nativeView.viewType, 'buzz/jump_to_latest_glass');
|
||||
expect(nativeView.creationParams, <String, Object>{
|
||||
'brightness': 'light',
|
||||
});
|
||||
expect(
|
||||
find.byKey(const ValueKey('channel-jump-to-latest-ios-glass')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
nativeView.onPlatformViewCreated!(41);
|
||||
await tester.pump();
|
||||
expect(
|
||||
methodCalls
|
||||
.lastWhere((call) => call.method == 'setBrightness')
|
||||
.arguments,
|
||||
'light',
|
||||
);
|
||||
|
||||
methodCalls.clear();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.dark(),
|
||||
home: Scaffold(body: JumpToLatestButton(onPressed: () {})),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
methodCalls
|
||||
.lastWhere((call) => call.method == 'setBrightness')
|
||||
.arguments,
|
||||
'dark',
|
||||
);
|
||||
} finally {
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
null,
|
||||
);
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,14 +2,25 @@ import 'package:buzz/features/channels/sticky_date_header.dart';
|
||||
import 'package:buzz/shared/theme/theme.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('uses native glass and updates its date on iOS', (tester) async {
|
||||
testWidgets('updates the native iOS glass date and app theme', (
|
||||
tester,
|
||||
) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
final state = ValueNotifier(
|
||||
const StickyDateHeaderState(label: 'Yesterday'),
|
||||
);
|
||||
const channel = MethodChannel('buzz/sticky_date_glass/42');
|
||||
final methodCalls = <MethodCall>[];
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
||||
call,
|
||||
) async {
|
||||
methodCalls.add(call);
|
||||
return null;
|
||||
});
|
||||
try {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@@ -20,15 +31,58 @@ void main() {
|
||||
|
||||
var nativeView = tester.widget<UiKitView>(find.byType(UiKitView));
|
||||
expect(nativeView.viewType, 'buzz/sticky_date_glass');
|
||||
expect(nativeView.creationParams, <String, Object>{'label': 'Yesterday'});
|
||||
expect(nativeView.creationParams, <String, Object>{
|
||||
'label': 'Yesterday',
|
||||
'brightness': 'light',
|
||||
});
|
||||
expect(find.byType(BackdropFilter), findsNothing);
|
||||
|
||||
nativeView.onPlatformViewCreated!(42);
|
||||
await tester.pump();
|
||||
expect(
|
||||
methodCalls.lastWhere((call) => call.method == 'setLabel').arguments,
|
||||
'Yesterday',
|
||||
);
|
||||
expect(
|
||||
methodCalls
|
||||
.lastWhere((call) => call.method == 'setBrightness')
|
||||
.arguments,
|
||||
'light',
|
||||
);
|
||||
|
||||
state.value = const StickyDateHeaderState(label: 'Today');
|
||||
await tester.pump();
|
||||
|
||||
nativeView = tester.widget<UiKitView>(find.byType(UiKitView));
|
||||
expect(nativeView.creationParams, <String, Object>{'label': 'Today'});
|
||||
expect(nativeView.creationParams, <String, Object>{
|
||||
'label': 'Today',
|
||||
'brightness': 'light',
|
||||
});
|
||||
expect(
|
||||
methodCalls.lastWhere((call) => call.method == 'setLabel').arguments,
|
||||
'Today',
|
||||
);
|
||||
|
||||
methodCalls.clear();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.dark(),
|
||||
home: Scaffold(body: StickyDateHeader(state: state)),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
methodCalls
|
||||
.lastWhere((call) => call.method == 'setBrightness')
|
||||
.arguments,
|
||||
'dark',
|
||||
);
|
||||
} finally {
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
null,
|
||||
);
|
||||
state.dispose();
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user