This commit is contained in:
Phuoc Nguyen
2025-10-03 17:54:39 +07:00
parent 762395ce50
commit 38a33743e6
16 changed files with 1096 additions and 228 deletions

View File

@@ -0,0 +1,28 @@
import 'package:base_flutter/core/utils/utils.dart';
import '../../../../core/network/dio_client.dart';
import '../../../../core/constants/environment_config.dart';
import '../../../../core/services/api_service.dart';
import '../models/todo_model.dart';
abstract class TodoRemoteDataSource {
Future<List<TodoModel>> getTodos();
}
class TodoRemoteDataSourceImpl extends BaseApiService
implements TodoRemoteDataSource {
TodoRemoteDataSourceImpl({required DioClient dioClient}) : super(dioClient);
@override
Future<List<TodoModel>> getTodos() async {
final response = await dioClient.get(EnvironmentConfig.todosEndpoint);
if (response.data is List) {
final List<dynamic> todosJson = response.data as List<dynamic>;
return todosJson
.map((json) => TodoModel.fromJson(json as DataMap))
.toList();
} else {
throw Exception('Expected List but got ${response.data.runtimeType}');
}
}
}

View File

@@ -0,0 +1,46 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/entities/todo.dart';
part 'todo_model.freezed.dart';
part 'todo_model.g.dart';
@freezed
class TodoModel with _$TodoModel {
const factory TodoModel({
required int id,
required String title,
String? description,
required bool completed,
required String userId,
@JsonKey(includeFromJson: false, includeToJson: false) Map<String, dynamic>? user,
DateTime? createdAt,
DateTime? updatedAt,
}) = _TodoModel;
const TodoModel._();
factory TodoModel.fromJson(Map<String, dynamic> json) =>
_$TodoModelFromJson(json);
/// Convert to domain entity
Todo toEntity() => Todo(
id: id,
title: title,
description: description,
completed: completed,
userId: userId,
createdAt: createdAt,
updatedAt: updatedAt,
);
/// Create from domain entity
factory TodoModel.fromEntity(Todo todo) => TodoModel(
id: todo.id,
title: todo.title,
description: todo.description,
completed: todo.completed,
userId: todo.userId,
createdAt: todo.createdAt,
updatedAt: todo.updatedAt,
);
}

View File

