From 21a81ec18e87bd8bc0f7bbeede92acca319fb140 Mon Sep 17 00:00:00 2001 From: Yellowtoast Date: Mon, 5 Feb 2024 12:03:10 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20[=EB=8F=8C=EB=B4=84=EA=B8=89=EA=B5=AC]?= =?UTF-8?q?=20=EB=8F=8C=EB=B4=84=EA=B8=89=EA=B5=AC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20ui=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/imgs/common/icon_cal.png | Bin 0 -> 237 bytes assets/imgs/common/icon_pay.png | Bin 0 -> 352 bytes assets/imgs/common/icon_pin.png | Bin 0 -> 856 bytes assets/imgs/home/icon_chat.png | Bin 0 -> 994 bytes assets/imgs/home/icon_community.png | Bin 0 -> 1258 bytes assets/imgs/home/icon_home.png | Bin 0 -> 1152 bytes assets/imgs/home/icon_user.png | Bin 0 -> 953 bytes lib/core/constants/images.dart | 5 +- lib/core/constants/sizes.dart | 2 + lib/core/constants/text_style.dart | 18 +- lib/presentation/pages/home/home_view.dart | 1 + .../home/widgets/home_location_button.dart | 14 +- .../pages/pet_sos/layouts/pet_filter.dart | 28 +++ .../pet_sos/layouts/pet_sos_list_view.dart | 47 +++++ .../pages/pet_sos/layouts/sort_filter.dart | 24 +++ .../pages/pet_sos/pet_sos_view.dart | 183 +++--------------- .../widgets/button/radio_button.dart | 38 ++++ .../widgets/dropdown/dropdown_button.dart | 37 ++++ .../widgets/dropdown/dropdown_item.dart | 19 ++ .../widgets/list_tile/post_list_tile.dart | 153 +++++++++++++++ pubspec.yaml | 2 + 21 files changed, 398 insertions(+), 173 deletions(-) create mode 100644 assets/imgs/common/icon_cal.png create mode 100644 assets/imgs/common/icon_pay.png create mode 100644 assets/imgs/common/icon_pin.png create mode 100644 assets/imgs/home/icon_chat.png create mode 100644 assets/imgs/home/icon_community.png create mode 100644 assets/imgs/home/icon_home.png create mode 100644 assets/imgs/home/icon_user.png create mode 100644 lib/presentation/pages/pet_sos/layouts/pet_filter.dart create mode 100644 lib/presentation/pages/pet_sos/layouts/pet_sos_list_view.dart create mode 100644 lib/presentation/pages/pet_sos/layouts/sort_filter.dart create mode 100644 lib/presentation/widgets/button/radio_button.dart create mode 100644 lib/presentation/widgets/dropdown/dropdown_button.dart create mode 100644 lib/presentation/widgets/dropdown/dropdown_item.dart create mode 100644 lib/presentation/widgets/list_tile/post_list_tile.dart diff --git a/assets/imgs/common/icon_cal.png b/assets/imgs/common/icon_cal.png new file mode 100644 index 0000000000000000000000000000000000000000..3d879ef058c5012befc93e1d5f583f5f047035b4 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9Drb3m9;?W~mvP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBev_w*V@L(#+X=Th8w_|{g9`=R3q;H>@RqRL zJ;M8nZI`F}3h65~E*An8@^f?97HGs=is5ijYm%C(^ZH_A>ZG*2l7WXeP2H1}ac!p9 z)h`#_Tg~)XwGw`=e0I8J=>Z3s?Q>aGmTgH9-gDx={}k>b$5~ChhaxK!``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{#Q>I$B+ufw^KIq9x@Pc^}i5M)iPy@=F1Ok z9lR2af;SZIDHsIaP*7GWDS4=KuB+HmGhIMv;-@9cXC(ap9Ld(uYMF46Y3;?nH5Y`= zKH%nIEf*FuF}K_k)N))_RrKPUm{%^Y$Hc6@2=$&nWRYRY>XkTgVWCAE+gZaKE!NB>3=;wiX8V!^L?C zHL>c2PK@67Lq%SlY^_w~kLG5b_o8#}XZKA@ze|5^xa7!W|9>@y utI1r89||^V&ENZV-mO@cz|CEMp8M>T-t&45L2bYQVDNPHb6Mw<&;$TTZ-!j} literal 0 HcmV?d00001 diff --git a/assets/imgs/common/icon_pin.png b/assets/imgs/common/icon_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..542d5b8e0935a481274aeffac7a2f7b4a6e282d1 GIT binary patch literal 856 zcmV-e1E>6nP)@~0drDELIAGL9O(c600d`2O+f$vv5yPbUPodh-9> z{nu-+t&*o$RwO$B5fKp)5fKp)5fKs9?hVQoVpcI9VlHDYMmc!c=TFRC%x%n1&}>c2 z*O(z@J2UdP>;3#1yUHoxAV-i1DgGf1Dw1=qKQ{v^8H!r?D*QMdD$v7M(Z_j&b26p#RPbYI;Mf|zihV3S z60q748(_28CA(fXF~9fUhnSDBYwP{~t1DK+PlxAcCs(jp=4jwh3;ja06WX;{Yx)L< zfn6{c&H+up(V1IbazHLgo`Y2fxLig2kNfA)!`FB}&VWQLmm)k5L$|1aOcP7J z8v3OKBw~34$eWmJ__Kljl6E$76b$h5%AdOI;fnS?tz|k?sz>?7A5gJ?@=wSODktmu zr54}i^*?j;D6jYf79^moSy7dM+0f8?g8) z=DcI>K&qyG$e%2%wICA#r)NmgL5sgLCWQ`9i!ZY#j`s<**uCOq$XV=ia&7?}a)^kC ih=_=Yh=_=Y$cewhLZ?RQ3Da=^0000+99jwh}vs+XvYc9`9rP9Os`FV@>KvV&w<9F!zHEjt*7~;^3 zEkrZ{y4`LKo&QWvM?6R8GKlgEzqz?%3cN!(K`|}1!$RQyQ%sE^MIed*olb{Gy-_&L zoo-%UUS_NA6N^bvyc0p@un3dMq#Dg{z( z7xXVKE`r;2lt~Hp_Vl;&JW_R1fK472;CJ{8(<3D%R0QBr@|QaC+G=Jh{^Q=&3%(%; z6#-=Q=iud9F_tVizy=OZ)Ktp~w5CAn3J~LgBES~K`-f%E3?Wzm_SD{*oo~C`Z3CR) zS67A@A4q{<0q|0ZS5@Dr_#7P_WnT4|6bKdoBawL39~2SsEzS>AV14TXtF;6R;6hr) z?d>DuKjY;E_}2**0HZ!!G=`{-;*+?7#&WCS1BR{)F;pDWDtN{5swxgQSO89AdDUYL z$FvFwmFI9iffNW9K(@S@tB=RyTZ+exD`2|yNdXlnh-t9cpy0)7F|0sB?L?~is}cY& z(;``(_$4D*fgf-5wSp}b0c4-vbajr_G(%Qy%TJME_jylBsO+iG|&0`5E=3XfcmBe+Y5}@Dt5VH!f`QxIV!z z^Y4L}KV7?$VQx?$QPSd)-hT8&m_H0c;P9>3F&<5PE=yoRA8}7gUaZgeaE^h)Y!(Lf zNB1ADe;>LVjP_wfl9>$@YtLLZlw(3`kg3(e&tDFM6Qt3<4a%tT5|(ap8#}zgCig&$ z(~u-g^k|S@4x@C+<_aM`(VvxK*&03+bFcHY?jD?f@Z*glp$XExe;)U7%j72WSXLAE z5l@yth<38;XR{X-U>C%}vIsZq3u1pfebumki5YzwhG7_nVHk#C82>B30Rd93tdudh QqW}N^07*qoM6N<$f<1@2g8%>k literal 0 HcmV?d00001 diff --git a/assets/imgs/home/icon_community.png b/assets/imgs/home/icon_community.png new file mode 100644 index 0000000000000000000000000000000000000000..e1158bb06674fa6f4a3eb325b15450789de74a3f GIT binary patch literal 1258 zcmVY6MfQ?{r1jZSFuU`)lfFm$O07PK;^--`9cs7D{2y6!U-^aejE!@a37nWS* z)vKaLn&}yhT7A6MKok%V5D*X$5D*X$5D@VHp+ujE>+9=!xm>P2KR;J+oZ~lbHk&i@ zh)+?#WHM<#Jw0{NtgN{l@9gZfd?~`a0&Z?@E^&y`&?a)@2>1ekplX0IndJU%||*fN;(9TZXJK9thO;eZ^X zOrC&=U6^*MR2tcnXRzO6(mdh0oT_u6B!}>(0IX`ww4scRzAeE|U$2ovcvAqWiJY9A z#7Tls(9-WYHjqR3RDdyS_Vr4VAY7zs+OV7latNOaSgz{h<71R02nFn`w$m)&GY0%# z4q7g^IpVLAW0HhJUCGAe5Z)B9ySqEUYRYvs*AvK&l+5+lHa;d_HD!RL=if`&2^tC z^VQXrQx3rJrT`9lRvpj-8{EUMgzE1Hj^DYD^RW>SD(n=WLWP0&=;&x6@b;h z(18yt+Dp5Kqd#Pqa%i_*S%Fgp2-}O)sa{rMgBZbt$Yg~IVu+J~=u`n5`1I=Qz?TKM z_gu9j7Xi_k0wnE4rmrWH6;d|GrVk}v&~Ja`F(|}Hw3YJoSTsB0dU~M z>bD9$pSRNf&>LIqV4MexvFP*OLE8R85(+DTCoascL+h#R5=?ZqDQaN)-K5nQR>?|E z4Y2R-r2>#uS*~#60Hx9ZFP7W zR?yfcg~?9w#5x_*D_I)q;tK7WEKFwP-WC^DS2ukQZty`}!<({~U!)?nKc}!lF~H6w z=(jAN?^rIfpwC(3LT(Gd%d}OyXHTBNgX3CL$rZ8!zQ@ia>n4VCT~hQpPqxUV0GY(n z^xm& zoGR%}0rRZ9qlg9k!tYGleGI%d@7h&h(&w-wa3x5u6I<*Wuj3R#$|mZbY3AosPS~Wx zPECAW7-FPkT6qg015CPuXZ6*yMqE;x6_%9C*O%6ohj|uag7!-Zj1{c-A>_tTPDEP( z!Hu`l>lw%ENm|T=LQIfrbzyxP6?z|phTBe_ffZ#=F-{v#ctV$QV5@3SY650q`^Mi% zwzp*^V){S`3+4DzSEm>MGJ%o?zW-pg0uDr*zV|sAuj+HWfPjF2fPjF2fPgQCe*ytu Uj83F&8UO$Q07*qoM6N<$f>#hwh5!Hn literal 0 HcmV?d00001 diff --git a/assets/imgs/home/icon_home.png b/assets/imgs/home/icon_home.png new file mode 100644 index 0000000000000000000000000000000000000000..937dbaac3fe6aa85a21bb39dde2b8f3f18731ad0 GIT binary patch literal 1152 zcmV-`1b_R9P)NCmh9lvjpJ{@-)NtTo8C zF1yNP_M6d2T2_d2*>hPT;_-Mq9*@W4@p!6WI-NFoNJZ48LLm6@>+9cY+|8i6fkQ!1?)k8^Yfwg(nzm!5i)|=R_Rg%mdWw zJqVc;ZiKN2)?ZixxSzlq2DZJyi2@+}HiVzj!XGb&yJM_-cz770gI+`s>cHw@JqA}JYb3ZO+H_!yUMV>p6^2eA5Z*|Urj z5O|#Q1{(p=N)S}5-^-%#;l{?s_f^6ZE(;2a%U|)B2FS;<(`Bh1ke1%m>V3%p1n%I} zv*it?DIm>l%|MqIT~1S6ZX3gEEvHo!R=~3K2AA)b8MsmcWTA9fNC8Z58t9Ui35r^M z$vZzym$*MYI5@bLjO12;3V(}Bvyow->$)gqFD@?V`p4!Nm5_Bwv zz*!xScX7!DS}e`MQurnrX-Ek`_)!+ktM~Nq$4BP+)%4lM_{}=iA6@?J?5xS)D>~MfXmB$u+}|5 z*D?VM4pcx`J%XV&*k%9>~1)^Mf(qRq_7lAqJkJTmX?<)za9 z5BL}G3Xq&&{YJ+1SE&e;$c1Wz+zLQ}@)4O?*JE?iTCPq*+hcQU{7y1b=)(wlczzdX z>n;DuVPGo8Ch$5c{5b9?nN_&QAQ>r?1=ykKS75p!`}v>vL&ODmSMvT;a!@D;GOh8G zxA}3R0vL;eJfFo#Y39(Y?OFB9*@W4@pwF*|C_(6n`JYy Stz(A(0000#teHY zABEAYll%ZecHR zEdgY78>Rh;a+0`4EKL>@01+UF+nCKJ<;PXp0SO@{04F;1GIMprcQ@;`oD{|Do)f1J znFnyZhY{1ujjGj04vlYJ7mc~ZetgFd5=3+pgcnSB2D!~Mie>ThH4)fLc#Jyx$O%Lb zMdD`x7~DV{|NYNRE0r0~EQ}4Kw;J#ePJdl>9E`t%CyA~9)3cBPmdhn?f{&CvobmE$ zNv{1EBY8ll(|O|AM01{DxX)uv7|$3liSZt=+wJ;T2A}Ed5p}c!ECMNpbfm{hh*PXSxp=Eh$c<)QWKv_hc`- zCrz|HAf@I%MdC9e0dQ9E)FLU`6O_%rkp@};*&8ps)bEHr28>#L#lZJg}!J?WRuIys!Ys=hdojpyP&liOvi0{pyKl z$wgrSu*0i2{YBT{)#3h7b~Ov;62$ zCWu0}hyou!d literal 0 HcmV?d00001 diff --git a/lib/core/constants/images.dart b/lib/core/constants/images.dart index ae606c2..e2bc61a 100644 --- a/lib/core/constants/images.dart +++ b/lib/core/constants/images.dart @@ -11,5 +11,8 @@ abstract class PNDImages { static const String chat = _homePath + 'icon_chat.png'; static const String user = _homePath + 'icon_user.png'; - static const String locationPin = _homePath + 'icon_pin.png'; + static const String _commonPath = _basePath + 'common/'; + static const String location = _commonPath + 'icon_pin.png'; + static const String payment = _commonPath + 'icon_pay.png'; + static const String calander = _commonPath + 'icon_cal.png'; } diff --git a/lib/core/constants/sizes.dart b/lib/core/constants/sizes.dart index 16a122f..007d084 100644 --- a/lib/core/constants/sizes.dart +++ b/lib/core/constants/sizes.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; /// Constant sizes to be used in the app (paddings, gaps, rounded corners etc.) class PNDSizes { + static const p2 = 2.0; static const p4 = 4.0; static const p8 = 8.0; static const p12 = 12.0; @@ -14,6 +15,7 @@ class PNDSizes { } /// Constant gap widths +const gapW2 = SizedBox(width: PNDSizes.p2); const gapW4 = SizedBox(width: PNDSizes.p4); const gapW8 = SizedBox(width: PNDSizes.p8); const gapW12 = SizedBox(width: PNDSizes.p12); diff --git a/lib/core/constants/text_style.dart b/lib/core/constants/text_style.dart index 4570b2c..1675830 100644 --- a/lib/core/constants/text_style.dart +++ b/lib/core/constants/text_style.dart @@ -28,20 +28,20 @@ abstract class AppTextStyle { fontWeight: FontWeight.w800, ); - static final TextStyle headlineBold1 = pretendardBoldStyle(24, 33); - static final TextStyle headlineBold2 = pretendardBoldStyle(20, 24); + static final TextStyle headlineBold1 = pretendardSemiBoldStyle(22, 29); + static final TextStyle headlineBold2 = pretendardSemiBoldStyle(20, 24); - static final TextStyle headlineRegular1 = pretendardRegularStyle(24, 33); - static final TextStyle headlineRegular2 = pretendardRegularStyle(20, 24); - - static final TextStyle headlineMedium1 = pretendardMediumStyle(24, 33); + static final TextStyle headlineMedium1 = pretendardMediumStyle(22, 29); static final TextStyle headlineMedium2 = pretendardMediumStyle(20, 24); + static final TextStyle headlineRegular1 = pretendardRegularStyle(22, 29); + static final TextStyle headlineRegular2 = pretendardRegularStyle(20, 24); + static final TextStyle bodyBold1 = pretendardSemiBoldStyle(16, 19); - static final TextStyle bodyBold2 = pretendardRegularStyle(14, 17); - static final TextStyle bodyBold3 = pretendardRegularStyle(12, 14); + static final TextStyle bodyBold2 = pretendardSemiBoldStyle(14, 17); + static final TextStyle bodyBold3 = pretendardSemiBoldStyle(12, 14); - static final TextStyle bodyRegular1 = pretendardSemiBoldStyle(16, 19); + static final TextStyle bodyRegular1 = pretendardRegularStyle(16, 19); static final TextStyle bodyRegular2 = pretendardRegularStyle(14, 17); static final TextStyle bodyRegular3 = pretendardRegularStyle(12, 14); } diff --git a/lib/presentation/pages/home/home_view.dart b/lib/presentation/pages/home/home_view.dart index f4f36a8..5170f3d 100644 --- a/lib/presentation/pages/home/home_view.dart +++ b/lib/presentation/pages/home/home_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:pets_next_door_flutter/core/constants/colors.dart'; +import 'package:pets_next_door_flutter/core/constants/images.dart'; import 'package:pets_next_door_flutter/core/constants/sizes.dart'; import 'package:pets_next_door_flutter/core/constants/text_style.dart'; import 'package:pets_next_door_flutter/presentation/pages/home/home_event.dart'; diff --git a/lib/presentation/pages/home/widgets/home_location_button.dart b/lib/presentation/pages/home/widgets/home_location_button.dart index 2a679d8..a4af9dd 100644 --- a/lib/presentation/pages/home/widgets/home_location_button.dart +++ b/lib/presentation/pages/home/widgets/home_location_button.dart @@ -1,5 +1,8 @@ part of '../home_view.dart'; +const double _kLocationIconSize = 24; +const double _kHighlightBoxHeight = 10; + class _HomeLocationButton extends StatelessWidget { const _HomeLocationButton(); @@ -12,7 +15,12 @@ class _HomeLocationButton extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Icon(Icons.location_on_sharp), + Image.asset( + PNDImages.location, + width: _kLocationIconSize, + height: _kLocationIconSize, + isAntiAlias: true, + ), gapW4, Row( children: [ @@ -22,8 +30,8 @@ class _HomeLocationButton extends StatelessWidget { children: [ Positioned( child: Container( - height: 10, - color: Colors.amber.withOpacity(0.2), + height: _kHighlightBoxHeight, + color: AppColor.of.primaryGreen.withOpacity(0.2), )), Text('용답동', style: AppTextStyle.headlineBold1), ], diff --git a/lib/presentation/pages/pet_sos/layouts/pet_filter.dart b/lib/presentation/pages/pet_sos/layouts/pet_filter.dart new file mode 100644 index 0000000..e9edebc --- /dev/null +++ b/lib/presentation/pages/pet_sos/layouts/pet_filter.dart @@ -0,0 +1,28 @@ +part of '../pet_sos_view.dart'; + +/// 돌봄급구 펫타입 필터 영역 +/// Radio 버튼 형식으로 구현되어 있음 +class _PetSosPetFilter extends ConsumerWidget with PetSosEvent { + const _PetSosPetFilter(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedPetFilter = ref.watch(petSosFilterProvider).petTypeFilter; + + return Wrap( + spacing: 8, + children: [ + ...PetTypeFilter.values + .map((petType) => GestureDetector( + onTap: () => onPetTypeChanged(ref, petType), + child: PndRadioButtonItem( + groupValue: selectedPetFilter, + value: petType, + text: petType.displayName, + ), + )) + .toList() + ], + ); + } +} 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 new file mode 100644 index 0000000..6c89a4b --- /dev/null +++ b/lib/presentation/pages/pet_sos/layouts/pet_sos_list_view.dart @@ -0,0 +1,47 @@ +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/layouts/sort_filter.dart b/lib/presentation/pages/pet_sos/layouts/sort_filter.dart new file mode 100644 index 0000000..71afe88 --- /dev/null +++ b/lib/presentation/pages/pet_sos/layouts/sort_filter.dart @@ -0,0 +1,24 @@ +part of '../pet_sos_view.dart'; + +/// 돌봄급구 게시물 정렬 필터 영역 +/// 드롭다운 메뉴 형식으로 구현되어 있음 +class _PetSosSortFilter extends ConsumerWidget with PetSosEvent { + const _PetSosSortFilter(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedSortFilter = ref.watch(petSosFilterProvider).sortFilter; + return PndDropdownButton( + onSelected: (sort) => onSortChanged(ref, sort), + selectedValueStr: selectedSortFilter.displayName, + itemBuilder: (_) => SortTypeFilter.values + .map( + (sortType) => PndDropdownItem( + value: sortType, + valueStr: sortType.displayName, + ), + ) + .toList(), + ); + } +} diff --git a/lib/presentation/pages/pet_sos/pet_sos_view.dart b/lib/presentation/pages/pet_sos/pet_sos_view.dart index f8703ff..6e942be 100644 --- a/lib/presentation/pages/pet_sos/pet_sos_view.dart +++ b/lib/presentation/pages/pet_sos/pet_sos_view.dart @@ -1,17 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:pets_next_door_flutter/app/router/app_router.dart'; -import 'package:pets_next_door_flutter/core/constants/sizes.dart'; -import 'package:pets_next_door_flutter/core/constants/text_style.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/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'; -class PetSosView extends HookConsumerWidget { +part 'layouts/pet_filter.dart'; +part 'layouts/pet_sos_list_view.dart'; +part 'layouts/sort_filter.dart'; + +class PetSosView extends StatelessWidget { const PetSosView({ super.key, this.onScrollDirectionChanged, @@ -20,171 +25,29 @@ class PetSosView extends HookConsumerWidget { 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]); - + Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const _SortFilter(), - const _PetFilter(), - ], - ), - ), - Expanded( - child: ListView.separated( - controller: _scrollController, - shrinkWrap: true, - itemBuilder: (context, index) => ContentsListTile(), - separatorBuilder: (context, index) => Divider(), - itemCount: 10), + _buildPetSosFilterGroup(), + _PetSosListView( + onScrollDirectionChanged: onScrollDirectionChanged, ) ], ); } -} -class ContentsListTile extends StatelessWidget { - const ContentsListTile({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () => context.goNamed(AppRoute.signIn.name), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 88, - width: 88, - color: Colors.yellow, - ), - gapW8, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '푸들 한마리 급하게 ㅁㅇ너랴ㅐㅓㅁㅇㄴ래ㅑ머ㅔㅐㅑ', - softWrap: false, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - Text( - '23.03.21 ~ 23.03.28', - softWrap: false, - ), - Text( - '용답동', - softWrap: false, - ), - Text( - '시급 9000원', - softWrap: false, - ), - ], - ), - ) - ], - ), + Padding _buildPetSosFilterGroup() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: const [ + _PetSosSortFilter(), + _PetSosPetFilter(), + ], ), ); } } - -class _SortFilter extends ConsumerWidget with PetSosEvent { - const _SortFilter(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return PopupMenuButton( - surfaceTintColor: Colors.white, - constraints: const BoxConstraints.tightFor(width: 90), - position: PopupMenuPosition.under, - child: Container( - height: 45, - child: Row( - children: [ - Text(ref.watch(petSosFilterProvider).sortFilter.displayName), - Icon(Icons.keyboard_arrow_down_rounded) - ], - )), - onSelected: (sort) => onSortChanged(ref, sort), - itemBuilder: (context) => SortTypeFilter.values - .map((e) => PopupMenuItem( - height: 40, - padding: EdgeInsets.all(0), - value: e, - child: Container( - padding: EdgeInsets.only(left: 10), - child: Text( - e.displayName, - )), - )) - .toList(), - ); - } -} - -class _PetFilter extends ConsumerWidget with PetSosEvent { - const _PetFilter(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final filter = ref.watch(petSosFilterProvider); - return Wrap( - spacing: 8, - children: [ - ...PetTypeFilter.values - .map((petType) => GestureDetector( - onTap: () => onPetTypeChanged(ref, petType), - child: Container( - color: Colors.transparent, - height: 30, - child: Row( - children: [ - (filter.petTypeFilter == petType) - ? Icon( - Icons.check_box_sharp, - size: 20, - ) - : Icon( - Icons.check_box_outline_blank_outlined, - size: 20, - ), - gapW4, - Text( - petType.displayName, - style: AppTextStyle.bodyRegular3, - ), - ], - ), - ), - )) - .toList() - ], - ); - } -} diff --git a/lib/presentation/widgets/button/radio_button.dart b/lib/presentation/widgets/button/radio_button.dart new file mode 100644 index 0000000..0daa984 --- /dev/null +++ b/lib/presentation/widgets/button/radio_button.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:pets_next_door_flutter/core/constants/colors.dart'; +import 'package:pets_next_door_flutter/core/constants/sizes.dart'; +import 'package:pets_next_door_flutter/core/constants/text_style.dart'; + +class PndRadioButtonItem extends StatelessWidget { + const PndRadioButtonItem({ + super.key, + required this.groupValue, + required this.value, + required this.text, + }); + + final T groupValue; + final T value; + final String text; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + height: 30, + child: Row( + children: [ + if (groupValue == value) + Icon(Icons.check, size: 15, color: AppColor.of.black), + gapW2, + Text( + text, + style: (groupValue == value) + ? AppTextStyle.bodyBold3 + : AppTextStyle.bodyRegular3.copyWith(color: AppColor.of.gray90), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/dropdown/dropdown_button.dart b/lib/presentation/widgets/dropdown/dropdown_button.dart new file mode 100644 index 0000000..8b222a6 --- /dev/null +++ b/lib/presentation/widgets/dropdown/dropdown_button.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:pets_next_door_flutter/core/constants/text_style.dart'; + +class PndDropdownButton extends StatelessWidget { + const PndDropdownButton({ + super.key, + required this.selectedValueStr, + required this.itemBuilder, + this.onSelected, + }); + + final String selectedValueStr; + final List> Function(BuildContext) itemBuilder; + final void Function(T)? onSelected; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + surfaceTintColor: Colors.white, + constraints: const BoxConstraints.tightFor(width: 90), + position: PopupMenuPosition.under, + child: Container( + height: 45, + child: Row( + children: [ + Text( + selectedValueStr, + style: AppTextStyle.bodyBold2, + ), + Icon(Icons.keyboard_arrow_down_rounded) + ], + )), + onSelected: onSelected, + itemBuilder: itemBuilder, + ); + } +} diff --git a/lib/presentation/widgets/dropdown/dropdown_item.dart b/lib/presentation/widgets/dropdown/dropdown_item.dart new file mode 100644 index 0000000..14c0212 --- /dev/null +++ b/lib/presentation/widgets/dropdown/dropdown_item.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class PndDropdownItem extends PopupMenuItem { + PndDropdownItem({ + super.key, + @override T? value, + String? valueStr, + }) : super( + padding: EdgeInsets.all(0), + height: 40, + value: value, + child: Container( + padding: EdgeInsets.only(left: 10), + child: Text( + valueStr ?? '', + ), + ), + ); +} diff --git a/lib/presentation/widgets/list_tile/post_list_tile.dart b/lib/presentation/widgets/list_tile/post_list_tile.dart new file mode 100644 index 0000000..63135a3 --- /dev/null +++ b/lib/presentation/widgets/list_tile/post_list_tile.dart @@ -0,0 +1,153 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:pets_next_door_flutter/core/constants/colors.dart'; +import 'package:pets_next_door_flutter/core/constants/images.dart'; +import 'package:pets_next_door_flutter/core/constants/sizes.dart'; +import 'package:pets_next_door_flutter/core/constants/text_style.dart'; + +typedef _ImgIconUrl = String; +typedef _TextWithIconImgUrl = ({_ImgIconUrl imgIconUrl, String text}); + +const double _kImageHeight = 88; +const double _kImageWidth = 88; +const double _kContentIconSize = 16; +const double _kContentSpacing = PNDSizes.p4; + +/// 앱에서 게시물 보여줄 때 사용하는 ListTile +class PndPostListTile extends StatelessWidget { + const PndPostListTile({ + super.key, + required this.title, + required this.imageUrl, + required this.contentsList, + this.imageHeight = _kImageHeight, + this.imageWidth = _kImageWidth, + this.contentsSpacing = _kContentSpacing, + }); + final String title; + final String imageUrl; + final double imageWidth; + final double imageHeight; + final List<_TextWithIconImgUrl> contentsList; + final double contentsSpacing; + + /// 돌봄급구에서 보여주는 ListTile + /// 나중에 돌봄급구와 메이트 디자인 변경될 수 있을 것 같아서 빼놓음 + factory PndPostListTile.sosPage({ + Key? key, + required String? imageUrl, + required String title, + required String dateInfo, + required String location, + required String pay, + }) { + return PndPostListTile( + key: key, + imageUrl: imageUrl ?? '', + title: title, + contentsList: [ + (imgIconUrl: PNDImages.calander, text: dateInfo), + (imgIconUrl: PNDImages.location, text: location), + (imgIconUrl: PNDImages.payment, text: pay), + ], + ); + } + + /// 돌봄메이트에서 보여주는 ListTile + factory PndPostListTile.matePage({ + Key? key, + required String? imageUrl, + required String title, + required String dateInfo, + required String location, + required String pay, + }) { + return PndPostListTile( + key: key, + imageUrl: imageUrl ?? '', + title: title, + contentsList: [ + (imgIconUrl: PNDImages.calander, text: dateInfo), + (imgIconUrl: PNDImages.location, text: location), + (imgIconUrl: PNDImages.payment, text: pay), + ], + ); + } + + @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, + ), + ), + )), + ), + 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, + ), + ], + ), + ), + ) + .toList(), + ) + ], + ), + ) + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index c45a8e5..2940709 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,6 +79,8 @@ flutter: - assets/imgs/ - assets/imgs/pet/ - assets/imgs/launch/ + - assets/imgs/home/ + - assets/imgs/common/ - assets/svgs/ - assets/svgs/common/ - assets/svgs/login/