Fliin
On this page

Blocks / lists

Task row

Shows a to-do with its checkbox, due day, priority and lateness written in words, read by screen readers as one checkbox node.

16 of 16 variants pass
Task row, todo state
Platform
Brightness
Text scale
Direction

Import

Task row is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add task-row

Also installs: core, checkbox, chip

Examples

Todo

A task to do, with its due time and its priority written as a word. Ticking the box calls onDoneChanged; touching the row opens the task with onPressed.

Task row, todo state
import 'package:your_app/ui/fliin/components/surface/fl_surface.dart';
import 'package:your_app/ui/fliin/components/task_row/fl_task_row.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:flutter/widgets.dart';

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

  @override
  State<TaskRowTodoExample> createState() => _TaskRowTodoExampleState();
}

class _TaskRowTodoExampleState extends State<TaskRowTodoExample> {
  var _done = false;

  @override
  Widget build(BuildContext context) {
    return FlSurface(
      padding: EdgeInsetsDirectional.symmetric(horizontal: FlScope.of(context).space.lg),
      child: FlTaskRow(
        title: 'Send Leah the quote',
        done: _done,
        onDoneChanged: (done) => setState(() => _done = done),
        due: 'Today · 4:30 PM',
        priority: FlTaskPriority.medium,
        priorityLabel: 'Medium priority',
        onPressed: () {},
        openLabel: 'Edit',
      ),
    );
  }
}

Overdue

A task past its due time: overdueLabel names the delay for the screen reader and the due time turns to danger.

Task row, overdue state
import 'package:your_app/ui/fliin/components/surface/fl_surface.dart';
import 'package:your_app/ui/fliin/components/task_row/fl_task_row.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:flutter/widgets.dart';

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

  @override
  State<TaskRowOverdueExample> createState() => _TaskRowOverdueExampleState();
}

class _TaskRowOverdueExampleState extends State<TaskRowOverdueExample> {
  var _done = false;

  @override
  Widget build(BuildContext context) {
    return FlSurface(
      padding: EdgeInsetsDirectional.symmetric(horizontal: FlScope.of(context).space.lg),
      child: FlTaskRow(
        title: 'Pay the rent',
        done: _done,
        onDoneChanged: (done) => setState(() => _done = done),
        due: 'Yesterday · 6:00 PM',
        overdueLabel: 'Overdue',
        priority: FlTaskPriority.high,
        priorityLabel: 'High priority',
        onPressed: () {},
        openLabel: 'Edit',
      ),
    );
  }
}

Done

A finished task: its title is struck through and quieter. Untick it to bring it back.

Task row, done state
import 'package:your_app/ui/fliin/components/surface/fl_surface.dart';
import 'package:your_app/ui/fliin/components/task_row/fl_task_row.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:flutter/widgets.dart';

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

  @override
  State<TaskRowDoneExample> createState() => _TaskRowDoneExampleState();
}

class _TaskRowDoneExampleState extends State<TaskRowDoneExample> {
  var _done = true;

  @override
  Widget build(BuildContext context) {
    return FlSurface(
      padding: EdgeInsetsDirectional.symmetric(horizontal: FlScope.of(context).space.lg),
      child: FlTaskRow(
        title: 'Buy light bulbs',
        done: _done,
        onDoneChanged: (done) => setState(() => _done = done),
        onPressed: () {},
        openLabel: 'Edit',
      ),
    );
  }
}

Adding

A task on its way to the server: status says so and onDoneChanged: null keeps the box until it is saved.

Task row, adding state
import 'package:your_app/ui/fliin/components/surface/fl_surface.dart';
import 'package:your_app/ui/fliin/components/task_row/fl_task_row.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return FlSurface(
      padding: EdgeInsetsDirectional.symmetric(horizontal: FlScope.of(context).space.lg),
      child: const FlTaskRow(title: 'Buy bread', done: false, onDoneChanged: null, status: 'Adding…'),
    );
  }
}

Not added

A task the server refused: statusFailed marks the status as an error, and the footer offers to try again or to remove it.

