add dropdown

This commit is contained in:
Phuoc Nguyen
2025-10-28 16:24:17 +07:00
parent 0010446298
commit 5cfc56f40d
20 changed files with 912 additions and 12 deletions

View File

@@ -0,0 +1,62 @@
import 'package:hive_ce/hive.dart';
import '../models/user_model.dart';
/// Abstract interface for users local data source
abstract class UsersLocalDataSource {
/// Get all users from local storage
Future<List<UserModel>> getUsers();
/// Save users to local storage
Future<void> saveUsers(List<UserModel> users);
/// Clear all users from local storage
Future<void> clearUsers();
}
/// Implementation of UsersLocalDataSource using Hive
class UsersLocalDataSourceImpl implements UsersLocalDataSource {
static const String _boxName = 'users';
Future<Box<UserModel>> get _box async {
if (!Hive.isBoxOpen(_boxName)) {
return await Hive.openBox<UserModel>(_boxName);
}
return Hive.box<UserModel>(_boxName);
}
@override
Future<List<UserModel>> getUsers() async {
try {
final box = await _box;
return box.values.toList();
} catch (e) {
throw Exception('Failed to get users from local storage: $e');
}
}
@override
Future<void> saveUsers(List<UserModel> users) async {
try {
final box = await _box;
await box.clear();
// Save users with their ID as key
for (final user in users) {
await box.put(user.id, user);
}
} catch (e) {
throw Exception('Failed to save users to local storage: $e');
}
}
@override
Future<void> clearUsers() async {
try {
final box = await _box;
await box.clear();
} catch (e) {
throw Exception('Failed to clear users from local storage: $e');
}
}
}

View File

@@ -0,0 +1,57 @@
import '../../../../core/constants/api_endpoints.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/network/api_response.dart';
import '../models/user_model.dart';
/// Abstract interface for users remote data source
abstract class UsersRemoteDataSource {
/// Fetch all users from the API
Future<List<UserModel>> getUsers();
}
/// Implementation of UsersRemoteDataSource using ApiClient
class UsersRemoteDataSourceImpl implements UsersRemoteDataSource {
final ApiClient apiClient;
UsersRemoteDataSourceImpl(this.apiClient);
@override
Future<List<UserModel>> getUsers() async {
try {
// Make API call to get all users
final response = await apiClient.get(ApiEndpoints.users);
// Parse the API response using ApiResponse wrapper
final apiResponse = ApiResponse.fromJson(
response.data as Map<String, dynamic>,
(json) => (json as List)
.map((e) => UserModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
// Check if the API call was successful
if (apiResponse.isSuccess && apiResponse.value != null) {
return apiResponse.value!;
} else {
// Throw exception with error message from API
throw ServerException(
apiResponse.errors.isNotEmpty
? apiResponse.errors.first
: 'Failed to get users',
);
}
} catch (e) {
// Re-throw ServerException as-is
if (e is ServerException) {
rethrow;
}
// Re-throw NetworkException as-is
if (e is NetworkException) {
rethrow;
}
// Wrap other exceptions in ServerException
throw ServerException('Failed to get users: ${e.toString()}');
}
}
}

View File

@@ -0,0 +1,163 @@
import 'package:hive_ce/hive.dart';
import '../../domain/entities/user_entity.dart';
part 'user_model.g.dart';
@HiveType(typeId: 1)
class UserModel extends UserEntity {
@HiveField(0)
@override
final int id;
@HiveField(1)
@override
final String firstName;
@HiveField(2)
@override
final String name;
@HiveField(3)
@override
final String? plateNumber;
@HiveField(4)
@override
final String email;
@HiveField(5)
@override
final String phone;
@HiveField(6)
@override
final bool isParent;
@HiveField(7)
@override
final String fullName;
@HiveField(8)
@override
final String fullNameEmail;
@HiveField(9)
@override
final String? referralCode;
@HiveField(10)
@override
final String? avatar;
@HiveField(11)
@override
final int departmentId;
@HiveField(12)
@override
final bool isWareHouseUser;
@HiveField(13)
@override
final int? wareHouseId;
@HiveField(14)
@override
final int roleId;
const UserModel({
required this.id,
required this.firstName,
required this.name,
this.plateNumber,
required this.email,
required this.phone,
this.isParent = false,
required this.fullName,
required this.fullNameEmail,
this.referralCode,
this.avatar,
required this.departmentId,
this.isWareHouseUser = false,
this.wareHouseId,
required this.roleId,
}) : super(
id: id,
firstName: firstName,
name: name,
plateNumber: plateNumber,
email: email,
phone: phone,
isParent: isParent,
fullName: fullName,
fullNameEmail: fullNameEmail,
referralCode: referralCode,
avatar: avatar,
departmentId: departmentId,
isWareHouseUser: isWareHouseUser,
wareHouseId: wareHouseId,
roleId: roleId,
);
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['Id'] ?? 0,
firstName: json['FirstName'] ?? '',
name: json['Name'] ?? '',
plateNumber: json['PlateNumber'],
email: json['Email'] ?? '',
phone: json['Phone'] ?? '',
isParent: json['IsParent'] ?? false,
fullName: json['FullName'] ?? '',
fullNameEmail: json['FullNameEmail'] ?? '',
referralCode: json['ReferralCode'],
avatar: json['Avatar'],
departmentId: json['DepartmentId'] ?? 0,
isWareHouseUser: json['IsWareHouseUser'] ?? false,
wareHouseId: json['WareHouseId'],
roleId: json['RoleId'] ?? 0,
);
}
Map<String, dynamic> toJson() {
return {
'Id': id,
'FirstName': firstName,
'Name': name,
'PlateNumber': plateNumber,
'Email': email,
'Phone': phone,
'IsParent': isParent,
'FullName': fullName,
'FullNameEmail': fullNameEmail,
'ReferralCode': referralCode,
'Avatar': avatar,
'DepartmentId': departmentId,
'IsWareHouseUser': isWareHouseUser,
'WareHouseId': wareHouseId,
'RoleId': roleId,
};
}
UserEntity toEntity() {
return UserEntity(
id: id,
firstName: firstName,
name: name,
plateNumber: plateNumber,
email: email,
phone: phone,
isParent: isParent,
fullName: fullName,
fullNameEmail: fullNameEmail,
referralCode: referralCode,
avatar: avatar,
departmentId: departmentId,
isWareHouseUser: isWareHouseUser,
wareHouseId: wareHouseId,
roleId: roleId,
);
}
}

