Menu
CoddyTech

Suivi des habitudes : Ajouter une habitude

Fait partie de la section Projets du Journey Flutter de Coddy. Leçon 33 sur 36.

Tout le monde suit des habitudes différentes, l’application doit donc permettre d’en ajouter une. Un FloatingActionButton rond dans le coin ouvre une boîte de dialogue qui demande son nom.

showDialog renvoie un Future contenant la valeur renvoyée par la boîte de dialogue. Ici, la boîte de dialogue renvoie true pour Ajouter et false pour Annuler, et le champ écrit ce que tu saisis dans une variable avec onChanged :

String name = '';
final added = await showDialog<bool>(
  context: context,
  builder: (context) => AlertDialog(
    title: const Text('New habit'),
    content: TextField(onChanged: (value) => name = value, ...),
    actions: [...],
  ),
);
if (added != true || name.trim().isEmpty) return;

added est un bool? : toucher à l’extérieur de la boîte de dialogue la ferme avec null. added != true couvre à la fois le cas Annuler et ce cas.

Une nouvelle habitude commence avec une nouvelle semaine : List.filled(7, false). Pas const [false, ...] : une liste const ne peut jamais changer, le premier cochement ferait donc planter l’application.

challenge icon

Défi

Difficile

Ajouter des habitudes :

  • ajouter au Scaffold un FloatingActionButton avec Icons.add qui appelle _addHabit
  • écrire Future<void> _addHabit() async : une AlertDialog intitulée New habit avec un TextField portant le libellé Habit name (avec autofocus: true), un TextButton Cancel et un FilledButton Add
  • après avoir appuyé sur Add avec un nom non vide, ajouter une habitude avec ce nom sans espaces superflus et une semaine vide

Essayez vous-même

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
      ),
      home: const HabitScreen(),
    );
  }
}

const List<String> dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

class Habit {
  Habit(this.name, this.days);

  final String name;
  final List<bool> days;

  int get done => days.where((day) => day).length;

  int get streak {
    int best = 0;
    int run = 0;
    for (bool day in days) {
      if (day) {
        run++;
        if (run > best) best = run;
      } else {
        run = 0;
      }
    }
    return best;
  }
}

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

  @override
  State<HabitScreen> createState() => _HabitScreenState();
}

class _HabitScreenState extends State<HabitScreen> {
  final List<Habit> _habits = [
    Habit('Drink water', [true, true, true, false, true, false, false]),
    Habit('Read 10 pages', [true, false, true, true, false, false, false]),
    Habit('Stretch', List.filled(7, false)),
  ];

  void _toggle(Habit habit, int day) {
    setState(() {
      habit.days[day] = !habit.days[day];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Habits')),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(16, 8, 16, 88),
        children: [
          for (Habit habit in _habits)
            HabitCard(
              habit: habit,
              onToggle: (day) => _toggle(habit, day),
            ),
        ],
      ),
    );
  }
}

class HabitCard extends StatelessWidget {
  const HabitCard({super.key, required this.habit, required this.onToggle});

  final Habit habit;
  final void Function(int day) onToggle;

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    return Card(
      margin: const EdgeInsets.only(top: 12),
      clipBehavior: Clip.antiAlias,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Expanded(child: Text(habit.name, style: textTheme.titleMedium)),
                Text('${habit.done} of 7 days', style: textTheme.bodySmall),
              ],
            ),
            const SizedBox(height: 12),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                for (int i = 0; i < 7; i++)
                  DayCircle(
                    label: dayNames[i],
                    done: habit.days[i],
                    onTap: () => onToggle(i),
                  ),
              ],
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Icon(
                  Icons.local_fire_department,
                  size: 20,
                  color: habit.streak > 0 ? Colors.orange : Theme.of(context).colorScheme.outline,
                ),
                const SizedBox(width: 4),
                Text(habit.streak == 0 ? 'No streak yet' : '${habit.streak} day streak'),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class DayCircle extends StatelessWidget {
  const DayCircle({super.key, required this.label, required this.done, required this.onTap});

  final String label;
  final bool done;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    return GestureDetector(
      onTap: onTap,
      child: Container(
        width: 40,
        height: 40,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: done ? colors.primary : colors.surfaceContainerHighest,
        ),
        child: Text(
          label,
          style: TextStyle(
            fontSize: 11,
            fontWeight: FontWeight.bold,
            color: done ? colors.onPrimary : colors.onSurfaceVariant,
          ),
        ),
      ),
    );
  }
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Projets

Entraînez-vous par vous-même : Compilateur Flutter en ligne