keyline_icons 1.0.0
keyline_icons: ^1.0.0 copied to clipboard
Typed Flutter icons with font and vector APIs for the Keyline collection.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:keyline_icons/keyline_icons.dart';
void main() => runApp(const IconGallery());
class IconGallery extends StatefulWidget {
const IconGallery({super.key, this.catalog});
final List<Map<String, dynamic>>? catalog;
@override
State<IconGallery> createState() => _IconGalleryState();
}
class _IconGalleryState extends State<IconGallery> {
late final Future<List<Map<String, dynamic>>> entries =
widget.catalog == null ? _load() : Future.value(widget.catalog!);
String query = '';
double size = 24;
Color color = Colors.indigo;
bool dark = false;
Future<List<Map<String, dynamic>>> _load() async {
final data = await rootBundle.loadString('assets/icon_catalog.json');
return (jsonDecode(data) as List).cast<Map<String, dynamic>>();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Keyline Icons',
themeMode: dark ? ThemeMode.dark : ThemeMode.light,
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true),
home: Scaffold(
appBar: AppBar(
title: const Text('Keyline Icons'),
actions: [
IconButton(
tooltip: dark ? 'Light theme' : 'Dark theme',
icon: Icon(dark ? Icons.light_mode : Icons.dark_mode),
onPressed: () => setState(() => dark = !dark),
),
],
),
body: FutureBuilder<List<Map<String, dynamic>>>(
future: entries,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('Catalog failed: ${snapshot.error}'));
}
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final all = snapshot.data!;
final filtered =
all.where((entry) {
final needle = query.toLowerCase();
return (entry['name'] as String).toLowerCase().contains(
needle,
) ||
(entry['source'] as String).toLowerCase().contains(
needle,
);
}).toList();
return Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: TextField(
decoration: InputDecoration(
labelText: 'Search ${all.length} icons',
prefixIcon: const Icon(Icons.search),
border: const OutlineInputBorder(),
),
onChanged: (value) => setState(() => query = value),
),
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
const Text('Size'),
SizedBox(
width: 180,
child: Slider(
value: size,
min: 16,
max: 48,
divisions: 16,
label: size.round().toString(),
onChanged: (value) => setState(() => size = value),
),
),
for (final choice in [
Colors.indigo,
Colors.red,
Colors.green,
Colors.black,
])
IconButton(
tooltip:
'Choose color ${choice.toARGB32().toRadixString(16)}',
onPressed: () => setState(() => color = choice),
icon: Icon(Icons.circle, color: choice),
),
],
),
Text('${filtered.length} shown'),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
final columns = (constraints.maxWidth / 180)
.floor()
.clamp(1, 8);
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisExtent: 180,
),
itemCount: filtered.length,
itemBuilder: (context, index) {
final entry = filtered[index];
final name = entry['name'] as String;
final source = entry['source'] as String;
final codepoint = entry['codepoint'] as int?;
final icon =
codepoint == null
? KeyLineVector(
KeyLineVectorAsset(
entry['svg'] as String,
matchTextDirection: entry['rtl'] as bool,
),
size: size,
color: color,
)
: Icon(
IconData(
// Gallery-only dynamic lookup.
// ignore: non_const_argument_for_const_parameter
codepoint,
fontFamily: 'KeyLineIcon',
fontPackage: 'keyline_icons',
matchTextDirection: entry['rtl'] as bool,
),
size: size,
color: color,
);
final identifier =
codepoint == null
? 'KeyLineVectorIcon.$name'
: 'KeyLineIcon.$name';
return Card(
child: InkWell(
onTap: () {
Clipboard.setData(
ClipboardData(text: identifier),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Copied $identifier')),
);
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
const SizedBox(height: 8),
Text(
name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
identifier,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style:
Theme.of(context).textTheme.bodySmall,
),
Text(
source,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style:
Theme.of(context).textTheme.bodySmall,
),
Text(
codepoint == null
? 'SVG'
: codepoint
.toRadixString(16)
.toUpperCase(),
),
],
),
),
),
);
},
);
},
),
),
],
);
},
),
),
);
}
}