From f7e611bf76c83b8d4783ae8d8f5d36fdb9675ce2 Mon Sep 17 00:00:00 2001 From: Yellowtoast Date: Mon, 5 Feb 2024 17:27:57 +0900 Subject: [PATCH] =?UTF-8?q?WIP:=20=EB=8F=8C=EB=B4=84=EA=B8=89=EA=B5=AC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A7=84=ED=96=89=EC=A4=91....?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/app/di/app_binding.dart | 2 + lib/app/di/modules/sos_post_di.dart | 36 ++ lib/core/helper/date_time_extension.dart | 8 + lib/features/media/api/media_api.dart | 7 + .../data/remote/media_remote_data_source.dart | 2 + .../remote/media_remote_data_source_impl.dart | 5 + .../media/repositories/media_repository.dart | 2 + .../repositories/media_repository_impl.dart | 10 + lib/features/pet/data/dto/pet_data_dto.dart | 35 ++ lib/features/pet/data/dto/pet_data_dto.g.dart | 30 ++ .../domain/breeds_pagination_response.dart | 1 - .../pet/entities/pet_data_entity.dart | 20 ++ lib/features/sos/api/sos_post_api.dart | 17 + lib/features/sos/api/sos_post_api.g.dart | 80 +++++ .../sos/data/dto/sos_condition_dto.dart | 19 ++ .../sos/data/dto/sos_condition_dto.g.dart | 19 ++ .../data/dto/sos_pagination_request_dto.dart | 27 ++ .../data/dto/sos_pagination_response_dto.dart | 45 +++ lib/features/sos/data/dto/sos_post_dto.dart | 48 +++ lib/features/sos/data/dto/sos_post_dto.g.dart | 42 +++ .../remote/sos_post_remote_data_source.dart | 7 + .../sos_post_remote_data_source_impl.dart | 19 ++ .../sos/entities/sos_condition_entity.dart | 20 ++ .../sos_condition_entity.freezed.dart | 172 ++++++++++ .../sos/entities/sos_post_entity.dart | 45 +++ .../sos/entities/sos_post_entity.freezed.dart | 308 ++++++++++++++++++ .../sos/repositories/sos_post_repository.dart | 8 + .../sos_post_repository_impl.dart | 30 ++ lib/features/sos/sos.dart | 12 + .../sos/usecases/get_sos_post_use_case.dart | 27 ++ lib/presentation/pages/home/home_event.dart | 4 +- lib/presentation/pages/home/home_view.dart | 15 +- .../home/widgets/home_location_button.dart | 60 ++-- .../home/widgets/home_tab_view_body.dart | 8 +- .../pet_sos/layouts/pet_sos_list_view.dart | 47 --- .../pages/pet_sos/pet_sos_event.dart | 21 -- .../{pet_sos => sos}/layouts/pet_filter.dart | 8 +- .../{pet_sos => sos}/layouts/sort_filter.dart | 9 +- .../pages/sos/layouts/sos_post_list_view.dart | 46 +++ .../providers/sos_post_filter_provider.dart} | 8 +- .../sos_post_filter_provider.g.dart} | 21 +- .../sos_post_paging_controller_provider.dart | 48 +++ ...sos_post_paging_controller_provider.g.dart | 28 ++ .../pages/sos/sos_post_event.dart | 29 ++ .../sos_post_view.dart} | 33 +- .../widgets/list_tile/post_list_tile.dart | 137 ++++---- .../pagination/infinite_paged_list.dart | 11 +- 47 files changed, 1415 insertions(+), 221 deletions(-) create mode 100644 lib/app/di/modules/sos_post_di.dart create mode 100644 lib/core/helper/date_time_extension.dart create mode 100644 lib/features/pet/data/dto/pet_data_dto.dart create mode 100644 lib/features/pet/data/dto/pet_data_dto.g.dart create mode 100644 lib/features/pet/entities/pet_data_entity.dart create mode 100644 lib/features/sos/api/sos_post_api.dart create mode 100644 lib/features/sos/api/sos_post_api.g.dart create mode 100644 lib/features/sos/data/dto/sos_condition_dto.dart create mode 100644 lib/features/sos/data/dto/sos_condition_dto.g.dart create mode 100644 lib/features/sos/data/dto/sos_pagination_request_dto.dart create mode 100644 lib/features/sos/data/dto/sos_pagination_response_dto.dart create mode 100644 lib/features/sos/data/dto/sos_post_dto.dart create mode 100644 lib/features/sos/data/dto/sos_post_dto.g.dart create mode 100644 lib/features/sos/data/remote/sos_post_remote_data_source.dart create mode 100644 lib/features/sos/data/remote/sos_post_remote_data_source_impl.dart create mode 100644 lib/features/sos/entities/sos_condition_entity.dart create mode 100644 lib/features/sos/entities/sos_condition_entity.freezed.dart create mode 100644 lib/features/sos/entities/sos_post_entity.dart create mode 100644 lib/features/sos/entities/sos_post_entity.freezed.dart create mode 100644 lib/features/sos/repositories/sos_post_repository.dart create mode 100644 lib/features/sos/repositories/sos_post_repository_impl.dart create mode 100644 lib/features/sos/sos.dart create mode 100644 lib/features/sos/usecases/get_sos_post_use_case.dart delete mode 100644 lib/presentation/pages/pet_sos/layouts/pet_sos_list_view.dart delete mode 100644 lib/presentation/pages/pet_sos/pet_sos_event.dart rename lib/presentation/pages/{pet_sos => sos}/layouts/pet_filter.dart (75%) rename lib/presentation/pages/{pet_sos => sos}/layouts/sort_filter.dart (73%) create mode 100644 lib/presentation/pages/sos/layouts/sos_post_list_view.dart rename lib/presentation/pages/{pet_sos/providers/pet_sos_filter_provider.dart => sos/providers/sos_post_filter_provider.dart} (83%) rename lib/presentation/pages/{pet_sos/providers/pet_sos_filter_provider.g.dart => sos/providers/sos_post_filter_provider.g.dart} (54%) create mode 100644 lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.dart create mode 100644 lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.g.dart create mode 100644 lib/presentation/pages/sos/sos_post_event.dart rename lib/presentation/pages/{pet_sos/pet_sos_view.dart => sos/sos_post_view.dart} (57%) diff --git a/lib/app/di/app_binding.dart b/lib/app/di/app_binding.dart index 0f9a3a3..57097b3 100644 --- a/lib/app/di/app_binding.dart +++ b/lib/app/di/app_binding.dart @@ -1,6 +1,7 @@ import 'package:pets_next_door_flutter/app/di/modules/auth_di.dart'; import 'package:pets_next_door_flutter/app/di/modules/media_di.dart'; import 'package:pets_next_door_flutter/app/di/modules/pet_di.dart'; +import 'package:pets_next_door_flutter/app/di/modules/sos_post_di.dart'; import 'package:pets_next_door_flutter/app/di/modules/user_di.dart'; final class AppBinder { @@ -18,6 +19,7 @@ final class AppBinder { UserDependencyInjection(), MediaDependencyInjection(), PetDependencyInjection(), + SosPostDependencyInjection(), ]) { di.init(); } diff --git a/lib/app/di/modules/sos_post_di.dart b/lib/app/di/modules/sos_post_di.dart new file mode 100644 index 0000000..e72df2b --- /dev/null +++ b/lib/app/di/modules/sos_post_di.dart @@ -0,0 +1,36 @@ +import 'package:get_it/get_it.dart'; +import 'package:pets_next_door_flutter/app/di/feature_di_interface.dart'; +import 'package:pets_next_door_flutter/features/sos/data/remote/sos_post_remote_data_source.dart'; +import 'package:pets_next_door_flutter/features/sos/data/remote/sos_post_remote_data_source_impl.dart'; +import 'package:pets_next_door_flutter/features/sos/repositories/sos_post_repository.dart'; +import 'package:pets_next_door_flutter/features/sos/repositories/sos_post_repository_impl.dart'; +import 'package:pets_next_door_flutter/features/sos/sos.dart'; +import 'package:pets_next_door_flutter/features/sos/usecases/get_sos_post_use_case.dart'; + +final class SosPostDependencyInjection extends FeatureDependencyInjection { + @override + void dataSources() { + GetIt.I.registerLazySingleton( + SosPostRemoteDataSourceImpl.new, + ); + } + + @override + void repositories() { + GetIt.I.registerLazySingleton( + () => SosPostRepositoryImpl( + sosPostRemoteDataSource, + ), + ); + } + + @override + void useCases() { + GetIt.I + ..registerFactory( + () => GetSosPostsUseCase( + sosPostRepository: sosPostRepository, + ), + ); + } +} diff --git a/lib/core/helper/date_time_extension.dart b/lib/core/helper/date_time_extension.dart new file mode 100644 index 0000000..ae8eee8 --- /dev/null +++ b/lib/core/helper/date_time_extension.dart @@ -0,0 +1,8 @@ +import 'package:intl/intl.dart'; + +extension DateTimeExt on DateTime { + /// 년도.월.일 형식의 String으로 반환 + String get formatyyMMdd { + return DateFormat('yy.MM.dd').format(this); + } +} diff --git a/lib/features/media/api/media_api.dart b/lib/features/media/api/media_api.dart index e051ae6..efa13f4 100644 --- a/lib/features/media/api/media_api.dart +++ b/lib/features/media/api/media_api.dart @@ -9,6 +9,7 @@ abstract class MediaAPI { factory MediaAPI(Dio dio, {String baseUrl}) = _MediaAPI; Future uploadImage(File file, String imageFormat); + Future getImage(int imageId); } class _MediaAPI implements MediaAPI { @@ -21,6 +22,12 @@ class _MediaAPI implements MediaAPI { String? baseUrl; + @override + Future getImage(int imageId) { + // TODO: implement getImage + throw UnimplementedError(); + } + @override Future uploadImage(File file, String imageFormat) async { const _extra = {}; diff --git a/lib/features/media/data/remote/media_remote_data_source.dart b/lib/features/media/data/remote/media_remote_data_source.dart index 6ef57ab..0188785 100644 --- a/lib/features/media/data/remote/media_remote_data_source.dart +++ b/lib/features/media/data/remote/media_remote_data_source.dart @@ -5,4 +5,6 @@ import 'package:pets_next_door_flutter/features/media/data/dto/media_image_dto.d abstract interface class MediaRemoteDataSource { Future uploadImage(File imageFile, ImageFormat imageFormat); + + Future getImage(int imageId); } diff --git a/lib/features/media/data/remote/media_remote_data_source_impl.dart b/lib/features/media/data/remote/media_remote_data_source_impl.dart index c100519..4d8be31 100644 --- a/lib/features/media/data/remote/media_remote_data_source_impl.dart +++ b/lib/features/media/data/remote/media_remote_data_source_impl.dart @@ -14,4 +14,9 @@ final class MediaRemoteDataSourceImpl implements MediaRemoteDataSource { Future uploadImage(File imageFile, ImageFormat imageFormat) { return _api.uploadImage(imageFile, imageFormat.name); } + + @override + Future getImage(int imageId) { + return _api.getImage(imageId); + } } diff --git a/lib/features/media/repositories/media_repository.dart b/lib/features/media/repositories/media_repository.dart index 9245fba..898ff76 100644 --- a/lib/features/media/repositories/media_repository.dart +++ b/lib/features/media/repositories/media_repository.dart @@ -7,4 +7,6 @@ import 'package:pets_next_door_flutter/features/media/entities/media_image_entit abstract interface class MediaRepository { Future> uploadImage(File image, {required ImageFormat imageFormat}); + + Future> getImage({required int imageId}); } diff --git a/lib/features/media/repositories/media_repository_impl.dart b/lib/features/media/repositories/media_repository_impl.dart index 996f41f..df8d2ca 100644 --- a/lib/features/media/repositories/media_repository_impl.dart +++ b/lib/features/media/repositories/media_repository_impl.dart @@ -20,4 +20,14 @@ final class MediaRepositoryImpl implements MediaRepository { return Result.failure(e); } } + + @override + Future> getImage({required int imageId}) async { + try { + final uploadedImage = await _dataSource.getImage(imageId); + return Result.success(MediaImageEntity.fromDto(dto: uploadedImage)); + } on Exception catch (e) { + return Result.failure(e); + } + } } diff --git a/lib/features/pet/data/dto/pet_data_dto.dart b/lib/features/pet/data/dto/pet_data_dto.dart new file mode 100644 index 0000000..f97c412 --- /dev/null +++ b/lib/features/pet/data/dto/pet_data_dto.dart @@ -0,0 +1,35 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'pet_data_dto.g.dart'; + +/// api 통신을 통해 가져오는 유저 데이터 모델 +@JsonSerializable() +class PetDataDto { + final int id; + @JsonKey(name: 'birth_date') + final String birthDate; + final String breed; + final String name; + final bool neutered; + @JsonKey(name: 'pet_type') + final String petType; + final String sex; + @JsonKey(name: 'weight_in_kg') + final int weightKg; + + PetDataDto({ + required this.id, + required this.birthDate, + required this.breed, + required this.name, + required this.neutered, + required this.petType, + required this.sex, + required this.weightKg, + }); + + factory PetDataDto.fromJson(Map json) => + _$PetDataDtoFromJson(json); +} diff --git a/lib/features/pet/data/dto/pet_data_dto.g.dart b/lib/features/pet/data/dto/pet_data_dto.g.dart new file mode 100644 index 0000000..b2847db --- /dev/null +++ b/lib/features/pet/data/dto/pet_data_dto.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pet_data_dto.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PetDataDto _$PetDataDtoFromJson(Map json) => PetDataDto( + id: json['id'] as int, + birthDate: json['birth_date'] as String, + breed: json['breed'] as String, + name: json['name'] as String, + neutered: json['neutered'] as bool, + petType: json['pet_type'] as String, + sex: json['sex'] as String, + weightKg: json['weight_in_kg'] as int, + ); + +Map _$PetDataDtoToJson(PetDataDto instance) => + { + 'id': instance.id, + 'birth_date': instance.birthDate, + 'breed': instance.breed, + 'name': instance.name, + 'neutered': instance.neutered, + 'pet_type': instance.petType, + 'sex': instance.sex, + 'weight_in_kg': instance.weightKg, + }; diff --git a/lib/features/pet/domain/breeds_pagination_response.dart b/lib/features/pet/domain/breeds_pagination_response.dart index f51f41e..9acf587 100644 --- a/lib/features/pet/domain/breeds_pagination_response.dart +++ b/lib/features/pet/domain/breeds_pagination_response.dart @@ -1,4 +1,3 @@ -import 'package:pets_next_door_flutter/core/enums/pet_type.dart'; import 'package:pets_next_door_flutter/core/pagination/pagination_response.dart'; import 'package:pets_next_door_flutter/features/pet/domain/breed.dart'; diff --git a/lib/features/pet/entities/pet_data_entity.dart b/lib/features/pet/entities/pet_data_entity.dart new file mode 100644 index 0000000..215c481 --- /dev/null +++ b/lib/features/pet/entities/pet_data_entity.dart @@ -0,0 +1,20 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:pets_next_door_flutter/features/pet/data/dto/pet_data_dto.dart'; + +part 'pet_data_entity.freezed.dart'; + +@freezed +class PetDataEntity with _$PetDataEntity { + const PetDataEntity._(); + const factory PetDataEntity({ + required int code, + required String iconImgUrl, + required String text, + }) = _PetDataEntity; + + factory PetDataEntity.fromDto(PetDataDto dto) => PetDataEntity( + code: dto.id, + iconImgUrl: '', + text: dto.name, + ); +} diff --git a/lib/features/sos/api/sos_post_api.dart b/lib/features/sos/api/sos_post_api.dart new file mode 100644 index 0000000..b1f4f84 --- /dev/null +++ b/lib/features/sos/api/sos_post_api.dart @@ -0,0 +1,17 @@ +import 'package:dio/dio.dart' hide Headers; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_request_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_response_dto.dart'; +import 'package:retrofit/http.dart'; + +part 'sos_post_api.g.dart'; + +@RestApi() +abstract class SosPostAPI { + factory SosPostAPI(Dio dio, {String baseUrl}) = _SosPostAPI; + + @GET("/posts/sos") + @Headers({"requiresToken": false}) + Future getSosPosts( + @Queries() SosPostPaginationRequestDto sosPostsRequest, + ); +} diff --git a/lib/features/sos/api/sos_post_api.g.dart b/lib/features/sos/api/sos_post_api.g.dart new file mode 100644 index 0000000..44560aa --- /dev/null +++ b/lib/features/sos/api/sos_post_api.g.dart @@ -0,0 +1,80 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sos_post_api.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers + +class _SosPostAPI implements SosPostAPI { + _SosPostAPI( + this._dio, { + this.baseUrl, + }); + + final Dio _dio; + + String? baseUrl; + + @override + Future getSosPosts( + SosPostPaginationRequestDto sosPostsRequest) async { + const _extra = {}; + final queryParameters = {}; + queryParameters.addAll(sosPostsRequest.toJson()); + final _headers = {r'requiresToken': false}; + _headers.removeWhere((k, v) => v == null); + final Map? _data = null; + final _result = await _dio.fetch>( + _setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/posts/sos', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final value = SosPostPaginationResponseDto.fromJson(_result.data!); + return value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls( + String dioBaseUrl, + String? baseUrl, + ) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/features/sos/data/dto/sos_condition_dto.dart b/lib/features/sos/data/dto/sos_condition_dto.dart new file mode 100644 index 0000000..a501de3 --- /dev/null +++ b/lib/features/sos/data/dto/sos_condition_dto.dart @@ -0,0 +1,19 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'sos_condition_dto.g.dart'; + +/// 급구의 돌봄조건 dto +@JsonSerializable() +class SosConditionDto { + final int id; + final String name; + + SosConditionDto({required this.id, required this.name}); + + factory SosConditionDto.fromJson(Map json) => + _$SosConditionDtoFromJson(json); + + Map toJson() => _$SosConditionDtoToJson(this); +} diff --git a/lib/features/sos/data/dto/sos_condition_dto.g.dart b/lib/features/sos/data/dto/sos_condition_dto.g.dart new file mode 100644 index 0000000..f5b15b2 --- /dev/null +++ b/lib/features/sos/data/dto/sos_condition_dto.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sos_condition_dto.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SosConditionDto _$SosConditionDtoFromJson(Map json) => + SosConditionDto( + id: json['id'] as int, + name: json['name'] as String, + ); + +Map _$SosConditionDtoToJson(SosConditionDto instance) => + { + 'id': instance.id, + 'name': instance.name, + }; diff --git a/lib/features/sos/data/dto/sos_pagination_request_dto.dart b/lib/features/sos/data/dto/sos_pagination_request_dto.dart new file mode 100644 index 0000000..87ec403 --- /dev/null +++ b/lib/features/sos/data/dto/sos_pagination_request_dto.dart @@ -0,0 +1,27 @@ +import 'package:pets_next_door_flutter/core/enums/sort_type_filter.enum.dart'; +import 'package:pets_next_door_flutter/core/pagination/pagination_request.dart'; + +/// Metadata used when fetching movies with the paginated search API. +class SosPostPaginationRequestDto implements PaginationRequest { + SosPostPaginationRequestDto({ + required this.page, + required this.size, + required this.sortType, + }); + @override + final int page; + + @override + final int size; + + final SortTypeFilter sortType; + + @override + Map toJson() { + return { + 'page': page, + 'size': size, + 'sort_by': sortType.code, + }; + } +} diff --git a/lib/features/sos/data/dto/sos_pagination_response_dto.dart b/lib/features/sos/data/dto/sos_pagination_response_dto.dart new file mode 100644 index 0000000..fca05e7 --- /dev/null +++ b/lib/features/sos/data/dto/sos_pagination_response_dto.dart @@ -0,0 +1,45 @@ +import 'package:pets_next_door_flutter/core/pagination/pagination_response.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_post_dto.dart'; + +/// Metadata used when fetching movies with the paginated search API. +class SosPostPaginationResponseDto implements PaginationResponse { + SosPostPaginationResponseDto({ + required this.page, + required this.size, + required this.items, + }); + + SosPostPaginationResponseDto.fromJson( + Map json, + ) : page = json['page'] as int, + size = json['size'] as int, + items = (json['items'] as List) + .map((e) => SosPostDto.fromJson(e)) + .toList(); + + @override + final int page; + + @override + final int size; + + @override + final List items; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SosPostPaginationResponseDto && + other.page == page && + other.size == size; + } + + @override + int get hashCode => page.hashCode ^ size.hashCode; + + @override + Map toJson() { + throw UnimplementedError(); + } +} diff --git a/lib/features/sos/data/dto/sos_post_dto.dart b/lib/features/sos/data/dto/sos_post_dto.dart new file mode 100644 index 0000000..e776a81 --- /dev/null +++ b/lib/features/sos/data/dto/sos_post_dto.dart @@ -0,0 +1,48 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:pets_next_door_flutter/features/media/data/dto/media_image_dto.dart'; +import 'package:pets_next_door_flutter/features/pet/data/dto/pet_data_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_condition_dto.dart'; + +part 'sos_post_dto.g.dart'; + +/// 급구의 돌봄조건 dto +@JsonSerializable() +class SosPostDto { + @JsonKey(name: 'author_id') + final int authorId; + final String title; + final String content; + @JsonKey(name: 'date_start_at') + final String dateStartAt; + @JsonKey(name: 'date_end_at') + final String dateEndAt; + @JsonKey(name: 'thumbnail_id') + final int thumbnailId; + final List conditions; + final List media; + final String reward; + @JsonKey(name: 'reward_amount') + final String rewardAmount; + final List pets; + + SosPostDto({ + required this.conditions, + required this.content, + required this.media, + required this.title, + required this.authorId, + required this.dateStartAt, + required this.dateEndAt, + required this.thumbnailId, + required this.reward, + required this.rewardAmount, + required this.pets, + }); + + factory SosPostDto.fromJson(Map json) => + _$SosPostDtoFromJson(json); + + Map toJson() => _$SosPostDtoToJson(this); +} diff --git a/lib/features/sos/data/dto/sos_post_dto.g.dart b/lib/features/sos/data/dto/sos_post_dto.g.dart new file mode 100644 index 0000000..5017e76 --- /dev/null +++ b/lib/features/sos/data/dto/sos_post_dto.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sos_post_dto.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SosPostDto _$SosPostDtoFromJson(Map json) => SosPostDto( + conditions: (json['conditions'] as List) + .map((e) => SosConditionDto.fromJson(e as Map)) + .toList(), + content: json['content'] as String, + media: (json['media'] as List) + .map((e) => MediaImageDto.fromJson(e as Map)) + .toList(), + title: json['title'] as String, + authorId: json['author_id'] as int, + dateStartAt: json['date_start_at'] as String, + dateEndAt: json['date_end_at'] as String, + thumbnailId: json['thumbnail_id'] as int, + reward: json['reward'] as String, + rewardAmount: json['reward_amount'] as String, + pets: (json['pets'] as List) + .map((e) => PetDataDto.fromJson(e as Map)) + .toList(), + ); + +Map _$SosPostDtoToJson(SosPostDto instance) => + { + 'author_id': instance.authorId, + 'title': instance.title, + 'content': instance.content, + 'date_start_at': instance.dateStartAt, + 'date_end_at': instance.dateEndAt, + 'thumbnail_id': instance.thumbnailId, + 'conditions': instance.conditions, + 'media': instance.media, + 'reward': instance.reward, + 'reward_amount': instance.rewardAmount, + 'pets': instance.pets, + }; diff --git a/lib/features/sos/data/remote/sos_post_remote_data_source.dart b/lib/features/sos/data/remote/sos_post_remote_data_source.dart new file mode 100644 index 0000000..69670ac --- /dev/null +++ b/lib/features/sos/data/remote/sos_post_remote_data_source.dart @@ -0,0 +1,7 @@ +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_request_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_response_dto.dart'; + +abstract interface class SosPostRemoteDataSource { + Future getSosPosts( + SosPostPaginationRequestDto request); +} diff --git a/lib/features/sos/data/remote/sos_post_remote_data_source_impl.dart b/lib/features/sos/data/remote/sos_post_remote_data_source_impl.dart new file mode 100644 index 0000000..8b79990 --- /dev/null +++ b/lib/features/sos/data/remote/sos_post_remote_data_source_impl.dart @@ -0,0 +1,19 @@ +import 'package:pets_next_door_flutter/app/env/flavors.dart'; +import 'package:pets_next_door_flutter/core/network_handling/app_dio.dart'; +import 'package:pets_next_door_flutter/features/sos/api/sos_post_api.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_request_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_response_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/data/remote/sos_post_remote_data_source.dart'; + +final class SosPostRemoteDataSourceImpl implements SosPostRemoteDataSource { + final SosPostAPI _sosPostAPI = SosPostAPI( + AppDio.instance, + baseUrl: Flavor.apiUrl, + ); + + @override + Future getSosPosts( + SosPostPaginationRequestDto request) { + return _sosPostAPI.getSosPosts(request); + } +} diff --git a/lib/features/sos/entities/sos_condition_entity.dart b/lib/features/sos/entities/sos_condition_entity.dart new file mode 100644 index 0000000..6f7336e --- /dev/null +++ b/lib/features/sos/entities/sos_condition_entity.dart @@ -0,0 +1,20 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_condition_dto.dart'; + +part 'sos_condition_entity.freezed.dart'; + +@freezed +class SosConditionEntity with _$SosConditionEntity { + const SosConditionEntity._(); + const factory SosConditionEntity({ + required int code, + required String iconImgUrl, + required String text, + }) = _SosConditionEntity; + + factory SosConditionEntity.fromDto(SosConditionDto dto) => SosConditionEntity( + code: dto.id, + iconImgUrl: '', + text: dto.name, + ); +} diff --git a/lib/features/sos/entities/sos_condition_entity.freezed.dart b/lib/features/sos/entities/sos_condition_entity.freezed.dart new file mode 100644 index 0000000..6ad260f --- /dev/null +++ b/lib/features/sos/entities/sos_condition_entity.freezed.dart @@ -0,0 +1,172 @@ +// 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 'sos_condition_entity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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#custom-getters-and-methods'); + +/// @nodoc +mixin _$SosConditionEntity { + int get code => throw _privateConstructorUsedError; + String get iconImgUrl => throw _privateConstructorUsedError; + String get text => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $SosConditionEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SosConditionEntityCopyWith<$Res> { + factory $SosConditionEntityCopyWith( + SosConditionEntity value, $Res Function(SosConditionEntity) then) = + _$SosConditionEntityCopyWithImpl<$Res, SosConditionEntity>; + @useResult + $Res call({int code, String iconImgUrl, String text}); +} + +/// @nodoc +class _$SosConditionEntityCopyWithImpl<$Res, $Val extends SosConditionEntity> + implements $SosConditionEntityCopyWith<$Res> { + _$SosConditionEntityCopyWithImpl(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? code = null, + Object? iconImgUrl = null, + Object? text = null, + }) { + return _then(_value.copyWith( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + iconImgUrl: null == iconImgUrl + ? _value.iconImgUrl + : iconImgUrl // ignore: cast_nullable_to_non_nullable + as String, + text: null == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_SosConditionEntityCopyWith<$Res> + implements $SosConditionEntityCopyWith<$Res> { + factory _$$_SosConditionEntityCopyWith(_$_SosConditionEntity value, + $Res Function(_$_SosConditionEntity) then) = + __$$_SosConditionEntityCopyWithImpl<$Res>; + @override + @useResult + $Res call({int code, String iconImgUrl, String text}); +} + +/// @nodoc +class __$$_SosConditionEntityCopyWithImpl<$Res> + extends _$SosConditionEntityCopyWithImpl<$Res, _$_SosConditionEntity> + implements _$$_SosConditionEntityCopyWith<$Res> { + __$$_SosConditionEntityCopyWithImpl( + _$_SosConditionEntity _value, $Res Function(_$_SosConditionEntity) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + Object? iconImgUrl = null, + Object? text = null, + }) { + return _then(_$_SosConditionEntity( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + iconImgUrl: null == iconImgUrl + ? _value.iconImgUrl + : iconImgUrl // ignore: cast_nullable_to_non_nullable + as String, + text: null == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_SosConditionEntity extends _SosConditionEntity { + const _$_SosConditionEntity( + {required this.code, required this.iconImgUrl, required this.text}) + : super._(); + + @override + final int code; + @override + final String iconImgUrl; + @override + final String text; + + @override + String toString() { + return 'SosConditionEntity(code: $code, iconImgUrl: $iconImgUrl, text: $text)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SosConditionEntity && + (identical(other.code, code) || other.code == code) && + (identical(other.iconImgUrl, iconImgUrl) || + other.iconImgUrl == iconImgUrl) && + (identical(other.text, text) || other.text == text)); + } + + @override + int get hashCode => Object.hash(runtimeType, code, iconImgUrl, text); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_SosConditionEntityCopyWith<_$_SosConditionEntity> get copyWith => + __$$_SosConditionEntityCopyWithImpl<_$_SosConditionEntity>( + this, _$identity); +} + +abstract class _SosConditionEntity extends SosConditionEntity { + const factory _SosConditionEntity( + {required final int code, + required final String iconImgUrl, + required final String text}) = _$_SosConditionEntity; + const _SosConditionEntity._() : super._(); + + @override + int get code; + @override + String get iconImgUrl; + @override + String get text; + @override + @JsonKey(ignore: true) + _$$_SosConditionEntityCopyWith<_$_SosConditionEntity> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/sos/entities/sos_post_entity.dart b/lib/features/sos/entities/sos_post_entity.dart new file mode 100644 index 0000000..bcc30d8 --- /dev/null +++ b/lib/features/sos/entities/sos_post_entity.dart @@ -0,0 +1,45 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:pets_next_door_flutter/features/media/entities/media_image_entity.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_post_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/entities/sos_condition_entity.dart'; + +part 'sos_post_entity.freezed.dart'; + +@freezed +class SosPostEntity with _$SosPostEntity { + const SosPostEntity._(); + const factory SosPostEntity({ + required String title, + required String thumbnailUrl, + required List mediaList, + required List conditionList, + required DateTime careStartAt, + required DateTime careEndAt, + required String reward, + required String rewardPer, + }) = _SosPostEntity; + + factory SosPostEntity.fromDto(SosPostDto dto) { + String thumbnailUrl; + try { + thumbnailUrl = + dto.media.firstWhere((element) => element.id == dto.thumbnailId).url; + } catch (e) { + // 썸네일이 없음 + thumbnailUrl = dto.media.firstOrNull?.url ?? ''; + } + + return SosPostEntity( + title: dto.title, + thumbnailUrl: thumbnailUrl, + mediaList: + dto.media.map((e) => MediaImageEntity.fromDto(dto: e)).toList(), + conditionList: + dto.conditions.map((e) => SosConditionEntity.fromDto(e)).toList(), + careEndAt: DateTime.parse(dto.dateEndAt), + careStartAt: DateTime.parse(dto.dateStartAt), + reward: dto.reward, + rewardPer: dto.rewardAmount, + ); + } +} diff --git a/lib/features/sos/entities/sos_post_entity.freezed.dart b/lib/features/sos/entities/sos_post_entity.freezed.dart new file mode 100644 index 0000000..2eb3676 --- /dev/null +++ b/lib/features/sos/entities/sos_post_entity.freezed.dart @@ -0,0 +1,308 @@ +// 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 'sos_post_entity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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#custom-getters-and-methods'); + +/// @nodoc +mixin _$SosPostEntity { + String get title => throw _privateConstructorUsedError; + String get thumbnailUrl => throw _privateConstructorUsedError; + List get mediaList => throw _privateConstructorUsedError; + List get conditionList => + throw _privateConstructorUsedError; + DateTime get careStartAt => throw _privateConstructorUsedError; + DateTime get careEndAt => throw _privateConstructorUsedError; + String get reward => throw _privateConstructorUsedError; + String get rewardPer => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $SosPostEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SosPostEntityCopyWith<$Res> { + factory $SosPostEntityCopyWith( + SosPostEntity value, $Res Function(SosPostEntity) then) = + _$SosPostEntityCopyWithImpl<$Res, SosPostEntity>; + @useResult + $Res call( + {String title, + String thumbnailUrl, + List mediaList, + List conditionList, + DateTime careStartAt, + DateTime careEndAt, + String reward, + String rewardPer}); +} + +/// @nodoc +class _$SosPostEntityCopyWithImpl<$Res, $Val extends SosPostEntity> + implements $SosPostEntityCopyWith<$Res> { + _$SosPostEntityCopyWithImpl(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? title = null, + Object? thumbnailUrl = null, + Object? mediaList = null, + Object? conditionList = null, + Object? careStartAt = null, + Object? careEndAt = null, + Object? reward = null, + Object? rewardPer = null, + }) { + return _then(_value.copyWith( + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + thumbnailUrl: null == thumbnailUrl + ? _value.thumbnailUrl + : thumbnailUrl // ignore: cast_nullable_to_non_nullable + as String, + mediaList: null == mediaList + ? _value.mediaList + : mediaList // ignore: cast_nullable_to_non_nullable + as List, + conditionList: null == conditionList + ? _value.conditionList + : conditionList // ignore: cast_nullable_to_non_nullable + as List, + careStartAt: null == careStartAt + ? _value.careStartAt + : careStartAt // ignore: cast_nullable_to_non_nullable + as DateTime, + careEndAt: null == careEndAt + ? _value.careEndAt + : careEndAt // ignore: cast_nullable_to_non_nullable + as DateTime, + reward: null == reward + ? _value.reward + : reward // ignore: cast_nullable_to_non_nullable + as String, + rewardPer: null == rewardPer + ? _value.rewardPer + : rewardPer // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_SosPostEntityCopyWith<$Res> + implements $SosPostEntityCopyWith<$Res> { + factory _$$_SosPostEntityCopyWith( + _$_SosPostEntity value, $Res Function(_$_SosPostEntity) then) = + __$$_SosPostEntityCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String title, + String thumbnailUrl, + List mediaList, + List conditionList, + DateTime careStartAt, + DateTime careEndAt, + String reward, + String rewardPer}); +} + +/// @nodoc +class __$$_SosPostEntityCopyWithImpl<$Res> + extends _$SosPostEntityCopyWithImpl<$Res, _$_SosPostEntity> + implements _$$_SosPostEntityCopyWith<$Res> { + __$$_SosPostEntityCopyWithImpl( + _$_SosPostEntity _value, $Res Function(_$_SosPostEntity) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? title = null, + Object? thumbnailUrl = null, + Object? mediaList = null, + Object? conditionList = null, + Object? careStartAt = null, + Object? careEndAt = null, + Object? reward = null, + Object? rewardPer = null, + }) { + return _then(_$_SosPostEntity( + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + thumbnailUrl: null == thumbnailUrl + ? _value.thumbnailUrl + : thumbnailUrl // ignore: cast_nullable_to_non_nullable + as String, + mediaList: null == mediaList + ? _value._mediaList + : mediaList // ignore: cast_nullable_to_non_nullable + as List, + conditionList: null == conditionList + ? _value._conditionList + : conditionList // ignore: cast_nullable_to_non_nullable + as List, + careStartAt: null == careStartAt + ? _value.careStartAt + : careStartAt // ignore: cast_nullable_to_non_nullable + as DateTime, + careEndAt: null == careEndAt + ? _value.careEndAt + : careEndAt // ignore: cast_nullable_to_non_nullable + as DateTime, + reward: null == reward + ? _value.reward + : reward // ignore: cast_nullable_to_non_nullable + as String, + rewardPer: null == rewardPer + ? _value.rewardPer + : rewardPer // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_SosPostEntity extends _SosPostEntity { + const _$_SosPostEntity( + {required this.title, + required this.thumbnailUrl, + required final List mediaList, + required final List conditionList, + required this.careStartAt, + required this.careEndAt, + required this.reward, + required this.rewardPer}) + : _mediaList = mediaList, + _conditionList = conditionList, + super._(); + + @override + final String title; + @override + final String thumbnailUrl; + final List _mediaList; + @override + List get mediaList { + if (_mediaList is EqualUnmodifiableListView) return _mediaList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_mediaList); + } + + final List _conditionList; + @override + List get conditionList { + if (_conditionList is EqualUnmodifiableListView) return _conditionList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_conditionList); + } + + @override + final DateTime careStartAt; + @override + final DateTime careEndAt; + @override + final String reward; + @override + final String rewardPer; + + @override + String toString() { + return 'SosPostEntity(title: $title, thumbnailUrl: $thumbnailUrl, mediaList: $mediaList, conditionList: $conditionList, careStartAt: $careStartAt, careEndAt: $careEndAt, reward: $reward, rewardPer: $rewardPer)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SosPostEntity && + (identical(other.title, title) || other.title == title) && + (identical(other.thumbnailUrl, thumbnailUrl) || + other.thumbnailUrl == thumbnailUrl) && + const DeepCollectionEquality() + .equals(other._mediaList, _mediaList) && + const DeepCollectionEquality() + .equals(other._conditionList, _conditionList) && + (identical(other.careStartAt, careStartAt) || + other.careStartAt == careStartAt) && + (identical(other.careEndAt, careEndAt) || + other.careEndAt == careEndAt) && + (identical(other.reward, reward) || other.reward == reward) && + (identical(other.rewardPer, rewardPer) || + other.rewardPer == rewardPer)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + title, + thumbnailUrl, + const DeepCollectionEquality().hash(_mediaList), + const DeepCollectionEquality().hash(_conditionList), + careStartAt, + careEndAt, + reward, + rewardPer); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_SosPostEntityCopyWith<_$_SosPostEntity> get copyWith => + __$$_SosPostEntityCopyWithImpl<_$_SosPostEntity>(this, _$identity); +} + +abstract class _SosPostEntity extends SosPostEntity { + const factory _SosPostEntity( + {required final String title, + required final String thumbnailUrl, + required final List mediaList, + required final List conditionList, + required final DateTime careStartAt, + required final DateTime careEndAt, + required final String reward, + required final String rewardPer}) = _$_SosPostEntity; + const _SosPostEntity._() : super._(); + + @override + String get title; + @override + String get thumbnailUrl; + @override + List get mediaList; + @override + List get conditionList; + @override + DateTime get careStartAt; + @override + DateTime get careEndAt; + @override + String get reward; + @override + String get rewardPer; + @override + @JsonKey(ignore: true) + _$$_SosPostEntityCopyWith<_$_SosPostEntity> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/sos/repositories/sos_post_repository.dart b/lib/features/sos/repositories/sos_post_repository.dart new file mode 100644 index 0000000..d289dcd --- /dev/null +++ b/lib/features/sos/repositories/sos_post_repository.dart @@ -0,0 +1,8 @@ +import 'package:pets_next_door_flutter/core/utils/result.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_request_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/entities/sos_post_entity.dart'; + +abstract interface class SosPostRepository { + Future>> getSosPosts( + SosPostPaginationRequestDto request); +} diff --git a/lib/features/sos/repositories/sos_post_repository_impl.dart b/lib/features/sos/repositories/sos_post_repository_impl.dart new file mode 100644 index 0000000..728ed67 --- /dev/null +++ b/lib/features/sos/repositories/sos_post_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:pets_next_door_flutter/core/utils/result.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_request_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/data/remote/sos_post_remote_data_source.dart'; +import 'package:pets_next_door_flutter/features/sos/entities/sos_post_entity.dart'; +import 'package:pets_next_door_flutter/features/sos/repositories/sos_post_repository.dart'; + +final class SosPostRepositoryImpl implements SosPostRepository { + const SosPostRepositoryImpl( + this._sosPostRemoteDataSource, + ); + + final SosPostRemoteDataSource _sosPostRemoteDataSource; + + @override + Future>> getSosPosts( + SosPostPaginationRequestDto request) async { + try { + final sosPostResponse = + await _sosPostRemoteDataSource.getSosPosts(request); + + return Result.success( + sosPostResponse.items + .map((postDto) => SosPostEntity.fromDto(postDto)) + .toList(), + ); + } on Exception catch (e) { + return Result.failure(e); + } + } +} diff --git a/lib/features/sos/sos.dart b/lib/features/sos/sos.dart new file mode 100644 index 0000000..232c34b --- /dev/null +++ b/lib/features/sos/sos.dart @@ -0,0 +1,12 @@ +import 'package:pets_next_door_flutter/app/di/locator.dart'; +import 'package:pets_next_door_flutter/features/sos/data/remote/sos_post_remote_data_source.dart'; +import 'package:pets_next_door_flutter/features/sos/repositories/sos_post_repository.dart'; +import 'package:pets_next_door_flutter/features/sos/usecases/get_sos_post_use_case.dart'; + +// export 'data/remote/pet_remote_data_source.dart'; +// export 'repository/pet_repository.dart'; +// export 'usecases/get_breeds_use_case.dart'; + +final sosPostRemoteDataSource = locator(); +final sosPostRepository = locator(); +final getSosPostUseCase = locator(); diff --git a/lib/features/sos/usecases/get_sos_post_use_case.dart b/lib/features/sos/usecases/get_sos_post_use_case.dart new file mode 100644 index 0000000..cea78d8 --- /dev/null +++ b/lib/features/sos/usecases/get_sos_post_use_case.dart @@ -0,0 +1,27 @@ +import 'package:pets_next_door_flutter/core/enums/sort_type_filter.enum.dart'; +import 'package:pets_next_door_flutter/core/utils/result.dart'; +import 'package:pets_next_door_flutter/features/sos/data/dto/sos_pagination_request_dto.dart'; +import 'package:pets_next_door_flutter/features/sos/entities/sos_post_entity.dart'; +import 'package:pets_next_door_flutter/features/sos/repositories/sos_post_repository.dart'; + +final class GetSosPostsUseCase { + const GetSosPostsUseCase({ + required SosPostRepository sosPostRepository, + }) : _sosPostRepository = sosPostRepository; + + final SosPostRepository _sosPostRepository; + + Future>> call({ + required int size, + required int page, + required SortTypeFilter sortType, + }) async { + return _sosPostRepository.getSosPosts( + SosPostPaginationRequestDto( + page: page, + size: size, + sortType: sortType, + ), + ); + } +} diff --git a/lib/presentation/pages/home/home_event.dart b/lib/presentation/pages/home/home_event.dart index 57a76e1..8d68a4e 100644 --- a/lib/presentation/pages/home/home_event.dart +++ b/lib/presentation/pages/home/home_event.dart @@ -5,7 +5,7 @@ import 'package:pets_next_door_flutter/presentation/pages/home/home_view.dart'; import 'package:pets_next_door_flutter/presentation/pages/home/providers/current_tab_type_provider.dart'; import 'package:pets_next_door_flutter/presentation/pages/home/providers/show_search_bar_provider.dart'; import 'package:pets_next_door_flutter/presentation/pages/pet_mate/providers/pet_mate_filter_provider.dart'; -import 'package:pets_next_door_flutter/presentation/pages/pet_sos/providers/pet_sos_filter_provider.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/providers/sos_post_filter_provider.dart'; import 'package:pets_next_door_flutter/presentation/widgets/toast/app_toast.dart'; abstract interface class _HomeEvent { @@ -30,7 +30,7 @@ mixin class HomeEvent implements _HomeEvent { final tabType = ref.read(currentTabTypeProvider); final filter = switch (tabType) { HomeTabType.petMate => ref.read(petMateFilterProvider), - HomeTabType.petSos => ref.read(petSosFilterProvider), + HomeTabType.petSos => ref.read(sosPostFilterProvider), }; // TODO: 필터 정보 가지고 게시물 불러오기 기능 구현 필요 diff --git a/lib/presentation/pages/home/home_view.dart b/lib/presentation/pages/home/home_view.dart index 5170f3d..b4af9cf 100644 --- a/lib/presentation/pages/home/home_view.dart +++ b/lib/presentation/pages/home/home_view.dart @@ -9,7 +9,7 @@ import 'package:pets_next_door_flutter/presentation/pages/home/home_event.dart'; import 'package:pets_next_door_flutter/presentation/pages/home/providers/current_tab_type_provider.dart'; import 'package:pets_next_door_flutter/presentation/pages/home/providers/show_search_bar_provider.dart'; import 'package:pets_next_door_flutter/presentation/pages/pet_mate/pet_mate_view.dart'; -import 'package:pets_next_door_flutter/presentation/pages/pet_sos/pet_sos_view.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/sos_post_view.dart'; import 'package:pets_next_door_flutter/presentation/widgets/search_bar/animated_search_bar.dart'; part 'widgets/home_location_button.dart'; @@ -31,11 +31,14 @@ class HomeView extends StatelessWidget { @override Widget build(BuildContext context) { return SafeArea( - child: Column( - children: [ - const _HomeLocationButton(), - const _HomeTabViewBody(), - ], + child: Scaffold( + body: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const _HomeLocationButton(), + const _HomeTabViewBody(), + ], + ), ), ); } diff --git a/lib/presentation/pages/home/widgets/home_location_button.dart b/lib/presentation/pages/home/widgets/home_location_button.dart index 0c93f33..d3a5548 100644 --- a/lib/presentation/pages/home/widgets/home_location_button.dart +++ b/lib/presentation/pages/home/widgets/home_location_button.dart @@ -5,39 +5,37 @@ class _HomeLocationButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( + return Container( + height: 45, padding: const EdgeInsets.only(left: 24), - child: SizedBox( - height: 45, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.asset( - PNDImages.location, - width: 24, - height: 24, - ), - gapW4, - Row( - children: [ - IntrinsicWidth( - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Positioned( - child: Container( - height: 10, - color: Colors.amber.withOpacity(0.2), - )), - Text('용답동', style: AppTextStyle.headlineBold1), - ], - ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + PNDImages.location, + width: 24, + height: 24, + ), + gapW4, + Row( + children: [ + IntrinsicWidth( + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Positioned( + child: Container( + height: 10, + color: Colors.amber.withOpacity(0.2), + )), + Text('용답동', style: AppTextStyle.headlineBold1), + ], ), - Text(' 의 멍냥모임', style: AppTextStyle.headlineBold1), - ], - ), - ], - ), + ), + Text(' 의 멍냥모임', style: AppTextStyle.headlineBold1), + ], + ), + ], ), ); } diff --git a/lib/presentation/pages/home/widgets/home_tab_view_body.dart b/lib/presentation/pages/home/widgets/home_tab_view_body.dart index 5f013ca..f0f79de 100644 --- a/lib/presentation/pages/home/widgets/home_tab_view_body.dart +++ b/lib/presentation/pages/home/widgets/home_tab_view_body.dart @@ -21,11 +21,10 @@ class _HomeTabViewBody extends HookConsumerWidget with HomeEvent { return Expanded( child: Column( - mainAxisSize: MainAxisSize.min, children: [ _buildTabBarIndicator(tabController), const _SearchBar(), - _buildTabViewList(tabController), + _buildTabView(tabController), ], ), ); @@ -33,6 +32,7 @@ class _HomeTabViewBody extends HookConsumerWidget with HomeEvent { Container _buildTabBarIndicator(TabController tabController) { return Container( + height: 50, padding: const EdgeInsets.only(left: 24, top: 8), alignment: Alignment.centerLeft, child: TabBar( @@ -57,13 +57,13 @@ class _HomeTabViewBody extends HookConsumerWidget with HomeEvent { ); } - Expanded _buildTabViewList(TabController tabController) { + Expanded _buildTabView(TabController tabController) { return Expanded( child: Consumer( builder: (context, ref, child) => TabBarView( controller: tabController, children: [ - PetSosView( + SosPostView( onScrollDirectionChanged: (direction) => onScrollDirectionChanged(ref, direction), ), diff --git a/lib/presentation/pages/pet_sos/layouts/pet_sos_list_view.dart b/lib/presentation/pages/pet_sos/layouts/pet_sos_list_view.dart deleted file mode 100644 index 6c89a4b..0000000 --- a/lib/presentation/pages/pet_sos/layouts/pet_sos_list_view.dart +++ /dev/null @@ -1,47 +0,0 @@ -part of '../pet_sos_view.dart'; - -class _PetSosListView extends HookConsumerWidget { - const _PetSosListView({ - this.onScrollDirectionChanged, - }); - - final void Function(ScrollDirection)? onScrollDirectionChanged; - - @override - Widget build(BuildContext context, WidgetRef ref) { - useAutomaticKeepAlive(); - - final _scrollController = useScrollController(); - - useEffect(() { - void _callBack() { - onScrollDirectionChanged - ?.call(_scrollController.position.userScrollDirection); - } - - _scrollController.addListener(_callBack); - return () => _scrollController.removeListener(_callBack); - }, [_scrollController]); - - return Expanded( - child: ListView.separated( - itemCount: 10, - controller: _scrollController, - shrinkWrap: true, - itemBuilder: (context, index) => PndPostListTile.sosPage( - imageUrl: - 'https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP6btQ%2Fbtq2dCpzo9w%2F6KwuwdcKiC01N800kBegAk%2Fimg.jpg', - title: '안녕하세요 푸들 한마리 돌봄 급하게 구합니다.', - dateInfo: '23.03.21 ~ 23.03.28', - location: '용답동', - pay: '시급 9000원', - ), - separatorBuilder: (context, index) => Divider( - height: 1, - thickness: 1, - color: AppColor.of.gray20, - ), - ), - ); - } -} diff --git a/lib/presentation/pages/pet_sos/pet_sos_event.dart b/lib/presentation/pages/pet_sos/pet_sos_event.dart deleted file mode 100644 index a75c7b5..0000000 --- a/lib/presentation/pages/pet_sos/pet_sos_event.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:pets_next_door_flutter/core/enums/pet_type_filter.enum.dart'; -import 'package:pets_next_door_flutter/core/enums/sort_type_filter.enum.dart'; -import 'package:pets_next_door_flutter/presentation/pages/pet_sos/providers/pet_sos_filter_provider.dart'; - -abstract interface class _PetSosEvent { - void onSortChanged(WidgetRef ref, SortTypeFilter sortType); - void onPetTypeChanged(WidgetRef ref, PetTypeFilter petType); -} - -mixin class PetSosEvent implements _PetSosEvent { - @override - void onSortChanged(WidgetRef ref, SortTypeFilter sortType) { - ref.read(petSosFilterProvider.notifier).setSortingFilter(sortType); - } - - @override - void onPetTypeChanged(WidgetRef ref, PetTypeFilter petType) { - ref.read(petSosFilterProvider.notifier).setPetFilter(petType); - } -} diff --git a/lib/presentation/pages/pet_sos/layouts/pet_filter.dart b/lib/presentation/pages/sos/layouts/pet_filter.dart similarity index 75% rename from lib/presentation/pages/pet_sos/layouts/pet_filter.dart rename to lib/presentation/pages/sos/layouts/pet_filter.dart index e9edebc..5077ee8 100644 --- a/lib/presentation/pages/pet_sos/layouts/pet_filter.dart +++ b/lib/presentation/pages/sos/layouts/pet_filter.dart @@ -1,13 +1,13 @@ -part of '../pet_sos_view.dart'; +part of '../sos_post_view.dart'; /// 돌봄급구 펫타입 필터 영역 /// Radio 버튼 형식으로 구현되어 있음 -class _PetSosPetFilter extends ConsumerWidget with PetSosEvent { - const _PetSosPetFilter(); +class _SosPetFilter extends ConsumerWidget with SosPostViewEvent { + const _SosPetFilter(); @override Widget build(BuildContext context, WidgetRef ref) { - final selectedPetFilter = ref.watch(petSosFilterProvider).petTypeFilter; + final selectedPetFilter = ref.watch(sosPostFilterProvider).petTypeFilter; return Wrap( spacing: 8, diff --git a/lib/presentation/pages/pet_sos/layouts/sort_filter.dart b/lib/presentation/pages/sos/layouts/sort_filter.dart similarity index 73% rename from lib/presentation/pages/pet_sos/layouts/sort_filter.dart rename to lib/presentation/pages/sos/layouts/sort_filter.dart index 71afe88..de9a9d6 100644 --- a/lib/presentation/pages/pet_sos/layouts/sort_filter.dart +++ b/lib/presentation/pages/sos/layouts/sort_filter.dart @@ -1,13 +1,14 @@ -part of '../pet_sos_view.dart'; +part of '../sos_post_view.dart'; /// 돌봄급구 게시물 정렬 필터 영역 /// 드롭다운 메뉴 형식으로 구현되어 있음 -class _PetSosSortFilter extends ConsumerWidget with PetSosEvent { - const _PetSosSortFilter(); +class _SosSortFilter extends ConsumerWidget with SosPostViewEvent { + const _SosSortFilter(); @override Widget build(BuildContext context, WidgetRef ref) { - final selectedSortFilter = ref.watch(petSosFilterProvider).sortFilter; + final selectedSortFilter = ref.watch(sosPostFilterProvider).sortFilter; + return PndDropdownButton( onSelected: (sort) => onSortChanged(ref, sort), selectedValueStr: selectedSortFilter.displayName, diff --git a/lib/presentation/pages/sos/layouts/sos_post_list_view.dart b/lib/presentation/pages/sos/layouts/sos_post_list_view.dart new file mode 100644 index 0000000..cfd52f7 --- /dev/null +++ b/lib/presentation/pages/sos/layouts/sos_post_list_view.dart @@ -0,0 +1,46 @@ +part of '../sos_post_view.dart'; + +class _SosPostListView extends HookConsumerWidget with SosPostViewEvent { + const _SosPostListView({ + this.onScrollDirectionChanged, + }); + + final void Function(ScrollDirection)? onScrollDirectionChanged; + + @override + Widget build(BuildContext context, WidgetRef ref) { + useAutomaticKeepAlive(); + + final _scrollController = useScrollController(); + + useEffect(() { + void _callBack() { + onScrollDirectionChanged + ?.call(_scrollController.position.userScrollDirection); + } + + _scrollController.addListener(_callBack); + return () => _scrollController.removeListener(_callBack); + }, [_scrollController]); + + return PagedListView.separated( + scrollController: _scrollController, + physics: AlwaysScrollableScrollPhysics(), + pagingController: ref.watch(sosPagingControllerProvider), + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, sosPost, index) => PndPostListTile.sosPage( + imageUrl: sosPost.thumbnailUrl, + title: sosPost.title, + dateInfo: + '${sosPost.careStartAt.formatyyMMdd} ~ ${sosPost.careEndAt.formatyyMMdd}', + location: '용답동', + pay: '${sosPost.rewardPer} ${sosPost.reward}'), + ), + separatorBuilder: (context, index) => Divider( + height: 1, + thickness: 1, + color: AppColor.of.gray20, + ), + ); + } +} diff --git a/lib/presentation/pages/pet_sos/providers/pet_sos_filter_provider.dart b/lib/presentation/pages/sos/providers/sos_post_filter_provider.dart similarity index 83% rename from lib/presentation/pages/pet_sos/providers/pet_sos_filter_provider.dart rename to lib/presentation/pages/sos/providers/sos_post_filter_provider.dart index e2e5803..6baf92b 100644 --- a/lib/presentation/pages/pet_sos/providers/pet_sos_filter_provider.dart +++ b/lib/presentation/pages/sos/providers/sos_post_filter_provider.dart @@ -2,18 +2,18 @@ import 'package:pets_next_door_flutter/core/enums/pet_type_filter.enum.dart'; import 'package:pets_next_door_flutter/core/enums/sort_type_filter.enum.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'pet_sos_filter_provider.g.dart'; +part 'sos_post_filter_provider.g.dart'; -typedef PetSosFilters = ({ +typedef SosPostFilters = ({ SortTypeFilter sortFilter, PetTypeFilter petTypeFilter }); /// 돌봄급구 필터 프로바이더 @Riverpod() -class PetSosFilter extends _$PetSosFilter { +class SosPostFilter extends _$SosPostFilter { @override - PetSosFilters build() { + SosPostFilters build() { return ( sortFilter: SortTypeFilter.newest, petTypeFilter: PetTypeFilter.cat diff --git a/lib/presentation/pages/pet_sos/providers/pet_sos_filter_provider.g.dart b/lib/presentation/pages/sos/providers/sos_post_filter_provider.g.dart similarity index 54% rename from lib/presentation/pages/pet_sos/providers/pet_sos_filter_provider.g.dart rename to lib/presentation/pages/sos/providers/sos_post_filter_provider.g.dart index 2122b86..1b132ed 100644 --- a/lib/presentation/pages/pet_sos/providers/pet_sos_filter_provider.g.dart +++ b/lib/presentation/pages/sos/providers/sos_post_filter_provider.g.dart @@ -1,28 +1,29 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'pet_sos_filter_provider.dart'; +part of 'sos_post_filter_provider.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$petSosFilterHash() => r'a4470aedf29a6adf6204b7b1edc30029d467f2f1'; +String _$sosPostFilterHash() => r'dfb7d515a3edeebb55d0b9a20b3d0326e6b28efa'; /// 돌봄급구 필터 프로바이더 /// -/// Copied from [PetSosFilter]. -@ProviderFor(PetSosFilter) -final petSosFilterProvider = AutoDisposeNotifierProvider.internal( - PetSosFilter.new, - name: r'petSosFilterProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$petSosFilterHash, + SosPostFilter.new, + name: r'sosPostFilterProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$sosPostFilterHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$PetSosFilter = AutoDisposeNotifier< +typedef _$SosPostFilter = AutoDisposeNotifier< ({PetTypeFilter petTypeFilter, SortTypeFilter sortFilter})>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.dart b/lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.dart new file mode 100644 index 0000000..e32f556 --- /dev/null +++ b/lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.dart @@ -0,0 +1,48 @@ +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:pets_next_door_flutter/features/sos/entities/sos_post_entity.dart'; +import 'package:pets_next_door_flutter/features/sos/sos.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/providers/sos_post_filter_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'sos_post_paging_controller_provider.g.dart'; + +const _pagingSize = 20; + +@riverpod +class SosPagingController extends _$SosPagingController { + @override + Raw> build() { + final controller = PagingController(firstPageKey: 1); + + controller.addPageRequestListener((pageKey) => fetchPage(pageKey)); + + ref.onDispose(() => controller.dispose()); + + return controller; + } + + Future fetchPage(int pageKey) async { + try { + final newPosts = await getSosPostUseCase.call( + size: _pagingSize, + page: pageKey, + sortType: ref.read(sosPostFilterProvider).sortFilter, + ); + + newPosts.fold( + onSuccess: (breeds) { + final isLastPage = breeds.length < _pagingSize; + + if (isLastPage) { + state.appendLastPage(breeds); + } else { + state.appendPage(breeds, pageKey + 1); + } + }, + onFailure: (e) => print('::: Fold Error ::: $e'), + ); + } catch (e) { + print('::: Catch Error ::: $e'); + } + } +} diff --git a/lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.g.dart b/lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.g.dart new file mode 100644 index 0000000..b8ad282 --- /dev/null +++ b/lib/presentation/pages/sos/providers/sos_post_paging_controller_provider.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sos_post_paging_controller_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$sosPagingControllerHash() => + r'6548bf58e58b7f337411a2f5ef7cc8325c6c0738'; + +/// See also [SosPagingController]. +@ProviderFor(SosPagingController) +final sosPagingControllerProvider = AutoDisposeNotifierProvider< + SosPagingController, PagingController>.internal( + SosPagingController.new, + name: r'sosPagingControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$sosPagingControllerHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$SosPagingController + = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member diff --git a/lib/presentation/pages/sos/sos_post_event.dart b/lib/presentation/pages/sos/sos_post_event.dart new file mode 100644 index 0000000..5dd9a3f --- /dev/null +++ b/lib/presentation/pages/sos/sos_post_event.dart @@ -0,0 +1,29 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pets_next_door_flutter/core/enums/pet_type_filter.enum.dart'; +import 'package:pets_next_door_flutter/core/enums/sort_type_filter.enum.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/providers/sos_post_filter_provider.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/providers/sos_post_paging_controller_provider.dart'; + +abstract interface class _SosPostViewEvent { + void onSortChanged(WidgetRef ref, SortTypeFilter sortType); + void onPetTypeChanged(WidgetRef ref, PetTypeFilter petType); + void onListRefresh(WidgetRef ref); +} + +mixin class SosPostViewEvent implements _SosPostViewEvent { + @override + void onSortChanged(WidgetRef ref, SortTypeFilter sortType) { + ref.read(sosPostFilterProvider.notifier).setSortingFilter(sortType); + ref.read(sosPagingControllerProvider).refresh(); + } + + @override + void onPetTypeChanged(WidgetRef ref, PetTypeFilter petType) { + ref.read(sosPostFilterProvider.notifier).setPetFilter(petType); + } + + @override + Future onListRefresh(WidgetRef ref) async { + ref.watch(sosPagingControllerProvider).refresh(); + } +} diff --git a/lib/presentation/pages/pet_sos/pet_sos_view.dart b/lib/presentation/pages/sos/sos_post_view.dart similarity index 57% rename from lib/presentation/pages/pet_sos/pet_sos_view.dart rename to lib/presentation/pages/sos/sos_post_view.dart index 6e942be..5cc2ac0 100644 --- a/lib/presentation/pages/pet_sos/pet_sos_view.dart +++ b/lib/presentation/pages/sos/sos_post_view.dart @@ -2,22 +2,26 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:pets_next_door_flutter/core/constants/colors.dart'; import 'package:pets_next_door_flutter/core/enums/pet_type_filter.enum.dart'; import 'package:pets_next_door_flutter/core/enums/sort_type_filter.enum.dart'; -import 'package:pets_next_door_flutter/presentation/pages/pet_sos/pet_sos_event.dart'; -import 'package:pets_next_door_flutter/presentation/pages/pet_sos/providers/pet_sos_filter_provider.dart'; +import 'package:pets_next_door_flutter/core/helper/date_time_extension.dart'; +import 'package:pets_next_door_flutter/features/sos/entities/sos_post_entity.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/providers/sos_post_filter_provider.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/providers/sos_post_paging_controller_provider.dart'; +import 'package:pets_next_door_flutter/presentation/pages/sos/sos_post_event.dart'; import 'package:pets_next_door_flutter/presentation/widgets/button/radio_button.dart'; import 'package:pets_next_door_flutter/presentation/widgets/dropdown/dropdown_button.dart'; import 'package:pets_next_door_flutter/presentation/widgets/dropdown/dropdown_item.dart'; import 'package:pets_next_door_flutter/presentation/widgets/list_tile/post_list_tile.dart'; part 'layouts/pet_filter.dart'; -part 'layouts/pet_sos_list_view.dart'; part 'layouts/sort_filter.dart'; +part 'layouts/sos_post_list_view.dart'; -class PetSosView extends StatelessWidget { - const PetSosView({ +class SosPostView extends StatelessWidget { + const SosPostView({ super.key, this.onScrollDirectionChanged, }); @@ -27,25 +31,26 @@ class PetSosView extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildPetSosFilterGroup(), - _PetSosListView( - onScrollDirectionChanged: onScrollDirectionChanged, + _buildSosPostFilters(), + Expanded( + child: _SosPostListView( + onScrollDirectionChanged: onScrollDirectionChanged, + ), ) ], ); } - Padding _buildPetSosFilterGroup() { - return Padding( + Container _buildSosPostFilters() { + return Container( + height: 50, padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: const [ - _PetSosSortFilter(), - _PetSosPetFilter(), + _SosSortFilter(), + _SosPetFilter(), ], ), ); diff --git a/lib/presentation/widgets/list_tile/post_list_tile.dart b/lib/presentation/widgets/list_tile/post_list_tile.dart index 63135a3..c4dd55f 100644 --- a/lib/presentation/widgets/list_tile/post_list_tile.dart +++ b/lib/presentation/widgets/list_tile/post_list_tile.dart @@ -76,76 +76,79 @@ class PndPostListTile extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Container( - height: imageHeight, - width: imageWidth, - decoration: BoxDecoration( - color: AppColor.of.gray20, - ), - child: CachedNetworkImage( - imageUrl: imageUrl, - fit: BoxFit.cover, - errorWidget: (context, url, error) => Center( - child: Icon( - Icons.image, - size: 25, - color: AppColor.of.gray30, + return SizedBox( + height: 112, + child: GestureDetector( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Container( + height: imageHeight, + width: imageWidth, + decoration: BoxDecoration( + color: AppColor.of.gray20, + ), + child: CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + errorWidget: (context, url, error) => Center( + child: Icon( + Icons.image, + size: 25, + color: AppColor.of.gray30, + ), ), + )), + ), + gapW8, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + softWrap: false, + overflow: TextOverflow.ellipsis, + style: AppTextStyle.bodyBold1, ), - )), - ), - gapW8, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - softWrap: false, - overflow: TextOverflow.ellipsis, - style: AppTextStyle.bodyBold1, - ), - gapH8, - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: contentsList.indexed - .map( - (contents) => Container( - margin: contents.$1 != contentsList.length - 1 - ? EdgeInsets.only(bottom: contentsSpacing) - : null, - child: Row( - children: [ - Image.asset( - contents.$2.imgIconUrl, - width: _kContentIconSize, - height: _kContentIconSize, - color: AppColor.of.gray90, - ), - gapW4, - Text( - contents.$2.text, - style: AppTextStyle.bodyRegular3, - softWrap: false, - ), - ], + gapH8, + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: contentsList.indexed + .map( + (contents) => Container( + margin: contents.$1 != contentsList.length - 1 + ? EdgeInsets.only(bottom: contentsSpacing) + : null, + child: Row( + children: [ + Image.asset( + contents.$2.imgIconUrl, + width: _kContentIconSize, + height: _kContentIconSize, + color: AppColor.of.gray90, + ), + gapW4, + Text( + contents.$2.text, + style: AppTextStyle.bodyRegular3, + softWrap: false, + ), + ], + ), ), - ), - ) - .toList(), - ) - ], - ), - ) - ], + ) + .toList(), + ) + ], + ), + ) + ], + ), ), ), ); diff --git a/lib/presentation/widgets/pagination/infinite_paged_list.dart b/lib/presentation/widgets/pagination/infinite_paged_list.dart index 04c47c3..5589fcc 100644 --- a/lib/presentation/widgets/pagination/infinite_paged_list.dart +++ b/lib/presentation/widgets/pagination/infinite_paged_list.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:pets_next_door_flutter/core/constants/sizes.dart'; -class PNDInfinitePagedList extends StatelessWidget { +class PNDInfinitePagedList extends StatelessWidget { PNDInfinitePagedList({ super.key, required this.pagingController, @@ -10,18 +10,17 @@ class PNDInfinitePagedList extends StatelessWidget { this.separatorBuilder, }); - final PagingController pagingController; - final PagedChildBuilderDelegate builderDelegate; + final PagingController pagingController; + final PagedChildBuilderDelegate builderDelegate; final IndexedWidgetBuilder? separatorBuilder; @override Widget build(BuildContext context) { return RefreshIndicator( - child: PagedListView.separated( + child: PagedListView.separated( pagingController: pagingController, builderDelegate: builderDelegate, - separatorBuilder: - separatorBuilder ?? (context, index) => gapH16, + separatorBuilder: separatorBuilder ?? (context, index) => gapH16, ), onRefresh: () => Future.sync( () => pagingController.refresh(),