flutterx_material_tool 1.1.0 flutterx_material_tool: ^1.1.0 copied to clipboard
Reverse engineered Google material tool allows to generate material palette from primary color
import 'package:example/dialog_about.dart';
import 'package:example/dialog_export.dart';
import 'package:example/shades_indicator.dart';
import 'package:example/shades_palette.dart';
import 'package:example/shades_title.dart';
import 'package:flutter/material.dart' hide DialogRoute, AboutDialog;
import 'package:flutter/services.dart';
import 'package:flutterx_application/flutterx_application.dart';
import 'package:flutterx_color_picker/flutterx_color_picker.dart';
import 'package:flutterx_live_data/flutterx_live_data.dart';
import 'package:flutterx_material_tool/flutterx_material_tool.dart';
import 'package:flutterx_utils/flutterx_utils.dart';
void main() => runMaterialApp(
name: 'Flutterx Material Tool Demo',
routes: {MaterialToolExample.route},
materialAppFactory: (key, initialRoute, onGenerateRoute, title, locale, localizationsDelegates, supportedLocales) {
final primary = MutableLiveData<Color>(initialValue: Colors.grey);
return AppColor(
primary: primary,
child: LiveDataBuilder<Color>(
data: primary,
builder: (context, value) => MaterialApp(
key: key,
initialRoute: initialRoute,
onGenerateRoute: onGenerateRoute,
title: title,
theme: ThemeData.from(
colorScheme: generateScheme(
primary: value.withOpacity(1), secondaryFromPrimary: MaterialVariant.primary))
.let((theme) => theme.copyWith(
textTheme: theme.textTheme.apply(fontFamily: 'RobotoMono'),
primaryTextTheme: theme.primaryTextTheme.apply(fontFamily: 'RobotoMono'),
)),
locale: locale,
localizationsDelegates: localizationsDelegates,
supportedLocales: supportedLocales,
debugShowCheckedModeBanner: false)));
});
class AppColor extends InheritedWidget {
final MutableLiveData<Color> primary;
const AppColor({Key? key, required this.primary, required Widget child}) : super(key: key, child: child);
static AppColor of(BuildContext context) => requireNotNull(context.dependOnInheritedWidgetOfExactType<AppColor>());
@override
bool updateShouldNotify(AppColor oldWidget) => oldWidget.primary != primary;
}
class MaterialToolExample extends StatefulWidget {
static final ActivityRoute<void> route =
ActivityRoute.builder(MaterialToolExample, builder: (context, args) => const MaterialToolExample());
const MaterialToolExample({Key? key}) : super(key: key);
@override
State<MaterialToolExample> createState() => _MaterialToolExampleState();
}
class _MaterialToolExampleState extends State<MaterialToolExample> {
final MaterialPalette<ColorSwatch<int>> _palette = MaterialPalette.primary;
late MutableLiveData<Color> _primary;
@override
Widget build(BuildContext context) {
_primary = AppColor.of(context).primary;
final actions = <Pair<String, IconData>, VoidCallback>{
const Pair('Reset', Icons.restore): _actionRestore,
const Pair('Copy', Icons.copy): _actionCopy,
const Pair('Paste', Icons.paste): _actionPaste,
const Pair('Export', Icons.reply): _actionExport,
const Pair('About', Icons.info_outline): _actionAbout,
};
return Scaffold(
appBar: AppBar(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text('Material Tool'),
LiveDataBuilder<Color>(
data: _primary,
builder: (context, value) {
final affinity = findAffinity(value);
return MaterialStatefulWidget(
onPressed: () => _primary.value = affinity.color,
builder: (context, states, child) => Text(
'#${affinity.src.hexString.substring(2)} ${affinity.exact ? '' : '~'}${affinity.name}',
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: DefaultTextStyle.of(context).style.copyWith(
fontSize: 12,
decoration: states.pressed || states.hovered ? TextDecoration.underline : null)));
}),
]),
actions: [
Builder(builder: (context) {
final onPrimary = DefaultTextStyle.of(context).style.color;
return PopupMenuButton<VoidCallback>(
icon: Icon(Icons.more_vert, color: onPrimary),
onSelected: (callback) => callback(),
itemBuilder: (context) => actions.entries
.map((entry) => PopupMenuItem<VoidCallback>(
value: entry.value,
child: Row(children: [
Builder(builder: (context) {
final iconColor = DefaultTextStyle.of(context).style.color;
return Icon(entry.key.second, color: iconColor, size: 20);
}),
const SizedBox(width: 8),
Text(entry.key.first)
])))
.toList(growable: false));
}),
]),
body: SingleChildScrollView(
child: MediatorLiveDataBuilder<Color, RGBColor>(
data: _primary, transform: (color) => color.rgb, builder: _buildPalettes)),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final result = await ColorPicker.open(
tracks: const {TrackType.hue, TrackType.saturation, TrackType.lightness},
context: context,
initialValue: _primary.value);
if (result != null) _primary.value = result;
},
child: const Icon(Icons.palette_outlined)));
}
Widget _buildPalettes(BuildContext context, RGBColor color) =>
Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
ShadesTitle(shades: _palette.shades, title: 'PRIMARY'),
ShadesPalette(palette: _palette, variant: MaterialVariant.primary, input: color, onColor: _onColor),
ShadesIndicator(shades: _palette.shades),
ShadesTitle(shades: _palette.shades, title: 'COMPLEMENTARY'),
ShadesPalette(palette: _palette, variant: MaterialVariant.complementary, input: color, onColor: _onColor),
ShadesIndicator(shades: _palette.shades),
ShadesTitle(shades: _palette.shades, title: 'ANALOGOUS'),
ShadesPalette(palette: _palette, variant: MaterialVariant.analogous_a, input: color, onColor: _onColor),
ShadesPalette(palette: _palette, variant: MaterialVariant.analogous_b, input: color, onColor: _onColor),
ShadesIndicator(shades: _palette.shades),
ShadesTitle(shades: _palette.shades, title: 'TRIADIC'),
ShadesPalette(palette: _palette, variant: MaterialVariant.triadic_a, input: color, onColor: _onColor),
ShadesPalette(palette: _palette, variant: MaterialVariant.triadic_b, input: color, onColor: _onColor),
ShadesIndicator(shades: _palette.shades),
const SizedBox(height: 86),
]);
void _onColor(MaterialVariant variant, PaletteData data, int index) =>
_primary.value = variant.remove(data.swatch[index].rgb).toColor();
void _actionExport() => ExportDialog.open(context, _palette, _primary.value.rgb);
void _actionRestore() {
_primary.value = Colors.grey;
_showSnackBar('Colors reset done');
}
void _actionCopy() {
final color = _primary.value;
Clipboard.setData(ClipboardData(text: '#${color.hexString}'));
_showSnackBar('Copied to clipboard: #${color.hexString.substring(2)}', color: color);
}
Future<void> _actionPaste() async {
final data = await Clipboard.getData(Clipboard.kTextPlain);
final value = int.tryParse(data?.text?.replaceAll('#', '') ?? '', radix: 16);
if (value == null) {
_showSnackBar('Clipboard data is not a color');
return;
}
final color = _primary.value = Color(value);
_showSnackBar('Pasted from clipboard: #${color.hexString.substring(2)}', color: color);
}
void _actionAbout() => AboutDialog.open(context);
void _showSnackBar(String message, {Color? color}) {
final messenger = ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar();
messenger.showSnackBar(SnackBar(
content: Text(message,
style: color == null ? null : TextStyle(color: color.brightness.isDark ? Colors.white : Colors.black)),
backgroundColor: color));
}
}