From 206ba1b557ff69b5f9047d5e742a91fc486dd30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20G=C3=B3es?= Date: Mon, 2 Sep 2024 16:30:17 -0300 Subject: [PATCH] Added API integration support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tiago Góes --- lib/constants/colors.dart | 2 +- lib/main.dart | 4 +- lib/models/task.dart | 30 ++++- lib/models/task.g.dart | 29 +++++ lib/repositories/api_task_repository.dart | 123 +++++++++++++++++++ lib/repositories/sqlite_task_repository.dart | 100 --------------- lib/widgets/task_add_widget.dart | 11 +- lib/widgets/task_item_widget.dart | 21 ++-- lib/widgets/task_list_widget.dart | 7 +- pubspec.lock | 32 +++++ pubspec.yaml | 2 + 11 files changed, 236 insertions(+), 125 deletions(-) create mode 100644 lib/models/task.g.dart create mode 100644 lib/repositories/api_task_repository.dart delete mode 100644 lib/repositories/sqlite_task_repository.dart diff --git a/lib/constants/colors.dart b/lib/constants/colors.dart index 0755e10..7ace558 100644 --- a/lib/constants/colors.dart +++ b/lib/constants/colors.dart @@ -20,4 +20,4 @@ class AppColors { } const AppColors(); -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index d96e595..d62056b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:todo/repositories/sqlite_task_repository.dart'; +import 'package:todo/repositories/api_task_repository.dart'; import 'package:todo/screens/home_screen.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); runApp(ChangeNotifierProvider( - create: (context) => SQLiteTaskRepository(), + create: (context) => APITaskRepository(), child: const App(), )); } diff --git a/lib/models/task.dart b/lib/models/task.dart index 71fab31..dbdd40b 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -1,5 +1,9 @@ +import 'package:json_annotation/json_annotation.dart'; import 'package:uuid/uuid.dart'; +part 'task.g.dart'; + +@JsonSerializable() class Task { String id; String title; @@ -19,11 +23,33 @@ class Task { this.rewardInSatoshis = 0, }) : id = id ?? const Uuid().v4(); - Map toMap() { + factory Task.fromJson(Map json) { + return Task( + id: json['id']?.toString(), // Convert int to String if necessary + title: json['title'] as String, + description: json['description'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + isCompleted: json['is_completed'] as bool, + rewardInSatoshis: json['reward_in_satoshis'] ?? 0, + ); + } + + Map toJson() { return { 'id': id, 'title': title, 'description': description, + 'created_at': createdAt.toIso8601String(), + 'is_completed': isCompleted, + 'reward_in_satoshis': rewardInSatoshis, + }; + } + + Map toMap() { + return { + 'id': id.toString(), + 'title': title, + 'description': description, 'createdAt': createdAt.toIso8601String(), 'completedAt': completedAt?.toIso8601String(), 'isCompleted': isCompleted, @@ -33,7 +59,7 @@ class Task { factory Task.fromMap(Map map) { return Task( - id: map['id'], + id: map['id'].toString(), title: map['title'], description: map['description'], createdAt: DateTime.parse(map['createdAt']), diff --git a/lib/models/task.g.dart b/lib/models/task.g.dart new file mode 100644 index 0000000..293c20c --- /dev/null +++ b/lib/models/task.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'task.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Task _$TaskFromJson(Map json) => Task( + id: json['id'] as String?, + title: json['title'] as String, + description: json['description'] as String, + createdAt: DateTime.parse(json['createdAt'] as String), + completedAt: json['completedAt'] == null + ? null + : DateTime.parse(json['completedAt'] as String), + isCompleted: json['isCompleted'] as bool? ?? false, + rewardInSatoshis: (json['rewardInSatoshis'] as num?)?.toInt() ?? 0, + ); + +Map _$TaskToJson(Task instance) => { + 'id': instance.id, + 'title': instance.title, + 'description': instance.description, + 'createdAt': instance.createdAt.toIso8601String(), + 'completedAt': instance.completedAt?.toIso8601String(), + 'isCompleted': instance.isCompleted, + 'rewardInSatoshis': instance.rewardInSatoshis, + }; diff --git a/lib/repositories/api_task_repository.dart b/lib/repositories/api_task_repository.dart new file mode 100644 index 0000000..f93f1f1 --- /dev/null +++ b/lib/repositories/api_task_repository.dart @@ -0,0 +1,123 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart' as http; + +import '../models/task.dart'; +import 'task_repository.dart'; + +class APITaskRepository extends ChangeNotifier implements TaskRepository { + @override + Future> getAllTasks() async { + List tasks = []; + try { + final response = await http.get(Uri.parse('http://localhost:8080/tasks'), + headers: { + 'Content-Type': 'application/json', + }); + + if (response.statusCode == 200) { + Iterable jsonResponse = jsonDecode(response.body); + tasks = jsonResponse.map((task) => Task.fromJson(task)).toList(); + } + return tasks; + } catch (error) { + throw Exception(error); + } + } + + @override + Future> getCompletedTasks() async { + List tasks = []; + try { + final response = await http.get( + Uri.parse('http://localhost:8080/tasks?completed=true'), + headers: { + 'Content-Type': 'application/json', + }); + + if (response.statusCode == 200) { + Iterable jsonResponse = jsonDecode(response.body); + tasks = jsonResponse.map((task) => Task.fromJson(task)).toList(); + } + + return tasks; + } catch (error) { + throw Exception(error); + } + } + + @override + Future getTaskById(String id) async { + Task? task; + try { + final response = await http.get( + Uri.parse('http://localhost:8080/task/$id'), + headers: { + 'Content-Type': 'application/json', + }); + + if (response.statusCode == 200) { + Iterable jsonResponse = jsonDecode(response.body); + task = jsonResponse.map((task) => Task.fromJson(task)) as Task; + } + return task; + } catch (error) { + throw Exception(error); + } + } + + @override + Future addTask(Task task) async { + try { + await http.post(Uri.parse('http://localhost:8080/task'), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'title': task.title, + 'description': 'test', + 'created_at': task.createdAt.toUtc().toIso8601String(), + 'is_completed': task.isCompleted, + 'reward_in_sats': task.rewardInSatoshis + })); + } catch (error) { + throw Exception(error); + } + notifyListeners(); + } + + @override + Future updateTask(Task task) async { + String taskId = task.id; + try { + await http.put(Uri.parse('http://localhost:8080/task/$taskId'), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'title': task.title, + 'description': task.description, + 'created_at': task.createdAt.toUtc().toIso8601String(), + 'is_completed': task.isCompleted, + 'reward_in_sats': task.rewardInSatoshis + })); + } catch (error) { + throw Exception(error); + } + notifyListeners(); + } + + @override + Future deleteTask(String id) async { + try { + await http.delete(Uri.parse('http://localhost:8080/task/$id'), + headers: { + 'Content-Type': 'application/json', + }); + } catch (error) { + throw Exception(error); + } + notifyListeners(); + } +} diff --git a/lib/repositories/sqlite_task_repository.dart b/lib/repositories/sqlite_task_repository.dart deleted file mode 100644 index 3afc70c..0000000 --- a/lib/repositories/sqlite_task_repository.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:path/path.dart'; -import '../models/task.dart'; -import 'task_repository.dart'; - -class SQLiteTaskRepository extends ChangeNotifier implements TaskRepository { - Database? _database; - - Future get database async { - if (_database != null) return _database!; - _database = await _initDatabase(); - return _database!; - } - - Future _initDatabase() async { - // await deleteDatabase(join(await getDatabasesPath(), 'tasks.db')); - return openDatabase( - join(await getDatabasesPath(), 'tasks.db'), - onCreate: (db, version) { - return db.execute( - 'CREATE TABLE tasks(id TEXT PRIMARY KEY, title TEXT, description TEXT, createdAt TEXT, completedAt TEXT, isCompleted INTEGER, rewardInSatoshis INTEGER)', - ); - }, - version: 1, - ); - } - - @override - Future> getAllTasks() async { - final db = await database; - final List> maps = - await db.query('tasks', orderBy: 'createdAt DESC', where: 'isCompleted = 0'); - - return List.generate(maps.length, (i) { - return Task.fromMap(maps[i]); - }); - } - - @override - Future> getCompletedTasks() async { - final db = await database; - final List> maps = - await db.query('tasks', orderBy: 'createdAt DESC'); - - return List.generate(maps.length, (i) { - return Task.fromMap(maps[i]); - }); - } - - @override - Future getTaskById(String id) async { - final db = await database; - final List> maps = await db.query( - 'tasks', - where: 'id = ?', - whereArgs: [id], - ); - - if (maps.isNotEmpty) { - return Task.fromMap(maps.first); - } else { - return null; - } - } - - @override - Future addTask(Task task) async { - final db = await database; - await db.insert( - 'tasks', - task.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); - notifyListeners(); - } - - @override - Future updateTask(Task task) async { - final db = await database; - await db.update( - 'tasks', - task.toMap(), - where: 'id = ?', - whereArgs: [task.id], - ); - notifyListeners(); - } - - @override - Future deleteTask(String id) async { - final db = await database; - await db.delete( - 'tasks', - where: 'id = ?', - whereArgs: [id], - ); - notifyListeners(); - } -} diff --git a/lib/widgets/task_add_widget.dart b/lib/widgets/task_add_widget.dart index 8af0772..169c885 100644 --- a/lib/widgets/task_add_widget.dart +++ b/lib/widgets/task_add_widget.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:todo/models/task.dart'; -import 'package:todo/repositories/task_repository.dart'; -import '../repositories/sqlite_task_repository.dart'; +import '../repositories/api_task_repository.dart'; class AddTask extends StatefulWidget { const AddTask({ @@ -15,13 +14,13 @@ class AddTask extends StatefulWidget { class _AddTaskState extends State { final textController = TextEditingController(); - late SQLiteTaskRepository taskRepository; + late APITaskRepository taskRepository; late Future> taskListFuture; @override void initState() { super.initState(); - taskRepository = SQLiteTaskRepository(); + taskRepository = APITaskRepository(); taskListFuture = taskRepository.getAllTasks(); } @@ -58,12 +57,12 @@ class _AddTaskState extends State { if (textController.text.isNotEmpty) { Task task = Task( title: textController.text, - description: '', + description: 'test', createdAt: DateTime.now(), isCompleted: false, rewardInSatoshis: 0, ); - await Provider.of(context, + await Provider.of(context, listen: false) .addTask(task); textController.clear(); diff --git a/lib/widgets/task_item_widget.dart b/lib/widgets/task_item_widget.dart index 5006641..5455f54 100644 --- a/lib/widgets/task_item_widget.dart +++ b/lib/widgets/task_item_widget.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:todo/constants/colors.dart'; import '../models/task.dart'; -import '../repositories/sqlite_task_repository.dart'; +import '../repositories/api_task_repository.dart'; class TaskItem extends StatefulWidget { final Task task; final Color taskItemColor; const TaskItem( - this.task, this.taskItemColor, { + this.task, + this.taskItemColor, { super.key, }); @@ -18,14 +18,13 @@ class TaskItem extends StatefulWidget { } class _TaskItemState extends State { - late SQLiteTaskRepository taskRepository; + late APITaskRepository taskRepository; late Future> taskListFuture; - @override void initState() { super.initState(); - taskRepository = SQLiteTaskRepository(); + taskRepository = APITaskRepository(); taskListFuture = taskRepository.getAllTasks(); } @@ -115,7 +114,7 @@ class _TaskItemState extends State { Task updatedTask = widget.task; updatedTask.isCompleted = true; updatedTask.completedAt = DateTime.now(); - Provider.of(context, + Provider.of(context, listen: false) .updateTask(updatedTask); }); @@ -140,11 +139,11 @@ class _TaskItemState extends State { onPressed: () { _showConfirmationDialog(context, "Would you like to delete this task?", - () { - Provider.of(context, + () { + Provider.of(context, listen: false) - .deleteTask(widget.task.id); - }); + .deleteTask(widget.task.id); + }); }, iconSize: 35, icon: const Icon( diff --git a/lib/widgets/task_list_widget.dart b/lib/widgets/task_list_widget.dart index 2fd769e..1e02e8c 100644 --- a/lib/widgets/task_list_widget.dart +++ b/lib/widgets/task_list_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:todo/constants/colors.dart'; -import 'package:todo/repositories/sqlite_task_repository.dart'; +import 'package:todo/repositories/api_task_repository.dart'; import 'package:todo/widgets/task_item_widget.dart'; import '../models/task.dart'; @@ -16,7 +16,7 @@ class TaskList extends StatefulWidget { class _TaskListState extends State { @override Widget build(BuildContext context) { - return Consumer( + return Consumer( builder: (context, taskRepository, child) => FutureBuilder>( future: taskRepository.getAllTasks(), builder: (context, snapshot) { @@ -25,7 +25,8 @@ class _TaskListState extends State { } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text('Add some work to do!')); + return const Center( + child: Text('Start adding some work to do!')); } else { List tasks = snapshot.data!; return ListView.builder( diff --git a/pubspec.lock b/pubspec.lock index e73db67..1ae54dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -251,6 +251,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + http: + dependency: "direct main" + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -291,6 +299,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.9.0" + json_serializable: + dependency: "direct main" + description: + name: json_serializable + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + url: "https://pub.dev" + source: hosted + version: "6.8.0" leak_tracker: dependency: transitive description: @@ -464,6 +480,22 @@ packages: description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" source_map_stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4d37ec5..f5e7c7a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,8 @@ dependencies: uuid: ^4.4.2 sqflite: ^2.3.3+1 path: ^1.9.0 + http: ^1.2.2 + json_serializable: ^6.8.0 dev_dependencies: flutter_test: