flutter/examples/api/lib/widgets/safe_area/safe_area.0.dart
2024-11-11 01:00:21 +00:00

289 lines
7.8 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package: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,
contentPadding: EdgeInsets.all(6.0),
),
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: <Widget>[
const SizedBox(height: 14),
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<Value> model);
List<Widget> get controls;
static const List<Value> allValues = <Value>[...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<Widget> get controls => <Widget>[
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<Widget> get controls => <Widget>[
Builder(
builder: (BuildContext context) => SwitchListTile(
title: Text(label),
value: of(context),
onChanged: (bool value) {
InsetsState.instance.toggle(this, value);
},
),
),
];
}
abstract class Model<E extends Value> extends InheritedModel<E> {
const Model({super.key, required super.child});
static M of<M extends Model<Value>>(BuildContext context, Value value) {
return context.dependOnInheritedWidgetOfExactType<M>(aspect: value)!;
}
@override
bool updateShouldNotify(Model<E> oldWidget) => true;
@override
bool updateShouldNotifyDependent(Model<E> oldWidget, Set<E> dependencies) {
return dependencies.any((E data) => data._getValue(this) != data._getValue(oldWidget));
}
}
class _InsetModel extends Model<Inset> {
const _InsetModel({required this.insets, required super.child});
final EdgeInsets insets;
}
class _ToggleModel extends Model<Toggle> {
_ToggleModel({required Set<Toggle> togglers, required super.child})
: buildAppBar = togglers.contains(Toggle.appBar),
buildSafeArea = togglers.contains(Toggle.safeArea);
final bool buildAppBar;
final bool buildSafeArea;
}
class Insets extends UniqueWidget<InsetsState> {
const Insets() : super(key: const GlobalObjectKey<InsetsState>('insets'));
@override
InsetsState createState() => InsetsState();
}
class InsetsState extends State<Insets> {
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<Toggle> _togglers = <Toggle>{};
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: <Widget>[
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),
],
),
),
);
}
}