Addresses #63507, and is a follow up to the engine PR https://github.com/flutter/engine/pull/46857
Changes the font family string when attempting to use Apple system fonts to the new proxies added by the engine. For the "Text" font this will be more secure in the future against possible changes to Apple's API. For the "Display" font, this will now work correctly when it didn't before.
I checked the letter spacing values against a native app for all font sizes between 17-28. I made a few adjustments to better match native, but especially for the "Text" font we were either really close, or close enough to not make a large breaking change to default fonts worth it.
| Before | After |
| ------------- | ------------- |
| <img width="466" alt="Screenshot 2023-11-02 at 11 45 12â¯AM" src="https://github.com/flutter/flutter/assets/58190796/627ed8ac-d848-4f71-aa62-a467b8aac62d"> | <img width="383" alt="Screenshot 2023-11-02 at 11 46 25â¯AM" src="https://github.com/flutter/flutter/assets/58190796/9a502021-7d2b-4e14-98f1-86971b3830a5"> |
The smaller text in both the before and after should be the same. The large system font that Flutter used before was incorrect, which caused it to look more spread out. Now we use the correct font.
I previously made a PR (#136140) that used `switch` expressions to make some parts of the Flutter codebase easier to understand. It was assigned to the framework team, and @christopherfujino let me know that it was too large to effectively review and recommended breaking it up into smaller pull requests.
Here's a PR that only targets files in the `dev/` directory. Hopefully this will be easier to work with!
(solves issue https://github.com/flutter/flutter/issues/136139)
This enables our various date picker classes to have a null `initialDate`.
It also fixes the logic of some of the widgets which used to do something when you _changed_ the `initial*` parameters, which is wrong for `initial*` properties (they by definition should only impact the initial state) and wrong for properties in general (behaviour should not change based on whether the widget was built with a new value or not, that violates the reactive design principles).
Fixes https://github.com/flutter/flutter/issues/638.
This PR aims to support Android's predictive back gesture when popping the entire Flutter app. Predictive route transitions between routes inside of a Flutter app will come later.
<img width="200" src="https://user-images.githubusercontent.com/389558/217918109-945febaa-9086-41cc-a476-1a189c7831d8.gif" />
### Trying it out
If you want to try this feature yourself, here are the necessary steps:
1. Run Android 33 or above.
1. Enable the feature flag for predictive back on the device under "Developer
options".
1. Create a Flutter project, or clone [my example project](https://github.com/justinmc/flutter_predictive_back_examples).
1. Set `android:enableOnBackInvokedCallback="true"` in
android/app/src/main/AndroidManifest.xml (already done in the example project).
1. Check out this branch.
1. Run the app. Perform a back gesture (swipe from the left side of the
screen).
You should see the predictive back animation like in the animation above and be able to commit or cancel it.
### go_router support
go_router works with predictive back out of the box because it uses a Navigator internally that dispatches NavigationNotifications!
~~go_router can be supported by adding a listener to the router and updating SystemNavigator.setFrameworkHandlesBack.~~
Similar to with nested Navigators, nested go_routers is supported by using a PopScope widget.
<details>
<summary>Full example of nested go_routers</summary>
```dart
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(_MyApp());
class _MyApp extends StatelessWidget {
final GoRouter router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) => _HomePage(),
),
GoRoute(
path: '/nested_navigators',
builder: (BuildContext context, GoRouterState state) => _NestedGoRoutersPage(),
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
class _HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Nested Navigators Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Home Page'),
const Text('A system back gesture here will exit the app.'),
const SizedBox(height: 20.0),
ListTile(
title: const Text('Nested go_router route'),
subtitle: const Text('This route has another go_router in addition to the one used with MaterialApp above.'),
onTap: () {
context.push('/nested_navigators');
},
),
],
),
),
);
}
}
class _NestedGoRoutersPage extends StatefulWidget {
@override
State<_NestedGoRoutersPage> createState() => _NestedGoRoutersPageState();
}
class _NestedGoRoutersPageState extends State<_NestedGoRoutersPage> {
late final GoRouter _router;
final GlobalKey<NavigatorState> _nestedNavigatorKey = GlobalKey<NavigatorState>();
// If the nested navigator has routes that can be popped, then we want to
// block the root navigator from handling the pop so that the nested navigator
// can handle it instead.
bool get _popEnabled {
// canPop will throw an error if called before build. Is this the best way
// to avoid that?
return _nestedNavigatorKey.currentState == null ? true : !_router.canPop();
}
void _onRouterChanged() {
// Here the _router reports the location correctly, but canPop is still out
// of date. Hence the post frame callback.
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
setState(() {});
});
}
@override
void initState() {
super.initState();
final BuildContext rootContext = context;
_router = GoRouter(
navigatorKey: _nestedNavigatorKey,
routes: [
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) => _LinksPage(
title: 'Nested once - home route',
backgroundColor: Colors.indigo,
onBack: () {
rootContext.pop();
},
buttons: <Widget>[
TextButton(
onPressed: () {
context.push('/two');
},
child: const Text('Go to another route in this nested Navigator'),
),
],
),
),
GoRoute(
path: '/two',
builder: (BuildContext context, GoRouterState state) => _LinksPage(
backgroundColor: Colors.indigo.withBlue(255),
title: 'Nested once - page two',
),
),
],
);
_router.addListener(_onRouterChanged);
}
@override
void dispose() {
_router.removeListener(_onRouterChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
popEnabled: _popEnabled,
onPopped: (bool success) {
if (success) {
return;
}
_router.pop();
},
child: Router<Object>.withConfig(
restorationScopeId: 'router-2',
config: _router,
),
);
}
}
class _LinksPage extends StatelessWidget {
const _LinksPage ({
required this.backgroundColor,
this.buttons = const <Widget>[],
this.onBack,
required this.title,
});
final Color backgroundColor;
final List<Widget> buttons;
final VoidCallback? onBack;
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(title),
//const Text('A system back here will go back to Nested Navigators Page One'),
...buttons,
TextButton(
onPressed: onBack ?? () {
context.pop();
},
child: const Text('Go back'),
),
],
),
),
);
}
}
```
</details>
### Resources
Fixes https://github.com/flutter/flutter/issues/109513
Depends on engine PR https://github.com/flutter/engine/pull/39208✔️
Design doc: https://docs.google.com/document/d/1BGCWy1_LRrXEB6qeqTAKlk-U2CZlKJ5xI97g45U7azk/edit#
Migration guide: https://github.com/flutter/website/pull/8952
Deprecate `textScaleFactor` in favor of `textScaler`, in preparation for Android 14 [Non-linear font scaling to 200%](https://developer.android.com/about/versions/14/features#non-linear-font-scaling). The `TextScaler` class can be moved to `dart:ui` in the future, if we decide to use the Android platform API or AndroidX to get the scaling curve instead of hard coding the curve in the framework.
I haven't put the Flutter version in the deprecation message so the analyzer checks are failing. Will do so after I finish the migration guide.
**Why `TextScaler.textScaleFactor`**
The author of a `TextScaler` subclass should provide a fallback `textScaleFactor`. By making `TextScaler` also contain the `textScaleFactor` information it also makes it easier to migrate: if a widget overrides `MediaQueryData.textScaler` in the tree, for unmigrated widgets in the subtree it would also have to override `MediaQueryData.textScaleFactor`, and that makes it difficult to remove `MediaQueryData.textScaleFactor` in the future.
## A full list of affected APIs in this PR
Deprecated: The method/getter/setter/argument is annotated with a `@Deprecated()` annotation in this PR, and the caller should replace it with `textScaler` instead. Unless otherwise specified there will be a Flutter fix available to help with migration but it's still recommended to migrate case-by-case.
**Replaced**: The method this `textScaleFactor` argument belongs to is rarely called directly by user code and is not overridden by any of the registered custom tests, so the argument is directly replaced by `TextScaler`.
**To Be Deprecated**: The method/getter/setter/argument can't be deprecated in this PR because a registered customer test depends on it and a Flutter fix isn't available (or the test was run without applying flutter fixes first). This method/getter/setter/argument will be deprecated in a followup PR once the registered test is migrated.
### `Painting` Library
| Affected API | State of `textScaleFactor` | Comment |
| --- | --- | --- |
| `InlineSpan.build({ double textScaleFactor = 1.0 })` argument | **Replaced** | |
| `TextStyle.getParagraphStyle({ double TextScaleFactor = 1.0 })` argument | **Replaced** | |
| `TextStyle.getTextStyle({ double TextScaleFactor = 1.0 })` argument| Deprecated | Can't replace: c47fd38dca/super_editor/lib/src/infrastructure/super_textfield/desktop/desktop_textfield.dart (L1903-L1905)|
| `TextPainter({ double TextScaleFactor = 1.0 })` constructor argument | Deprecated | |
| `TextPainter.textScaleFactor` getter and setter | Deprecated | No Flutter Fix, not expressible yet |
| `TextPainter.computeWidth({ double TextScaleFactor = 1.0 })` argument | Deprecated | |
| `TextPainter.computeMaxIntrinsicWidth({ double TextScaleFactor = 1.0 })` argument | Deprecated | |
### `Rendering` Library
| Affected API | State of `textScaleFactor` | Comment |
| --- | --- | --- |
| `RenderEditable({ double TextScaleFactor = 1.0 })` constructor argument | Deprecated | |
| `RenderEditable.textScaleFactor` getter and setter | Deprecated | No Flutter Fix, not expressible yet |
| `RenderParagraph({ double TextScaleFactor = 1.0 })` constructor argument | Deprecated | |
| `RenderParagraph.textScaleFactor` getter and setter | Deprecated | No Flutter Fix, not expressible yet |
### `Widgets` Library
| Affected API | State of `textScaleFactor` | Comment |
| --- | --- | --- |
| `MediaQueryData({ double TextScaleFactor = 1.0 })` constructor argument | **To Be Deprecated** | cd7b93532e/packages/flutter_markdown/test/text_scale_factor_test.dart (LL39C21-L39C35) |
| `MediaQueryData.textScaleFactor` getter | Deprecated | |
| `MediaQueryData.copyWith({ double? TextScaleFactor })` argument | Deprecated | |
| `MediaQuery.maybeTextScaleFactorOf(BuildContext context)` static method | Deprecated | No Flutter Fix, not expressible yet |
| `MediaQuery.textScaleFactorOf(BuildContext context)` static method | **To Be Deprecated** | cd7b93532e/packages/flutter_markdown/lib/src/_functions_io.dart (L68-L70), No Flutter Fix, not expressible yet |
| `RichText({ double TextScaleFactor = 1.0 })` constructor argument | **To Be Deprecated** | cd7b93532e/packages/flutter_markdown/lib/src/builder.dart (L829-L843) |
| `RichText.textScaleFactor` getter | **To Be Deprecated** | A constructor argument can't be deprecated right away|
| `Text({ double? TextScaleFactor = 1.0 })` constructor argument | **To Be Deprecated** | 914d120da1/packages/rfw/lib/src/flutter/core_widgets.dart (L647) , No Flutter Fix because of https://github.com/dart-lang/sdk/issues/52664 |
| `Text.rich({ double? TextScaleFactor = 1.0 })` constructor argument | **To Be Deprecated** | The default constructor has an argument that can't be deprecated right away. No Flutter Fix because of https://github.com/dart-lang/sdk/issues/52664 |
| `Text.textScaleFactor` getter | **To Be Deprecated** | A constructor argument can't be deprecated right away |
| `EditableText({ double? TextScaleFactor = 1.0 })` constructor argument | Deprecated | No Flutter Fix because of https://github.com/dart-lang/sdk/issues/52664 |
| `EditableText.textScaleFactor` getter | Deprecated | |
### `Material` Library
| Affected API | State of `textScaleFactor` | Comment |
| --- | --- | --- |
| `SelectableText({ double? TextScaleFactor = 1.0 })` constructor argument | **To Be Deprecated** | cd7b93532e/packages/flutter_markdown/lib/src/builder.dart (L829-L843), No Flutter Fix because of https://github.com/dart-lang/sdk/issues/52664 |
| `SelectableText.rich({ double? TextScaleFactor = 1.0 })` constructor argument | **To Be Deprecated** | The default constructor has an argument that can't be deprecated right away. No Flutter Fix because of https://github.com/dart-lang/sdk/issues/52664 |
| `SelectableText.textScaleFactor` getter | **To Be Deprecated** | A constructor argument can't be deprecated right away |
A lot of material widgets (`Slider`, `RangeSlider`, `TimePicker`, and different types of buttons) also change their layout based on `textScaleFactor`. These need to be handled in a case-by-case fashion and will be migrated in follow-up PRs.
Updated tests in dev, examples/api, and tests/widgets to ensure that
they continue to pass when the default for `ThemeData.useMaterial3` is
changed to true.
This is the final set of changes required for
https://github.com/flutter/flutter/issues/127064.
* Update parameters to the `styleFrom` button methods.
* Updated the Flutter fix data to point to this PR.
* Updated handling of background color to better maintain backwards compatibility with previous API.
* Added the new Material 3 colors to ColorScheme.
* Deprecated primaryVariant and secondaryVariant colors in the ColorScheme.
* Added a flutter fix for the deprecated colors.