Menu
CoddyTech

מעקב הרגלים: רצף

חלק מהיחידה פרויקטים במסלול ה-Flutter של Coddy. שיעור 32 מתוך 36.

רצפים גורמים לאנשים לחזור: ימים רצופים מרגישים כמו משהו שכדאי להגן עליו. כאן הרצף הוא רצף הימים הארוך ביותר שבהם ההרגל בוצע במהלך השבוע. שתיית מים, שבוצעה מיום שני עד יום רביעי וביום שישי, מגיעה לרצף של 3 ימים.

כדי לספור רצף, עוברים על הימים לפי הסדר עם שני מספרים: run, מספר הימים הרצופים עד כה, ו-best, הרצף הארוך ביותר שנצפה. יום שבו ההרגל בוצע מוסיף לרצף; יום שבו ההרגל לא בוצע מאפס אותו ל-0.

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;
}

החזר את best, ולא את run: בסוף הלולאה, run הוא רק הרצף האחרון, והוא 0 אם יום ראשון הוא יום שבו ההרגל לא בוצע.

הצג אותו מתחת לשבוע עם סמל של להבה: בכתום כשיש רצף, ובאפור של outline של ערכת העיצוב כשאין רצף. "רצף של 0 ימים" נשמע מוזר, אז במקרה כזה כתוב "עדיין אין רצף".

challenge icon

אתגר

קשה

הצגת רצפים:

  • הוסיפו את ה־getter streak ל־Habit
  • מתחת לשבוע, אחרי 12 פיקסלים, הוסיפו Row: Icons.local_fire_department בגודל 20 (בכתום כשהרצף גדול מ־0, וב־outline של ערכת הנושא אחרת), 4 פיקסלים והטקסט
  • הטקסט הוא 3 day streak (עם המספר האמיתי), או No streak yet כשמדובר ב־0

נסו בעצמכם

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;
}

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),
                  ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה פרויקטים

תרגלו בעצמכם: קומפיילר Flutter אונליין