Unverified Commit b8a035a3 authored by xster's avatar xster Committed by GitHub

Adds CupertinoTheme (#23759)

parent 8b34a12d
e07cc0cb4fdf912062e71a6fd97cc91478d6e3b9
c47f1308188dca65b3899228cac37f252ea8b411
......@@ -13,8 +13,11 @@ class CupertinoProgressIndicatorDemo extends StatelessWidget {
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
previousPageTitle: 'Back',
middle: const Text('Cupertino Activity Indicator'),
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
middle: const Text('Activity Indicator'),
trailing: CupertinoDemoDocumentationButton(routeName),
),
child: const Center(
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
......@@ -19,64 +18,71 @@ class _CupertinoButtonDemoState extends State<CupertinoButtonsDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cupertino Buttons'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoButtonsDemo.routeName)],
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Buttons'),
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
trailing: CupertinoDemoDocumentationButton(CupertinoButtonsDemo.routeName),
),
body: Column(
children: <Widget> [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'iOS themed buttons are flat. They can have borders or backgrounds but '
'only when necessary.'
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget> [
Text(_pressedCount > 0
? 'Button pressed $_pressedCount time${_pressedCount == 1 ? "" : "s"}'
: ' '),
const Padding(padding: EdgeInsets.all(12.0)),
Align(
alignment: const Alignment(0.0, -0.2),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CupertinoButton(
child: const Text('Cupertino Button'),
onPressed: () {
setState(() { _pressedCount += 1; });
}
),
const CupertinoButton(
child: Text('Disabled'),
onPressed: null,
),
],
),
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: SafeArea(
child: Column(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'iOS themed buttons are flat. They can have borders or backgrounds but '
'only when necessary.'
),
const Padding(padding: EdgeInsets.all(12.0)),
CupertinoButton(
child: const Text('With Background'),
color: CupertinoColors.activeBlue,
onPressed: () {
setState(() { _pressedCount += 1; });
}
),
const Padding(padding: EdgeInsets.all(12.0)),
const CupertinoButton(
child: Text('Disabled'),
color: CupertinoColors.activeBlue,
onPressed: null,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget> [
Text(_pressedCount > 0
? 'Button pressed $_pressedCount time${_pressedCount == 1 ? "" : "s"}'
: ' '),
const Padding(padding: EdgeInsets.all(12.0)),
Align(
alignment: const Alignment(0.0, -0.2),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CupertinoButton(
child: const Text('Cupertino Button'),
onPressed: () {
setState(() { _pressedCount += 1; });
}
),
const CupertinoButton(
child: Text('Disabled'),
onPressed: null,
),
],
),
),
const Padding(padding: EdgeInsets.all(12.0)),
CupertinoButton.filled(
child: const Text('With Background'),
onPressed: () {
setState(() { _pressedCount += 1; });
}
),
const Padding(padding: EdgeInsets.all(12.0)),
const CupertinoButton.filled(
child: Text('Disabled'),
onPressed: null,
),
],
),
],
)
),
],
),
],
),
)
);
}
......
......@@ -52,11 +52,7 @@ class CupertinoNavigationDemo extends StatelessWidget {
// Prevent swipe popping of this page. Use explicit exit buttons only.
onWillPop: () => Future<bool>.value(true),
child: DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
style: CupertinoTheme.of(context).textTheme.textStyle,
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
......@@ -241,7 +237,6 @@ class Tab1RowItem extends StatelessWidget {
CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.plus_circled,
color: CupertinoColors.activeBlue,
semanticLabel: 'Add',
),
onPressed: () { },
......@@ -249,7 +244,6 @@ class Tab1RowItem extends StatelessWidget {
CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.share,
color: CupertinoColors.activeBlue,
semanticLabel: 'Share',
),
onPressed: () { },
......@@ -352,8 +346,7 @@ class Tab1ItemPageState extends State<Tab1ItemPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CupertinoButton(
color: CupertinoColors.activeBlue,
CupertinoButton.filled(
minSize: 30.0,
padding: const EdgeInsets.symmetric(horizontal: 24.0),
borderRadius: BorderRadius.circular(32.0),
......@@ -367,12 +360,11 @@ class Tab1ItemPageState extends State<Tab1ItemPage> {
),
onPressed: () { },
),
CupertinoButton(
color: CupertinoColors.activeBlue,
CupertinoButton.filled(
minSize: 30.0,
padding: EdgeInsets.zero,
borderRadius: BorderRadius.circular(32.0),
child: const Icon(CupertinoIcons.ellipsis, color: CupertinoColors.white),
child: const Icon(CupertinoIcons.ellipsis),
onPressed: () { },
),
],
......@@ -722,7 +714,11 @@ class CupertinoDemoTab3 extends StatelessWidget {
trailing: trailingButtons,
),
child: DecoratedBox(
decoration: const BoxDecoration(color: Color(0xFFEFEFF4)),
decoration: BoxDecoration(
color: CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.extraLightBackgroundGray
: CupertinoColors.darkBackgroundGray,
),
child: ListView(
children: <Widget>[
const Padding(padding: EdgeInsets.only(top: 32.0)),
......@@ -736,9 +732,9 @@ class CupertinoDemoTab3 extends StatelessWidget {
);
},
child: Container(
decoration: const BoxDecoration(
color: CupertinoColors.white,
border: Border(
decoration: BoxDecoration(
color: CupertinoTheme.of(context).scaffoldBackgroundColor,
border: const Border(
top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
),
......@@ -750,10 +746,10 @@ class CupertinoDemoTab3 extends StatelessWidget {
top: false,
bottom: false,
child: Row(
children: const <Widget>[
children: <Widget>[
Text(
'Sign in',
style: TextStyle(color: CupertinoColors.activeBlue),
style: TextStyle(color: CupertinoTheme.of(context).primaryColor),
)
],
),
......@@ -791,8 +787,7 @@ class Tab3Dialog extends StatelessWidget {
color: Color(0xFF646464),
),
const Padding(padding: EdgeInsets.only(top: 18.0)),
CupertinoButton(
color: CupertinoColors.activeBlue,
CupertinoButton.filled(
child: const Text('Sign in'),
onPressed: () {
Navigator.pop(context);
......
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../gallery/demo.dart';
......@@ -34,9 +33,9 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
Widget _buildMenu(List<Widget> children) {
return Container(
decoration: const BoxDecoration(
color: CupertinoColors.white,
border: Border(
decoration: BoxDecoration(
color: CupertinoTheme.of(context).scaffoldBackgroundColor,
border: const Border(
top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
),
......@@ -47,16 +46,9 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
child: SafeArea(
top: false,
bottom: false,
child: DefaultTextStyle(
style: const TextStyle(
letterSpacing: -0.24,
fontSize: 17.0,
color: CupertinoColors.black,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
),
),
......@@ -249,19 +241,23 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cupertino Picker'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoPickerDemo.routeName)],
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Picker'),
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
trailing: CupertinoDemoDocumentationButton(CupertinoPickerDemo.routeName),
),
body: DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: DecoratedBox(
decoration: const BoxDecoration(color: Color(0xFFEFEFF4)),
decoration: BoxDecoration(
color: CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.extraLightBackgroundGray
: CupertinoColors.darkBackgroundGray,
),
child: ListView(
children: <Widget>[
const Padding(padding: EdgeInsets.only(top: 32.0)),
......
......@@ -39,19 +39,21 @@ class _CupertinoRefreshControlDemoState extends State<CupertinoRefreshControlDem
@override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 17.0,
color: CupertinoColors.black,
),
style: CupertinoTheme.of(context).textTheme.textStyle,
child: CupertinoPageScaffold(
child: DecoratedBox(
decoration: const BoxDecoration(color: Color(0xFFEFEFF4)),
decoration: BoxDecoration(
color: CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.extraLightBackgroundGray
: CupertinoColors.darkBackgroundGray,
),
child: CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: const Text('Cupertino Refresh'),
largeTitle: const Text('Refresh'),
// We're specifying a back label here because the previous page
// is a Material page. CupertinoPageRoutes could auto-populate
// these back labels.
previousPageTitle: 'Cupertino',
trailing: CupertinoDemoDocumentationButton(CupertinoRefreshControlDemo.routeName),
),
......@@ -152,7 +154,7 @@ class _ListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: CupertinoColors.white,
color: CupertinoTheme.of(context).scaffoldBackgroundColor,
height: 60.0,
padding: const EdgeInsets.only(top: 9.0),
child: Row(
......@@ -215,11 +217,11 @@ class _ListItem extends StatelessWidget {
letterSpacing: -0.41,
),
),
const Padding(
padding: EdgeInsets.only(left: 9.0),
Padding(
padding: const EdgeInsets.only(left: 9.0),
child: Icon(
CupertinoIcons.info,
color: CupertinoColors.activeBlue
color: CupertinoTheme.of(context).primaryColor,
),
),
],
......
......@@ -50,68 +50,77 @@ class _CupertinoSegmentedControlDemoState extends State<CupertinoSegmentedContro
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Segmented Control'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoSegmentedControlDemo.routeName)],
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Segmented Control'),
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
trailing: CupertinoDemoDocumentationButton(CupertinoSegmentedControlDemo.routeName),
),
body: Column(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(16.0),
),
SizedBox(
width: 500.0,
child: CupertinoSegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 32.0,
horizontal: 16.0,
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: SafeArea(
child: Column(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(16.0),
),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 64.0,
horizontal: 16.0,
SizedBox(
width: 500.0,
child: CupertinoSegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
decoration: BoxDecoration(
color: CupertinoColors.white,
borderRadius: BorderRadius.circular(3.0),
boxShadow: const <BoxShadow>[
BoxShadow(
offset: Offset(0.0, 3.0),
blurRadius: 5.0,
spreadRadius: -1.0,
color: _kKeyUmbraOpacity,
),
BoxShadow(
offset: Offset(0.0, 6.0),
blurRadius: 10.0,
spreadRadius: 0.0,
color: _kKeyPenumbraOpacity,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 32.0,
horizontal: 16.0,
),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 64.0,
horizontal: 16.0,
),
BoxShadow(
offset: Offset(0.0, 1.0),
blurRadius: 18.0,
spreadRadius: 0.0,
color: _kAmbientShadowOpacity,
decoration: BoxDecoration(
color: CupertinoTheme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(3.0),
boxShadow: const <BoxShadow>[
BoxShadow(
offset: Offset(0.0, 3.0),
blurRadius: 5.0,
spreadRadius: -1.0,
color: _kKeyUmbraOpacity,
),
BoxShadow(
offset: Offset(0.0, 6.0),
blurRadius: 10.0,
spreadRadius: 0.0,
color: _kKeyPenumbraOpacity,
),
BoxShadow(
offset: Offset(0.0, 1.0),
blurRadius: 18.0,
spreadRadius: 0.0,
color: _kAmbientShadowOpacity,
),
],
),
],
child: icons[sharedValue],
),
),
child: icons[sharedValue],
),
),
],
),
],
),
),
);
}
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
......@@ -20,49 +19,58 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cupertino Sliders'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoSliderDemo.routeName)],
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Sliders'),
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
trailing: CupertinoDemoDocumentationButton(CupertinoSliderDemo.routeName),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget> [
CupertinoSlider(
value: _value,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_value = value;
});
}
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget> [
CupertinoSlider(
value: _value,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_value = value;
});
}
),
Text('Cupertino Continuous: ${_value.toStringAsFixed(1)}'),
]
),
Text('Cupertino Continuous: ${_value.toStringAsFixed(1)}'),
]
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget> [
CupertinoSlider(
value: _discreteValue,
min: 0.0,
max: 100.0,
divisions: 5,
onChanged: (double value) {
setState(() {
_discreteValue = value;
});
}
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget> [
CupertinoSlider(
value: _discreteValue,
min: 0.0,
max: 100.0,
divisions: 5,
onChanged: (double value) {
setState(() {
_discreteValue = value;
});
}
),
Text('Cupertino Discrete: $_discreteValue'),
]
),
Text('Cupertino Discrete: $_discreteValue'),
]
],
),
],
),
),
),
);
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
......@@ -20,62 +19,71 @@ class _CupertinoSwitchDemoState extends State<CupertinoSwitchDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cupertino Switch'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoSwitchDemo.routeName)],
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Switch'),
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
trailing: CupertinoDemoDocumentationButton(CupertinoSwitchDemo.routeName),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Semantics(
container: true,
child: Column(
children: <Widget>[
CupertinoSwitch(
value: _switchValue,
onChanged: (bool value) {
setState(() {
_switchValue = value;
});
},
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Semantics(
container: true,
child: Column(
children: <Widget>[
CupertinoSwitch(
value: _switchValue,
onChanged: (bool value) {
setState(() {
_switchValue = value;
});
},
),
const Text(
'Active'
),
],
),
const Text(
'Active'
),
Semantics(
container: true,
child: Column(
children: const <Widget>[
CupertinoSwitch(
value: true,
onChanged: null,
),
Text(
'Disabled'
),
],
),
],
),
),
Semantics(
container: true,
child: Column(
children: const <Widget>[
CupertinoSwitch(
value: true,
onChanged: null,
),
Text(
'Disabled'
),
],
),
),
Semantics(
container: true,
child: Column(
children: const <Widget>[
CupertinoSwitch(
value: false,
onChanged: null,
),
Text(
'Disabled'
),
Semantics(
container: true,
child: Column(
children: const <Widget>[
CupertinoSwitch(
value: false,
onChanged: null,
),
Text(
'Disabled'
),
],
),
],
),
),
],
),
],
),
),
),
);
......
......@@ -160,6 +160,9 @@ class _CupertinoTextFieldDemoState extends State<CupertinoTextFieldDemo> {
),
child: CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
middle: Text('Text Fields'),
),
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
......@@ -125,7 +126,6 @@ class _GalleryAppState extends State<GalleryApp> {
child: home,
);
}
return MaterialApp(
theme: _options.theme.data.copyWith(platform: _options.platform),
title: 'Flutter Gallery',
......@@ -137,7 +137,17 @@ class _GalleryAppState extends State<GalleryApp> {
builder: (BuildContext context, Widget child) {
return Directionality(
textDirection: _options.textDirection,
child: _applyTextScaleFactor(child),
child: _applyTextScaleFactor(
// Specifically use a blank Cupertino theme here and do not transfer
// over the Material primary color etc except the brightness to
// showcase standard iOS looks.
CupertinoTheme(
data: CupertinoThemeData(
brightness: _options.theme.data.brightness,
),
child: child,
),
),
);
},
home: home,
......
......@@ -30,5 +30,7 @@ export 'src/cupertino/tab_scaffold.dart';
export 'src/cupertino/tab_view.dart';
export 'src/cupertino/text_field.dart';
export 'src/cupertino/text_selection.dart';
export 'src/cupertino/text_theme.dart';
export 'src/cupertino/theme.dart';
export 'src/cupertino/thumb_painter.dart';
export 'widgets.dart';
......@@ -10,16 +10,7 @@ import 'colors.dart';
import 'icons.dart';
import 'localizations.dart';
import 'route.dart';
// Based on specs from https://developer.apple.com/design/resources/ for
// iOS 12.
const TextStyle _kDefaultTextStyle = TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 17.0,
letterSpacing: -0.38,
color: CupertinoColors.black,
decoration: TextDecoration.none,
);
import 'theme.dart';
/// An application that uses Cupertino design.
///
......@@ -79,6 +70,7 @@ class CupertinoApp extends StatefulWidget {
Key key,
this.navigatorKey,
this.home,
this.theme,
this.routes = const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
......@@ -114,6 +106,12 @@ class CupertinoApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.home}
final Widget home;
/// The top-level [CupertinoTheme] styling.
///
/// A null [theme] or unspecified [theme] attributes will default to iOS
/// system values.
final CupertinoThemeData theme;
/// The application's top-level routing table.
///
/// When a named route is pushed with [Navigator.pushNamed], the route name is
......@@ -266,48 +264,52 @@ class _CupertinoAppState extends State<CupertinoApp> {
@override
Widget build(BuildContext context) {
final CupertinoThemeData effectiveThemeData = widget.theme ?? const CupertinoThemeData();
return ScrollConfiguration(
behavior: _AlwaysCupertinoScrollBehavior(),
child: WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: _navigatorObservers,
// TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved
// this can use type arguments again
pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
CupertinoPageRoute<dynamic>(settings: settings, builder: builder),
home: widget.home,
routes: widget.routes,
initialRoute: widget.initialRoute,
onGenerateRoute: widget.onGenerateRoute,
onUnknownRoute: widget.onUnknownRoute,
builder: widget.builder,
title: widget.title,
onGenerateTitle: widget.onGenerateTitle,
textStyle: _kDefaultTextStyle,
color: widget.color ?? CupertinoColors.activeBlue,
locale: widget.locale,
localizationsDelegates: _localizationsDelegates,
localeResolutionCallback: widget.localeResolutionCallback,
localeListResolutionCallback: widget.localeListResolutionCallback,
supportedLocales: widget.supportedLocales,
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
showSemanticsDebugger: widget.showSemanticsDebugger,
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
return CupertinoButton(
child: const Icon(
CupertinoIcons.search,
size: 28.0,
color: CupertinoColors.white,
),
color: CupertinoColors.activeBlue,
padding: EdgeInsets.zero,
onPressed: onPressed,
);
},
child: CupertinoTheme(
data: effectiveThemeData,
child: WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: _navigatorObservers,
// TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved
// this can use type arguments again
pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
CupertinoPageRoute<dynamic>(settings: settings, builder: builder),
home: widget.home,
routes: widget.routes,
initialRoute: widget.initialRoute,
onGenerateRoute: widget.onGenerateRoute,
onUnknownRoute: widget.onUnknownRoute,
builder: widget.builder,
title: widget.title,
onGenerateTitle: widget.onGenerateTitle,
textStyle: effectiveThemeData.textTheme.textStyle,
color: widget.color ?? CupertinoColors.activeBlue,
locale: widget.locale,
localizationsDelegates: _localizationsDelegates,
localeResolutionCallback: widget.localeResolutionCallback,
localeListResolutionCallback: widget.localeListResolutionCallback,
supportedLocales: widget.supportedLocales,
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
showSemanticsDebugger: widget.showSemanticsDebugger,
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
return CupertinoButton.filled(
child: const Icon(
CupertinoIcons.search,
size: 28.0,
color: CupertinoColors.white,
),
padding: EdgeInsets.zero,
onPressed: onPressed,
);
},
),
),
);
}
......
......@@ -7,11 +7,11 @@ import 'dart:ui' show ImageFilter;
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
// Standard iOS 10 tab bar height.
const double _kTabBarHeight = 50.0;
const Color _kDefaultTabBarBackgroundColor = Color(0xCCF8F8F8);
const Color _kDefaultTabBarBorderColor = Color(0x4C000000);
/// An iOS-styled bottom navigation tab bar.
......@@ -21,10 +21,12 @@ const Color _kDefaultTabBarBorderColor = Color(0x4C000000);
///
/// This [StatelessWidget] doesn't store the active tab itself. You must
/// listen to the [onTap] callbacks and call `setState` with a new [currentIndex]
/// for the new selection to reflect.
/// for the new selection to reflect. This can also be done automatically
/// by wrapping this with a [CupertinoTabScaffold].
///
/// Tab changes typically trigger a switch between [Navigator]s, each with its
/// own navigation stack, per standard iOS design.
/// own navigation stack, per standard iOS design. This can be done by using
/// [CupertinoTabView]s inside each tab builder in [CupertinoTabScaffold].
///
/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by
/// default), it will produce a blurring effect to the content behind it.
......@@ -40,8 +42,8 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
@required this.items,
this.onTap,
this.currentIndex = 0,
this.backgroundColor = _kDefaultTabBarBackgroundColor,
this.activeColor = CupertinoColors.activeBlue,
this.backgroundColor,
this.activeColor,
this.inactiveColor = CupertinoColors.inactiveGray,
this.iconSize = 30.0,
this.border = const Border(
......@@ -56,6 +58,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
assert(currentIndex != null),
assert(0 <= currentIndex && currentIndex < items.length),
assert(iconSize != null),
assert(inactiveColor != null),
super(key: key);
/// The interactive items laid out within the bottom navigation bar.
......@@ -78,14 +81,20 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
/// The background color of the tab bar. If it contains transparency, the
/// tab bar will automatically produce a blurring effect to the content
/// behind it.
///
/// Defaults to [CupertinoTheme]'s `barBackgroundColor` when null.
final Color backgroundColor;
/// The foreground color of the icon and title for the [BottomNavigationBarItem]
/// of the selected tab.
///
/// Defaults to [CupertinoTheme]'s `primaryColor` if null.
final Color activeColor;
/// The foreground color of the icon and title for the [BottomNavigationBarItem]s
/// in the unselected state.
///
/// Defaults to [CupertinoColors.inactiveGray] and cannot be null.
final Color inactiveColor;
/// The size of all of the [BottomNavigationBarItem] icons.
......@@ -102,19 +111,25 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
/// The default value is a one physical pixel top border with grey color.
final Border border;
/// True if the tab bar's background color has no transparency.
bool get opaque => backgroundColor.alpha == 0xFF;
@override
Size get preferredSize => const Size.fromHeight(_kTabBarHeight);
/// Indicates whether the tab bar is fully opaque or can have contents behind
/// it show through it.
bool opaque(BuildContext context) {
final Color backgroundColor =
this.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor;
return backgroundColor.alpha == 0xFF;
}
@override
Widget build(BuildContext context) {
final double bottomPadding = MediaQuery.of(context).padding.bottom;
Widget result = DecoratedBox(
decoration: BoxDecoration(
border: border,
color: backgroundColor,
color: backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor,
),
child: SizedBox(
height: _kTabBarHeight + bottomPadding,
......@@ -124,19 +139,13 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
size: iconSize,
),
child: DefaultTextStyle( // Default with the inactive state.
style: TextStyle(
fontFamily: '.SF UI Text',
fontSize: 10.0,
letterSpacing: 0.1,
fontWeight: FontWeight.w400,
color: inactiveColor,
),
style: CupertinoTheme.of(context).textTheme.tabLabelTextStyle.copyWith(color: inactiveColor),
child: Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: Row(
// Align bottom since we want the labels to be aligned.
crossAxisAlignment: CrossAxisAlignment.end,
children: _buildTabItems(),
children: _buildTabItems(context),
),
),
),
......@@ -144,7 +153,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
),
);
if (!opaque) {
if (!opaque(context)) {
// For non-opaque backgrounds, apply a blur effect.
result = ClipRect(
child: BackdropFilter(
......@@ -157,13 +166,14 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
return result;
}
List<Widget> _buildTabItems() {
List<Widget> _buildTabItems(BuildContext context) {
final List<Widget> result = <Widget>[];
for (int index = 0; index < items.length; index += 1) {
final bool active = index == currentIndex;
result.add(
_wrapActiveItem(
context,
Expanded(
child: Semantics(
selected: active,
......@@ -205,10 +215,11 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
}
/// Change the active tab item's icon and title colors to active.
Widget _wrapActiveItem(Widget item, { @required bool active }) {
Widget _wrapActiveItem(BuildContext context, Widget item, { @required bool active }) {
if (!active)
return item;
final Color activeColor = this.activeColor ?? CupertinoTheme.of(context).primaryColor;
return IconTheme.merge(
data: IconThemeData(color: activeColor),
child: DefaultTextStyle.merge(
......
......@@ -5,28 +5,11 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
const Color _kDisabledBackground = Color(0xFFA9A9A9);
const Color _kDisabledForeground = Color(0xFFC4C4C4);
const TextStyle _kButtonTextStyle = TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 17.5,
letterSpacing: -0.24,
fontWeight: FontWeight.w400,
color: CupertinoColors.activeBlue,
textBaseline: TextBaseline.alphabetic,
);
final TextStyle _kDisabledButtonTextStyle = _kButtonTextStyle.copyWith(
color: _kDisabledForeground,
);
final TextStyle _kBackgroundButtonTextStyle = _kButtonTextStyle.copyWith(
color: CupertinoColors.white,
);
// Measured against iOS 12 in Xcode.
const Color _kDisabledForeground = Color(0xFFD1D1D1);
const EdgeInsets _kButtonPadding = EdgeInsets.all(16.0);
const EdgeInsets _kBackgroundButtonPadding = EdgeInsets.symmetric(
......@@ -53,7 +36,26 @@ class CupertinoButton extends StatefulWidget {
this.pressedOpacity = 0.1,
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
@required this.onPressed,
}) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0));
}) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
_filled = false;
/// Creates an iOS-style button with a filled background.
///
/// The background color is derived from the [CupertinoTheme]'s `primaryColor`.
///
/// To specify a custom background color, use the [color] argument of the
/// default constructor.
const CupertinoButton.filled({
@required this.child,
this.padding,
this.disabledColor,
this.minSize = 44.0,
this.pressedOpacity = 0.1,
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
@required this.onPressed,
}) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
color = null,
_filled = true;
/// The widget below this widget in the tree.
///
......@@ -68,6 +70,9 @@ class CupertinoButton extends StatefulWidget {
/// The color of the button's background.
///
/// Defaults to null which produces a button with no background or border.
///
/// Defaults to the [CupertinoTheme]'s `primaryColor` when the
/// [CupertinoButton.filled] constructor is used.
final Color color;
/// The color of the button's background when the button is disabled.
......@@ -105,6 +110,8 @@ class CupertinoButton extends StatefulWidget {
/// Defaults to round corners of 8 logical pixels.
final BorderRadius borderRadius;
final bool _filled;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
/// enable a button, set its [onPressed] property to a non-null value.
bool get enabled => onPressed != null;
......@@ -198,7 +205,15 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
@override
Widget build(BuildContext context) {
final bool enabled = widget.enabled;
final Color backgroundColor = widget.color;
final Color primaryColor = CupertinoTheme.of(context).primaryColor;
final Color backgroundColor = widget.color ?? (widget._filled ? primaryColor : null);
final Color foregroundColor = backgroundColor != null
? CupertinoTheme.of(context).primaryContrastingColor
: enabled
? primaryColor
: _kDisabledForeground;
final TextStyle textStyle =
CupertinoTheme.of(context).textTheme.textStyle.copyWith(color: foregroundColor);
return GestureDetector(
behavior: HitTestBehavior.opaque,
......@@ -232,12 +247,11 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
widthFactor: 1.0,
heightFactor: 1.0,
child: DefaultTextStyle(
style: backgroundColor != null
? _kBackgroundButtonTextStyle
: enabled
? _kButtonTextStyle
: _kDisabledButtonTextStyle,
child: widget.child,
style: textStyle,
child: IconTheme(
data: IconThemeData(color: foregroundColor),
child: widget.child,
),
),
),
),
......
......@@ -11,15 +11,27 @@ class CupertinoColors {
/// iOS 10's default blue color. Used to indicate active elements such as
/// buttons, selected tabs and your own chat bubbles.
///
/// This is SystemBlue in the iOS palette.
static const Color activeBlue = Color(0xFF007AFF);
/// iOS 10's default green color. Used to indicate active accents such as
/// the switch in its on state and some accent buttons such as the call button
/// and Apple Map's 'Go' button.
///
/// This is SystemGreen in the iOS palette.
static const Color activeGreen = Color(0xFF4CD964);
/// iOS 12's default dark mode color. Used in place of the [activeBlue] color
/// as the default active elements' color when the theme's brightness is dark.
///
/// This is SystemOrange in the iOS palette.
static const Color activeOrange = Color(0xFFFF9500);
/// Opaque white color. Used for backgrounds and fonts against dark backgrounds.
///
/// This is SystemWhiteColor in the iOS palette.
///
/// See also:
///
/// * [material.Colors.white], the same color, in the material design palette.
......@@ -28,6 +40,8 @@ class CupertinoColors {
/// Opaque black color. Used for texts against light backgrounds.
///
/// This is SystemBlackColor in the iOS palette.
///
/// See also:
///
/// * [material.Colors.black], the same color, in the material design palette.
......@@ -35,12 +49,26 @@ class CupertinoColors {
static const Color black = Color(0xFF000000);
/// Used in iOS 10 for light background fills such as the chat bubble background.
///
/// This is SystemLightGrayColor in the iOS palette.
static const Color lightBackgroundGray = Color(0xFFE5E5EA);
/// Used in iOS 12 for very light background fills in tables between cell groups.
///
/// This is SystemExtraLightGrayColor in the iOS palette.
static const Color extraLightBackgroundGray = Color(0xFFEFEFF4);
/// Used in iOS 12 for very dark background fills in tables between cell groups
/// in dark mode.
// Value derived from screenshot from the dark themed Apple Watch app.
static const Color darkBackgroundGray = Color(0xFF171717);
/// Used in iOS 11 for unselected selectables such as tab bar items in their
/// inactive state or de-emphasized subtitles and details text.
///
/// Not the same gray as disabled buttons etc.
///
/// This is SystemGrayColor in the iOS palette.
static const Color inactiveGray = Color(0xFF8E8E93);
/// Used for iOS 10 for destructive actions such as the delete actions in
......@@ -48,5 +76,7 @@ class CupertinoColors {
///
/// Not the same red as the camera shutter or springboard icon notifications
/// or the foreground red theme in various native apps such as HealthKit.
///
/// This is SystemRed in the iOS palette.
static const Color destructiveRed = Color(0xFFFF3B30);
}
......@@ -4,7 +4,7 @@
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
/// Implements a single iOS application page's layout.
///
......@@ -21,7 +21,7 @@ class CupertinoPageScaffold extends StatelessWidget {
const CupertinoPageScaffold({
Key key,
this.navigationBar,
this.backgroundColor = CupertinoColors.white,
this.backgroundColor,
this.resizeToAvoidBottomInset = true,
@required this.child,
}) : assert(child != null),
......@@ -48,7 +48,7 @@ class CupertinoPageScaffold extends StatelessWidget {
/// The color of the widget that underlies the entire scaffold.
///
/// By default uses [CupertinoColors.white] color.
/// By default uses [CupertinoTheme]'s `scaffoldBackgroundColor` when null.
final Color backgroundColor;
/// Whether the [child] should size itself to avoid the window's bottom inset.
......@@ -78,10 +78,13 @@ class CupertinoPageScaffold extends StatelessWidget {
? existingMediaQuery.viewInsets.bottom
: 0.0;
final bool fullObstruction =
navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;
// If navigation bar is opaquely obstructing, directly shift the main content
// down. If translucent, let main content draw behind navigation bar but hint the
// obstructed area.
if (navigationBar.fullObstruction) {
if (fullObstruction) {
paddedContent = Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: child,
......@@ -114,7 +117,9 @@ class CupertinoPageScaffold extends StatelessWidget {
}
return DecoratedBox(
decoration: BoxDecoration(color: backgroundColor),
decoration: BoxDecoration(
color: backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
),
child: Stack(
children: stacked,
),
......
......@@ -9,7 +9,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
import 'thumb_painter.dart';
// Examples can assume:
......@@ -189,7 +189,7 @@ class CupertinoSlider extends StatefulWidget {
/// The color to use for the portion of the slider that has been selected.
///
/// Defaults to [CupertinoColors.activeBlue].
/// Defaults to the [CupertinoTheme]'s primary color if null.
final Color activeColor;
@override
......@@ -228,7 +228,7 @@ class _CupertinoSliderState extends State<CupertinoSlider> with TickerProviderSt
return _CupertinoSliderRenderObjectWidget(
value: (widget.value - widget.min) / (widget.max - widget.min),
divisions: widget.divisions,
activeColor: widget.activeColor ?? CupertinoColors.activeBlue,
activeColor: widget.activeColor ?? CupertinoTheme.of(context).primaryColor,
onChanged: widget.onChanged != null ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
......@@ -485,15 +485,14 @@ class _RenderCupertinoSlider extends RenderConstrainedBox {
final double trackActive = offset.dx + _thumbCenter;
final Canvas canvas = context.canvas;
final Paint paint = Paint();
if (visualPosition > 0.0) {
paint.color = rightColor;
final Paint paint = Paint()..color = rightColor;
canvas.drawRRect(RRect.fromLTRBXY(trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0), paint);
}
if (visualPosition < 1.0) {
paint.color = leftColor;
final Paint paint = Paint()..color = leftColor;
canvas.drawRRect(RRect.fromLTRBXY(trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0), paint);
}
......
......@@ -88,7 +88,8 @@ class CupertinoSwitch extends StatefulWidget {
/// The color to use when this switch is on.
///
/// Defaults to [CupertinoColors.activeGreen].
/// Defaults to [CupertinoColors.activeGreen] when null and ignores the
/// [CupertinoTheme] in accordance to native iOS behavior.
final Color activeColor;
@override
......
......@@ -4,6 +4,7 @@
import 'package:flutter/widgets.dart';
import 'bottom_tab_bar.dart';
import 'theme.dart';
/// Implements a tabbed iOS application's root layout and behavior structure.
///
......@@ -173,13 +174,13 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
// TODO(xster): Use real size after partial layout instead of preferred size.
// https://github.com/flutter/flutter/issues/12912
final double bottomPadding = widget.tabBar.preferredSize.height
+ existingMediaQuery.padding.bottom;
final double bottomPadding =
widget.tabBar.preferredSize.height + existingMediaQuery.padding.bottom;
// If tab bar opaque, directly stop the main content higher. If
// translucent, let main content draw behind the tab bar but hint the
// obstructed area.
if (widget.tabBar.opaque) {
if (widget.tabBar.opaque(context)) {
content = Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: content,
......@@ -219,8 +220,13 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
));
}
return Stack(
children: stacked,
return DecoratedBox(
decoration: BoxDecoration(
color: CupertinoTheme.of(context).scaffoldBackgroundColor
),
child: Stack(
children: stacked,
),
);
}
}
......
......@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'icons.dart';
import 'text_selection.dart';
import 'theme.dart';
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization;
......@@ -32,15 +33,6 @@ const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
);
// Default iOS style from HIG specs with larger font.
const TextStyle _kDefaultTextStyle = TextStyle(
fontFamily: '.SF Pro Text',
fontSize: 17.0,
letterSpacing: -0.38,
color: CupertinoColors.black,
decoration: TextDecoration.none,
);
// Value extracted via color reader from iOS simulator.
const Color _kSelectionHighlightColor = Color(0x667FAACF);
const Color _kInactiveTextColor = Color(0xFFC2C2C2);
......@@ -160,7 +152,7 @@ class CupertinoTextField extends StatefulWidget {
TextInputType keyboardType,
this.textInputAction,
this.textCapitalization = TextCapitalization.none,
this.style = _kDefaultTextStyle,
this.style,
this.textAlign = TextAlign.start,
this.autofocus = false,
this.obscureText = false,
......@@ -271,7 +263,7 @@ class CupertinoTextField extends StatefulWidget {
///
/// Also serves as a base for the [placeholder] text's style.
///
/// Defaults to a standard iOS style and cannot be null.
/// Defaults to the standard iOS font style from [CupertinoTheme] if null.
final TextStyle style;
/// {@macro flutter.widgets.editableText.textAlign}
......@@ -558,7 +550,9 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
);
}
Widget _addTextDependentAttachments(Widget editableText) {
Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle) {
assert(editableText != null);
assert(textStyle != null);
// If there are no surrounding widgets, just return the core editable text
// part.
if (widget.placeholder == null &&
......@@ -593,7 +587,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
widget.placeholder,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: widget.style.merge(
style: textStyle.merge(
const TextStyle(
color: _kInactiveTextColor,
fontWeight: FontWeight.w300,
......@@ -637,14 +631,13 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
Widget build(BuildContext context) {
super.build(context); // See AutomaticKeepAliveClientMixin.
assert(debugCheckHasDirectionality(context));
final Brightness keyboardAppearance = widget.keyboardAppearance;
final TextEditingController controller = _effectiveController;
final FocusNode focusNode = _effectiveFocusNode;
final List<TextInputFormatter> formatters = widget.inputFormatters ?? <TextInputFormatter>[];
final bool enabled = widget.enabled ?? true;
if (widget.maxLength != null && widget.maxLengthEnforced) {
formatters.add(LengthLimitingTextInputFormatter(widget.maxLength));
}
final TextStyle textStyle = widget.style ?? CupertinoTheme.of(context).textTheme.textStyle;
final Widget paddedEditable = Padding(
padding: widget.padding,
......@@ -652,11 +645,11 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
child: EditableText(
key: _editableTextKey,
controller: controller,
focusNode: focusNode,
focusNode: _effectiveFocusNode,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
textCapitalization: widget.textCapitalization,
style: widget.style,
style: textStyle,
textAlign: widget.textAlign,
autofocus: widget.autofocus,
obscureText: widget.obscureText,
......@@ -674,7 +667,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
cursorColor: widget.cursorColor,
backgroundCursorColor: CupertinoColors.inactiveGray,
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
keyboardAppearance: widget.keyboardAppearance,
),
),
);
......@@ -692,14 +685,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
decoration: widget.decoration,
// The main decoration and the disabled scrim exists separately.
child: Container(
color: enabled ? null : _kDisabledBackground,
color: enabled
? null
: CupertinoTheme.of(context).brightness == Brightness.light
? _kDisabledBackground
: CupertinoColors.darkBackgroundGray,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTapDown: _handleTapDown,
onTapUp: _handleTapUp,
onLongPress: _handleLongPress,
excludeFromSemantics: true,
child: _addTextDependentAttachments(paddedEditable),
child: _addTextDependentAttachments(paddedEditable, textStyle),
),
),
),
......
// Copyright 2018 The Chromium 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:flutter/foundation.dart';
import 'package:flutter/services.dart' show Brightness;
import 'package:flutter/widgets.dart';
import 'colors.dart';
// Values derived from https://developer.apple.com/design/resources/.
const TextStyle _kDefaultLightTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Text',
fontSize: 17.0,
letterSpacing: -0.41,
color: CupertinoColors.black,
decoration: TextDecoration.none,
);
// Values derived from https://developer.apple.com/design/resources/.
const TextStyle _kDefaultDarkTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Text',
fontSize: 17.0,
letterSpacing: -0.41,
color: CupertinoColors.white,
decoration: TextDecoration.none,
);
// Values derived from https://developer.apple.com/design/resources/.
const TextStyle _kDefaultActionTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Text',
fontSize: 17.0,
letterSpacing: -0.41,
color: CupertinoColors.activeBlue,
decoration: TextDecoration.none,
);
// Values derived from https://developer.apple.com/design/resources/.
const TextStyle _kDefaultTabLabelTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Text',
fontSize: 10.0,
letterSpacing: -0.24,
color: CupertinoColors.inactiveGray,
);
const TextStyle _kDefaultMiddleTitleLightTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Text',
fontSize: 17.0,
fontWeight: FontWeight.w600,
letterSpacing: -0.41,
color: CupertinoColors.black,
);
const TextStyle _kDefaultMiddleTitleDarkTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Text',
fontSize: 17.0,
fontWeight: FontWeight.w600,
letterSpacing: -0.41,
color: CupertinoColors.white,
);
const TextStyle _kDefaultLargeTitleLightTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Display',
fontSize: 34.0,
fontWeight: FontWeight.w700,
letterSpacing: 0.41,
color: CupertinoColors.black,
);
const TextStyle _kDefaultLargeTitleDarkTextStyle = TextStyle(
inherit: false,
fontFamily: '.SF Pro Display',
fontSize: 34.0,
fontWeight: FontWeight.w700,
letterSpacing: 0.41,
color: CupertinoColors.white,
);
/// Cupertino typography theme in a [CupertinoThemeData].
@immutable
class CupertinoTextThemeData extends Diagnosticable {
/// Create a [CupertinoTextThemeData].
///
/// The [primaryColor] and [isLight] parameters are used to derive TextStyle
/// defaults of other attributes such as [textStyle] and [actionTextStyle]
/// etc. The default value of [primaryColor] is [CupertinoColors.activeBlue]
/// and the default value of [isLight] is true.
///
/// Other [TextStyle] parameters default to default iOS text styles when
/// unspecified.
const CupertinoTextThemeData({
Color primaryColor,
Brightness brightness,
TextStyle textStyle,
TextStyle actionTextStyle,
TextStyle tabLabelTextStyle,
TextStyle navTitleTextStyle,
TextStyle navLargeTitleTextStyle,
TextStyle navActionTextStyle,
}) : _primaryColor = primaryColor ?? CupertinoColors.activeBlue,
_brightness = brightness,
_textStyle = textStyle,
_actionTextStyle = actionTextStyle,
_tabLabelTextStyle = tabLabelTextStyle,
_navTitleTextStyle = navTitleTextStyle,
_navLargeTitleTextStyle = navLargeTitleTextStyle,
_navActionTextStyle = navActionTextStyle;
final Color _primaryColor;
final Brightness _brightness;
bool get _isLight => _brightness != Brightness.dark;
final TextStyle _textStyle;
/// Typography of general text content for Cupertino widgets.
TextStyle get textStyle => _textStyle ?? (_isLight ? _kDefaultLightTextStyle : _kDefaultDarkTextStyle);
final TextStyle _actionTextStyle;
/// Typography of interactive text content such as text in a button without background.
TextStyle get actionTextStyle {
return _actionTextStyle ?? _kDefaultActionTextStyle.copyWith(
color: _primaryColor,
);
}
final TextStyle _tabLabelTextStyle;
/// Typography of unselected tabs.
TextStyle get tabLabelTextStyle => _tabLabelTextStyle ?? _kDefaultTabLabelTextStyle;
final TextStyle _navTitleTextStyle;
/// Typography of titles in standard navigation bars.
TextStyle get navTitleTextStyle {
return _navTitleTextStyle ??
(_isLight ? _kDefaultMiddleTitleLightTextStyle : _kDefaultMiddleTitleDarkTextStyle);
}
final TextStyle _navLargeTitleTextStyle;
/// Typography of large titles in sliver navigation bars.
TextStyle get navLargeTitleTextStyle {
return _navLargeTitleTextStyle ??
(_isLight ? _kDefaultLargeTitleLightTextStyle : _kDefaultLargeTitleDarkTextStyle);
}
final TextStyle _navActionTextStyle;
/// Typography of interative text content in navigation bars.
TextStyle get navActionTextStyle {
return _navActionTextStyle ?? _kDefaultActionTextStyle.copyWith(
color: _primaryColor,
);
}
/// Returns a copy of the current [CupertinoTextThemeData] instance with
/// specified overrides.
CupertinoTextThemeData copyWith({
Color primaryColor,
Brightness brightness,
TextStyle textStyle,
TextStyle actionTextStyle,
TextStyle tabLabelTextStyle,
TextStyle navTitleTextStyle,
TextStyle navLargeTitleTextStyle,
TextStyle navActionTextStyle,
}) {
return CupertinoTextThemeData(
primaryColor: primaryColor ?? _primaryColor,
brightness: brightness ?? _brightness,
textStyle: textStyle ?? _textStyle,
actionTextStyle: actionTextStyle ?? _actionTextStyle,
tabLabelTextStyle: tabLabelTextStyle ?? _tabLabelTextStyle,
navTitleTextStyle: navTitleTextStyle ?? _navTitleTextStyle,
navLargeTitleTextStyle: navLargeTitleTextStyle ?? _navLargeTitleTextStyle,
navActionTextStyle: navActionTextStyle ?? _navActionTextStyle,
);
}
}
This diff is collapsed.
......@@ -443,7 +443,7 @@ class _MaterialAppState extends State<MaterialApp> {
mini: true,
);
},
)
),
);
assert(() {
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
......@@ -143,7 +144,15 @@ class Theme extends StatelessWidget {
theme: this,
child: IconTheme(
data: data.iconTheme,
child: child,
child: CupertinoTheme(
// We're using a MaterialBasedCupertinoThemeData here instead of a
// CupertinoThemeData because it defers some properties to the Material
// ThemeData.
data: MaterialBasedCupertinoThemeData(
materialTheme: data,
),
child: child,
),
),
);
}
......
......@@ -4,6 +4,7 @@
import 'dart:ui' show Color, hashValues;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
......@@ -153,6 +154,7 @@ class ThemeData extends Diagnosticable {
ColorScheme colorScheme,
DialogTheme dialogTheme,
Typography typography,
CupertinoThemeData cupertinoOverrideTheme
}) {
brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark;
......@@ -246,6 +248,7 @@ class ThemeData extends Diagnosticable {
labelStyle: textTheme.body2,
);
dialogTheme ??= const DialogTheme();
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw(
brightness: brightness,
......@@ -294,11 +297,13 @@ class ThemeData extends Diagnosticable {
colorScheme: colorScheme,
dialogTheme: dialogTheme,
typography: typography,
cupertinoOverrideTheme: cupertinoOverrideTheme,
);
}
/// Create a [ThemeData] given a set of exact values. All the values
/// must be specified.
/// Create a [ThemeData] given a set of exact values. All the values must be
/// specified. They all must also be non-null except for
/// [cupertinoOverrideTheme].
///
/// This will rarely be used directly. It is used by [lerp] to
/// create intermediate themes based on two themes created with the
......@@ -353,6 +358,7 @@ class ThemeData extends Diagnosticable {
@required this.colorScheme,
@required this.dialogTheme,
@required this.typography,
@required this.cupertinoOverrideTheme,
}) : assert(brightness != null),
assert(primaryColor != null),
assert(primaryColorBrightness != null),
......@@ -630,6 +636,18 @@ class ThemeData extends Diagnosticable {
/// [primaryTextTheme], and [accentTextTheme].
final Typography typography;
/// Components of the [CupertinoThemeData] to override from the Material
/// [ThemeData] adaptation.
///
/// By default, [cupertinoOverrideTheme] is null and Cupertino widgets
/// descendant to the Material [Theme] will adhere to a [CupertinoTheme]
/// derived from the Material [ThemeData]. e.g. [ThemeData]'s [ColorTheme]
/// will also inform the [CupertinoThemeData]'s `primaryColor` etc.
///
/// This cascading effect for individual attributes of the [CupertinoThemeData]
/// can be overridden using attributes of this [cupertinoOverrideTheme].
final CupertinoThemeData cupertinoOverrideTheme;
/// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({
Brightness brightness,
......@@ -678,7 +696,9 @@ class ThemeData extends Diagnosticable {
ColorScheme colorScheme,
DialogTheme dialogTheme,
Typography typography,
CupertinoThemeData cupertinoOverrideTheme,
}) {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw(
brightness: brightness ?? this.brightness,
primaryColor: primaryColor ?? this.primaryColor,
......@@ -726,6 +746,7 @@ class ThemeData extends Diagnosticable {
colorScheme: colorScheme ?? this.colorScheme,
dialogTheme: dialogTheme ?? this.dialogTheme,
typography: typography ?? this.typography,
cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
);
}
......@@ -853,6 +874,7 @@ class ThemeData extends Diagnosticable {
colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
typography: Typography.lerp(a.typography, b.typography, t),
cupertinoOverrideTheme: t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme,
);
}
......@@ -909,7 +931,8 @@ class ThemeData extends Diagnosticable {
(otherData.pageTransitionsTheme == pageTransitionsTheme) &&
(otherData.colorScheme == colorScheme) &&
(otherData.dialogTheme == dialogTheme) &&
(otherData.typography == typography);
(otherData.typography == typography) &&
(otherData.cupertinoOverrideTheme == cupertinoOverrideTheme);
}
@override
......@@ -967,6 +990,7 @@ class ThemeData extends Diagnosticable {
colorScheme,
dialogTheme,
typography,
cupertinoOverrideTheme,
),
),
);
......@@ -1019,6 +1043,109 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme));
properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme));
properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography));
properties.add(DiagnosticsProperty<CupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme));
}
}
/// A [CupertinoThemeData] that defers unspecified theme attributes to an
/// upstream Material [ThemeData].
///
/// This type of [CupertinoThemeData] is used by the Material [Theme] to
/// harmonize the [CupertinoTheme] with the material theme's colors and text
/// styles.
///
/// In the most basic case, [ThemeData]'s `cupertinoOverrideTheme` is null and
/// and descendant Cupertino widgets' styling is derived from the Material theme.
///
/// To override individual parts of the Material-derived Cupertino styling,
/// `cupertinoOverrideTheme`'s construction parameters can be used.
///
/// To completely decouple the Cupertino styling from Material theme derivation,
/// another [CupertinoTheme] widget can be inserted as a descendant of the
/// Material [Theme]. On a [MaterialApp], this can be done using the `builder`
/// parameter on the constructor.
///
/// See also:
///
/// * [CupertinoThemeData], whose null constructor parameters default to
/// reasonable iOS styling defaults rather than harmonizing with a Material
/// theme.
/// * [Theme], widget which inserts a [CupertinoTheme] with this
/// [MaterialBasedCupertinoThemeData].
// This class subclasses CupertinoThemeData rather than composes one because it
// _is_ a CupertinoThemeData with partially altered behavior. e.g. its textTheme
// is from the superclass and based on the primaryColor but the primaryColor
// comes from the Material theme unless overridden.
class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
/// Create a [MaterialBasedCupertinoThemeData] based on a Material [ThemeData]
/// and its `cupertinoOverrideTheme`.
///
/// The [materialTheme] parameter must not be null.
MaterialBasedCupertinoThemeData({
@required ThemeData materialTheme,
}) : assert(materialTheme != null),
_materialTheme = materialTheme,
// Pass all values to the superclass so Material-agnostic properties
// like barBackgroundColor can still behave like a normal
// CupertinoThemeData.
super.raw(
materialTheme.cupertinoOverrideTheme?.brightness,
materialTheme.cupertinoOverrideTheme?.primaryColor,
materialTheme.cupertinoOverrideTheme?.primaryContrastingColor,
materialTheme.cupertinoOverrideTheme?.textTheme,
materialTheme.cupertinoOverrideTheme?.barBackgroundColor,
materialTheme.cupertinoOverrideTheme?.scaffoldBackgroundColor,
);
final ThemeData _materialTheme;
@override
Brightness get brightness => _materialTheme.cupertinoOverrideTheme?.brightness ?? _materialTheme.brightness;
@override
Color get primaryColor => _materialTheme.cupertinoOverrideTheme?.primaryColor ?? _materialTheme.colorScheme.primary;
@override
Color get primaryContrastingColor => _materialTheme.cupertinoOverrideTheme?.primaryContrastingColor ?? _materialTheme.colorScheme.onPrimary;
@override
Color get scaffoldBackgroundColor => _materialTheme.cupertinoOverrideTheme?.scaffoldBackgroundColor ?? _materialTheme.scaffoldBackgroundColor;
/// Copies the [ThemeData]'s `cupertinoOverrideTheme`.
///
/// Only the specified override attributes of the [ThemeData]'s
/// `cupertinoOverrideTheme` and the newly specified parameters are in the
/// returned [CupertinoThemeData]. No derived attributes from iOS defaults or
/// from cascaded Material theme attributes are copied.
///
/// [MaterialBasedCupertinoThemeData.copyWith] cannot change the base
/// Material [ThemeData]. To change the base Material [ThemeData], create a
/// new Material [Theme] and use `copyWith` on the Material [ThemeData]
/// instead.
@override
CupertinoThemeData copyWith({
Brightness brightness,
Color primaryColor,
Color primaryContrastingColor,
CupertinoTextThemeData textTheme,
Color barBackgroundColor,
Color scaffoldBackgroundColor,
}) {
return _materialTheme.cupertinoOverrideTheme?.copyWith(
brightness: brightness,
primaryColor: primaryColor,
primaryContrastingColor: primaryContrastingColor,
textTheme: textTheme,
barBackgroundColor: barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor,
) ?? CupertinoThemeData(
brightness: brightness,
primaryColor: primaryColor,
primaryContrastingColor: primaryContrastingColor,
textTheme: textTheme,
barBackgroundColor: barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor,
);
}
}
......
......@@ -182,7 +182,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
// Proof by contradiction:
//
// If we introduce a loop, this retained layer must be appended to one of
// its descendent layers, say A. That means the child structure of A has
// its descendant layers, say A. That means the child structure of A has
// changed so A's _needsAddToScene is true. This contradicts
// _subtreeNeedsAddToScene being false.
if (!_subtreeNeedsAddToScene && _engineLayer != null) {
......
......@@ -46,7 +46,7 @@ import 'framework.dart';
///
/// When the inherited model is rebuilt the [updateShouldNotify] and
/// [updateShouldNotifyDependent] methods are used to decide what
/// should be rebuilt. If [updateShouldNotify] returns true, then the
/// should be rebuilt. If [updateShouldNotify] returns true, then the
/// inherited model's [updateShouldNotifyDependent] method is tested for
/// each dependent and the set of aspect objects it depends on.
/// The [updateShouldNotifyDependent] method must compare the set of aspect
......@@ -153,6 +153,8 @@ abstract class InheritedModel<T> extends InheritedWidget {
///
/// If [aspect] is null this method is the same as
/// `context.inheritFromWidgetOfExactType(T)`.
///
/// If no ancestor of type T exists, null is returned.
static T inheritFrom<T extends InheritedModel<Object>>(BuildContext context, { Object aspect }) {
if (aspect == null)
return context.inheritFromWidgetOfExactType(T);
......@@ -160,6 +162,10 @@ abstract class InheritedModel<T> extends InheritedWidget {
// Create a dependency on all of the type T ancestor models up until
// a model is found for which isSupportedAspect(aspect) is true.
final List<InheritedElement> models = _findModels<T>(context, aspect).toList();
if (models.isEmpty) {
return null;
}
final InheritedElement lastModel = models.last;
for (InheritedElement model in models) {
final T value = context.inheritFromElement(model, aspect: aspect);
......
......@@ -43,7 +43,7 @@ typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, W
/// * [AnimatedBuilder], which also triggers rebuilds from a [Listenable]
/// without passing back a specific value from a [ValueListenable].
/// * [NotificationListener], which lets you rebuild based on [Notification]
/// coming from its descendent widgets rather than a [ValueListenable] that
/// coming from its descendant widgets rather than a [ValueListenable] that
/// you have a direct reference to.
/// * [StreamBuilder], where a builder can depend on a [Stream] rather than
/// a [ValueListenable] for more advanced use cases.
......
......@@ -68,6 +68,70 @@ void main() {
expect(actualActive.text.style.color, const Color(0xFF123456));
});
testWidgets('Tabs respects themes', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: ImageIcon(TestImageProvider(24, 24)),
title: Text('Tab 1'),
),
BottomNavigationBarItem(
icon: ImageIcon(TestImageProvider(24, 24)),
title: Text('Tab 2'),
),
],
currentIndex: 1,
),
),
);
RichText actualInactive = tester.widget(find.descendant(
of: find.text('Tab 1'),
matching: find.byType(RichText),
));
expect(actualInactive.text.style.color, CupertinoColors.inactiveGray);
RichText actualActive = tester.widget(find.descendant(
of: find.text('Tab 2'),
matching: find.byType(RichText),
));
expect(actualActive.text.style.color, CupertinoColors.activeBlue);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: ImageIcon(TestImageProvider(24, 24)),
title: Text('Tab 1'),
),
BottomNavigationBarItem(
icon: ImageIcon(TestImageProvider(24, 24)),
title: Text('Tab 2'),
),
],
currentIndex: 1,
),
),
);
actualInactive = tester.widget(find.descendant(
of: find.text('Tab 1'),
matching: find.byType(RichText),
));
expect(actualInactive.text.style.color, CupertinoColors.inactiveGray);
actualActive = tester.widget(find.descendant(
of: find.text('Tab 2'),
matching: find.byType(RichText),
));
expect(actualActive.text.style.color, CupertinoColors.activeOrange);
});
testWidgets('Use active icon', (WidgetTester tester) async {
const TestImageProvider activeIcon = TestImageProvider(16, 16);
const TestImageProvider inactiveIcon = TestImageProvider(24, 24);
......
......@@ -12,6 +12,7 @@ import '../widgets/semantics_tester.dart';
const TextStyle testStyle = TextStyle(
fontFamily: 'Ahem',
fontSize: 10.0,
letterSpacing: 0.0,
);
void main() {
......@@ -226,6 +227,80 @@ void main() {
expect(boxDecoration.color, const Color(0x00FF00));
});
testWidgets('Botton respects themes', (WidgetTester tester) async {
TextStyle textStyle;
await tester.pumpWidget(
CupertinoApp(
home: CupertinoButton(
onPressed: () {},
child: Builder(builder: (BuildContext context) {
textStyle = DefaultTextStyle.of(context).style;
return const Placeholder();
}),
),
),
);
expect(textStyle.color, CupertinoColors.activeBlue);
await tester.pumpWidget(
CupertinoApp(
home: CupertinoButton.filled(
onPressed: () {},
child: Builder(builder: (BuildContext context) {
textStyle = DefaultTextStyle.of(context).style;
return const Placeholder();
}),
),
),
);
expect(textStyle.color, CupertinoColors.white);
BoxDecoration decoration = tester.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoButton),
matching: find.byType(DecoratedBox)
)
).decoration;
expect(decoration.color, CupertinoColors.activeBlue);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoButton(
onPressed: () {},
child: Builder(builder: (BuildContext context) {
textStyle = DefaultTextStyle.of(context).style;
return const Placeholder();
}),
),
),
);
expect(textStyle.color, CupertinoColors.activeOrange);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoButton.filled(
onPressed: () {},
child: Builder(builder: (BuildContext context) {
textStyle = DefaultTextStyle.of(context).style;
return const Placeholder();
}),
),
),
);
expect(textStyle.color, CupertinoColors.black);
decoration = tester.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoButton),
matching: find.byType(DecoratedBox)
)
).decoration;
expect(decoration.color, CupertinoColors.activeOrange);
});
}
Widget boilerplate({ Widget child }) {
......
......@@ -148,15 +148,62 @@ void main() {
expect(tester.getCenter(find.text('Title')).dx, 400.0);
});
testWidgets('Verify styles of each slot', (WidgetTester tester) async {
testWidgets('Nav bar uses theme defaults', (WidgetTester tester) async {
count = 0x000000;
await tester.pumpWidget(
const CupertinoApp(
CupertinoApp(
home: CupertinoNavigationBar(
leading: CupertinoButton(
onPressed: () {},
child: const _ExpectStyles(color: CupertinoColors.activeBlue, index: 0x000001),
),
middle: const _ExpectStyles(color: CupertinoColors.black, index: 0x000100),
trailing: CupertinoButton(
onPressed: () {},
child: const _ExpectStyles(color: CupertinoColors.activeBlue, index: 0x010000),
),
),
),
);
expect(count, 0x010101);
});
testWidgets('Nav bar respects themes', (WidgetTester tester) async {
count = 0x000000;
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoNavigationBar(
leading: _ExpectStyles(color: Color(0xFF001122), index: 0x000001),
middle: _ExpectStyles(color: Color(0xFF000000), letterSpacing: -0.08, index: 0x000100),
trailing: _ExpectStyles(color: Color(0xFF001122), index: 0x010000),
actionsForegroundColor: Color(0xFF001122),
leading: CupertinoButton(
onPressed: () {},
child: const _ExpectStyles(color: CupertinoColors.activeOrange, index: 0x000001),
),
middle: const _ExpectStyles(color: CupertinoColors.white, index: 0x000100),
trailing: CupertinoButton(
onPressed: () {},
child: const _ExpectStyles(color: CupertinoColors.activeOrange, index: 0x010000),
),
),
),
);
expect(count, 0x010101);
});
testWidgets('Theme active color can be overriden', (WidgetTester tester) async {
count = 0x000000;
await tester.pumpWidget(
CupertinoApp(
home: CupertinoNavigationBar(
leading: CupertinoButton(
onPressed: () {},
child: const _ExpectStyles(color: Color(0xFF001122), index: 0x000001),
),
middle: const _ExpectStyles(color: Color(0xFF000000), index: 0x000100),
trailing: CupertinoButton(
onPressed: () {},
child: const _ExpectStyles(color: Color(0xFF001122), index: 0x010000),
),
actionsForegroundColor: const Color(0xFF001122),
),
),
);
......@@ -845,18 +892,18 @@ void main() {
}
class _ExpectStyles extends StatelessWidget {
const _ExpectStyles({ this.color, this.letterSpacing, this.index });
const _ExpectStyles({ this.color, this.index });
final Color color;
final double letterSpacing;
final int index;
@override
Widget build(BuildContext context) {
final TextStyle style = DefaultTextStyle.of(context).style;
expect(style.color, color);
expect(style.fontFamily, '.SF Pro Text');
expect(style.fontSize, 17.0);
expect(style.letterSpacing, letterSpacing ?? -0.24);
expect(style.letterSpacing, -0.41);
count += index;
return Container();
}
......
......@@ -142,7 +142,7 @@ void main() {
}
});
testWidgets('Children, onValueChanged, and color arguments can not be null',
testWidgets('Children and onValueChanged arguments can not be null',
(WidgetTester tester) async {
try {
await tester.pumpWidget(
......@@ -175,21 +175,6 @@ void main() {
} on AssertionError catch (e) {
expect(e.toString(), contains('onValueChanged'));
}
try {
await tester.pumpWidget(
boilerplate(
child: CupertinoSegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {},
unselectedColor: null,
),
),
);
fail('Should not be possible to create segmented control with null unselectedColor');
} on AssertionError catch (e) {
expect(e.toString(), contains('unselectedColor'));
}
});
testWidgets('Widgets have correct default text/icon styles, change correctly on selection',
......@@ -236,6 +221,52 @@ void main() {
expect(iconTheme.data.color, CupertinoColors.white);
});
testWidgets(
'Segmented controls respects themes',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Icon(IconData(1));
int sharedValue = 0;
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return CupertinoSegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
);
},
),
),
);
DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first);
IconTheme iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
expect(textStyle.style.color, CupertinoColors.black);
expect(iconTheme.data.color, CupertinoColors.activeOrange);
await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1)));
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first);
iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
expect(textStyle.style.color, CupertinoColors.activeOrange);
expect(iconTheme.data.color, CupertinoColors.black);
},
);
testWidgets('SegmentedControl is correct when user provides custom colors',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
......@@ -1171,13 +1202,13 @@ void main() {
await tester.tap(find.text('B'));
await tester.pump();
expect(getBackgroundColor(tester, 0), const Color(0xff007aff));
expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
expect(getBackgroundColor(tester, 0), CupertinoColors.white);
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
expect(getBackgroundColor(tester, 3), CupertinoColors.white);
await tester.pump(const Duration(milliseconds: 40));
expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff));
expect(getBackgroundColor(tester, 1), const Color(0x64007aff));
expect(getBackgroundColor(tester, 0), CupertinoColors.white);
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
expect(getBackgroundColor(tester, 3), CupertinoColors.white);
await tester.pump(const Duration(milliseconds: 150));
......
......@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
void main() {
......@@ -359,4 +360,57 @@ void main() {
handle.dispose();
});
testWidgets('Slider respects themes', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: Center(
child: CupertinoSlider(
onChanged: (double value) {},
value: 0.5,
),
),
),
);
expect(
find.byType(CupertinoSlider),
// First line it paints is blue.
paints..rrect(color: CupertinoColors.activeBlue),
);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: Center(
child: CupertinoSlider(
onChanged: (double value) {},
value: 0.5,
),
),
),
);
expect(
find.byType(CupertinoSlider),
paints..rrect(color: CupertinoColors.activeOrange),
);
});
testWidgets('Themes can be overridden', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: Center(
child: CupertinoSlider(
activeColor: CupertinoColors.activeGreen,
onChanged: (double value) {},
value: 0.5,
),
),
),
);
expect(
find.byType(CupertinoSlider),
paints..rrect(color: CupertinoColors.activeGreen),
);
});
}
......@@ -254,6 +254,64 @@ void main() {
expect(tabsPainted, <int>[0, 1, 0]);
expect(selectedTabs, <int>[0]);
});
testWidgets('Tab bar respects themes', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
BoxDecoration tabDecoration = tester.widget<DecoratedBox>(find.descendant(
of: find.byType(CupertinoTabBar),
matching: find.byType(DecoratedBox),
)).decoration;
expect(tabDecoration.color, const Color(0xCCF8F8F8));
await tester.tap(find.text('Tab 2'));
await tester.pump();
// Pump again but with dark theme.
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(
brightness: Brightness.dark,
primaryColor: CupertinoColors.destructiveRed,
),
home: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
tabDecoration = tester.widget<DecoratedBox>(find.descendant(
of: find.byType(CupertinoTabBar),
matching: find.byType(DecoratedBox),
)).decoration;
expect(tabDecoration.color, const Color(0xB7212121));
final RichText tab1 = tester.widget(find.descendant(
of: find.text('Tab 1'),
matching: find.byType(RichText),
));
// Tab 2 should still be selected after changing theme.
expect(tab1.text.style.color, CupertinoColors.inactiveGray);
final RichText tab2 = tester.widget(find.descendant(
of: find.text('Tab 2'),
matching: find.byType(RichText),
));
expect(tab2.text.style.color, CupertinoColors.destructiveRed);
});
}
CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
......@@ -268,7 +326,6 @@ CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
title: Text('Tab 2'),
),
],
backgroundColor: CupertinoColors.white,
currentIndex: selectedTab,
onTap: (int newTab) => selectedTabs.add(newTab),
);
......
......@@ -1098,4 +1098,42 @@ void main() {
expect(find.byType(CupertinoButton), findsNWidgets(3));
},
);
testWidgets(
'text field respects theme',
(WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
theme: CupertinoThemeData(
brightness: Brightness.dark,
),
home: Center(
child: CupertinoTextField(),
),
),
);
final BoxDecoration decoration = tester.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoTextField),
matching: find.byType(DecoratedBox)
),
).decoration;
expect(
decoration.border.bottom.color,
CupertinoColors.lightBackgroundGray, // Border color is the same regardless.
);
await tester.enterText(find.byType(CupertinoTextField), 'smoked meat');
await tester.pump();
expect(
tester.renderObject<RenderEditable>(
find.byElementPredicate((Element element) => element.renderObject is RenderEditable)
).text.style.color,
CupertinoColors.white,
);
},
);
}
// Copyright 2018 The Chromium 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 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
int buildCount;
CupertinoThemeData actualTheme;
final Widget singletonThemeSubtree = Builder(
builder: (BuildContext context) {
buildCount++;
actualTheme = CupertinoTheme.of(context);
return const Placeholder();
},
);
Future<CupertinoThemeData> testTheme(WidgetTester tester, CupertinoThemeData theme) async {
await tester.pumpWidget(
CupertinoTheme(
data: theme,
child: singletonThemeSubtree,
),
);
return actualTheme;
}
void main() {
setUp(() {
buildCount = 0;
actualTheme = null;
});
testWidgets('Default theme has defaults', (WidgetTester tester) async {
final CupertinoThemeData theme = await testTheme(tester, const CupertinoThemeData());
expect(theme.brightness, Brightness.light);
expect(theme.primaryColor, CupertinoColors.activeBlue);
expect(theme.textTheme.textStyle.fontSize, 17.0);
});
testWidgets('Theme attributes cascade', (WidgetTester tester) async {
final CupertinoThemeData theme = await testTheme(tester, const CupertinoThemeData(
primaryColor: CupertinoColors.destructiveRed,
));
expect(theme.textTheme.actionTextStyle.color, CupertinoColors.destructiveRed);
});
testWidgets('Dependent attribute can be overridden from cascaded value', (WidgetTester tester) async {
final CupertinoThemeData theme = await testTheme(tester, const CupertinoThemeData(
brightness: Brightness.dark,
textTheme: CupertinoTextThemeData(
textStyle: TextStyle(color: CupertinoColors.black),
)
));
// The brightness still cascaded down to the background color.
expect(theme.scaffoldBackgroundColor, CupertinoColors.black);
// But not to the font color which we overrode.
expect(theme.textTheme.textStyle.color, CupertinoColors.black);
});
testWidgets(
'Reading themes creates dependencies',
(WidgetTester tester) async {
// Reading the theme creates a dependency.
CupertinoThemeData theme = await testTheme(tester, const CupertinoThemeData(
// Default brightness is light,
barBackgroundColor: Color(0x11223344),
textTheme: CupertinoTextThemeData(
textStyle: TextStyle(fontFamily: 'Skeuomorphic'),
),
));
expect(buildCount, 1);
expect(theme.textTheme.textStyle.fontFamily, 'Skeuomorphic');
// Changing another property also triggers a rebuild.
theme = await testTheme(tester, const CupertinoThemeData(
brightness: Brightness.light,
barBackgroundColor: Color(0x11223344),
textTheme: CupertinoTextThemeData(
textStyle: TextStyle(fontFamily: 'Skeuomorphic'),
),
));
expect(buildCount, 2);
// Re-reading the same value doesn't change anything.
expect(theme.textTheme.textStyle.fontFamily, 'Skeuomorphic');
theme = await testTheme(tester, const CupertinoThemeData(
brightness: Brightness.light,
barBackgroundColor: Color(0x11223344),
textTheme: CupertinoTextThemeData(
textStyle: TextStyle(fontFamily: 'Flat'),
),
));
expect(buildCount, 3);
expect(theme.textTheme.textStyle.fontFamily, 'Flat');
},
);
testWidgets(
'copyWith works',
(WidgetTester tester) async {
const CupertinoThemeData originalTheme = CupertinoThemeData(
brightness: Brightness.dark,
);
final CupertinoThemeData theme = await testTheme(tester, originalTheme.copyWith(
primaryColor: CupertinoColors.activeGreen,
));
expect(theme.brightness, Brightness.dark);
expect(theme.primaryColor, CupertinoColors.activeGreen);
// Now check calculated derivatives.
expect(theme.textTheme.actionTextStyle.color, CupertinoColors.activeGreen);
expect(theme.scaffoldBackgroundColor, CupertinoColors.black);
},
);
}
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/src/foundation/diagnostics.dart';
......@@ -412,6 +413,227 @@ void main() {
expect(theme.textTheme.display4.debugLabel, '(englishLike display4 2014).merge(blackMountainView display4)');
});
group('Cupertino theme', () {
int buildCount;
CupertinoThemeData actualTheme;
final Widget singletonThemeSubtree = Builder(
builder: (BuildContext context) {
buildCount++;
actualTheme = CupertinoTheme.of(context);
return const Placeholder();
},
);
Future<CupertinoThemeData> testTheme(WidgetTester tester, ThemeData theme) async {
await tester.pumpWidget(
Theme(
data: theme,
child: singletonThemeSubtree,
),
);
return actualTheme;
}
setUp(() {
buildCount = 0;
actualTheme = null;
});
testWidgets('Default theme has defaults', (WidgetTester tester) async {
final CupertinoThemeData theme = await testTheme(tester, ThemeData.light());
expect(theme.brightness, Brightness.light);
expect(theme.primaryColor, Colors.blue);
expect(theme.scaffoldBackgroundColor, Colors.grey[50]);
expect(theme.primaryContrastingColor, Colors.white);
expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text');
expect(theme.textTheme.textStyle.fontSize, 17.0);
});
testWidgets('Dark theme has defaults', (WidgetTester tester) async {
final CupertinoThemeData theme = await testTheme(tester, ThemeData.dark());
expect(theme.brightness, Brightness.dark);
expect(theme.primaryColor, Colors.blue);
expect(theme.primaryContrastingColor, Colors.white);
expect(theme.scaffoldBackgroundColor, Colors.grey[850]);
expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text');
expect(theme.textTheme.textStyle.fontSize, 17.0);
});
testWidgets('Can override material theme', (WidgetTester tester) async {
final CupertinoThemeData theme = await testTheme(tester, ThemeData(
cupertinoOverrideTheme: const CupertinoThemeData(
scaffoldBackgroundColor: CupertinoColors.lightBackgroundGray,
),
));
expect(theme.brightness, Brightness.light);
// We took the scaffold background override but the rest are still cascaded
// to the material theme.
expect(theme.primaryColor, Colors.blue);
expect(theme.primaryContrastingColor, Colors.white);
expect(theme.scaffoldBackgroundColor, CupertinoColors.lightBackgroundGray);
expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text');
expect(theme.textTheme.textStyle.fontSize, 17.0);
});
testWidgets('Can override properties that are independent of material', (WidgetTester tester) async {
final CupertinoThemeData theme = await testTheme(tester, ThemeData(
cupertinoOverrideTheme: const CupertinoThemeData(
// The bar colors ignore all things material except brightness.
barBackgroundColor: CupertinoColors.black,
),
));
expect(theme.primaryColor, Colors.blue);
// MaterialBasedCupertinoThemeData should also function like a normal CupertinoThemeData.
expect(theme.barBackgroundColor, CupertinoColors.black);
});
testWidgets('Changing material theme triggers rebuilds', (WidgetTester tester) async {
CupertinoThemeData theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.red,
));
expect(buildCount, 1);
expect(theme.primaryColor, Colors.red);
theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.orange,
));
expect(buildCount, 2);
expect(theme.primaryColor, Colors.orange);
});
testWidgets(
'Changing cupertino theme override triggers rebuilds',
(WidgetTester tester) async {
CupertinoThemeData theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.purple,
cupertinoOverrideTheme: const CupertinoThemeData(
primaryColor: CupertinoColors.activeOrange,
),
));
expect(buildCount, 1);
expect(theme.primaryColor, CupertinoColors.activeOrange);
theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.purple,
cupertinoOverrideTheme: const CupertinoThemeData(
primaryColor: CupertinoColors.activeGreen,
),
));
expect(buildCount, 2);
expect(theme.primaryColor, CupertinoColors.activeGreen);
},
);
testWidgets(
'Cupertino theme override blocks derivative changes',
(WidgetTester tester) async {
CupertinoThemeData theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.purple,
cupertinoOverrideTheme: const CupertinoThemeData(
primaryColor: CupertinoColors.activeOrange,
),
));
expect(buildCount, 1);
expect(theme.primaryColor, CupertinoColors.activeOrange);
// Change the upstream material primary color.
theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.blue,
cupertinoOverrideTheme: const CupertinoThemeData(
// But the primary material color is preempted by the override.
primaryColor: CupertinoColors.activeOrange,
),
));
expect(buildCount, 2);
expect(theme.primaryColor, CupertinoColors.activeOrange);
},
);
testWidgets(
'Cupertino overrides do not block derivatives triggering rebuilds when derivatives are not overridden',
(WidgetTester tester) async {
CupertinoThemeData theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.purple,
cupertinoOverrideTheme: const CupertinoThemeData(
primaryContrastingColor: CupertinoColors.destructiveRed,
),
));
expect(buildCount, 1);
expect(theme.textTheme.actionTextStyle.color, Colors.purple);
expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed);
theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.green,
cupertinoOverrideTheme: const CupertinoThemeData(
primaryContrastingColor: CupertinoColors.destructiveRed,
),
));
expect(buildCount, 2);
expect(theme.textTheme.actionTextStyle.color, Colors.green);
expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed);
},
);
testWidgets(
'copyWith only copies the overrides, not the material or cupertino derivatives',
(WidgetTester tester) async {
final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData(
primarySwatch: Colors.purple,
cupertinoOverrideTheme: const CupertinoThemeData(
primaryContrastingColor: CupertinoColors.activeOrange,
),
));
final CupertinoThemeData copiedTheme = originalTheme.copyWith(
barBackgroundColor: CupertinoColors.destructiveRed,
);
final CupertinoThemeData theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.blue,
cupertinoOverrideTheme: copiedTheme,
));
expect(theme.primaryColor, Colors.blue);
expect(theme.primaryContrastingColor, CupertinoColors.activeOrange);
expect(theme.barBackgroundColor, CupertinoColors.destructiveRed);
},
);
testWidgets(
"Material themes with no cupertino overrides can also be copyWith'ed",
(WidgetTester tester) async {
final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData(
primarySwatch: Colors.purple,
));
final CupertinoThemeData copiedTheme = originalTheme.copyWith(
primaryContrastingColor: CupertinoColors.destructiveRed,
);
final CupertinoThemeData theme = await testTheme(tester, ThemeData(
primarySwatch: Colors.blue,
cupertinoOverrideTheme: copiedTheme,
));
expect(theme.primaryColor, Colors.blue);
expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed);
},
);
});
}
int testBuildCalled;
......
......@@ -190,6 +190,22 @@ void main() {
expect(find.text('a: 2 b: 2 c: 3'), findsOneWidget);
});
testWidgets('Looking up an non existent InherintedModel ancestor returns null', (WidgetTester tester) async {
ABCModel inheritedModel;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
inheritedModel = InheritedModel.inheritFrom(context);
return Container();
},
),
);
// Shouldn't crash first of all.
expect(inheritedModel, null);
});
testWidgets('Inner InheritedModel shadows the outer one', (WidgetTester tester) async {
int _a = 0;
int _b = 1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment