flutter_body_atlas 0.1.2 copy "flutter_body_atlas: ^0.1.2" to clipboard
flutter_body_atlas: ^0.1.2 copied to clipboard

An interactive, SVG-based human body atlas for Flutter. High-fidelity anatomical diagrams with built-in support for hit testing, highlighting, and custom integration

example/lib/main.dart

import 'dart:collection';

import 'package:flutter/material.dart';
import 'package:flutter_body_atlas/flutter_body_atlas.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Body Atlas Demo',
      theme: ThemeData(
        colorScheme: .fromSeed(seedColor: Colors.indigo),
      ),
      home: const BodyAtlasDemo(),
    );
  }
}

class BodyAtlasDemo extends StatefulWidget {
  const BodyAtlasDemo({super.key});

  @override
  State<BodyAtlasDemo> createState() => _BodyAtlasDemoState();
}

class _BodyAtlasDemoState extends State<BodyAtlasDemo> {
  final _searchController = TextEditingController();
  final _selected = <AtlasElementInfo>{};
  AtlasElementInfo? _hoveredOver;

  final _localizer = const MuscleLocalizerEn();
  late final AtlasSearch<MuscleInfo> _search = MuscleSearch(localizer: _localizer);

  final _defaultColors = UnmodifiableMapView(<MuscleGroup, Color?>{
    .adductors: Colors.orange[500],
    .arms: Colors.blue[500],
    .back: Colors.pink[500],
    .chest: Colors.amber,
    .core: Colors.yellow[500],
    .glutes: Colors.teal[500],
    .hamstrings: Colors.green[500],
    .legs: Colors.purple[500],
    .neck: Colors.red[500],
    .shoulders: Colors.brown[500],
  });

