flexify 3.0.0
flexify: ^3.0.0 copied to clipboard
Responsive Flutter UI scaled from your design size with .rw, .rh, .rt and .rs, plus navigation without BuildContext and 14 page transitions.
// flexify by Azerosoft (https://azerosoft.com)
// Licensed under the MIT License. See the LICENSE file for details.
import 'package:flexify/flexify.dart';
import 'package:flutter/material.dart';
/// Azerosoft brand colours.
const Color azerosoftBlue = Color(0xFF2C55F0);
const Color azerosoftBlueDark = Color(0xFF7390FF);
/// The screen size the demo was designed for.
const double designWidth = 375;
const double designHeight = 812;
const Duration transitionDuration = Duration(milliseconds: 600);
void main() {
runApp(const FlexifyDemoApp());
}
class FlexifyDemoApp extends StatelessWidget {
const FlexifyDemoApp({super.key});
@override
Widget build(BuildContext context) {
return Flexify(
designWidth: designWidth,
designHeight: designHeight,
// Keeps text and heights at their portrait size in landscape.
matchOrientation: true,
app: MaterialApp(
title: 'Flexify',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: azerosoftBlue).copyWith(
primary: azerosoftBlue,
onPrimary: Colors.white,
),
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: azerosoftBlue,
brightness: Brightness.dark,
).copyWith(
primary: azerosoftBlueDark,
onPrimary: const Color(0xFF0D1016),
),
),
home: const HomeScreen(),
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String? _city;
Future<void> _pickCity() async {
// The picker returns the city it was closed with, like Navigator.push.
final String? city = await Flexify.go<String>(
const CityPickerScreen(),
animation: FlexifyRouteAnimations.slideFromBottom,
settings: const RouteSettings(name: '/city'),
);
if (city != null) {
setState(() => _city = city);
}
}
@override
Widget build(BuildContext context) {
final Size screen = Flexify.screenSize;
return Scaffold(
appBar:
AppBar(title: Text('Flexify', style: _style(20, FontWeight.w600))),
body: ListView(
padding: EdgeInsets.all(16.rw),
children: <Widget>[
_Section(
title: 'Responsive sizes',
children: <Widget>[
// In landscape, matchOrientation compares the screen with the
// design turned on its side.
_InfoRow(
'Design size',
'${(screen.width / Flexify.scaleWidth).round()} × '
'${(screen.height / Flexify.scaleHeight).round()}',
),
_InfoRow(
'This screen',
'${screen.width.round()} × ${screen.height.round()}',
),
_InfoRow('Screen type', Flexify.screenType.name),
_InfoRow('Width scale', Flexify.scaleWidth.toStringAsFixed(2)),
_InfoRow('Height scale', Flexify.scaleHeight.toStringAsFixed(2)),
12.verticalSpace,
Row(
children: <Widget>[
_Box(width: 200.rw, height: 72.rh, label: '200.rw × 72.rh'),
12.horizontalSpace,
_Box(width: 72.rs, height: 72.rs, label: '72.rs'),
],
),
12.verticalSpace,
// Percentages of the whole screen, not of the design.
_Box(width: 50.sw, height: 6.sh, label: '50.sw × 6.sh'),
],
),
16.verticalSpace,
_Section(
title: 'Page transitions',
children: <Widget>[
Text('Tap one to open a page with it.', style: _style(14)),
12.verticalSpace,
Wrap(
spacing: 8.rw,
runSpacing: 8.rh,
children: <Widget>[
for (final FlexifyRouteAnimations animation
in FlexifyRouteAnimations.values)
OutlinedButton(
onPressed: () => Flexify.go(
TransitionScreen(animation: animation),
animation: animation,
duration: transitionDuration,
),
child: Text(animation.name, style: _style(13)),
),
],
),
],
),
16.verticalSpace,
_Section(
title: 'Results',
children: <Widget>[
Text(
'A page can return a value to the page that opened it.',
style: _style(14),
),
12.verticalSpace,
_InfoRow('Picked city', _city ?? 'none yet'),
12.verticalSpace,
ElevatedButton(
onPressed: _pickCity,
child: Text('Pick a city', style: _style(15)),
),
],
),
24.verticalSpace,
const _MadeByAzerosoft(),
],
),
);
}
}
class TransitionScreen extends StatelessWidget {
const TransitionScreen({super.key, required this.animation});
final FlexifyRouteAnimations animation;
@override
Widget build(BuildContext context) {
const List<FlexifyRouteAnimations> all = FlexifyRouteAnimations.values;
final FlexifyRouteAnimations next = all[(animation.index + 1) % all.length];
final ColorScheme colors = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: Text(animation.name, style: _style(20, FontWeight.w600)),
),
body: Padding(
padding: EdgeInsets.all(24.rw),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const Spacer(),
Icon(Icons.auto_awesome, size: 72.rs, color: colors.primary),
16.verticalSpace,
Text(
'Opened with FlexifyRouteAnimations.${animation.name}',
textAlign: TextAlign.center,
style: _style(18, FontWeight.w600),
),
8.verticalSpace,
Text(
_descriptions[animation]!,
textAlign: TextAlign.center,
style: _style(14).copyWith(color: colors.onSurfaceVariant),
),
const Spacer(),
ElevatedButton(
onPressed: Flexify.back,
child: Text('Back', style: _style(15)),
),
8.verticalSpace,
OutlinedButton(
onPressed: () => Flexify.goRemove(
TransitionScreen(animation: next),
animation: next,
duration: transitionDuration,
),
child: Text('Replace with ${next.name}', style: _style(15)),
),
8.verticalSpace,
TextButton(
onPressed: Flexify.backToFirst,
child: Text('Back to the first page', style: _style(15)),
),
],
),
),
);
}
}
class CityPickerScreen extends StatelessWidget {
const CityPickerScreen({super.key});
static const List<String> cities = <String>[
'Baku',
'Ganja',
'Shaki',
'Shusha',
'Lankaran',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick a city', style: _style(20, FontWeight.w600)),
),
body: ListView(
padding: EdgeInsets.symmetric(vertical: 8.rh),
children: <Widget>[
for (final String city in cities)
ListTile(
title: Text(city, style: _style(16)),
trailing: Icon(Icons.chevron_right, size: 24.rs),
// Closes the picker and gives the city to the page below.
onTap: () => Flexify.back(city),
),
],
),
);
}
}
const Map<FlexifyRouteAnimations, String> _descriptions =
<FlexifyRouteAnimations, String>{
FlexifyRouteAnimations.fade: 'Fades the page in.',
FlexifyRouteAnimations.slide: 'Slides the page in from the right.',
FlexifyRouteAnimations.scale: 'Grows the page from the center.',
FlexifyRouteAnimations.rotate: 'Spins the page in by one full turn.',
FlexifyRouteAnimations.zoom: 'Grows the page at a constant speed.',
FlexifyRouteAnimations.size: 'Reveals the page from the middle outwards.',
FlexifyRouteAnimations.elastic: 'Grows the page with an elastic bounce.',
FlexifyRouteAnimations.flip: 'Flips the page around its vertical axis.',
FlexifyRouteAnimations.slideFromBottom: 'Slides the page up from the bottom.',
FlexifyRouteAnimations.fadeAndScale:
'Fades the page in while it grows from 80 % of its size.',
FlexifyRouteAnimations.blur: 'Fades the page in over a blurred background.',
FlexifyRouteAnimations.slideAndFade:
'Slides the page in from the right while fading it in.',
FlexifyRouteAnimations.rotateAndScale:
'Spins and grows the page at the same time.',
FlexifyRouteAnimations.flipAndFade: 'Flips the page while fading it in.',
};
TextStyle _style(double fontSize, [FontWeight? weight]) {
return TextStyle(fontSize: fontSize.rt, fontWeight: weight);
}
class _Section extends StatelessWidget {
const _Section({required this.title, required this.children});
final String title;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(16.rw),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title, style: _style(16, FontWeight.w600)),
12.verticalSpace,
...children,
],
),
),
);
}
}
class _InfoRow extends StatelessWidget {
const _InfoRow(this.label, this.value);
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4.rh),
child: Row(
children: <Widget>[
Expanded(child: Text(label, style: _style(14))),
Text(value, style: _style(14, FontWeight.w600)),
],
),
);
}
}
class _Box extends StatelessWidget {
const _Box({required this.width, required this.height, required this.label});
final double width;
final double height;
final String label;
@override
Widget build(BuildContext context) {
final ColorScheme colors = Theme.of(context).colorScheme;
return Container(
width: width,
height: height,
alignment: Alignment.center,
decoration: BoxDecoration(
color: colors.primary,
borderRadius: BorderRadius.circular(12.rs),
),
child: Text(
label,
textAlign: TextAlign.center,
style: _style(12, FontWeight.w600).copyWith(color: colors.onPrimary),
),
);
}
}
/// "by Azerosoft" signature with the Azerosoft mark.
class _MadeByAzerosoft extends StatelessWidget {
const _MadeByAzerosoft();
@override
Widget build(BuildContext context) {
final Color color = Theme.of(context).colorScheme.onSurfaceVariant;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 20.rs,
height: 20.rs,
alignment: Alignment.center,
decoration: BoxDecoration(
color: azerosoftBlue,
borderRadius: BorderRadius.circular(5.rs),
),
child: Text(
'a.',
style: TextStyle(
color: Colors.white,
fontSize: 12.rt,
fontWeight: FontWeight.w700,
height: 1,
),
),
),
8.horizontalSpace,
Text(
'flexify by Azerosoft · azerosoft.com',
style: _style(12).copyWith(color: color),
),
],
);
}
}