Task row, not-added state
import 'package:your_app/ui/fliin/components/link_button/fl_link_button.dart';
import 'package:your_app/ui/fliin/components/surface/fl_surface.dart';
import 'package:your_app/ui/fliin/components/task_row/fl_task_row.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    final space = FlScope.of(context).space;
    return FlSurface(
      padding: EdgeInsetsDirectional.symmetric(horizontal: space.lg),
      child: FlTaskRow(
        title: 'Call the bank',
        done: false,
        onDoneChanged: null,
        status: 'Not added',
        statusFailed: true,
        footer: Wrap(
          spacing: space.lg,
          children: [
            FlLinkButton(label: 'Try again', onPressed: () {}),
            FlLinkButton(label: 'Remove', onPressed: () {}),
          ],
        ),
      ),
    );
  }
}

Tagged

Tasks as an issue tracker shows them: their labels in colored chips (tags), and the assignee's avatar at the end (trailing), named for the screen reader by trailingLabel.

Task row, tagged state
import 'package:your_app/ui/fliin/components/avatar/fl_avatar.dart';
import 'package:your_app/ui/fliin/components/chip/fl_chip.dart';
import 'package:your_app/ui/fliin/components/surface/fl_surface.dart';
import 'package:your_app/ui/fliin/components/task_row/fl_task_row.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:flutter/widgets.dart';

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

  @override
  State<TaskRowTaggedExample> createState() => _TaskRowTaggedExampleState();
}

class _TaskRowTaggedExampleState extends State<TaskRowTaggedExample> {
  final _done = <int>{};

  @override
  Widget build(BuildContext context) {
    FlTaskRow task(int id, String title, List<FlTaskTag> tags, int photo, String assignee, {String? due}) =>
        FlTaskRow(
          title: title,
          done: _done.contains(id),
          onDoneChanged: (done) => setState(() => done ? _done.add(id) : _done.remove(id)),
          due: due,
          tags: tags,
          trailing: FlAvatar(image: AssetImage('images/people/$photo.jpg'), size: FlAvatarSize.sm),
          trailingLabel: 'Assigned to $assignee',
          onPressed: () {},
          openLabel: 'Edit',
        );
    return FlSurface(
      padding: EdgeInsetsDirectional.symmetric(horizontal: FlScope.of(context).space.lg),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          task(1, 'Fix the checkout total rounding', const [
            FlTaskTag('Bug', color: FlChipColor.danger),
            FlTaskTag('Payments', color: FlChipColor.success),
          ], 1, 'Kate Wilson', due: 'Today'),
          task(2, 'Draft the onboarding screens', const [
            FlTaskTag('Design', color: FlChipColor.accent),
          ], 2, 'John Doe', due: 'Fri, Nov 7'),
          task(3, 'Update the privacy policy', const [FlTaskTag('Legal', color: FlChipColor.warning)], 4, 'Michael Brown'),
        ],
      ),
    );
  }
}

Usage

The code the reference images render:

final example = ColoredBox(
  color: FlScope.of(context).colors.surface,
  child: SingleChildScrollView(
    padding: EdgeInsetsDirectional.all(FlScope.of(context).space.lg),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        FlTaskRow(
          title: (variant.dir == TextDirection.rtl
              ? "إرسال عرض السعر إلى ليا"
              : "Send Leah the quote"),
          done: false,
          onDoneChanged: (_) {},
          due: (variant.dir == TextDirection.rtl
              ? "اليوم · \u2066١٦:٣٠\u2069"
              : "Today · 16:30"),
          priority: FlTaskPriority.medium,
          priorityLabel: (variant.dir == TextDirection.rtl
              ? "أولوية متوسطة"
              : "Medium priority"),
          onPressed: () {},
          openLabel: (variant.dir == TextDirection.rtl ? "تعديل" : "Edit"),
        ),
        FlTaskRow(
          title: (variant.dir == TextDirection.rtl
              ? "دفع الإيجار"
              : "Pay the rent"),
          done: false,
          onDoneChanged: (_) {},
          due: (variant.dir == TextDirection.rtl
              ? "أمس · \u2066١٨:٠٠\u2069"
              : "Yesterday · 18:00"),
          overdueLabel: (variant.dir == TextDirection.rtl
              ? "متأخرة"
              : "Overdue"),
          priority: FlTaskPriority.high,
          priorityLabel: (variant.dir == TextDirection.rtl
              ? "أولوية عالية"
              : "High priority"),
          onPressed: () {},
          openLabel: (variant.dir == TextDirection.rtl ? "تعديل" : "Edit"),
        ),
        FlTaskRow(
          title: (variant.dir == TextDirection.rtl
              ? "شراء مصابيح"
              : "Buy light bulbs"),
          done: true,
          onDoneChanged: (_) {},
          onPressed: () {},
          openLabel: (variant.dir == TextDirection.rtl ? "تعديل" : "Edit"),
        ),
        FlTaskRow(
          title: (variant.dir == TextDirection.rtl
              ? "شراء الخبز"
              : "Buy bread"),
          done: false,
          onDoneChanged: null,
          status: (variant.dir == TextDirection.rtl
              ? "جارٍ الإضافة…"
              : "Adding…"),
        ),
        FlTaskRow(
          title: (variant.dir == TextDirection.rtl
              ? "الاتصال بالبنك"
              : "Call the bank"),
          done: false,
          onDoneChanged: null,
          status: (variant.dir == TextDirection.rtl ? "لم تُضف" : "Not added"),
          statusFailed: true,
          footer: Wrap(
            spacing: FlScope.of(context).space.lg,
            children: [
              FlLinkButton(
                label: (variant.dir == TextDirection.rtl
                    ? "أعد المحاولة"
                    : "Try again"),
                onPressed: () {},
              ),
              FlLinkButton(
                label: (variant.dir == TextDirection.rtl ? "إزالة" : "Remove"),
                onPressed: () {},
              ),
            ],
          ),
        ),
      ],
    ),
  ),
);

