m3e_core 0.1.4
m3e_core: ^0.1.4 copied to clipboard
A single Flutter package providing Material 3 expressive components like material shapes, card list, dismissible cards, expandable cards and dropdown menu.
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:m3e_core/m3e_core.dart';
import 'screens/m3e_dropdown_screen.dart';
import 'screens/m3e_button_screen.dart';
import 'screens/m3e_expandable_screen.dart';
import 'screens/m3e_card_screen.dart';
import 'screens/m3e_dismissible_screen.dart';
import 'screens/m3e_floating_toolbar_screen.dart';
import 'screens/m3e_slider_screen.dart';
import 'screens/m3e_progress_indicator_screen.dart';
import 'screens/m3e_loading_indicator_screen.dart';
void main() {
runApp(const MyApp());
}
final ValueNotifier<ThemeMode> themeNotifier = ValueNotifier(ThemeMode.system);
class ThemeSettings {
final Color seedColor;
final M3EColorVariant variant;
final double contrastLevel;
final bool useM3EColorScheme;
final bool useSystemColor;
ThemeSettings({
required this.seedColor,
required this.variant,
required this.contrastLevel,
required this.useM3EColorScheme,
required this.useSystemColor,
});
ThemeSettings copyWith({
Color? seedColor,
M3EColorVariant? variant,
double? contrastLevel,
bool? useM3EColorScheme,
bool? useSystemColor,
}) {
return ThemeSettings(
seedColor: seedColor ?? this.seedColor,
variant: variant ?? this.variant,
contrastLevel: contrastLevel ?? this.contrastLevel,
useM3EColorScheme: useM3EColorScheme ?? this.useM3EColorScheme,
useSystemColor: useSystemColor ?? this.useSystemColor,
);
}
}
final ValueNotifier<ThemeSettings> themeSettingsNotifier = ValueNotifier(
ThemeSettings(
seedColor: Colors.deepPurple,
variant: M3EColorVariant.tonalSpot,
contrastLevel: 0.0,
useM3EColorScheme: true,
useSystemColor: true,
),
);
final Map<String, Color> seedColors = {
'Purple': Colors.deepPurple,
'Indigo': Colors.indigo,
'Blue': Colors.blue,
'Teal': Colors.teal,
'Green': Colors.green,
'Yellow': Colors.yellow,
'Orange': Colors.orange,
'Red': Colors.red,
'Pink': Colors.pink,
};
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return ValueListenableBuilder<ThemeMode>(
valueListenable: themeNotifier,
builder: (context, ThemeMode currentMode, _) {
return ValueListenableBuilder<ThemeSettings>(
valueListenable: themeSettingsNotifier,
builder: (context, ThemeSettings settings, _) {
final systemLightColor = lightDynamic?.primary;
final systemDarkColor = darkDynamic?.primary;
final lightSeed =
(settings.useSystemColor && systemLightColor != null)
? systemLightColor
: settings.seedColor;
final darkSeed =
(settings.useSystemColor && systemDarkColor != null)
? systemDarkColor
: settings.seedColor;
final lightScheme = settings.useM3EColorScheme
? M3EColorScheme.light(
seedColor: lightSeed,
systemColorScheme: settings.useSystemColor
? lightDynamic
: null,
variant: settings.variant,
contrastLevel: settings.contrastLevel,
)
: ((settings.useSystemColor && lightDynamic != null)
? lightDynamic
: ColorScheme.fromSeed(
seedColor: lightSeed,
brightness: Brightness.light,
));
final darkScheme = settings.useM3EColorScheme
? M3EColorScheme.dark(
seedColor: darkSeed,
systemColorScheme: settings.useSystemColor
? darkDynamic
: null,
variant: settings.variant,
contrastLevel: settings.contrastLevel,
)
: ((settings.useSystemColor && darkDynamic != null)
? darkDynamic
: ColorScheme.fromSeed(
seedColor: darkSeed,
brightness: Brightness.dark,
));
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'M3E Card List Demo',
themeMode: currentMode,
theme: ThemeData(
colorScheme: lightScheme,
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: darkScheme,
useMaterial3: true,
),
home: const ExampleHomePage(),
);
},
);
},
);
},
);
}
}
class ExampleHomePage extends StatelessWidget {
const ExampleHomePage({super.key});
Widget _buildNavCard(
BuildContext context, {
required String title,
required String subtitle,
required IconData icon,
required Widget destination,
}) {
final cs = Theme.of(context).colorScheme;
return Card(
elevation: 0,
margin: const EdgeInsets.only(bottom: 16),
color: cs.surfaceContainer,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
child: InkWell(
onTap: () {
Navigator.of(
context,
).push(MaterialPageRoute(builder: (context) => destination));
},
child: Padding(
padding: const EdgeInsets.all(24),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: cs.primaryContainer,
shape: BoxShape.circle,
),
child: Icon(icon, color: cs.onPrimaryContainer, size: 28),
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: cs.onSurfaceVariant,
),
),
],
),
),
Icon(Icons.chevron_right, color: cs.onSurfaceVariant),
],
),
),
),
);
}
void _showThemeSettings(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return const ThemeSettingsSheet();
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLowest,
appBar: AppBar(
title: const Text('M3E Component Library'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
IconButton(
icon: const Icon(Icons.palette_outlined),
onPressed: () => _showThemeSettings(context),
tooltip: 'Theme Customizer',
),
IconButton(
icon: Icon(
Theme.of(context).brightness == Brightness.light
? Icons.dark_mode
: Icons.light_mode,
),
onPressed: () {
themeNotifier.value =
Theme.of(context).brightness == Brightness.light
? ThemeMode.dark
: ThemeMode.light;
},
),
],
),
body: ListView(
padding: const EdgeInsets.all(24),
children: [
Padding(
padding: const EdgeInsets.only(bottom: 32, left: 8, right: 8),
child: Text(
'Explore the Expressive Component Library built for beautiful and responsive Flutter applications.',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
_buildNavCard(
context,
title: 'M3E Cards',
subtitle:
'Standard expressive cards with custom splash states and radius bounds',
icon: Icons.list_alt_rounded,
destination: const M3ECardScreen(),
),
_buildNavCard(
context,
title: 'M3E Dismissible',
subtitle:
'Archive or delete data using robust, highly-damped dismissible lists',
icon: Icons.swipe_rounded,
destination: const DismissibleM3EScreen(),
),
_buildNavCard(
context,
title: 'M3E Expandable',
subtitle:
'Richly animated spring expansion lists covering Columns, Slivers, and ListViews',
icon: Icons.expand_circle_down_rounded,
destination: const ExpandableM3EScreen(),
),
_buildNavCard(
context,
title: 'M3E Dropdown',
subtitle:
'Smooth OverlayPortal powered dropdowns with fluid chip morphing',
icon: Icons.arrow_drop_down_circle_rounded,
destination: const DropdownM3EScreen(),
),
_buildNavCard(
context,
title: 'M3E Buttons',
subtitle:
'Expressive button system with neighbor squish, shape morphing, and overflow management',
icon: Icons.smart_button_rounded,
destination: const ButtonM3EScreen(),
),
_buildNavCard(
context,
title: 'M3E Floating Toolbar',
subtitle:
'Expressive floating toolbars with morphing FAB and exit-always scroll physics',
icon: Icons.subtitles_rounded,
destination: const FloatingToolbarM3EScreen(),
),
_buildNavCard(
context,
title: 'M3E Slider',
subtitle:
'Premium expressive sliders with dual-thumb ranges, ticks, custom decorations & haptics',
icon: Icons.linear_scale_rounded,
destination: const M3ESliderScreen(),
),
_buildNavCard(
context,
title: 'M3E Progress Indicators',
subtitle:
'Expressive progress indicators including standard and wavy linear/circular rings',
icon: Icons.hourglass_empty_rounded,
destination: const M3EProgressIndicatorScreen(),
),
_buildNavCard(
context,
title: 'M3E Loading Indicators',
subtitle:
'Expressive loading indicators that morph smoothly between vector path shapes',
icon: Icons.refresh_rounded,
destination: const M3ELoadingIndicatorScreen(),
),
],
),
);
}
}
class ThemeSettingsSheet extends StatelessWidget {
const ThemeSettingsSheet({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
return ValueListenableBuilder<ThemeSettings>(
valueListenable: themeSettingsNotifier,
builder: (context, settings, _) {
return Container(
padding: const EdgeInsets.fromLTRB(24, 16, 24, 32),
decoration: BoxDecoration(
color: cs.surfaceContainerHigh,
borderRadius: const BorderRadius.vertical(top: Radius.circular(28)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Container(
width: 32,
height: 4,
decoration: BoxDecoration(
color: cs.outlineVariant,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 20),
Text(
'Theme Settings',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Enable M3E Color Scheme'),
subtitle: const Text('AOSP ColorSpec2026 overrides'),
value: settings.useM3EColorScheme,
onChanged: (val) {
themeSettingsNotifier.value = settings.copyWith(
useM3EColorScheme: val,
);
},
),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Use System Dynamic Colors'),
subtitle: const Text(
'Syncs with wallpaper colors if supported',
),
value: settings.useSystemColor,
onChanged: (val) {
themeSettingsNotifier.value = settings.copyWith(
useSystemColor: val,
);
},
),
if (!settings.useSystemColor) ...[
const SizedBox(height: 12),
Text('Manual Seed Color', style: theme.textTheme.titleSmall),
const SizedBox(height: 8),
SizedBox(
height: 48,
child: ListView(
scrollDirection: Axis.horizontal,
children: seedColors.entries.map((entry) {
final isSelected = settings.seedColor == entry.value;
return Padding(
padding: const EdgeInsets.only(right: 12),
child: GestureDetector(
onTap: () {
themeSettingsNotifier.value = settings.copyWith(
seedColor: entry.value,
);
},
child: Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: entry.value,
shape: BoxShape.circle,
border: isSelected
? Border.all(color: cs.onSurface, width: 3)
: null,
),
),
),
);
}).toList(),
),
),
],
if (settings.useM3EColorScheme) ...[
const SizedBox(height: 16),
Row(
children: [
const Text('Color Variant: '),
const Spacer(),
DropdownButton<M3EColorVariant>(
value: settings.variant,
onChanged: (val) {
if (val != null) {
themeSettingsNotifier.value = settings.copyWith(
variant: val,
);
}
},
items: M3EColorVariant.values.map((v) {
return DropdownMenuItem(value: v, child: Text(v.name));
}).toList(),
),
],
),
const SizedBox(height: 12),
Row(
children: [
const Text('Contrast Level: '),
Expanded(
child: Slider(
value: settings.contrastLevel,
min: -1.0,
max: 1.0,
divisions: 8,
label: settings.contrastLevel.toStringAsFixed(2),
onChanged: (val) {
themeSettingsNotifier.value = settings.copyWith(
contrastLevel: val,
);
},
),
),
Text(settings.contrastLevel.toStringAsFixed(2)),
],
),
],
],
),
);
},
);
}
}