  late final _colorsByMuscleGroup = Map.of(_defaultColors);

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const .all(8),
        child: Row(
          children: [
            Expanded(
              flex: 2,
              child: Column(
                children: [
                  Expanded(
                    child: _view(.musclesFront),
                  ),
                  SizedBox(height: 8),
                  Expanded(
                    child: _view(.musclesBack),
                  ),
                ],
              ),
            ),
            SizedBox(
              width: 300,
              child: Column(
                children: [
                  Padding(
                    padding: const .all(8.0),
                    child: TextField(
                      decoration: const InputDecoration(
                        hintText: 'Try searching, e.g., "triceps"',
                      ),
                      controller: _searchController,
                    ),
                  ),
                  Align(
                    alignment: .centerRight,
                    child: TextButton(
                      onPressed: _toggleAll,
                      child: Text(_selected.length == MuscleCatalog.all.toSet().length ? 'Deselect all' : 'Select all'),
                    ),
                  ),
                  Expanded(
                    child: ValueListenableBuilder<TextEditingValue>(
                      valueListenable: _searchController,
                      builder: (_, v, _) {
                        final found = _search.search(v.text.trim());
                        return ListView(
                          children: found.map(
                            (item) {
                              final selected = _selected.contains(item);
                              return ListTile(
                                selected: selected,
                                title: Text(item.displayName),
                                subtitle: Text('aka ${item.aliases.join(', ')}'),
                                trailing: Tooltip(
                                  message: item.group.name,
                                  child: Container(
                                    height: 18,
                                    width: 18,
                                    decoration: BoxDecoration(
                                      color: _colorsByMuscleGroup[item.group],
                                      borderRadius: .circular(6),
                                    ),
                                  ),
                                ),
                                onTap: () => _toggle(item),
                                leading: Checkbox.adaptive(
                                  value: _selected.contains(item),
                                  onChanged: (_) => _toggle(item),
                                ),
                              );
                            },
                          ).toList(),
                        );
                      },
                    ),
                  ),
                  Expanded(child: _colorPicker()),
                ],
              ),
            ),
            const SizedBox(width: 16),
          ],
        ),
      ),
    );
  }

  Widget _view(AtlasAsset asset) {
    return Container(
      decoration: BoxDecoration(border: .all(width: .5), borderRadius: .circular(12)),
      child: InteractiveViewer(
        child: Padding(
          padding: const .symmetric(vertical: 8.0),
          child: BodyAtlasView(
            view: asset,
            resolver: const MuscleResolver(),
            onTapElement: _toggle,
            hoveredOver: _hoveredOver,
            onHoverOverElement: (m) {
              setState(() => _hoveredOver = m);
            },
            hoverColor: (color) => color.withValues(alpha: .5),
            colorMapping: Map<AtlasElementInfo, Color?>.fromIterables(
              _selected,
              List.generate(
                _selected.length,
                (index) {
                  final muscle = _selected.toList()[index];
                  if (muscle == _hoveredOver) {
                    return _colorOf(muscle)?.withValues(alpha: .5);
                  }

                  return _colorOf(muscle);
                },
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _colorPicker() {
    return Column(
      crossAxisAlignment: .start,
      children: [
        Padding(
          padding: const .only(top: 8, bottom: 4, left: 12),
          child: Row(
            mainAxisAlignment: .spaceBetween,
            children: [
              Text(
                'Select colors',
                style: Theme.of(context).textTheme.titleMedium,
              ),
              TextButton(
                onPressed: () {
                  setState(() {
                    _colorsByMuscleGroup
                      ..clear()
                      ..addAll(_defaultColors);
                  });
                },
                child: Text('To defaults'),
              ),
            ],
          ),
        ),
        Expanded(
          child: ListView(
            children: _colorsByMuscleGroup.entries.map(
              (entry) {
                final MapEntry(key: muscleGroup, value: color) = entry;
                return ListTile(
                  title: Text(muscleGroup.name),
                  trailing: Container(
                    width: 28,
                    height: 28,
                    decoration: BoxDecoration(color: color, borderRadius: .circular(6)),
                  ),
                  onTap: () {
                    final original = color;
                    showDialog(
                      context: context,
                      builder: (context) {
                        return AlertDialog(
                          title: const Text('Pick a color!'),
                          content: SingleChildScrollView(
                            child: ColorPicker(
                              pickerColor: color!,
                              onColorChanged: (picked) {
                                setState(() {
                                  _colorsByMuscleGroup[muscleGroup] = picked;
                                });
                              },
                            ),
                          ),
                          actions: <Widget>[
                            TextButton(
                              child: const Text('Cancel'),
                              onPressed: () {
                                setState(() {
                                  _colorsByMuscleGroup[muscleGroup] = original;
                                });
                                Navigator.of(context).pop();
                              },
                            ),
                            TextButton(
                              child: const Text('Select'),
                              onPressed: () {
                                Navigator.of(context).pop();
                              },
                            ),
                          ],
                        );
                      },
                    );
                  },
                );
              },
            ).toList(),
          ),
        ),
      ],
    );
  }

  Color? _colorOf(AtlasElementInfo element) {
    return switch (element) {
      MuscleInfo muscle => _colorsByMuscleGroup[muscle.group],
      AtlasElementInfo() => null,
    };
  }

  void _toggle(AtlasElementInfo item) {
    setState(() {
      if (_selected.contains(item)) {
        _selected.remove(item);
      } else {
        _selected.add(item);
      }
    });
  }

  void _toggleAll() {
    setState(() {
      if (_selected.length == MuscleCatalog.all.toSet().length) {
        _selected.clear();
      } else {
        _selected.addAll(MuscleCatalog.all);
      }
    });
  }
}
3
likes
160
points
207
downloads

Publisher

verified publisherheart-of.me

Weekly Downloads

An interactive, SVG-based human body atlas for Flutter. High-fidelity anatomical diagrams with built-in support for hit testing, highlighting, and custom integration

Homepage
Repository (GitHub)
View/report issues

Topics

#flutter #anatomy #fitness #health #education

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_svg, path_drawing, xml

More

Packages that depend on flutter_body_atlas