API

FlTaskPriority: enum {low, medium, high}
FlTaskPriority.low: static final FlTaskPriority
FlTaskPriority.medium: static final FlTaskPriority
FlTaskPriority.high: static final FlTaskPriority
FlTaskRowAction: final class
FlTaskRowAction.new: const new({required String label, required VoidCallback onPressed})
FlTaskRowAction.label: get String
FlTaskRowAction.onPressed: get VoidCallback
FlTaskTag: final class
FlTaskTag.new: const new(String label, {FlChipColor color})
FlTaskTag.label: get String
FlTaskTag.color: get FlChipColor
FlTaskRow: class extends StatelessWidget
FlTaskRow.new: const new({super.key, required String title, required bool done, required ValueChanged<bool>? onDoneChanged, String? due, String? overdueLabel, bool overdueShown, FlTaskPriority? priority, String? priorityLabel, String? status, bool statusFailed, VoidCallback? onPressed, String? openLabel, List<FlTaskRowAction> actions, Widget? footer, String separator, List<FlTaskTag> tags, Widget? trailing, String? trailingLabel})
FlTaskRow.title: get String
FlTaskRow.done: get bool
FlTaskRow.onDoneChanged: get ValueChanged<bool>?
FlTaskRow.due: get String?
FlTaskRow.overdueLabel: get String?
FlTaskRow.overdueShown: get bool
FlTaskRow.priority: get FlTaskPriority?
FlTaskRow.priorityLabel: get String?
FlTaskRow.status: get String?
FlTaskRow.statusFailed: get bool
FlTaskRow.onPressed: get VoidCallback?
FlTaskRow.openLabel: get String?
FlTaskRow.actions: get List<FlTaskRowAction>
FlTaskRow.footer: get Widget?
FlTaskRow.separator: get String
FlTaskRow.tags: get List<FlTaskTag>
FlTaskRow.trailing: get Widget?
FlTaskRow.trailingLabel: get String?
FlTaskRow.build: Widget(BuildContext context)

Reference renders

The 16 images published with version 0.2.5, rendered by the shared harness on macOS with Flutter 3.47.7. fliin verify task-row compares your copy to them.

iOS · Light
Task row, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Task row, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Task row, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Task row, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Task row, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Task row, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Task row, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Task row, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Task row, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Task row, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Task row, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Task row, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Task row, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Task row, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Task row, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Task row, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • toggle a task between done and to do
  • show when a task is due and that it is late
  • show how urgent a task is
  • open a task to edit it
  • show a task the server has not received yet
Not for
  • a single box in a form: use the checkbox (checkbox)
  • an agenda event: use an event row (event-row)
States
  • Todo
  • Overdue
  • Done
  • Adding
  • Not added

Arrows to move, Enter to open, Escape to close.