diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart index 5723c64c28a..1a8d7ac99d2 100644 --- a/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart @@ -1 +1,2 @@ export 'platform/catalyst_platform.dart'; +export 'platform_aware_builder/platform_aware_builder.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/io_platform.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/io_platform.dart index bf4faa30aa0..5b3db720fea 100644 --- a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/io_platform.dart +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/io_platform.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:catalyst_voices_shared/src/platform/platform_key.dart'; + final class CatalystPlatform { static bool get isAndroid => Platform.isAndroid; @@ -23,4 +25,18 @@ final class CatalystPlatform { static bool get isWebDesktop => false; static bool get isWindows => Platform.isWindows; + + static Map get identifiers => { + PlatformKey.android: isAndroid, + PlatformKey.desktop: isDesktop, + PlatformKey.fuchsia: isFuchsia, + PlatformKey.iOS: isIOS, + PlatformKey.linux: isLinux, + PlatformKey.macOS: isMacOS, + PlatformKey.mobile: isMobile, + PlatformKey.mobileWeb: isMobileWeb, + PlatformKey.web: isWeb, + PlatformKey.webDesktop: isWebDesktop, + PlatformKey.windows: isWindows, + }; } diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/platform_key.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/platform_key.dart new file mode 100644 index 00000000000..e8b6a7890d7 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/platform_key.dart @@ -0,0 +1,14 @@ +enum PlatformKey { + android, + desktop, + fuchsia, + iOS, + linux, + macOS, + mobile, + mobileWeb, + web, + webDesktop, + windows, + other, +} diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/stub_platform.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/stub_platform.dart index 539a794b214..ab1c6929bae 100644 --- a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/stub_platform.dart +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/stub_platform.dart @@ -1,3 +1,5 @@ +import 'package:catalyst_voices_shared/src/platform/platform_key.dart'; + final class CatalystPlatform { static bool get isAndroid { throw UnimplementedError('Stub CatalystPlatform'); @@ -43,5 +45,9 @@ final class CatalystPlatform { throw UnimplementedError('Stub CatalystPlatform'); } + static Map get identifiers { + throw UnimplementedError('Stub CatalystPlatform'); + } + const CatalystPlatform._(); } diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/web_platform.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/web_platform.dart index 4f74a8726f2..a7a9d074d79 100644 --- a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/web_platform.dart +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform/web_platform.dart @@ -1,3 +1,4 @@ +import 'package:catalyst_voices_shared/src/platform/platform_key.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:web/web.dart'; @@ -33,6 +34,20 @@ final class CatalystPlatform { ]; return mobileIdentifiers.any(userAgent.contains); } + + static Map get identifiers => { + PlatformKey.android: isAndroid, + PlatformKey.desktop: isDesktop, + PlatformKey.fuchsia: isFuchsia, + PlatformKey.iOS: isIOS, + PlatformKey.linux: isLinux, + PlatformKey.macOS: isMacOS, + PlatformKey.mobile: isMobile, + PlatformKey.mobileWeb: isMobileWeb, + PlatformKey.web: isWeb, + PlatformKey.webDesktop: isWebDesktop, + PlatformKey.windows: isWindows, + }; const CatalystPlatform._(); } diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform_aware_builder/platform_aware_builder.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform_aware_builder/platform_aware_builder.dart new file mode 100644 index 00000000000..8a444265191 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/platform_aware_builder/platform_aware_builder.dart @@ -0,0 +1,96 @@ +import 'package:catalyst_voices_shared/src/platform/catalyst_platform.dart'; +import 'package:catalyst_voices_shared/src/platform/platform_key.dart'; +import 'package:flutter/widgets.dart'; + +// A [PlatformAwareBuilder] is a StatelessWidget that is aware of the current +// platform. +// +// This is an abstract widget that has a required argument [builder] that can +// consume platform-specific data automatically based on platform that is +// detected. +// +// The platform detection happens in [CatalystPlatform]. +// +// The widget accepts an argument for each specific platform defined in +// [PlatformKey]. The platform specific [data] is selected when two conditions +// are verified at the same time: +// - the platform is detected +// - the platform-specific argument is present +// In case those conditions are not verified the [other] argument is used (and +// because of this it is required). +// The type of the platform specific data is generic. +// +// A simple usage is to render a string based on the platform: +// +// ```dart +// PlatformWidgetBuilder( +// android: 'This is an Android platform.', +// other: 'This is an other platform.', +// builder: (context, title) => Text(title!), +// ); +// ``` +// +// or to have a specific Padding: +// +// ```dart +// PlatformWidgetBuilder( +// android: EdgeInsets.all(10.0), +// other: EdgeInsets.all(40.0), +// builder: (context, padding) => Padding( +// padding: padding, +// child: Text('This is an example.') +// ), +// ); +// ``` + +class PlatformAwareBuilder extends StatelessWidget { + final Widget Function(BuildContext context, T? data) builder; + final Map _platformData; + + PlatformAwareBuilder({ + super.key, + required this.builder, + T? android, + T? desktop, + T? fuchsia, + T? iOS, + T? linux, + T? macOS, + T? mobile, + T? mobileWeb, + T? web, + T? webDesktop, + T? windows, + required T other, + }) : _platformData = { + PlatformKey.android: android, + PlatformKey.desktop: desktop, + PlatformKey.fuchsia: fuchsia, + PlatformKey.iOS: iOS, + PlatformKey.linux: linux, + PlatformKey.macOS: macOS, + PlatformKey.mobile: mobile, + PlatformKey.mobileWeb: mobileWeb, + PlatformKey.web: web, + PlatformKey.webDesktop: webDesktop, + PlatformKey.windows: windows, + PlatformKey.other: other, + }; + + @override + Widget build(BuildContext context) { + return builder(context, _getPlatformData()); + } + + T _getPlatformData() { + final currentPlatformKey = CatalystPlatform.identifiers.entries + .firstWhere( + // We select the platform only if the platform-specific data + // is also present. + (entry) => entry.value && (_platformData[entry.key] != null), + orElse: () => const MapEntry(PlatformKey.other, true), + ) + .key; + return _platformData[currentPlatformKey]!; + } +} diff --git a/catalyst_voices/packages/catalyst_voices_shared/pubspec.yaml b/catalyst_voices/packages/catalyst_voices_shared/pubspec.yaml index 9729ed1af42..56359919b80 100644 --- a/catalyst_voices/packages/catalyst_voices_shared/pubspec.yaml +++ b/catalyst_voices/packages/catalyst_voices_shared/pubspec.yaml @@ -15,5 +15,7 @@ dependencies: dev_dependencies: catalyst_analysis: path: ../../../catalyst_voices_packages/catalyst_analysis + flutter_test: + sdk: flutter test: ^1.24.9 diff --git a/catalyst_voices/packages/catalyst_voices_shared/test/src/catalyst_voices_shared_test.dart b/catalyst_voices/packages/catalyst_voices_shared/test/src/catalyst_voices_shared_test.dart index ab73b3a234a..7430153ff4e 100644 --- a/catalyst_voices/packages/catalyst_voices_shared/test/src/catalyst_voices_shared_test.dart +++ b/catalyst_voices/packages/catalyst_voices_shared/test/src/catalyst_voices_shared_test.dart @@ -1 +1 @@ -void main() {} +export 'platform_aware_builder/platform_aware_builder_test.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_shared/test/src/platform_aware_builder/platform_aware_builder_test.dart b/catalyst_voices/packages/catalyst_voices_shared/test/src/platform_aware_builder/platform_aware_builder_test.dart new file mode 100644 index 00000000000..fac277aba99 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_shared/test/src/platform_aware_builder/platform_aware_builder_test.dart @@ -0,0 +1,26 @@ +import 'package:catalyst_voices_shared/src/catalyst_voices_shared.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + + Widget buildApp() => MaterialApp( + home: Scaffold( + body: PlatformAwareBuilder( + other: 'other', + builder: (context, platformData) => Text(platformData!), + ), + ), + ); + + group('Test platform detection', () { + testWidgets('PlatformWidgetBuilder fallbacks to other', (tester) async { + await tester.pumpWidget(buildApp()); + // Verify the Widget renders properly + expect(find.byType(Text), findsOneWidget); + // Check the output contains the platform that was tested. + expect(find.text('other'), findsOneWidget); + + }); + }); +}