Skip to content

Commit

Permalink
Add a showcase of a sheet with TextField
Browse files Browse the repository at this point in the history
  • Loading branch information
fujidaiti committed Jan 28, 2024
1 parent ed78f42 commit edb91b2
Show file tree
Hide file tree
Showing 7 changed files with 553 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
"program": "lib/showcase/airbnb_mobile_app.dart",
"cwd": "./cookbook"
},
{
"name": "Todo List",
"request": "launch",
"type": "dart",
"program": "lib/showcase/todo_list/main.dart",
"cwd": "./cookbook"
},
{
"name": "Scrollable Sheet",
"request": "launch",
Expand Down
3 changes: 0 additions & 3 deletions cookbook/lib/showcase/todo_list.dart

This file was deleted.

171 changes: 171 additions & 0 deletions cookbook/lib/showcase/todo_list/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import 'package:cookbook/showcase/todo_list/models.dart';
import 'package:cookbook/showcase/todo_list/todo_editor.dart';
import 'package:flutter/material.dart';

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

class _TodoListExample extends StatelessWidget {
const _TodoListExample();

@override
Widget build(BuildContext context) {
return const MaterialApp(home: _Home());
}
}

class _Home extends StatefulWidget {
const _Home();

@override
State<_Home> createState() => _HomeState();
}

class _HomeState extends State<_Home> {
late final TodoList _todoList;

@override
void initState() {
super.initState();
_todoList = TodoList();
}

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

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todo List'),
),
body: _TodoListView(todoList: _todoList),
floatingActionButton: FloatingActionButton(
onPressed: () => addTodo(),
child: const Icon(Icons.add),
),
);
}

Future<void> addTodo() async {
final todo = await showTodoEditor(context);
if (todo != null) {
_todoList.add(todo);
}
}
}

class _TodoListView extends StatelessWidget {
const _TodoListView({
required this.todoList,
});

final TodoList todoList;

@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: todoList,
builder: (context, _) {
return ListView.separated(
itemCount: todoList.length,
itemBuilder: (context, index) {
return _TodoListViewItem(
todo: todoList[index],
checkboxCallback: (value) => todoList.toggle(index),
);
},
separatorBuilder: (context, index) {
return const Divider(indent: 24);
},
);
},
);
}
}

class _TodoListViewItem extends StatelessWidget {
const _TodoListViewItem({
Key? key,
required this.todo,
required this.checkboxCallback,
}) : super(key: key);

final Todo todo;
final ValueChanged<bool?> checkboxCallback;

@override
Widget build(BuildContext context) {
final statuses = <Widget>[
if (todo.priority != Priority.none)
_StatusChip(
icon: Icons.flag,
color: todo.priority.color,
label: todo.priority.displayName,
),
];

final description = switch (todo.description) {
null => null,
final text => Text(
text,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(color: Colors.black54),
),
};

final secondaryContent = [
if (description != null) ...[description, const SizedBox(height: 8)],
if (statuses.isNotEmpty && !todo.isDone) Wrap(children: statuses),
];

return CheckboxListTile(
value: todo.isDone,
controlAffinity: ListTileControlAffinity.leading,
onChanged: checkboxCallback,
title: Text(todo.title),
isThreeLine: secondaryContent.isNotEmpty,
subtitle: switch (secondaryContent.isNotEmpty) {
true => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: secondaryContent,
),
false => null,
},
);
}
}

class _StatusChip extends StatelessWidget {
const _StatusChip({
required this.icon,
required this.color,
required this.label,
});

final IconData icon;
final Color? color;
final String label;

@override
Widget build(BuildContext context) {
return Chip(
avatar: Icon(icon, color: color),
label: Text(label),
padding: EdgeInsets.zero,
labelPadding: const EdgeInsets.only(right: 12),
visualDensity: const VisualDensity(
vertical: -4,
horizontal: -4,
),
);
}
}
55 changes: 55 additions & 0 deletions cookbook/lib/showcase/todo_list/models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';

// TODO: Add 'reminder' and 'due date' fields.
class Todo {
final String title;
final String? description;
final Priority priority;
final bool isDone;

const Todo({
required this.title,
this.description,
this.priority = Priority.none,
this.isDone = false,
});
}

enum Priority {
high(displayName: 'High Priority', color: Colors.red),
medium(displayName: 'Medium Priority', color: Colors.orange),
low(displayName: 'Low Priority', color: Colors.blue),
none(displayName: 'No Priority');

const Priority({
required this.displayName,
this.color,
});

final String displayName;
final Color? color;
}

class TodoList extends ChangeNotifier {
final List<Todo> _todos = [];

int get length => _todos.length;

Todo operator [](int index) => _todos[index];

void add(Todo todo) {
_todos.insert(0, todo);
notifyListeners();
}

void toggle(int index) {
final todo = _todos[index];
_todos[index] = Todo(
title: todo.title,
description: todo.description,
priority: todo.priority,
isDone: !todo.isDone,
);
notifyListeners();
}
}
Loading

0 comments on commit edb91b2

Please sign in to comment.