View File

@@ -0,0 +1,83 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class UserModelAdapter extends TypeAdapter<UserModel> {
@override
final typeId = 1;
@override
UserModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return UserModel(
id: (fields[0] as num).toInt(),
firstName: fields[1] as String,
name: fields[2] as String,
plateNumber: fields[3] as String?,
email: fields[4] as String,
phone: fields[5] as String,
isParent: fields[6] == null ? false : fields[6] as bool,
fullName: fields[7] as String,
fullNameEmail: fields[8] as String,
referralCode: fields[9] as String?,
avatar: fields[10] as String?,
departmentId: (fields[11] as num).toInt(),
isWareHouseUser: fields[12] == null ? false : fields[12] as bool,
wareHouseId: (fields[13] as num?)?.toInt(),
roleId: (fields[14] as num).toInt(),
);
}
@override
void write(BinaryWriter writer, UserModel obj) {
writer
..writeByte(15)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.firstName)
..writeByte(2)
..write(obj.name)
..writeByte(3)
..write(obj.plateNumber)
..writeByte(4)
..write(obj.email)
..writeByte(5)
..write(obj.phone)
..writeByte(6)
..write(obj.isParent)
..writeByte(7)
..write(obj.fullName)
..writeByte(8)
..write(obj.fullNameEmail)
..writeByte(9)
..write(obj.referralCode)
..writeByte(10)
..write(obj.avatar)
..writeByte(11)
..write(obj.departmentId)
..writeByte(12)
..write(obj.isWareHouseUser)
..writeByte(13)
..write(obj.wareHouseId)
..writeByte(14)
..write(obj.roleId);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,67 @@
import 'package:dartz/dartz.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/errors/failures.dart';
import '../../domain/entities/user_entity.dart';
import '../../domain/repositories/users_repository.dart';
import '../datasources/users_local_datasource.dart';
import '../datasources/users_remote_datasource.dart';
/// Implementation of UsersRepository
class UsersRepositoryImpl implements UsersRepository {
final UsersRemoteDataSource remoteDataSource;
final UsersLocalDataSource localDataSource;
UsersRepositoryImpl({
required this.remoteDataSource,
required this.localDataSource,
});
@override
Future<Either<Failure, List<UserEntity>>> getUsers() async {
try {
// Try to get users from local storage first
final localUsers = await localDataSource.getUsers();
if (localUsers.isNotEmpty) {
// Return local users if available
return Right(localUsers.map((model) => model.toEntity()).toList());
}
// If no local users, fetch from API
return await syncUsers();
} catch (e) {
return Left(CacheFailure(e.toString()));
}
}
@override
Future<Either<Failure, List<UserEntity>>> syncUsers() async {
try {
// Fetch users from API
final users = await remoteDataSource.getUsers();
// Save to local storage
await localDataSource.saveUsers(users);
// Return as entities
return Right(users.map((model) => model.toEntity()).toList());
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} on NetworkException catch (e) {
return Left(NetworkFailure(e.message));
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
@override
Future<Either<Failure, void>> clearUsers() async {
try {
await localDataSource.clearUsers();
return const Right(null);
} catch (e) {
return Left(CacheFailure(e.toString()));
}
}
}