Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cupertino_ui/lib/cupertino_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export 'src/list_tile.dart';
export 'src/localizations.dart';
export 'src/magnifier.dart';
export 'src/menu_anchor.dart';
export 'src/migration_utility.dart';
export 'src/nav_bar.dart';
export 'src/page_scaffold.dart';
export 'src/picker.dart';
Expand Down
136 changes: 136 additions & 0 deletions packages/cupertino_ui/lib/src/migration_utility.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:cupertino_ui/cupertino_ui.dart' as modern; // ignore: prefer_relative_imports
import 'package:flutter/cupertino.dart' as legacy;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep an eye out for any side effects of this import, but I can't think of any specific reason why it should complicate things.

import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

/// A bridge widget for applications that have migrated to `package:cupertino_ui`
/// but still contain subtrees or package dependencies using `package:flutter/cupertino.dart`.
///
/// This widget maps the parent [modern.CupertinoThemeData] from `package:cupertino_ui` into
/// a legacy [legacy.CupertinoThemeData] and overrides [legacy.Localizations] with delegates
/// so that legacy widgets in [child] can resolve `legacy.CupertinoTheme.of(context)` and
/// `legacy.CupertinoLocalizations.of(context)`.
///
/// ## App-wide usage
///
/// The recommended way to use this bridge across an entire application is to
/// wrap the app once using [modern.CupertinoApp.builder]:
///
/// ```dart
/// import 'package:cupertino_ui/cupertino_ui.dart';
///
/// void main() {
/// runApp(const MyApp());
/// }
///
/// class MyApp extends StatelessWidget {
/// const MyApp({super.key});
///
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoApp(
/// theme: const CupertinoThemeData(
/// primaryColor: CupertinoColors.systemIndigo,
/// ),
/// builder: (BuildContext context, Widget? child) {
/// return CupertinoUiCompatibilityBridge(child: child!);
/// },
/// home: const HomeScreen(),
/// );
/// }
/// }
/// ```
///
/// ## Subtree usage
///
/// Alternatively, wrap specific legacy package widgets or subtrees:
///
/// ```dart
/// import 'package:cupertino_ui/cupertino_ui.dart';
/// import 'package:some_legacy_package/some_legacy_package.dart';
///
/// class MyScreen extends StatelessWidget {
/// const MyScreen({super.key});
///
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoPageScaffold(
/// navigationBar: const CupertinoNavigationBar(
/// middle: Text('Migrated Screen'),
/// ),
/// child: CupertinoUiCompatibilityBridge(
/// child: LegacyPackageWidget(),
/// ),
/// );
/// }
/// }
/// ```
@Deprecated(
'CupertinoUiCompatibilityBridge is a temporary migration utility intended for use only while '
'dependencies are being migrated to package:cupertino_ui. '
'This feature was deprecated to indicate it will be removed in a future release.',
)
class CupertinoUiCompatibilityBridge extends StatelessWidget {
/// Creates a [CupertinoUiCompatibilityBridge].
@Deprecated(
'CupertinoUiCompatibilityBridge is a temporary migration utility intended for use only while '
'dependencies are being migrated to package:cupertino_ui. '
'This feature was deprecated to indicate it will be removed in a future release.',
)
const CupertinoUiCompatibilityBridge({super.key, required this.child, this.delegates});

/// The widget below this widget in the tree.
final Widget child;

/// The localizations delegates for legacy widgets.
///
/// Defaults to `[GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate]`.
final List<LocalizationsDelegate<dynamic>>? delegates;

@override
Widget build(BuildContext context) {
final modern.CupertinoThemeData modernTheme = modern.CupertinoTheme.of(context);

return legacy.CupertinoTheme(
data: _mapToLegacy(modernTheme),
child: legacy.Localizations.override(
context: context,
delegates:
delegates ??
const <LocalizationsDelegate<dynamic>>[
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
child: child,
),
);
}

legacy.CupertinoThemeData _mapToLegacy(modern.CupertinoThemeData modernTheme) {
return legacy.CupertinoThemeData(
brightness: modernTheme.brightness,
primaryColor: modernTheme.primaryColor,
primaryContrastingColor: modernTheme.primaryContrastingColor,
barBackgroundColor: modernTheme.barBackgroundColor,
scaffoldBackgroundColor: modernTheme.scaffoldBackgroundColor,
selectionHandleColor: modernTheme.selectionHandleColor,
applyThemeToAll: modernTheme.applyThemeToAll,
textTheme: legacy.CupertinoTextThemeData(
primaryColor: modernTheme.primaryColor,
textStyle: modernTheme.textTheme.textStyle,
actionTextStyle: modernTheme.textTheme.actionTextStyle,
actionSmallTextStyle: modernTheme.textTheme.actionSmallTextStyle,
tabLabelTextStyle: modernTheme.textTheme.tabLabelTextStyle,
navTitleTextStyle: modernTheme.textTheme.navTitleTextStyle,
navLargeTitleTextStyle: modernTheme.textTheme.navLargeTitleTextStyle,
navActionTextStyle: modernTheme.textTheme.navActionTextStyle,
pickerTextStyle: modernTheme.textTheme.pickerTextStyle,
dateTimePickerTextStyle: modernTheme.textTheme.dateTimePickerTextStyle,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Adds CupertinoUiCompatibilityBridge for legacy flutter/cupertino.dart compatibility.
version: minor
182 changes: 182 additions & 0 deletions packages/cupertino_ui/test/migration_utility_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: deprecated_member_use

import 'package:cupertino_ui/cupertino_ui.dart' as modern;
import 'package:flutter/cupertino.dart' as legacy;
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('CupertinoUiCompatibilityBridge provides mapped CupertinoThemeData properties', (
WidgetTester tester,
) async {
legacy.CupertinoThemeData? capturedLegacyTheme;

const modernThemeData = modern.CupertinoThemeData(
brightness: Brightness.dark,
primaryColor: Color(0xFF00FF00),
primaryContrastingColor: Color(0xFFFF0000),
barBackgroundColor: Color(0xFF111111),
scaffoldBackgroundColor: Color(0xFF222222),
);

await tester.pumpWidget(
modern.CupertinoApp(
theme: modernThemeData,
home: modern.CupertinoUiCompatibilityBridge(
child: Builder(
builder: (BuildContext context) {
capturedLegacyTheme = legacy.CupertinoTheme.of(context);
return const SizedBox.shrink();
},
),
),
),
);

expect(capturedLegacyTheme, isNotNull);
expect(capturedLegacyTheme!.brightness, modernThemeData.brightness);
expect(capturedLegacyTheme!.primaryColor, modernThemeData.primaryColor);
expect(capturedLegacyTheme!.primaryContrastingColor, modernThemeData.primaryContrastingColor);
expect(capturedLegacyTheme!.barBackgroundColor, modernThemeData.barBackgroundColor);
expect(capturedLegacyTheme!.scaffoldBackgroundColor, modernThemeData.scaffoldBackgroundColor);
});

testWidgets('CupertinoUiCompatibilityBridge maps CupertinoTextThemeData properties', (
WidgetTester tester,
) async {
legacy.CupertinoThemeData? capturedLegacyTheme;

const modernTextTheme = modern.CupertinoTextThemeData(
navTitleTextStyle: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
actionTextStyle: TextStyle(fontSize: 16.0, color: Color(0xFF123456)),
);

const modernThemeData = modern.CupertinoThemeData(textTheme: modernTextTheme);

await tester.pumpWidget(
modern.CupertinoApp(
theme: modernThemeData,
home: modern.CupertinoUiCompatibilityBridge(
child: Builder(
builder: (BuildContext context) {
capturedLegacyTheme = legacy.CupertinoTheme.of(context);
return const SizedBox.shrink();
},
),
),
),
);

expect(capturedLegacyTheme, isNotNull);
expect(
capturedLegacyTheme!.textTheme.navTitleTextStyle.fontSize,
modernTextTheme.navTitleTextStyle.fontSize,
);
expect(
capturedLegacyTheme!.textTheme.navTitleTextStyle.fontWeight,
modernTextTheme.navTitleTextStyle.fontWeight,
);
expect(
capturedLegacyTheme!.textTheme.actionTextStyle.color,
modernTextTheme.actionTextStyle.color,
);
});

testWidgets('CupertinoUiCompatibilityBridge provides CupertinoLocalizations', (
WidgetTester tester,
) async {
legacy.CupertinoLocalizations? capturedLegacyLocalizations;

await tester.pumpWidget(
modern.CupertinoApp(
home: modern.CupertinoUiCompatibilityBridge(
child: Builder(
builder: (BuildContext context) {
capturedLegacyLocalizations = legacy.CupertinoLocalizations.of(context);
return const SizedBox.shrink();
},
),
),
),
);

expect(capturedLegacyLocalizations, isNotNull);
expect(capturedLegacyLocalizations!.datePickerDateOrder, isNotNull);
expect(capturedLegacyLocalizations!.modalBarrierDismissLabel, isNotEmpty);
});

testWidgets('CupertinoUiCompatibilityBridge allows rendering legacy Cupertino widgets', (
WidgetTester tester,
) async {
await tester.pumpWidget(
modern.CupertinoApp(
home: modern.CupertinoUiCompatibilityBridge(
child: legacy.CupertinoPageScaffold(
navigationBar: const legacy.CupertinoNavigationBar(middle: Text('Legacy Title')),
child: Center(
child: legacy.CupertinoButton(onPressed: () {}, child: const Text('Legacy Button')),
),
),
),
),
);

expect(find.text('Legacy Title'), findsOneWidget);
expect(find.text('Legacy Button'), findsOneWidget);
expect(find.byType(legacy.CupertinoButton), findsOneWidget);
expect(find.byType(legacy.CupertinoPageScaffold), findsOneWidget);
});

testWidgets('CupertinoUiCompatibilityBridge supports custom delegates', (
WidgetTester tester,
) async {
legacy.CupertinoLocalizations? capturedLegacyLocalizations;

await tester.pumpWidget(
modern.CupertinoApp(
home: modern.CupertinoUiCompatibilityBridge(
delegates: const <LocalizationsDelegate<dynamic>>[GlobalCupertinoLocalizations.delegate],
child: Builder(
builder: (BuildContext context) {
capturedLegacyLocalizations = legacy.CupertinoLocalizations.of(context);
return const SizedBox.shrink();
},
),
),
),
);

expect(capturedLegacyLocalizations, isNotNull);
expect(capturedLegacyLocalizations!.modalBarrierDismissLabel, isNotEmpty);
});

testWidgets('CupertinoUiCompatibilityBridge works at root in CupertinoApp.builder', (
WidgetTester tester,
) async {
await tester.pumpWidget(
modern.CupertinoApp(
builder: (BuildContext context, Widget? child) {
return modern.CupertinoUiCompatibilityBridge(child: child!);
},
home: modern.CupertinoPageScaffold(
child: Column(
children: <Widget>[
modern.CupertinoButton(onPressed: () {}, child: const Text('Modern Button')),
legacy.CupertinoButton(onPressed: () {}, child: const Text('Legacy Button')),
],
),
),
),
);

expect(find.text('Modern Button'), findsOneWidget);
expect(find.text('Legacy Button'), findsOneWidget);
expect(find.byType(modern.CupertinoButton), findsOneWidget);
expect(find.byType(legacy.CupertinoButton), findsOneWidget);
});
}
1 change: 1 addition & 0 deletions packages/material_ui/lib/material_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export 'src/menu_button_theme.dart';
export 'src/menu_style.dart';
export 'src/menu_theme.dart';
export 'src/mergeable_material.dart';
export 'src/migration_utility.dart';
export 'src/motion.dart';
export 'src/navigation_bar.dart';
export 'src/navigation_bar_theme.dart';
Expand Down
Loading
Loading