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 { ...@@ -13,8 +13,11 @@ class CupertinoProgressIndicatorDemo extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar( navigationBar: CupertinoNavigationBar(
previousPageTitle: 'Back', // We're specifying a back label here because the previous page is a
middle: const Text('Cupertino Activity Indicator'), // Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
middle: const Text('Activity Indicator'),
trailing: CupertinoDemoDocumentationButton(routeName), trailing: CupertinoDemoDocumentationButton(routeName),
), ),
child: const Center( child: const Center(
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart'; import '../../gallery/demo.dart';
...@@ -19,64 +18,71 @@ class _CupertinoButtonDemoState extends State<CupertinoButtonsDemo> { ...@@ -19,64 +18,71 @@ class _CupertinoButtonDemoState extends State<CupertinoButtonsDemo> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return CupertinoPageScaffold(
appBar: AppBar( navigationBar: CupertinoNavigationBar(
title: const Text('Cupertino Buttons'), middle: const Text('Buttons'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoButtonsDemo.routeName)], // 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( child: DefaultTextStyle(
children: <Widget> [ style: CupertinoTheme.of(context).textTheme.textStyle,
const Padding( child: SafeArea(
padding: EdgeInsets.all(16.0), child: Column(
child: Text( children: <Widget>[
'iOS themed buttons are flat. They can have borders or backgrounds but ' const Padding(
'only when necessary.' padding: EdgeInsets.all(16.0),
), child: Text(
), 'iOS themed buttons are flat. They can have borders or backgrounds but '
Expanded( 'only when necessary.'
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( Expanded(
child: const Text('With Background'), child: Column(
color: CupertinoColors.activeBlue, mainAxisAlignment: MainAxisAlignment.center,
onPressed: () { children: <Widget> [
setState(() { _pressedCount += 1; }); Text(_pressedCount > 0
} ? 'Button pressed $_pressedCount time${_pressedCount == 1 ? "" : "s"}'
), : ' '),
const Padding(padding: EdgeInsets.all(12.0)), const Padding(padding: EdgeInsets.all(12.0)),
const CupertinoButton( Align(
child: Text('Disabled'), alignment: const Alignment(0.0, -0.2),
color: CupertinoColors.activeBlue, child: Row(
onPressed: null, 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 { ...@@ -52,11 +52,7 @@ class CupertinoNavigationDemo extends StatelessWidget {
// Prevent swipe popping of this page. Use explicit exit buttons only. // Prevent swipe popping of this page. Use explicit exit buttons only.
onWillPop: () => Future<bool>.value(true), onWillPop: () => Future<bool>.value(true),
child: DefaultTextStyle( child: DefaultTextStyle(
style: const TextStyle( style: CupertinoTheme.of(context).textTheme.textStyle,
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: CupertinoTabScaffold( child: CupertinoTabScaffold(
tabBar: CupertinoTabBar( tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[ items: const <BottomNavigationBarItem>[
...@@ -241,7 +237,6 @@ class Tab1RowItem extends StatelessWidget { ...@@ -241,7 +237,6 @@ class Tab1RowItem extends StatelessWidget {
CupertinoButton( CupertinoButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.plus_circled, child: const Icon(CupertinoIcons.plus_circled,
color: CupertinoColors.activeBlue,
semanticLabel: 'Add', semanticLabel: 'Add',
), ),
onPressed: () { }, onPressed: () { },
...@@ -249,7 +244,6 @@ class Tab1RowItem extends StatelessWidget { ...@@ -249,7 +244,6 @@ class Tab1RowItem extends StatelessWidget {
CupertinoButton( CupertinoButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.share, child: const Icon(CupertinoIcons.share,
color: CupertinoColors.activeBlue,
semanticLabel: 'Share', semanticLabel: 'Share',
), ),
onPressed: () { }, onPressed: () { },
...@@ -352,8 +346,7 @@ class Tab1ItemPageState extends State<Tab1ItemPage> { ...@@ -352,8 +346,7 @@ class Tab1ItemPageState extends State<Tab1ItemPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
CupertinoButton( CupertinoButton.filled(
color: CupertinoColors.activeBlue,
minSize: 30.0, minSize: 30.0,
padding: const EdgeInsets.symmetric(horizontal: 24.0), padding: const EdgeInsets.symmetric(horizontal: 24.0),
borderRadius: BorderRadius.circular(32.0), borderRadius: BorderRadius.circular(32.0),
...@@ -367,12 +360,11 @@ class Tab1ItemPageState extends State<Tab1ItemPage> { ...@@ -367,12 +360,11 @@ class Tab1ItemPageState extends State<Tab1ItemPage> {
), ),
onPressed: () { }, onPressed: () { },
), ),
CupertinoButton( CupertinoButton.filled(
color: CupertinoColors.activeBlue,
minSize: 30.0, minSize: 30.0,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
borderRadius: BorderRadius.circular(32.0), borderRadius: BorderRadius.circular(32.0),
child: const Icon(CupertinoIcons.ellipsis, color: CupertinoColors.white), child: const Icon(CupertinoIcons.ellipsis),
onPressed: () { }, onPressed: () { },
), ),
], ],
...@@ -722,7 +714,11 @@ class CupertinoDemoTab3 extends StatelessWidget { ...@@ -722,7 +714,11 @@ class CupertinoDemoTab3 extends StatelessWidget {
trailing: trailingButtons, trailing: trailingButtons,
), ),
child: DecoratedBox( child: DecoratedBox(
decoration: const BoxDecoration(color: Color(0xFFEFEFF4)), decoration: BoxDecoration(
color: CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.extraLightBackgroundGray
: CupertinoColors.darkBackgroundGray,
),
child: ListView( child: ListView(
children: <Widget>[ children: <Widget>[
const Padding(padding: EdgeInsets.only(top: 32.0)), const Padding(padding: EdgeInsets.only(top: 32.0)),
...@@ -736,9 +732,9 @@ class CupertinoDemoTab3 extends StatelessWidget { ...@@ -736,9 +732,9 @@ class CupertinoDemoTab3 extends StatelessWidget {
); );
}, },
child: Container( child: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
color: CupertinoColors.white, color: CupertinoTheme.of(context).scaffoldBackgroundColor,
border: Border( border: const Border(
top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
), ),
...@@ -750,10 +746,10 @@ class CupertinoDemoTab3 extends StatelessWidget { ...@@ -750,10 +746,10 @@ class CupertinoDemoTab3 extends StatelessWidget {
top: false, top: false,
bottom: false, bottom: false,
child: Row( child: Row(
children: const <Widget>[ children: <Widget>[
Text( Text(
'Sign in', 'Sign in',
style: TextStyle(color: CupertinoColors.activeBlue), style: TextStyle(color: CupertinoTheme.of(context).primaryColor),
) )
], ],
), ),
...@@ -791,8 +787,7 @@ class Tab3Dialog extends StatelessWidget { ...@@ -791,8 +787,7 @@ class Tab3Dialog extends StatelessWidget {
color: Color(0xFF646464), color: Color(0xFF646464),
), ),
const Padding(padding: EdgeInsets.only(top: 18.0)), const Padding(padding: EdgeInsets.only(top: 18.0)),
CupertinoButton( CupertinoButton.filled(
color: CupertinoColors.activeBlue,
child: const Text('Sign in'), child: const Text('Sign in'),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../gallery/demo.dart'; import '../../gallery/demo.dart';
...@@ -34,9 +33,9 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -34,9 +33,9 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
Widget _buildMenu(List<Widget> children) { Widget _buildMenu(List<Widget> children) {
return Container( return Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
color: CupertinoColors.white, color: CupertinoTheme.of(context).scaffoldBackgroundColor,
border: Border( border: const Border(
top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0),
), ),
...@@ -47,16 +46,9 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -47,16 +46,9 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
child: SafeArea( child: SafeArea(
top: false, top: false,
bottom: false, bottom: false,
child: DefaultTextStyle( child: Row(
style: const TextStyle( mainAxisAlignment: MainAxisAlignment.spaceBetween,
letterSpacing: -0.24, children: children,
fontSize: 17.0,
color: CupertinoColors.black,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
), ),
), ),
), ),
...@@ -249,19 +241,23 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -249,19 +241,23 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return CupertinoPageScaffold(
appBar: AppBar( navigationBar: CupertinoNavigationBar(
title: const Text('Cupertino Picker'), middle: const Text('Picker'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoPickerDemo.routeName)], // 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( child: DefaultTextStyle(
style: const TextStyle( style: CupertinoTheme.of(context).textTheme.textStyle,
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: DecoratedBox( child: DecoratedBox(
decoration: const BoxDecoration(color: Color(0xFFEFEFF4)), decoration: BoxDecoration(
color: CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.extraLightBackgroundGray
: CupertinoColors.darkBackgroundGray,
),
child: ListView( child: ListView(
children: <Widget>[ children: <Widget>[
const Padding(padding: EdgeInsets.only(top: 32.0)), const Padding(padding: EdgeInsets.only(top: 32.0)),
......
...@@ -39,19 +39,21 @@ class _CupertinoRefreshControlDemoState extends State<CupertinoRefreshControlDem ...@@ -39,19 +39,21 @@ class _CupertinoRefreshControlDemoState extends State<CupertinoRefreshControlDem
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultTextStyle( return DefaultTextStyle(
style: const TextStyle( style: CupertinoTheme.of(context).textTheme.textStyle,
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 17.0,
color: CupertinoColors.black,
),
child: CupertinoPageScaffold( child: CupertinoPageScaffold(
child: DecoratedBox( child: DecoratedBox(
decoration: const BoxDecoration(color: Color(0xFFEFEFF4)), decoration: BoxDecoration(
color: CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.extraLightBackgroundGray
: CupertinoColors.darkBackgroundGray,
),
child: CustomScrollView( child: CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
CupertinoSliverNavigationBar( 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', previousPageTitle: 'Cupertino',
trailing: CupertinoDemoDocumentationButton(CupertinoRefreshControlDemo.routeName), trailing: CupertinoDemoDocumentationButton(CupertinoRefreshControlDemo.routeName),
), ),
...@@ -152,7 +154,7 @@ class _ListItem extends StatelessWidget { ...@@ -152,7 +154,7 @@ class _ListItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: CupertinoColors.white, color: CupertinoTheme.of(context).scaffoldBackgroundColor,
height: 60.0, height: 60.0,
padding: const EdgeInsets.only(top: 9.0), padding: const EdgeInsets.only(top: 9.0),
child: Row( child: Row(
...@@ -215,11 +217,11 @@ class _ListItem extends StatelessWidget { ...@@ -215,11 +217,11 @@ class _ListItem extends StatelessWidget {
letterSpacing: -0.41, letterSpacing: -0.41,
), ),
), ),
const Padding( Padding(
padding: EdgeInsets.only(left: 9.0), padding: const EdgeInsets.only(left: 9.0),
child: Icon( child: Icon(
CupertinoIcons.info, CupertinoIcons.info,
color: CupertinoColors.activeBlue color: CupertinoTheme.of(context).primaryColor,
), ),
), ),
], ],
......
...@@ -50,68 +50,77 @@ class _CupertinoSegmentedControlDemoState extends State<CupertinoSegmentedContro ...@@ -50,68 +50,77 @@ class _CupertinoSegmentedControlDemoState extends State<CupertinoSegmentedContro
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return CupertinoPageScaffold(
appBar: AppBar( navigationBar: CupertinoNavigationBar(
title: const Text('Segmented Control'), middle: const Text('Segmented Control'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoSegmentedControlDemo.routeName)], // 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( child: DefaultTextStyle(
children: <Widget>[ style: CupertinoTheme.of(context).textTheme.textStyle,
const Padding( child: SafeArea(
padding: EdgeInsets.all(16.0), child: Column(
), children: <Widget>[
SizedBox( const Padding(
width: 500.0, padding: EdgeInsets.all(16.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: Container( SizedBox(
padding: const EdgeInsets.symmetric( width: 500.0,
vertical: 64.0, child: CupertinoSegmentedControl<int>(
horizontal: 16.0, children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
), ),
decoration: BoxDecoration( ),
color: CupertinoColors.white, Expanded(
borderRadius: BorderRadius.circular(3.0), child: Padding(
boxShadow: const <BoxShadow>[ padding: const EdgeInsets.symmetric(
BoxShadow( vertical: 32.0,
offset: Offset(0.0, 3.0), horizontal: 16.0,
blurRadius: 5.0, ),
spreadRadius: -1.0, child: Container(
color: _kKeyUmbraOpacity, padding: const EdgeInsets.symmetric(
), vertical: 64.0,
BoxShadow( horizontal: 16.0,
offset: Offset(0.0, 6.0),
blurRadius: 10.0,
spreadRadius: 0.0,
color: _kKeyPenumbraOpacity,
), ),
BoxShadow( decoration: BoxDecoration(
offset: Offset(0.0, 1.0), color: CupertinoTheme.of(context).scaffoldBackgroundColor,
blurRadius: 18.0, borderRadius: BorderRadius.circular(3.0),
spreadRadius: 0.0, boxShadow: const <BoxShadow>[
color: _kAmbientShadowOpacity, 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 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart'; import '../../gallery/demo.dart';
...@@ -20,49 +19,58 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> { ...@@ -20,49 +19,58 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return CupertinoPageScaffold(
appBar: AppBar( navigationBar: CupertinoNavigationBar(
title: const Text('Cupertino Sliders'), middle: const Text('Sliders'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoSliderDemo.routeName)], // 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: DefaultTextStyle(
child: Column( style: CupertinoTheme.of(context).textTheme.textStyle,
mainAxisAlignment: MainAxisAlignment.spaceAround, child: SafeArea(
children: <Widget>[ child: Center(
Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget> [ children: <Widget>[
CupertinoSlider( Column(
value: _value, mainAxisSize: MainAxisSize.min,
min: 0.0, children: <Widget> [
max: 100.0, CupertinoSlider(
onChanged: (double value) { value: _value,
setState(() { min: 0.0,
_value = value; 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> [
Column( CupertinoSlider(
mainAxisSize: MainAxisSize.min, value: _discreteValue,
children: <Widget> [ min: 0.0,
CupertinoSlider( max: 100.0,
value: _discreteValue, divisions: 5,
min: 0.0, onChanged: (double value) {
max: 100.0, setState(() {
divisions: 5, _discreteValue = value;
onChanged: (double value) { });
setState(() { }
_discreteValue = value; ),
}); Text('Cupertino Discrete: $_discreteValue'),
} ]
), ),
Text('Cupertino Discrete: $_discreteValue'), ],
]
), ),
], ),
), ),
), ),
); );
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart'; import '../../gallery/demo.dart';
...@@ -20,62 +19,71 @@ class _CupertinoSwitchDemoState extends State<CupertinoSwitchDemo> { ...@@ -20,62 +19,71 @@ class _CupertinoSwitchDemoState extends State<CupertinoSwitchDemo> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return CupertinoPageScaffold(
appBar: AppBar( navigationBar: CupertinoNavigationBar(
title: const Text('Cupertino Switch'), middle: const Text('Switch'),
actions: <Widget>[MaterialDemoDocumentationButton(CupertinoSwitchDemo.routeName)], // 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: DefaultTextStyle(
child: Column( style: CupertinoTheme.of(context).textTheme.textStyle,
mainAxisAlignment: MainAxisAlignment.spaceAround, child: SafeArea(
children: <Widget>[ child: Center(
Semantics( child: Column(
container: true, mainAxisAlignment: MainAxisAlignment.spaceAround,
child: Column( children: <Widget>[
children: <Widget>[ Semantics(
CupertinoSwitch( container: true,
value: _switchValue, child: Column(
onChanged: (bool value) { children: <Widget>[
setState(() { CupertinoSwitch(
_switchValue = value; 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,
Semantics( child: Column(
container: true, children: const <Widget>[
child: Column( CupertinoSwitch(
children: const <Widget>[ value: false,
CupertinoSwitch( onChanged: null,
value: true, ),
onChanged: null, Text(
), 'Disabled'
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> { ...@@ -160,6 +160,9 @@ class _CupertinoTextFieldDemoState extends State<CupertinoTextFieldDemo> {
), ),
child: CupertinoPageScaffold( child: CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar( 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', previousPageTitle: 'Cupertino',
middle: Text('Text Fields'), middle: Text('Text Fields'),
), ),
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show defaultTargetPlatform; import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation; import 'package:flutter/scheduler.dart' show timeDilation;
...@@ -125,7 +126,6 @@ class _GalleryAppState extends State<GalleryApp> { ...@@ -125,7 +126,6 @@ class _GalleryAppState extends State<GalleryApp> {
child: home, child: home,
); );
} }
return MaterialApp( return MaterialApp(
theme: _options.theme.data.copyWith(platform: _options.platform), theme: _options.theme.data.copyWith(platform: _options.platform),
title: 'Flutter Gallery', title: 'Flutter Gallery',
...@@ -137,7 +137,17 @@ class _GalleryAppState extends State<GalleryApp> { ...@@ -137,7 +137,17 @@ class _GalleryAppState extends State<GalleryApp> {
builder: (BuildContext context, Widget child) { builder: (BuildContext context, Widget child) {
return Directionality( return Directionality(
textDirection: _options.textDirection, 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, home: home,
......
...@@ -30,5 +30,7 @@ export 'src/cupertino/tab_scaffold.dart'; ...@@ -30,5 +30,7 @@ export 'src/cupertino/tab_scaffold.dart';
export 'src/cupertino/tab_view.dart'; export 'src/cupertino/tab_view.dart';
export 'src/cupertino/text_field.dart'; export 'src/cupertino/text_field.dart';
export 'src/cupertino/text_selection.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 'src/cupertino/thumb_painter.dart';
export 'widgets.dart'; export 'widgets.dart';
...@@ -10,16 +10,7 @@ import 'colors.dart'; ...@@ -10,16 +10,7 @@ import 'colors.dart';
import 'icons.dart'; import 'icons.dart';
import 'localizations.dart'; import 'localizations.dart';
import 'route.dart'; import 'route.dart';
import 'theme.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,
);
/// An application that uses Cupertino design. /// An application that uses Cupertino design.
/// ///
...@@ -79,6 +70,7 @@ class CupertinoApp extends StatefulWidget { ...@@ -79,6 +70,7 @@ class CupertinoApp extends StatefulWidget {
Key key, Key key,
this.navigatorKey, this.navigatorKey,
this.home, this.home,
this.theme,
this.routes = const <String, WidgetBuilder>{}, this.routes = const <String, WidgetBuilder>{},
this.initialRoute, this.initialRoute,
this.onGenerateRoute, this.onGenerateRoute,
...@@ -114,6 +106,12 @@ class CupertinoApp extends StatefulWidget { ...@@ -114,6 +106,12 @@ class CupertinoApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.home} /// {@macro flutter.widgets.widgetsApp.home}
final Widget 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. /// The application's top-level routing table.
/// ///
/// When a named route is pushed with [Navigator.pushNamed], the route name is /// When a named route is pushed with [Navigator.pushNamed], the route name is
...@@ -266,48 +264,52 @@ class _CupertinoAppState extends State<CupertinoApp> { ...@@ -266,48 +264,52 @@ class _CupertinoAppState extends State<CupertinoApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final CupertinoThemeData effectiveThemeData = widget.theme ?? const CupertinoThemeData();
return ScrollConfiguration( return ScrollConfiguration(
behavior: _AlwaysCupertinoScrollBehavior(), behavior: _AlwaysCupertinoScrollBehavior(),
child: WidgetsApp( child: CupertinoTheme(
key: GlobalObjectKey(this), data: effectiveThemeData,
navigatorKey: widget.navigatorKey, child: WidgetsApp(
navigatorObservers: _navigatorObservers, key: GlobalObjectKey(this),
// TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved navigatorKey: widget.navigatorKey,
// this can use type arguments again navigatorObservers: _navigatorObservers,
pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) => // TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved
CupertinoPageRoute<dynamic>(settings: settings, builder: builder), // this can use type arguments again
home: widget.home, pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
routes: widget.routes, CupertinoPageRoute<dynamic>(settings: settings, builder: builder),
initialRoute: widget.initialRoute, home: widget.home,
onGenerateRoute: widget.onGenerateRoute, routes: widget.routes,
onUnknownRoute: widget.onUnknownRoute, initialRoute: widget.initialRoute,
builder: widget.builder, onGenerateRoute: widget.onGenerateRoute,
title: widget.title, onUnknownRoute: widget.onUnknownRoute,
onGenerateTitle: widget.onGenerateTitle, builder: widget.builder,
textStyle: _kDefaultTextStyle, title: widget.title,
color: widget.color ?? CupertinoColors.activeBlue, onGenerateTitle: widget.onGenerateTitle,
locale: widget.locale, textStyle: effectiveThemeData.textTheme.textStyle,
localizationsDelegates: _localizationsDelegates, color: widget.color ?? CupertinoColors.activeBlue,
localeResolutionCallback: widget.localeResolutionCallback, locale: widget.locale,
localeListResolutionCallback: widget.localeListResolutionCallback, localizationsDelegates: _localizationsDelegates,
supportedLocales: widget.supportedLocales, localeResolutionCallback: widget.localeResolutionCallback,
showPerformanceOverlay: widget.showPerformanceOverlay, localeListResolutionCallback: widget.localeListResolutionCallback,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, supportedLocales: widget.supportedLocales,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, showPerformanceOverlay: widget.showPerformanceOverlay,
showSemanticsDebugger: widget.showSemanticsDebugger, checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { showSemanticsDebugger: widget.showSemanticsDebugger,
return CupertinoButton( debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
child: const Icon( inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
CupertinoIcons.search, return CupertinoButton.filled(
size: 28.0, child: const Icon(
color: CupertinoColors.white, CupertinoIcons.search,
), size: 28.0,
color: CupertinoColors.activeBlue, color: CupertinoColors.white,
padding: EdgeInsets.zero, ),
onPressed: onPressed, padding: EdgeInsets.zero,
); onPressed: onPressed,
}, );
},
),
), ),
); );
} }
......
...@@ -7,11 +7,11 @@ import 'dart:ui' show ImageFilter; ...@@ -7,11 +7,11 @@ import 'dart:ui' show ImageFilter;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'theme.dart';
// Standard iOS 10 tab bar height. // Standard iOS 10 tab bar height.
const double _kTabBarHeight = 50.0; const double _kTabBarHeight = 50.0;
const Color _kDefaultTabBarBackgroundColor = Color(0xCCF8F8F8);
const Color _kDefaultTabBarBorderColor = Color(0x4C000000); const Color _kDefaultTabBarBorderColor = Color(0x4C000000);
/// An iOS-styled bottom navigation tab bar. /// An iOS-styled bottom navigation tab bar.
...@@ -21,10 +21,12 @@ const Color _kDefaultTabBarBorderColor = Color(0x4C000000); ...@@ -21,10 +21,12 @@ const Color _kDefaultTabBarBorderColor = Color(0x4C000000);
/// ///
/// This [StatelessWidget] doesn't store the active tab itself. You must /// This [StatelessWidget] doesn't store the active tab itself. You must
/// listen to the [onTap] callbacks and call `setState` with a new [currentIndex] /// 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 /// 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 /// 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. /// default), it will produce a blurring effect to the content behind it.
...@@ -40,8 +42,8 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -40,8 +42,8 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
@required this.items, @required this.items,
this.onTap, this.onTap,
this.currentIndex = 0, this.currentIndex = 0,
this.backgroundColor = _kDefaultTabBarBackgroundColor, this.backgroundColor,
this.activeColor = CupertinoColors.activeBlue, this.activeColor,
this.inactiveColor = CupertinoColors.inactiveGray, this.inactiveColor = CupertinoColors.inactiveGray,
this.iconSize = 30.0, this.iconSize = 30.0,
this.border = const Border( this.border = const Border(
...@@ -56,6 +58,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -56,6 +58,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
assert(currentIndex != null), assert(currentIndex != null),
assert(0 <= currentIndex && currentIndex < items.length), assert(0 <= currentIndex && currentIndex < items.length),
assert(iconSize != null), assert(iconSize != null),
assert(inactiveColor != null),
super(key: key); super(key: key);
/// The interactive items laid out within the bottom navigation bar. /// The interactive items laid out within the bottom navigation bar.
...@@ -78,14 +81,20 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -78,14 +81,20 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
/// The background color of the tab bar. If it contains transparency, the /// The background color of the tab bar. If it contains transparency, the
/// tab bar will automatically produce a blurring effect to the content /// tab bar will automatically produce a blurring effect to the content
/// behind it. /// behind it.
///
/// Defaults to [CupertinoTheme]'s `barBackgroundColor` when null.
final Color backgroundColor; final Color backgroundColor;
/// The foreground color of the icon and title for the [BottomNavigationBarItem] /// The foreground color of the icon and title for the [BottomNavigationBarItem]
/// of the selected tab. /// of the selected tab.
///
/// Defaults to [CupertinoTheme]'s `primaryColor` if null.
final Color activeColor; final Color activeColor;
/// The foreground color of the icon and title for the [BottomNavigationBarItem]s /// The foreground color of the icon and title for the [BottomNavigationBarItem]s
/// in the unselected state. /// in the unselected state.
///
/// Defaults to [CupertinoColors.inactiveGray] and cannot be null.
final Color inactiveColor; final Color inactiveColor;
/// The size of all of the [BottomNavigationBarItem] icons. /// The size of all of the [BottomNavigationBarItem] icons.
...@@ -102,19 +111,25 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -102,19 +111,25 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
/// The default value is a one physical pixel top border with grey color. /// The default value is a one physical pixel top border with grey color.
final Border border; final Border border;
/// True if the tab bar's background color has no transparency.
bool get opaque => backgroundColor.alpha == 0xFF;
@override @override
Size get preferredSize => const Size.fromHeight(_kTabBarHeight); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double bottomPadding = MediaQuery.of(context).padding.bottom; final double bottomPadding = MediaQuery.of(context).padding.bottom;
Widget result = DecoratedBox( Widget result = DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
border: border, border: border,
color: backgroundColor, color: backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor,
), ),
child: SizedBox( child: SizedBox(
height: _kTabBarHeight + bottomPadding, height: _kTabBarHeight + bottomPadding,
...@@ -124,19 +139,13 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -124,19 +139,13 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
size: iconSize, size: iconSize,
), ),
child: DefaultTextStyle( // Default with the inactive state. child: DefaultTextStyle( // Default with the inactive state.
style: TextStyle( style: CupertinoTheme.of(context).textTheme.tabLabelTextStyle.copyWith(color: inactiveColor),
fontFamily: '.SF UI Text',
fontSize: 10.0,
letterSpacing: 0.1,
fontWeight: FontWeight.w400,
color: inactiveColor,
),
child: Padding( child: Padding(
padding: EdgeInsets.only(bottom: bottomPadding), padding: EdgeInsets.only(bottom: bottomPadding),
child: Row( child: Row(
// Align bottom since we want the labels to be aligned. // Align bottom since we want the labels to be aligned.
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: _buildTabItems(), children: _buildTabItems(context),
), ),
), ),
), ),
...@@ -144,7 +153,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -144,7 +153,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
), ),
); );
if (!opaque) { if (!opaque(context)) {
// For non-opaque backgrounds, apply a blur effect. // For non-opaque backgrounds, apply a blur effect.
result = ClipRect( result = ClipRect(
child: BackdropFilter( child: BackdropFilter(
...@@ -157,13 +166,14 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -157,13 +166,14 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
return result; return result;
} }
List<Widget> _buildTabItems() { List<Widget> _buildTabItems(BuildContext context) {
final List<Widget> result = <Widget>[]; final List<Widget> result = <Widget>[];
for (int index = 0; index < items.length; index += 1) { for (int index = 0; index < items.length; index += 1) {
final bool active = index == currentIndex; final bool active = index == currentIndex;
result.add( result.add(
_wrapActiveItem( _wrapActiveItem(
context,
Expanded( Expanded(
child: Semantics( child: Semantics(
selected: active, selected: active,
...@@ -205,10 +215,11 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -205,10 +215,11 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
} }
/// Change the active tab item's icon and title colors to active. /// 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) if (!active)
return item; return item;
final Color activeColor = this.activeColor ?? CupertinoTheme.of(context).primaryColor;
return IconTheme.merge( return IconTheme.merge(
data: IconThemeData(color: activeColor), data: IconThemeData(color: activeColor),
child: DefaultTextStyle.merge( child: DefaultTextStyle.merge(
......
...@@ -5,28 +5,11 @@ ...@@ -5,28 +5,11 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'theme.dart';
const Color _kDisabledBackground = Color(0xFFA9A9A9); const Color _kDisabledBackground = Color(0xFFA9A9A9);
const Color _kDisabledForeground = Color(0xFFC4C4C4); // Measured against iOS 12 in Xcode.
const Color _kDisabledForeground = Color(0xFFD1D1D1);
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,
);
const EdgeInsets _kButtonPadding = EdgeInsets.all(16.0); const EdgeInsets _kButtonPadding = EdgeInsets.all(16.0);
const EdgeInsets _kBackgroundButtonPadding = EdgeInsets.symmetric( const EdgeInsets _kBackgroundButtonPadding = EdgeInsets.symmetric(
...@@ -53,7 +36,26 @@ class CupertinoButton extends StatefulWidget { ...@@ -53,7 +36,26 @@ class CupertinoButton extends StatefulWidget {
this.pressedOpacity = 0.1, this.pressedOpacity = 0.1,
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)), this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
@required this.onPressed, @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. /// The widget below this widget in the tree.
/// ///
...@@ -68,6 +70,9 @@ class CupertinoButton extends StatefulWidget { ...@@ -68,6 +70,9 @@ class CupertinoButton extends StatefulWidget {
/// The color of the button's background. /// The color of the button's background.
/// ///
/// Defaults to null which produces a button with no background or border. /// 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; final Color color;
/// The color of the button's background when the button is disabled. /// The color of the button's background when the button is disabled.
...@@ -105,6 +110,8 @@ class CupertinoButton extends StatefulWidget { ...@@ -105,6 +110,8 @@ class CupertinoButton extends StatefulWidget {
/// Defaults to round corners of 8 logical pixels. /// Defaults to round corners of 8 logical pixels.
final BorderRadius borderRadius; final BorderRadius borderRadius;
final bool _filled;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To /// 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. /// enable a button, set its [onPressed] property to a non-null value.
bool get enabled => onPressed != null; bool get enabled => onPressed != null;
...@@ -198,7 +205,15 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv ...@@ -198,7 +205,15 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool enabled = widget.enabled; 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( return GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
...@@ -232,12 +247,11 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv ...@@ -232,12 +247,11 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
widthFactor: 1.0, widthFactor: 1.0,
heightFactor: 1.0, heightFactor: 1.0,
child: DefaultTextStyle( child: DefaultTextStyle(
style: backgroundColor != null style: textStyle,
? _kBackgroundButtonTextStyle child: IconTheme(
: enabled data: IconThemeData(color: foregroundColor),
? _kButtonTextStyle child: widget.child,
: _kDisabledButtonTextStyle, ),
child: widget.child,
), ),
), ),
), ),
......
...@@ -11,15 +11,27 @@ class CupertinoColors { ...@@ -11,15 +11,27 @@ class CupertinoColors {
/// iOS 10's default blue color. Used to indicate active elements such as /// iOS 10's default blue color. Used to indicate active elements such as
/// buttons, selected tabs and your own chat bubbles. /// buttons, selected tabs and your own chat bubbles.
///
/// This is SystemBlue in the iOS palette.
static const Color activeBlue = Color(0xFF007AFF); static const Color activeBlue = Color(0xFF007AFF);
/// iOS 10's default green color. Used to indicate active accents such as /// 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 /// the switch in its on state and some accent buttons such as the call button
/// and Apple Map's 'Go' button. /// and Apple Map's 'Go' button.
///
/// This is SystemGreen in the iOS palette.
static const Color activeGreen = Color(0xFF4CD964); 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. /// Opaque white color. Used for backgrounds and fonts against dark backgrounds.
/// ///
/// This is SystemWhiteColor in the iOS palette.
///
/// See also: /// See also:
/// ///
/// * [material.Colors.white], the same color, in the material design palette. /// * [material.Colors.white], the same color, in the material design palette.
...@@ -28,6 +40,8 @@ class CupertinoColors { ...@@ -28,6 +40,8 @@ class CupertinoColors {
/// Opaque black color. Used for texts against light backgrounds. /// Opaque black color. Used for texts against light backgrounds.
/// ///
/// This is SystemBlackColor in the iOS palette.
///
/// See also: /// See also:
/// ///
/// * [material.Colors.black], the same color, in the material design palette. /// * [material.Colors.black], the same color, in the material design palette.
...@@ -35,12 +49,26 @@ class CupertinoColors { ...@@ -35,12 +49,26 @@ class CupertinoColors {
static const Color black = Color(0xFF000000); static const Color black = Color(0xFF000000);
/// Used in iOS 10 for light background fills such as the chat bubble background. /// 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); 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 /// Used in iOS 11 for unselected selectables such as tab bar items in their
/// inactive state or de-emphasized subtitles and details text. /// inactive state or de-emphasized subtitles and details text.
/// ///
/// Not the same gray as disabled buttons etc. /// Not the same gray as disabled buttons etc.
///
/// This is SystemGrayColor in the iOS palette.
static const Color inactiveGray = Color(0xFF8E8E93); static const Color inactiveGray = Color(0xFF8E8E93);
/// Used for iOS 10 for destructive actions such as the delete actions in /// Used for iOS 10 for destructive actions such as the delete actions in
...@@ -48,5 +76,7 @@ class CupertinoColors { ...@@ -48,5 +76,7 @@ class CupertinoColors {
/// ///
/// Not the same red as the camera shutter or springboard icon notifications /// Not the same red as the camera shutter or springboard icon notifications
/// or the foreground red theme in various native apps such as HealthKit. /// 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); static const Color destructiveRed = Color(0xFFFF3B30);
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'theme.dart';
/// Implements a single iOS application page's layout. /// Implements a single iOS application page's layout.
/// ///
...@@ -21,7 +21,7 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -21,7 +21,7 @@ class CupertinoPageScaffold extends StatelessWidget {
const CupertinoPageScaffold({ const CupertinoPageScaffold({
Key key, Key key,
this.navigationBar, this.navigationBar,
this.backgroundColor = CupertinoColors.white, this.backgroundColor,
this.resizeToAvoidBottomInset = true, this.resizeToAvoidBottomInset = true,
@required this.child, @required this.child,
}) : assert(child != null), }) : assert(child != null),
...@@ -48,7 +48,7 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -48,7 +48,7 @@ class CupertinoPageScaffold extends StatelessWidget {
/// The color of the widget that underlies the entire scaffold. /// 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; final Color backgroundColor;
/// Whether the [child] should size itself to avoid the window's bottom inset. /// Whether the [child] should size itself to avoid the window's bottom inset.
...@@ -78,10 +78,13 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -78,10 +78,13 @@ class CupertinoPageScaffold extends StatelessWidget {
? existingMediaQuery.viewInsets.bottom ? existingMediaQuery.viewInsets.bottom
: 0.0; : 0.0;
final bool fullObstruction =
navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;
// If navigation bar is opaquely obstructing, directly shift the main content // If navigation bar is opaquely obstructing, directly shift the main content
// down. If translucent, let main content draw behind navigation bar but hint the // down. If translucent, let main content draw behind navigation bar but hint the
// obstructed area. // obstructed area.
if (navigationBar.fullObstruction) { if (fullObstruction) {
paddedContent = Padding( paddedContent = Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding), padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: child, child: child,
...@@ -114,7 +117,9 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -114,7 +117,9 @@ class CupertinoPageScaffold extends StatelessWidget {
} }
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration(color: backgroundColor), decoration: BoxDecoration(
color: backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
),
child: Stack( child: Stack(
children: stacked, children: stacked,
), ),
......
...@@ -9,7 +9,7 @@ import 'package:flutter/gestures.dart'; ...@@ -9,7 +9,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'theme.dart';
import 'thumb_painter.dart'; import 'thumb_painter.dart';
// Examples can assume: // Examples can assume:
...@@ -189,7 +189,7 @@ class CupertinoSlider extends StatefulWidget { ...@@ -189,7 +189,7 @@ class CupertinoSlider extends StatefulWidget {
/// The color to use for the portion of the slider that has been selected. /// 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; final Color activeColor;
@override @override
...@@ -228,7 +228,7 @@ class _CupertinoSliderState extends State<CupertinoSlider> with TickerProviderSt ...@@ -228,7 +228,7 @@ class _CupertinoSliderState extends State<CupertinoSlider> with TickerProviderSt
return _CupertinoSliderRenderObjectWidget( return _CupertinoSliderRenderObjectWidget(
value: (widget.value - widget.min) / (widget.max - widget.min), value: (widget.value - widget.min) / (widget.max - widget.min),
divisions: widget.divisions, divisions: widget.divisions,
activeColor: widget.activeColor ?? CupertinoColors.activeBlue, activeColor: widget.activeColor ?? CupertinoTheme.of(context).primaryColor,
onChanged: widget.onChanged != null ? _handleChanged : null, onChanged: widget.onChanged != null ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null, onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null, onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
...@@ -485,15 +485,14 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { ...@@ -485,15 +485,14 @@ class _RenderCupertinoSlider extends RenderConstrainedBox {
final double trackActive = offset.dx + _thumbCenter; final double trackActive = offset.dx + _thumbCenter;
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
final Paint paint = Paint();
if (visualPosition > 0.0) { 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); canvas.drawRRect(RRect.fromLTRBXY(trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0), paint);
} }
if (visualPosition < 1.0) { 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); canvas.drawRRect(RRect.fromLTRBXY(trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0), paint);
} }
......
...@@ -88,7 +88,8 @@ class CupertinoSwitch extends StatefulWidget { ...@@ -88,7 +88,8 @@ class CupertinoSwitch extends StatefulWidget {
/// The color to use when this switch is on. /// 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; final Color activeColor;
@override @override
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'bottom_tab_bar.dart'; import 'bottom_tab_bar.dart';
import 'theme.dart';
/// Implements a tabbed iOS application's root layout and behavior structure. /// Implements a tabbed iOS application's root layout and behavior structure.
/// ///
...@@ -173,13 +174,13 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -173,13 +174,13 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
// TODO(xster): Use real size after partial layout instead of preferred size. // TODO(xster): Use real size after partial layout instead of preferred size.
// https://github.com/flutter/flutter/issues/12912 // https://github.com/flutter/flutter/issues/12912
final double bottomPadding = widget.tabBar.preferredSize.height final double bottomPadding =
+ existingMediaQuery.padding.bottom; widget.tabBar.preferredSize.height + existingMediaQuery.padding.bottom;
// If tab bar opaque, directly stop the main content higher. If // If tab bar opaque, directly stop the main content higher. If
// translucent, let main content draw behind the tab bar but hint the // translucent, let main content draw behind the tab bar but hint the
// obstructed area. // obstructed area.
if (widget.tabBar.opaque) { if (widget.tabBar.opaque(context)) {
content = Padding( content = Padding(
padding: EdgeInsets.only(bottom: bottomPadding), padding: EdgeInsets.only(bottom: bottomPadding),
child: content, child: content,
...@@ -219,8 +220,13 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -219,8 +220,13 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
)); ));
} }
return Stack( return DecoratedBox(
children: stacked, decoration: BoxDecoration(
color: CupertinoTheme.of(context).scaffoldBackgroundColor
),
child: Stack(
children: stacked,
),
); );
} }
} }
......
...@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart'; ...@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'icons.dart'; import 'icons.dart';
import 'text_selection.dart'; import 'text_selection.dart';
import 'theme.dart';
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization; export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization;
...@@ -32,15 +33,6 @@ const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration( ...@@ -32,15 +33,6 @@ const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.0)), 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. // Value extracted via color reader from iOS simulator.
const Color _kSelectionHighlightColor = Color(0x667FAACF); const Color _kSelectionHighlightColor = Color(0x667FAACF);
const Color _kInactiveTextColor = Color(0xFFC2C2C2); const Color _kInactiveTextColor = Color(0xFFC2C2C2);
...@@ -160,7 +152,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -160,7 +152,7 @@ class CupertinoTextField extends StatefulWidget {
TextInputType keyboardType, TextInputType keyboardType,
this.textInputAction, this.textInputAction,
this.textCapitalization = TextCapitalization.none, this.textCapitalization = TextCapitalization.none,
this.style = _kDefaultTextStyle, this.style,
this.textAlign = TextAlign.start, this.textAlign = TextAlign.start,
this.autofocus = false, this.autofocus = false,
this.obscureText = false, this.obscureText = false,
...@@ -271,7 +263,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -271,7 +263,7 @@ class CupertinoTextField extends StatefulWidget {
/// ///
/// Also serves as a base for the [placeholder] text's style. /// 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; final TextStyle style;
/// {@macro flutter.widgets.editableText.textAlign} /// {@macro flutter.widgets.editableText.textAlign}
...@@ -558,7 +550,9 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -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 // If there are no surrounding widgets, just return the core editable text
// part. // part.
if (widget.placeholder == null && if (widget.placeholder == null &&
...@@ -593,7 +587,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -593,7 +587,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
widget.placeholder, widget.placeholder,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: widget.style.merge( style: textStyle.merge(
const TextStyle( const TextStyle(
color: _kInactiveTextColor, color: _kInactiveTextColor,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
...@@ -637,14 +631,13 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -637,14 +631,13 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); // See AutomaticKeepAliveClientMixin. super.build(context); // See AutomaticKeepAliveClientMixin.
assert(debugCheckHasDirectionality(context)); assert(debugCheckHasDirectionality(context));
final Brightness keyboardAppearance = widget.keyboardAppearance;
final TextEditingController controller = _effectiveController; final TextEditingController controller = _effectiveController;
final FocusNode focusNode = _effectiveFocusNode;
final List<TextInputFormatter> formatters = widget.inputFormatters ?? <TextInputFormatter>[]; final List<TextInputFormatter> formatters = widget.inputFormatters ?? <TextInputFormatter>[];
final bool enabled = widget.enabled ?? true; final bool enabled = widget.enabled ?? true;
if (widget.maxLength != null && widget.maxLengthEnforced) { if (widget.maxLength != null && widget.maxLengthEnforced) {
formatters.add(LengthLimitingTextInputFormatter(widget.maxLength)); formatters.add(LengthLimitingTextInputFormatter(widget.maxLength));
} }
final TextStyle textStyle = widget.style ?? CupertinoTheme.of(context).textTheme.textStyle;
final Widget paddedEditable = Padding( final Widget paddedEditable = Padding(
padding: widget.padding, padding: widget.padding,
...@@ -652,11 +645,11 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -652,11 +645,11 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
child: EditableText( child: EditableText(
key: _editableTextKey, key: _editableTextKey,
controller: controller, controller: controller,
focusNode: focusNode, focusNode: _effectiveFocusNode,
keyboardType: widget.keyboardType, keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction, textInputAction: widget.textInputAction,
textCapitalization: widget.textCapitalization, textCapitalization: widget.textCapitalization,
style: widget.style, style: textStyle,
textAlign: widget.textAlign, textAlign: widget.textAlign,
autofocus: widget.autofocus, autofocus: widget.autofocus,
obscureText: widget.obscureText, obscureText: widget.obscureText,
...@@ -674,7 +667,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -674,7 +667,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
cursorColor: widget.cursorColor, cursorColor: widget.cursorColor,
backgroundCursorColor: CupertinoColors.inactiveGray, backgroundCursorColor: CupertinoColors.inactiveGray,
scrollPadding: widget.scrollPadding, scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance, keyboardAppearance: widget.keyboardAppearance,
), ),
), ),
); );
...@@ -692,14 +685,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -692,14 +685,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
decoration: widget.decoration, decoration: widget.decoration,
// The main decoration and the disabled scrim exists separately. // The main decoration and the disabled scrim exists separately.
child: Container( child: Container(
color: enabled ? null : _kDisabledBackground, color: enabled
? null
: CupertinoTheme.of(context).brightness == Brightness.light
? _kDisabledBackground
: CupertinoColors.darkBackgroundGray,
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTapDown: _handleTapDown, onTapDown: _handleTapDown,
onTapUp: _handleTapUp, onTapUp: _handleTapUp,
onLongPress: _handleLongPress, onLongPress: _handleLongPress,
excludeFromSemantics: true, 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> { ...@@ -443,7 +443,7 @@ class _MaterialAppState extends State<MaterialApp> {
mini: true, mini: true,
); );
}, },
) ),
); );
assert(() { assert(() {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -143,7 +144,15 @@ class Theme extends StatelessWidget { ...@@ -143,7 +144,15 @@ class Theme extends StatelessWidget {
theme: this, theme: this,
child: IconTheme( child: IconTheme(
data: data.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 @@ ...@@ -4,6 +4,7 @@
import 'dart:ui' show Color, hashValues; import 'dart:ui' show Color, hashValues;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -153,6 +154,7 @@ class ThemeData extends Diagnosticable { ...@@ -153,6 +154,7 @@ class ThemeData extends Diagnosticable {
ColorScheme colorScheme, ColorScheme colorScheme,
DialogTheme dialogTheme, DialogTheme dialogTheme,
Typography typography, Typography typography,
CupertinoThemeData cupertinoOverrideTheme
}) { }) {
brightness ??= Brightness.light; brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark; final bool isDark = brightness == Brightness.dark;
...@@ -246,6 +248,7 @@ class ThemeData extends Diagnosticable { ...@@ -246,6 +248,7 @@ class ThemeData extends Diagnosticable {
labelStyle: textTheme.body2, labelStyle: textTheme.body2,
); );
dialogTheme ??= const DialogTheme(); dialogTheme ??= const DialogTheme();
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw( return ThemeData.raw(
brightness: brightness, brightness: brightness,
...@@ -294,11 +297,13 @@ class ThemeData extends Diagnosticable { ...@@ -294,11 +297,13 @@ class ThemeData extends Diagnosticable {
colorScheme: colorScheme, colorScheme: colorScheme,
dialogTheme: dialogTheme, dialogTheme: dialogTheme,
typography: typography, typography: typography,
cupertinoOverrideTheme: cupertinoOverrideTheme,
); );
} }
/// Create a [ThemeData] given a set of exact values. All the values /// Create a [ThemeData] given a set of exact values. All the values must be
/// must be specified. /// specified. They all must also be non-null except for
/// [cupertinoOverrideTheme].
/// ///
/// This will rarely be used directly. It is used by [lerp] to /// This will rarely be used directly. It is used by [lerp] to
/// create intermediate themes based on two themes created with the /// create intermediate themes based on two themes created with the
...@@ -353,6 +358,7 @@ class ThemeData extends Diagnosticable { ...@@ -353,6 +358,7 @@ class ThemeData extends Diagnosticable {
@required this.colorScheme, @required this.colorScheme,
@required this.dialogTheme, @required this.dialogTheme,
@required this.typography, @required this.typography,
@required this.cupertinoOverrideTheme,
}) : assert(brightness != null), }) : assert(brightness != null),
assert(primaryColor != null), assert(primaryColor != null),
assert(primaryColorBrightness != null), assert(primaryColorBrightness != null),
...@@ -630,6 +636,18 @@ class ThemeData extends Diagnosticable { ...@@ -630,6 +636,18 @@ class ThemeData extends Diagnosticable {
/// [primaryTextTheme], and [accentTextTheme]. /// [primaryTextTheme], and [accentTextTheme].
final Typography typography; 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. /// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({ ThemeData copyWith({
Brightness brightness, Brightness brightness,
...@@ -678,7 +696,9 @@ class ThemeData extends Diagnosticable { ...@@ -678,7 +696,9 @@ class ThemeData extends Diagnosticable {
ColorScheme colorScheme, ColorScheme colorScheme,
DialogTheme dialogTheme, DialogTheme dialogTheme,
Typography typography, Typography typography,
CupertinoThemeData cupertinoOverrideTheme,
}) { }) {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw( return ThemeData.raw(
brightness: brightness ?? this.brightness, brightness: brightness ?? this.brightness,
primaryColor: primaryColor ?? this.primaryColor, primaryColor: primaryColor ?? this.primaryColor,
...@@ -726,6 +746,7 @@ class ThemeData extends Diagnosticable { ...@@ -726,6 +746,7 @@ class ThemeData extends Diagnosticable {
colorScheme: colorScheme ?? this.colorScheme, colorScheme: colorScheme ?? this.colorScheme,
dialogTheme: dialogTheme ?? this.dialogTheme, dialogTheme: dialogTheme ?? this.dialogTheme,
typography: typography ?? this.typography, typography: typography ?? this.typography,
cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
); );
} }
...@@ -853,6 +874,7 @@ class ThemeData extends Diagnosticable { ...@@ -853,6 +874,7 @@ class ThemeData extends Diagnosticable {
colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t), colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t), dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
typography: Typography.lerp(a.typography, b.typography, 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 { ...@@ -909,7 +931,8 @@ class ThemeData extends Diagnosticable {
(otherData.pageTransitionsTheme == pageTransitionsTheme) && (otherData.pageTransitionsTheme == pageTransitionsTheme) &&
(otherData.colorScheme == colorScheme) && (otherData.colorScheme == colorScheme) &&
(otherData.dialogTheme == dialogTheme) && (otherData.dialogTheme == dialogTheme) &&
(otherData.typography == typography); (otherData.typography == typography) &&
(otherData.cupertinoOverrideTheme == cupertinoOverrideTheme);
} }
@override @override
...@@ -967,6 +990,7 @@ class ThemeData extends Diagnosticable { ...@@ -967,6 +990,7 @@ class ThemeData extends Diagnosticable {
colorScheme, colorScheme,
dialogTheme, dialogTheme,
typography, typography,
cupertinoOverrideTheme,
), ),
), ),
); );
...@@ -1019,6 +1043,109 @@ class ThemeData extends Diagnosticable { ...@@ -1019,6 +1043,109 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme)); properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme));
properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme)); properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme));
properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography)); 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 { ...@@ -182,7 +182,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
// Proof by contradiction: // Proof by contradiction:
// //
// If we introduce a loop, this retained layer must be appended to one of // 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 // changed so A's _needsAddToScene is true. This contradicts
// _subtreeNeedsAddToScene being false. // _subtreeNeedsAddToScene being false.
if (!_subtreeNeedsAddToScene && _engineLayer != null) { if (!_subtreeNeedsAddToScene && _engineLayer != null) {
......
...@@ -46,7 +46,7 @@ import 'framework.dart'; ...@@ -46,7 +46,7 @@ import 'framework.dart';
/// ///
/// When the inherited model is rebuilt the [updateShouldNotify] and /// When the inherited model is rebuilt the [updateShouldNotify] and
/// [updateShouldNotifyDependent] methods are used to decide what /// [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 /// inherited model's [updateShouldNotifyDependent] method is tested for
/// each dependent and the set of aspect objects it depends on. /// each dependent and the set of aspect objects it depends on.
/// The [updateShouldNotifyDependent] method must compare the set of aspect /// The [updateShouldNotifyDependent] method must compare the set of aspect
...@@ -153,6 +153,8 @@ abstract class InheritedModel<T> extends InheritedWidget { ...@@ -153,6 +153,8 @@ abstract class InheritedModel<T> extends InheritedWidget {
/// ///
/// If [aspect] is null this method is the same as /// If [aspect] is null this method is the same as
/// `context.inheritFromWidgetOfExactType(T)`. /// `context.inheritFromWidgetOfExactType(T)`.
///
/// If no ancestor of type T exists, null is returned.
static T inheritFrom<T extends InheritedModel<Object>>(BuildContext context, { Object aspect }) { static T inheritFrom<T extends InheritedModel<Object>>(BuildContext context, { Object aspect }) {
if (aspect == null) if (aspect == null)
return context.inheritFromWidgetOfExactType(T); return context.inheritFromWidgetOfExactType(T);
...@@ -160,6 +162,10 @@ abstract class InheritedModel<T> extends InheritedWidget { ...@@ -160,6 +162,10 @@ abstract class InheritedModel<T> extends InheritedWidget {
// Create a dependency on all of the type T ancestor models up until // Create a dependency on all of the type T ancestor models up until
// a model is found for which isSupportedAspect(aspect) is true. // a model is found for which isSupportedAspect(aspect) is true.
final List<InheritedElement> models = _findModels<T>(context, aspect).toList(); final List<InheritedElement> models = _findModels<T>(context, aspect).toList();
if (models.isEmpty) {
return null;
}
final InheritedElement lastModel = models.last; final InheritedElement lastModel = models.last;
for (InheritedElement model in models) { for (InheritedElement model in models) {
final T value = context.inheritFromElement(model, aspect: aspect); final T value = context.inheritFromElement(model, aspect: aspect);
......
...@@ -43,7 +43,7 @@ typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, W ...@@ -43,7 +43,7 @@ typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, W
/// * [AnimatedBuilder], which also triggers rebuilds from a [Listenable] /// * [AnimatedBuilder], which also triggers rebuilds from a [Listenable]
/// without passing back a specific value from a [ValueListenable]. /// without passing back a specific value from a [ValueListenable].
/// * [NotificationListener], which lets you rebuild based on [Notification] /// * [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. /// you have a direct reference to.
/// * [StreamBuilder], where a builder can depend on a [Stream] rather than /// * [StreamBuilder], where a builder can depend on a [Stream] rather than
/// a [ValueListenable] for more advanced use cases. /// a [ValueListenable] for more advanced use cases.
......
...@@ -68,6 +68,70 @@ void main() { ...@@ -68,6 +68,70 @@ void main() {
expect(actualActive.text.style.color, const Color(0xFF123456)); 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 { testWidgets('Use active icon', (WidgetTester tester) async {
const TestImageProvider activeIcon = TestImageProvider(16, 16); const TestImageProvider activeIcon = TestImageProvider(16, 16);
const TestImageProvider inactiveIcon = TestImageProvider(24, 24); const TestImageProvider inactiveIcon = TestImageProvider(24, 24);
......
...@@ -12,6 +12,7 @@ import '../widgets/semantics_tester.dart'; ...@@ -12,6 +12,7 @@ import '../widgets/semantics_tester.dart';
const TextStyle testStyle = TextStyle( const TextStyle testStyle = TextStyle(
fontFamily: 'Ahem', fontFamily: 'Ahem',
fontSize: 10.0, fontSize: 10.0,
letterSpacing: 0.0,
); );
void main() { void main() {
...@@ -226,6 +227,80 @@ void main() { ...@@ -226,6 +227,80 @@ void main() {
expect(boxDecoration.color, const Color(0x00FF00)); 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 }) { Widget boilerplate({ Widget child }) {
......
...@@ -148,15 +148,62 @@ void main() { ...@@ -148,15 +148,62 @@ void main() {
expect(tester.getCenter(find.text('Title')).dx, 400.0); 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; count = 0x000000;
await tester.pumpWidget( 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( home: CupertinoNavigationBar(
leading: _ExpectStyles(color: Color(0xFF001122), index: 0x000001), leading: CupertinoButton(
middle: _ExpectStyles(color: Color(0xFF000000), letterSpacing: -0.08, index: 0x000100), onPressed: () {},
trailing: _ExpectStyles(color: Color(0xFF001122), index: 0x010000), child: const _ExpectStyles(color: CupertinoColors.activeOrange, index: 0x000001),
actionsForegroundColor: Color(0xFF001122), ),
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() { ...@@ -845,18 +892,18 @@ void main() {
} }
class _ExpectStyles extends StatelessWidget { class _ExpectStyles extends StatelessWidget {
const _ExpectStyles({ this.color, this.letterSpacing, this.index }); const _ExpectStyles({ this.color, this.index });
final Color color; final Color color;
final double letterSpacing;
final int index; final int index;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TextStyle style = DefaultTextStyle.of(context).style; final TextStyle style = DefaultTextStyle.of(context).style;
expect(style.color, color); expect(style.color, color);
expect(style.fontFamily, '.SF Pro Text');
expect(style.fontSize, 17.0); expect(style.fontSize, 17.0);
expect(style.letterSpacing, letterSpacing ?? -0.24); expect(style.letterSpacing, -0.41);
count += index; count += index;
return Container(); return Container();
} }
......
...@@ -142,7 +142,7 @@ void main() { ...@@ -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 { (WidgetTester tester) async {
try { try {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -175,21 +175,6 @@ void main() { ...@@ -175,21 +175,6 @@ void main() {
} on AssertionError catch (e) { } on AssertionError catch (e) {
expect(e.toString(), contains('onValueChanged')); 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', testWidgets('Widgets have correct default text/icon styles, change correctly on selection',
...@@ -236,6 +221,52 @@ void main() { ...@@ -236,6 +221,52 @@ void main() {
expect(iconTheme.data.color, CupertinoColors.white); 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', testWidgets('SegmentedControl is correct when user provides custom colors',
(WidgetTester tester) async { (WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{}; final Map<int, Widget> children = <int, Widget>{};
...@@ -1171,13 +1202,13 @@ void main() { ...@@ -1171,13 +1202,13 @@ void main() {
await tester.tap(find.text('B')); await tester.tap(find.text('B'));
await tester.pump(); await tester.pump();
expect(getBackgroundColor(tester, 0), const Color(0xff007aff)); expect(getBackgroundColor(tester, 0), CupertinoColors.white);
expect(getBackgroundColor(tester, 1), const Color(0x33007aff)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
expect(getBackgroundColor(tester, 3), CupertinoColors.white); expect(getBackgroundColor(tester, 3), CupertinoColors.white);
await tester.pump(const Duration(milliseconds: 40)); await tester.pump(const Duration(milliseconds: 40));
expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff)); expect(getBackgroundColor(tester, 0), CupertinoColors.white);
expect(getBackgroundColor(tester, 1), const Color(0x64007aff)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
expect(getBackgroundColor(tester, 3), CupertinoColors.white); expect(getBackgroundColor(tester, 3), CupertinoColors.white);
await tester.pump(const Duration(milliseconds: 150)); await tester.pump(const Duration(milliseconds: 150));
......
...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
void main() { void main() {
...@@ -359,4 +360,57 @@ void main() { ...@@ -359,4 +360,57 @@ void main() {
handle.dispose(); 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() { ...@@ -254,6 +254,64 @@ void main() {
expect(tabsPainted, <int>[0, 1, 0]); expect(tabsPainted, <int>[0, 1, 0]);
expect(selectedTabs, <int>[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 }) { CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
...@@ -268,7 +326,6 @@ CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) { ...@@ -268,7 +326,6 @@ CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
title: Text('Tab 2'), title: Text('Tab 2'),
), ),
], ],
backgroundColor: CupertinoColors.white,
currentIndex: selectedTab, currentIndex: selectedTab,
onTap: (int newTab) => selectedTabs.add(newTab), onTap: (int newTab) => selectedTabs.add(newTab),
); );
......
...@@ -1098,4 +1098,42 @@ void main() { ...@@ -1098,4 +1098,42 @@ void main() {
expect(find.byType(CupertinoButton), findsNWidgets(3)); 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 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/src/foundation/diagnostics.dart'; import 'package:flutter/src/foundation/diagnostics.dart';
...@@ -412,6 +413,227 @@ void main() { ...@@ -412,6 +413,227 @@ void main() {
expect(theme.textTheme.display4.debugLabel, '(englishLike display4 2014).merge(blackMountainView display4)'); 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; int testBuildCalled;
......
...@@ -190,6 +190,22 @@ void main() { ...@@ -190,6 +190,22 @@ void main() {
expect(find.text('a: 2 b: 2 c: 3'), findsOneWidget); 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 { testWidgets('Inner InheritedModel shadows the outer one', (WidgetTester tester) async {
int _a = 0; int _a = 0;
int _b = 1; 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