-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
393 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,12 @@ | ||
include: package:very_good_analysis/analysis_options.yaml | ||
|
||
analyzer: | ||
errors: | ||
always_put_required_named_parameters_first: ignore | ||
plugins: | ||
- custom_lint | ||
|
||
linter: | ||
rules: | ||
# since this is just an example, there is no need for ubiquitous documentation. | ||
public_member_api_docs: false | ||
public_member_api_docs: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import 'package:disco/disco.dart'; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:flutter_solidart/flutter_solidart.dart' hide Provider; | ||
import 'package:solidart_example/domain/todo.dart'; | ||
|
||
final todosControllerProvider = Provider<TodosController>( | ||
(context) => TodosController(initialTodos: Todo.sample), | ||
dispose: (controller) => controller.dispose(), | ||
); | ||
|
||
/// Contains the state of the [todos] list and allows to | ||
/// - `add`: Add a todo in the list of [todos] | ||
/// - `remove`: Removes a todo with the given id from the list of [todos] | ||
/// - `toggle`: Toggles a todo with the given id | ||
/// The list of todos exposed is a [ReadSignal] so the user cannot mutate | ||
/// the signal without using this controller. | ||
@immutable | ||
class TodosController { | ||
TodosController({ | ||
List<Todo> initialTodos = const [], | ||
}) : todos = ListSignal(initialTodos); | ||
|
||
// The list of todos | ||
final ListSignal<Todo> todos; | ||
|
||
/// The list of completed todos | ||
late final completedTodos = Computed( | ||
() => todos.where((todo) => todo.completed).toList(), | ||
); | ||
|
||
/// The list of incomplete todos | ||
late final incompleteTodos = Computed( | ||
() => todos.where((todo) => !todo.completed).toList(), | ||
); | ||
|
||
/// Add a todo | ||
void add(Todo todo) { | ||
todos.add(todo); | ||
} | ||
|
||
/// Remove a todo with the given [id] | ||
void remove(String id) { | ||
todos.removeWhere((todo) => todo.id == id); | ||
} | ||
|
||
/// Toggle a todo with the given [id] | ||
void toggle(String id) { | ||
final todoIndex = todos.indexWhere((element) => element.id == id); | ||
final todo = todos[todoIndex]; | ||
todos[todoIndex] = todo.copyWith(completed: !todo.completed); | ||
} | ||
|
||
void dispose() { | ||
todos.dispose(); | ||
completedTodos.dispose(); | ||
incompleteTodos.dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:uuid/uuid.dart'; | ||
|
||
@immutable | ||
class Todo { | ||
const Todo({ | ||
required this.id, | ||
required this.task, | ||
required this.completed, | ||
}); | ||
|
||
factory Todo.create(String task) { | ||
final uuid = const Uuid().v4(); | ||
return Todo(id: uuid, task: task, completed: false); | ||
} | ||
|
||
final String id; | ||
final String task; | ||
final bool completed; | ||
|
||
static List<Todo> get sample { | ||
return [ | ||
Todo.create('Learn solidart'), | ||
Todo.create('Wash the car'), | ||
Todo.create('Go shopping'), | ||
]; | ||
} | ||
|
||
Todo copyWith({bool? completed}) { | ||
return Todo( | ||
id: id, | ||
task: task, | ||
completed: completed ?? this.completed, | ||
); | ||
} | ||
|
||
@override | ||
String toString() => 'Todo(id: $id, task: $task, completed: $completed)'; | ||
} | ||
|
||
enum TodosFilter { | ||
all, | ||
incomplete, | ||
completed; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,18 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:solidart_example/pages/todos.dart'; | ||
|
||
void main() { | ||
runApp(const MainApp()); | ||
runApp(const MyApp()); | ||
} | ||
|
||
class MainApp extends StatelessWidget { | ||
const MainApp({super.key}); | ||
class MyApp extends StatelessWidget { | ||
const MyApp({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
// TODO(nank1ro): create an example with signals | ||
return const MaterialApp( | ||
home: Scaffold( | ||
body: Center( | ||
child: Text('Hello World!'), | ||
), | ||
), | ||
title: 'Todos Example', | ||
home: TodosPage(), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import 'package:disco/disco.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:solidart_example/controllers/todos.dart'; | ||
import 'package:solidart_example/widgets/todos_body.dart'; | ||
|
||
class TodosPage extends StatelessWidget { | ||
const TodosPage({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
// Using ProviderScope here to provide the [TodosController] to descendants. | ||
return ProviderScope( | ||
providers: [todosControllerProvider], | ||
child: Scaffold( | ||
appBar: AppBar( | ||
title: const Text('Todos'), | ||
), | ||
body: const Padding( | ||
padding: EdgeInsets.all(8.0), | ||
child: TodosBody(), | ||
), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:solidart_example/controllers/todos.dart'; | ||
import 'package:solidart_example/domain/todo.dart'; | ||
|
||
/// Renders a ListTile with a checkbox where you can change | ||
/// the "completion" status of a [Todo] | ||
class TodoItem extends StatelessWidget { | ||
const TodoItem({ | ||
super.key, | ||
required this.todo, | ||
this.onStatusChanged, | ||
}); | ||
|
||
final Todo todo; | ||
final ValueChanged<bool?>? onStatusChanged; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Dismissible( | ||
key: ValueKey(todo.id), | ||
direction: DismissDirection.endToStart, | ||
onDismissed: (_) { | ||
// remove the todo | ||
todosControllerProvider.of(context).remove(todo.id); | ||
}, | ||
background: Container( | ||
decoration: const BoxDecoration(color: Colors.red), | ||
alignment: Alignment.centerRight, | ||
padding: const EdgeInsets.only(right: 8), | ||
child: const Icon( | ||
Icons.delete, | ||
color: Colors.white, | ||
), | ||
), | ||
child: CheckboxListTile( | ||
title: Text(todo.task), | ||
value: todo.completed, | ||
onChanged: onStatusChanged, | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import 'package:disco/disco.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_solidart/flutter_solidart.dart' hide Provider; | ||
import 'package:solidart_example/controllers/todos.dart'; | ||
import 'package:solidart_example/domain/todo.dart'; | ||
import 'package:solidart_example/widgets/todos_list.dart'; | ||
import 'package:solidart_example/widgets/toolbar.dart'; | ||
|
||
final todosFilterProvider = Provider((context) => Signal(TodosFilter.all)); | ||
|
||
class TodosBody extends StatefulWidget { | ||
const TodosBody({super.key}); | ||
|
||
@override | ||
State<TodosBody> createState() => _TodosBodyState(); | ||
} | ||
|
||
class _TodosBodyState extends State<TodosBody> { | ||
final textController = TextEditingController(); | ||
|
||
@override | ||
void dispose() { | ||
textController.dispose(); | ||
super.dispose(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
// retrieve the [TodosController], you're safe to retrieve Provider in both | ||
// the `initState` and `build` methods. | ||
final todosController = todosControllerProvider.of(context); | ||
|
||
return ProviderScope( | ||
providers: [ | ||
// make the active filter signal visible only to descendants. | ||
// scoped here because this is where it starts to be necessary. | ||
todosFilterProvider, | ||
], | ||
child: Column( | ||
children: [ | ||
TextFormField( | ||
controller: textController, | ||
decoration: const InputDecoration( | ||
hintText: 'Write new todo', | ||
), | ||
validator: (v) { | ||
if (v == null || v.isEmpty) { | ||
return 'Cannot be empty'; | ||
} | ||
return null; | ||
}, | ||
onFieldSubmitted: (task) { | ||
if (task.isEmpty) return; | ||
final newTodo = Todo.create(task); | ||
todosController.add(newTodo); | ||
textController.clear(); | ||
}, | ||
), | ||
const SizedBox(height: 16), | ||
const Toolbar(), | ||
const SizedBox(height: 16), | ||
Expanded( | ||
child: TodoList( | ||
onTodoToggle: todosController.toggle, | ||
), | ||
), | ||
], | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_solidart/flutter_solidart.dart'; | ||
import 'package:solidart_example/controllers/todos.dart'; | ||
import 'package:solidart_example/domain/todo.dart'; | ||
import 'package:solidart_example/widgets/todo_item.dart'; | ||
import 'package:solidart_example/widgets/todos_body.dart'; | ||
|
||
class TodoList extends StatefulWidget { | ||
const TodoList({ | ||
super.key, | ||
this.onTodoToggle, | ||
}); | ||
|
||
final ValueChanged<String>? onTodoToggle; | ||
|
||
@override | ||
State<TodoList> createState() => _TodoListState(); | ||
} | ||
|
||
class _TodoListState extends State<TodoList> { | ||
// retrieve the [TodosController] | ||
late final todosController = todosControllerProvider.of(context); | ||
|
||
// Given a [filter] return the correct list of todos | ||
ReadSignal<List<Todo>> mapFilterToTodosList(TodosFilter filter) { | ||
switch (filter) { | ||
case TodosFilter.all: | ||
return todosController.todos; | ||
case TodosFilter.incomplete: | ||
return todosController.incompleteTodos; | ||
case TodosFilter.completed: | ||
return todosController.completedTodos; | ||
} | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return SignalBuilder( | ||
builder: (context, child) { | ||
// rebuilds every time the activeFilter value changes | ||
final activeFilter = todosFilterProvider.of(context).value; | ||
// react to the correct list of todos list | ||
final todos = mapFilterToTodosList(activeFilter).value; | ||
return ListView.builder( | ||
itemCount: todos.length, | ||
itemBuilder: (BuildContext context, int index) { | ||
final todo = todos[index]; | ||
return TodoItem( | ||
todo: todo, | ||
onStatusChanged: (_) { | ||
widget.onTodoToggle?.call(todo.id); | ||
}, | ||
); | ||
}, | ||
); | ||
}, | ||
); | ||
} | ||
} |
Oops, something went wrong.