// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Flutter code sample for [SafeArea]. /// /// The app is wrapped with [Insets] (defined below) /// to simulate a mobile device with a notched screen. void main() => runApp(const Insets()); class SafeAreaExampleApp extends StatelessWidget { const SafeAreaExampleApp({super.key}); static const Color spring = Color(0xFF00FF80); static final ColorScheme colors = ColorScheme.fromSeed(seedColor: spring); static final ThemeData theme = ThemeData( colorScheme: colors, sliderTheme: SliderThemeData( trackHeight: 8, activeTrackColor: colors.primary.withValues(alpha: 0.5), thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 12), ), scaffoldBackgroundColor: const Color(0xFFD0FFE8), listTileTheme: const ListTileThemeData( tileColor: Colors.white70, visualDensity: VisualDensity(horizontal: -4, vertical: -4), ), appBarTheme: AppBarTheme( backgroundColor: colors.secondary, foregroundColor: colors.onSecondary, ), ); static final AppBar appBar = AppBar(title: const Text('SafeArea Demo')); @override Widget build(BuildContext context) { return MaterialApp( theme: theme, debugShowCheckedModeBanner: false, home: Builder( builder: (BuildContext context) => Scaffold( appBar: Toggle.appBar.of(context) ? appBar : null, body: const DefaultTextStyle( style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black), child: Center(child: SafeAreaExample()), ), ), ), ); } } class SafeAreaExample extends StatelessWidget { const SafeAreaExample({super.key}); static final Widget controls = Column( children: [ const SizedBox(height: 6), Builder( builder: (BuildContext context) => Text( Toggle.safeArea.of(context) ? 'safe area!' : 'no safe area', style: const TextStyle(fontSize: 24), ), ), const Spacer(flex: 2), for (final Value data in Value.allValues) ...data.controls, ], ); @override Widget build(BuildContext context) { final bool hasSafeArea = Toggle.safeArea.of(context); return SafeArea( top: hasSafeArea, bottom: hasSafeArea, left: hasSafeArea, right: hasSafeArea, child: controls, ); } } sealed class Value implements Enum { Object _getValue(covariant Model model); List get controls; static const List allValues = [...Inset.values, ...Toggle.values]; } enum Inset implements Value { top('top notch'), sides('side padding'), bottom('bottom indicator'); const Inset(this.label); final String label; @override double _getValue(_InsetModel model) => switch (this) { top => model.insets.top, sides => model.insets.left, bottom => model.insets.bottom, }; double of(BuildContext context) => _getValue(Model.of<_InsetModel>(context, this)); @override List get controls => [ Text(label), Builder( builder: (BuildContext context) => Slider( max: 50, value: of(context), onChanged: (double newValue) { InsetsState.instance.changeInset(this, newValue); }, ), ), const Spacer(), ]; } enum Toggle implements Value { appBar('Build an AppBar?'), safeArea("Wrap Scaffold's body with SafeArea?"); const Toggle(this.label); final String label; @override bool _getValue(_ToggleModel model) => switch (this) { appBar => model.buildAppBar, safeArea => model.buildSafeArea, }; bool of(BuildContext context) => _getValue(Model.of<_ToggleModel>(context, this)); @override List get controls => [ Builder( builder: (BuildContext context) => SwitchListTile( title: Text(label), value: of(context), onChanged: (bool value) { InsetsState.instance.toggle(this, value); }, ), ), ]; } abstract class Model extends InheritedModel { const Model({super.key, required super.child}); static M of>(BuildContext context, Value value) { return context.dependOnInheritedWidgetOfExactType(aspect: value)!; } @override bool updateShouldNotify(Model oldWidget) => true; @override bool updateShouldNotifyDependent(Model oldWidget, Set dependencies) { return dependencies.any((E data) => data._getValue(this) != data._getValue(oldWidget)); } } class _InsetModel extends Model { const _InsetModel({required this.insets, required super.child}); final EdgeInsets insets; } class _ToggleModel extends Model { _ToggleModel({required Set togglers, required super.child}) : buildAppBar = togglers.contains(Toggle.appBar), buildSafeArea = togglers.contains(Toggle.safeArea); final bool buildAppBar; final bool buildSafeArea; } class Insets extends UniqueWidget { const Insets() : super(key: const GlobalObjectKey('insets')); @override InsetsState createState() => InsetsState(); } class InsetsState extends State { static InsetsState get instance => const Insets().currentState!; EdgeInsets insets = const EdgeInsets.fromLTRB(8, 25, 8, 12); void changeInset(Inset inset, double value) { setState(() { insets = switch (inset) { Inset.top => insets.copyWith(top: value), Inset.sides => insets.copyWith(left: value, right: value), Inset.bottom => insets.copyWith(bottom: value), }; }); } final Set _togglers = {}; void toggle(Toggle toggler, bool value) { setState(() { value ? _togglers.add(toggler) : _togglers.remove(toggler); }); } @override Widget build(BuildContext context) { final Widget topNotch = ClipRRect( borderRadius: BorderRadius.vertical(bottom: Radius.circular(insets.top)), child: SizedBox( height: insets.top, child: const FractionallySizedBox( widthFactor: 1 / 2, child: ColoredBox(color: Colors.black), ), ), ); final Widget bottomIndicator = SizedBox( width: double.infinity, height: insets.bottom, child: const FractionallySizedBox( heightFactor: 0.5, widthFactor: 0.5, child: PhysicalShape( clipper: ShapeBorderClipper(shape: StadiumBorder()), color: Color(0xC0000000), child: SizedBox.expand(), ), ), ); final Widget sideBar = SizedBox( width: insets.left, height: double.infinity, child: const IgnorePointer(child: ColoredBox(color: Colors.black12)), ); final Widget app = _ToggleModel( togglers: _togglers, child: Builder( builder: (BuildContext context) => MediaQuery( data: MediaQuery.of(context).copyWith( viewInsets: EdgeInsets.only(top: insets.top), viewPadding: insets, padding: insets, ), child: const SafeAreaExampleApp(), ), ), ); return _InsetModel( insets: insets, child: Directionality( textDirection: TextDirection.ltr, child: Stack( children: [ app, Align(alignment: Alignment.topCenter, child: topNotch), Align(alignment: Alignment.bottomCenter, child: bottomIndicator), Align(alignment: Alignment.centerLeft, child: sideBar), Align(alignment: Alignment.centerRight, child: sideBar), ], ), ), ); } }