feat: aggiungi guida utente e icona launcher

Guida utente:
- Pulsante info nell'AppBar principale
- InfoBottomSheet: icona app, versione, autore, licenza, link alla guida
- GuidePage: tre tab (Pasti / Converti / Spesa) con card e step numerati,
  portata dalla DocsPanel Vue originale

AppBar globale in app.dart con titolo dinamico per tab corrente.

Icona launcher:
- flutter_launcher_icons eseguito in build.sh prima di flutter build apk
  (sia debug che release) per applicare assets/icon-only.png all'APK.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 18:38:02 +02:00
parent 36e8034efd
commit cba83c9559
5 changed files with 517 additions and 2 deletions
@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/app_constants.dart';
import '../pages/guide_page.dart';
void showInfoBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (_) => const _InfoSheet(),
);
}
class _InfoSheet extends StatelessWidget {
const _InfoSheet();
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// handle
Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: scheme.outlineVariant,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 20),
// icona + nome + versione
Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
'assets/icon-only.png',
width: 52,
height: 52,
fit: BoxFit.cover,
),
),
const SizedBox(width: 14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('BitePlan',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
Text('Versione $kAppVersion',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: scheme.onSurfaceVariant)),
],
),
],
),
const SizedBox(height: 20),
// righe info
_InfoCard(children: [
_InfoRow(label: 'Autore', value: kAppAuthor),
_InfoRow(label: 'Licenza', value: kAppLicense),
_InfoRowButton(
label: 'Guida',
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const GuidePage()),
);
},
),
]),
],
),
),
);
}
}
class _InfoCard extends StatelessWidget {
final List<Widget> children;
const _InfoCard({required this.children});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
decoration: BoxDecoration(
border: Border.all(color: scheme.outlineVariant),
borderRadius: BorderRadius.circular(12),
),
child: Column(children: children),
);
}
}
class _InfoRow extends StatelessWidget {
final String label;
final String value;
const _InfoRow({required this.label, required this.value});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: Theme.of(context).textTheme.bodyMedium),
Text(value,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: scheme.onSurfaceVariant)),
],
),
);
}
}
class _InfoRowButton extends StatelessWidget {
final String label;
final VoidCallback onTap;
const _InfoRowButton({required this.label, required this.onTap});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(12)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: Theme.of(context).textTheme.bodyMedium),
Row(
children: [
Text('Apri',
style: TextStyle(
color: scheme.primary, fontWeight: FontWeight.w600)),
const SizedBox(width: 4),
Icon(Icons.chevron_right, size: 18, color: scheme.primary),
],
),
],
),
),
);
}
}