@@ -0,0 +1,325 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'todo_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
TodoModel _$TodoModelFromJson(Map<String, dynamic> json) {
return _TodoModel.fromJson(json);
}
/// @nodoc
mixin _$TodoModel {
int get id => throw _privateConstructorUsedError;
String get title => throw _privateConstructorUsedError;
String? get description => throw _privateConstructorUsedError;
bool get completed => throw _privateConstructorUsedError;
String get userId => throw _privateConstructorUsedError;
@JsonKey(includeFromJson: false, includeToJson: false)
Map<String, dynamic>? get user => throw _privateConstructorUsedError;
DateTime? get createdAt => throw _privateConstructorUsedError;
DateTime? get updatedAt => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TodoModelCopyWith<TodoModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TodoModelCopyWith<$Res> {
factory $TodoModelCopyWith(TodoModel value, $Res Function(TodoModel) then) =
_$TodoModelCopyWithImpl<$Res, TodoModel>;
@useResult
$Res call(
{int id,
String title,
String? description,
bool completed,
String userId,
@JsonKey(includeFromJson: false, includeToJson: false)
Map<String, dynamic>? user,
DateTime? createdAt,
DateTime? updatedAt});
}
/// @nodoc
class _$TodoModelCopyWithImpl<$Res, $Val extends TodoModel>
implements $TodoModelCopyWith<$Res> {
_$TodoModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? title = null,
Object? description = freezed,
Object? completed = null,
Object? userId = null,
Object? user = freezed,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
title: null == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
completed: null == completed
? _value.completed
: completed // ignore: cast_nullable_to_non_nullable
as bool,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
user: freezed == user
? _value.user
: user // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
) as $Val);
}
}
/// @nodoc
abstract class _$$TodoModelImplCopyWith<$Res>
implements $TodoModelCopyWith<$Res> {
factory _$$TodoModelImplCopyWith(
_$TodoModelImpl value, $Res Function(_$TodoModelImpl) then) =
__$$TodoModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
String title,
String? description,
bool completed,
String userId,
@JsonKey(includeFromJson: false, includeToJson: false)
Map<String, dynamic>? user,
DateTime? createdAt,
DateTime? updatedAt});
}
/// @nodoc
class __$$TodoModelImplCopyWithImpl<$Res>
extends _$TodoModelCopyWithImpl<$Res, _$TodoModelImpl>
implements _$$TodoModelImplCopyWith<$Res> {
__$$TodoModelImplCopyWithImpl(
_$TodoModelImpl _value, $Res Function(_$TodoModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? title = null,
Object? description = freezed,
Object? completed = null,
Object? userId = null,
Object? user = freezed,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(_$TodoModelImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
title: null == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
completed: null == completed
? _value.completed
: completed // ignore: cast_nullable_to_non_nullable
as bool,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
user: freezed == user
? _value._user
: user // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TodoModelImpl extends _TodoModel {
const _$TodoModelImpl(
{required this.id,
required this.title,
this.description,
required this.completed,
required this.userId,
@JsonKey(includeFromJson: false, includeToJson: false)
final Map<String, dynamic>? user,
this.createdAt,
this.updatedAt})
: _user = user,
super._();
factory _$TodoModelImpl.fromJson(Map<String, dynamic> json) =>
_$$TodoModelImplFromJson(json);
@override
final int id;
@override
final String title;
@override
final String? description;
@override
final bool completed;
@override
final String userId;
final Map<String, dynamic>? _user;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
Map<String, dynamic>? get user {
final value = _user;
if (value == null) return null;
if (_user is EqualUnmodifiableMapView) return _user;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override
final DateTime? createdAt;
@override
final DateTime? updatedAt;
@override
String toString() {
return 'TodoModel(id: $id, title: $title, description: $description, completed: $completed, userId: $userId, user: $user, createdAt: $createdAt, updatedAt: $updatedAt)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TodoModelImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.title, title) || other.title == title) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.completed, completed) ||
other.completed == completed) &&
(identical(other.userId, userId) || other.userId == userId) &&
const DeepCollectionEquality().equals(other._user, _user) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
id,
title,
description,
completed,
userId,
const DeepCollectionEquality().hash(_user),
createdAt,
updatedAt);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$TodoModelImplCopyWith<_$TodoModelImpl> get copyWith =>
__$$TodoModelImplCopyWithImpl<_$TodoModelImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TodoModelImplToJson(
this,
);
}
}
abstract class _TodoModel extends TodoModel {
const factory _TodoModel(
{required final int id,
required final String title,
final String? description,
required final bool completed,
required final String userId,
@JsonKey(includeFromJson: false, includeToJson: false)
final Map<String, dynamic>? user,
final DateTime? createdAt,
final DateTime? updatedAt}) = _$TodoModelImpl;
const _TodoModel._() : super._();
factory _TodoModel.fromJson(Map<String, dynamic> json) =
_$TodoModelImpl.fromJson;
@override
int get id;
@override
String get title;
@override
String? get description;
@override
bool get completed;
@override
String get userId;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
Map<String, dynamic>? get user;
@override
DateTime? get createdAt;
@override
DateTime? get updatedAt;
@override
@JsonKey(ignore: true)
_$$TodoModelImplCopyWith<_$TodoModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'todo_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TodoModelImpl _$$TodoModelImplFromJson(Map<String, dynamic> json) =>
_$TodoModelImpl(
id: (json['id'] as num).toInt(),
title: json['title'] as String,
description: json['description'] as String?,
completed: json['completed'] as bool,
userId: json['userId'] as String,
createdAt: json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String),
updatedAt: json['updatedAt'] == null
? null
: DateTime.parse(json['updatedAt'] as String),
);
Map<String, dynamic> _$$TodoModelImplToJson(_$TodoModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'description': instance.description,
'completed': instance.completed,
'userId': instance.userId,
'createdAt': instance.createdAt?.toIso8601String(),
'updatedAt': instance.updatedAt?.toIso8601String(),
};

View File

@@ -0,0 +1,49 @@
import 'package:fpdart/fpdart.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/errors/failures.dart';
import '../../../../core/network/network_info.dart';
import '../../domain/entities/todo.dart';
import '../../domain/repositories/todo_repository.dart';
import '../datasources/todo_remote_datasource.dart';
class TodoRepositoryImpl implements TodoRepository {
final TodoRemoteDataSource remoteDataSource;
final NetworkInfo networkInfo;
TodoRepositoryImpl({
required this.remoteDataSource,
required this.networkInfo,
});
@override
Future<Either<Failure, List<Todo>>> getTodos() async {
if (!await networkInfo.isConnected) {
return const Left(NetworkFailure('No internet connection'));
}
try {
final todos = await remoteDataSource.getTodos();
return Right(todos.map((model) => model.toEntity()).toList());
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
@override
Future<Either<Failure, void>> refreshTodos() async {
if (!await networkInfo.isConnected) {
return const Left(NetworkFailure('No internet connection'));
}
try {
await remoteDataSource.getTodos();
return const Right(null);
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
}