From 27609e7789ea160bcb7b41cd31e476af70b7e0c2 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Wed, 1 Jan 2025 15:46:22 +0100 Subject: [PATCH] feat: favorites tab --- app/(auth)/(tabs)/(favorites)/_layout.tsx | 24 ++++ app/(auth)/(tabs)/(favorites)/index.tsx | 34 +++++ .../actors/[actorId].tsx | 0 .../albums/[albumId].tsx | 0 .../artists/[artistId].tsx | 0 .../artists/index.tsx | 0 .../collections/[collectionId].tsx | 0 .../items/page.tsx | 0 .../jellyseerr/page.tsx | 0 .../livetv/_layout.tsx | 0 .../livetv/channels.tsx | 0 .../livetv/guide.tsx | 0 .../livetv/programs.tsx | 0 .../livetv/recordings.tsx | 0 .../series/[id].tsx | 0 app/(auth)/(tabs)/(search)/index.tsx | 4 +- app/(auth)/(tabs)/_layout.tsx | 11 ++ assets/icons/heart.png | Bin 0 -> 21969 bytes components/common/TouchableItemRouter.tsx | 7 +- components/home/Favorites.tsx | 119 ++++++++++++++++++ components/home/ScrollingCollectionList.tsx | 15 +-- 21 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 app/(auth)/(tabs)/(favorites)/_layout.tsx create mode 100644 app/(auth)/(tabs)/(favorites)/index.tsx rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/actors/[actorId].tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/albums/[albumId].tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/artists/[artistId].tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/artists/index.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/collections/[collectionId].tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/items/page.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/jellyseerr/page.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/livetv/_layout.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/livetv/channels.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/livetv/guide.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/livetv/programs.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/livetv/recordings.tsx (100%) rename app/(auth)/(tabs)/{(home,libraries,search) => (home,libraries,search,favorites)}/series/[id].tsx (100%) create mode 100644 assets/icons/heart.png create mode 100644 components/home/Favorites.tsx diff --git a/app/(auth)/(tabs)/(favorites)/_layout.tsx b/app/(auth)/(tabs)/(favorites)/_layout.tsx new file mode 100644 index 00000000..f96cd516 --- /dev/null +++ b/app/(auth)/(tabs)/(favorites)/_layout.tsx @@ -0,0 +1,24 @@ +import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack"; +import { Stack } from "expo-router"; +import { Platform } from "react-native"; + +export default function SearchLayout() { + return ( + + + {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( + + ))} + + ); +} diff --git a/app/(auth)/(tabs)/(favorites)/index.tsx b/app/(auth)/(tabs)/(favorites)/index.tsx new file mode 100644 index 00000000..e01f975e --- /dev/null +++ b/app/(auth)/(tabs)/(favorites)/index.tsx @@ -0,0 +1,34 @@ +import { Favorites } from "@/components/home/Favorites"; +import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache"; +import React, { useCallback, useState } from "react"; +import { RefreshControl, ScrollView, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +export default function favorites() { + const invalidateCache = useInvalidatePlaybackProgressCache(); + + const [loading, setLoading] = useState(false); + const refetch = useCallback(async () => { + setLoading(true); + await invalidateCache(); + setLoading(false); + }, []); + const insets = useSafeAreaInsets(); + + return ( + + } + contentContainerStyle={{ + paddingLeft: insets.left, + paddingRight: insets.right, + paddingBottom: 16, + }} + > + + + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search)/actors/[actorId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/actors/[actorId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/actors/[actorId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/actors/[actorId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/albums/[albumId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/albums/[albumId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/artists/[artistId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/artists/[artistId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/artists/index.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/artists/index.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/collections/[collectionId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/collections/[collectionId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/jellyseerr/page.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/_layout.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/_layout.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/guide.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/guide.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/programs.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/programs.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/recordings.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/recordings.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/livetv/recordings.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/recordings.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx index ac3d83fe..48507dbe 100644 --- a/app/(auth)/(tabs)/(search)/index.tsx +++ b/app/(auth)/(tabs)/(search)/index.tsx @@ -318,7 +318,7 @@ export default function search() { text="Library" textClass="p-1" className={ - searchType === "Library" ? "bg-neutral-600" : undefined + searchType === "Library" ? "bg-purple-600" : undefined } /> @@ -327,7 +327,7 @@ export default function search() { text="Discover" textClass="p-1" className={ - searchType === "Discover" ? "bg-neutral-600" : undefined + searchType === "Discover" ? "bg-purple-600" : undefined } /> diff --git a/app/(auth)/(tabs)/_layout.tsx b/app/(auth)/(tabs)/_layout.tsx index f256ab50..11fa4b67 100644 --- a/app/(auth)/(tabs)/_layout.tsx +++ b/app/(auth)/(tabs)/_layout.tsx @@ -62,6 +62,17 @@ export default function TabLayout() { : () => ({ sfSymbol: "magnifyingglass" }), }} /> + + require("@/assets/icons/heart.png") + : () => ({ sfSymbol: "heart" }), + }} + /> 5{-znC}A zvH<^mi@BGD^}nCTZW^*akk-V&fMCc=iEDXa?xf#od8<9ug&DkSN3JiykS8vqp$3uq zK0uYP^#BX+Q1j_iwa;o4_qJ6DL7~~rdEzFBU)hJ}Vx1zLjqY}7lLzIsnJARku7G)F z>xAdRb=!K2`FA6)_vAKACV>CnUQMc_;_WWvcJNM(aKfhGTic zWj8F37(A^YQ3oeDo>WsxS;fF3*wDI_FVBnNb)^(=f0z+>mZR`y^T{cn$cxp}2S!`Y z#|Zx;@*3V`|2X$~W@BR-#d{X=G*JDt^)m@eMw=bo_ZxUJ@)wvc1Wopf2X-{fxiisV zVM3c8BDLi)b8GS={k(xuQ!M@X?mI&UMNh0%f1i2J1syduIWMYL(8|;)f%swTj;7|! zA4srko=34~uMN_(oC)YCF6+2pH90~&ck}Rp&>i5>sd|8&Px;Z*u(_>e9;x#i&8Xy9 z*p~B{rfH55pix zWr6~P3;FPReih z4EiNgPY0y~Topz*K2Pkv&uHb7jR=2B`erNAzX?N?dhmFTdb5?AIg5P$%*Hl|Px6os z>JB_wX-N1nu+rExZ_7*UfGj6yV~V#GqKkmeggthkluq}*3bo@bf0-0d2-iE%4n4EQ zifXzT(zypWXZfw$zG2Vw*I)9J?S2%oHHBo#>M;|_ zwNK`;Q3+O`jL$w$nu{wZ5}~ z5f0{6yJxx64-ggarHRUqZ62I~hu^(`h62-C`T|Q8mE$YNYzu*Q;gXPvwksOe80#@M zXbRAUr0YCJe~P=1T%yjXv|CZ$K*UJ1Dx#{ZBdsMosN&nC>10JKa8=D;3-~*t=0LsUzK^39I0{mS|9& zjM)TrvldDe-0c@)r^M_3?Sdw_Jq6K7HvGg1dIgOC<`;Aok})7QYfA3|*!-}fvHeHB z1}mkwTCzn~2I!*qk*SV4WVer{+#=gz{6y5^qebLYrUK5aGDkZ^KWXW^f|XJOG4tM# zY6epdtEF|$2Yq0j8CY#%-afi(%X{Sg@)em*K~Y7{aTxnBBcswHpZE2%;jdhEL^%8~ z0s@(&TyG3Z1FzjB_k;1f;D+$Y_COWer-AemnZXjG;iJms18M!hX^_Qy&lTeRp9tS8 zTueWcf@{^uWMv9Jb+ShDXw`vks8QqRAf5h^Z{Pa{!S9E8;q{K+zF7FIW*3zo@Gko^ zhH8jyk7AX~BsA6I;cz(jHA|bmC+8_|<(fv0Df^<8h}a`c#5W*Prp?V&$w0GWNXJ@1 zEY_B3kQFXFrJl*HY&Aj^-<2ANVxMybpQ;W%ajLJOypBPTqcPJK!n4uECk#)Y{PcOJ z@x9*+RM|ucp!+$PLhnUHqxo+kp4J)fJHINLcIV+;Z+*+DjT(WY-wP>U3;UOI)t9U) z7-K~F`TvosW>{~fj+8P{Pa6W7POTMvF553)vj2Xmo~P*7GD=Tq6GW(HI$Rk(UfYf# z5|Lt;inXX#<2fSEfAtjyaqfkccU^W(cWGjm#`_GmRrSDdw_3!?^djP%x#H2{TJhnI z#q%Z--l5+N2>YCYT!9pvj%P_KCaX&Rl7g&7P7g~vl zyk(bQU(OESb_pSuhIV&u_;b)Fg(YZq_+aN-9WC>zMkagZ#8cKY+Pr5(sURu3tbv)- z82!8JSyXmyS$eCo+1kO4*#RKIlE5iE(}?TUnqx) z5se}EZio@HZFiiM_Rh=JRFIZDr1QlL{qa{LgSX$cM-!mP@$K=>A!6+gXKJQFY|My2 z_S5wD8QZyOP55&k!~EX0AQTMEJC5BXAfZtAQYg8y#6a#7CHR^tEkiU{_N;I8s*^8O z_damf?66#J&Z~H`+u>-WnBQ7V*~GsiLU_{;^@}QGHM)hj;F9-fo=PR;wFLvbJH%5p zZwe8koLO7(xS#R!M@Sfu2X0G{$2%NXHwrD@tP&!$G!5tnYA`;fjIDHWPbS+_TVMf@VfF$CgjX!Lw(}WR&~Ctd<3g<6|udD!rAQ47g;cD zn)P+{%nHWnasxGqDpy%Z3-3=)Z};HuQ$k~_WvdP>`i%I!(HR!>qsmwsr79~WXBv&z zBH<;^FEcIWrzdg_EToSFYo2RV)we1@K;HH)o298%J_S-pyrTmB))2i@p9WWvCj3jX zV#XKb8{PH^1${Y3STM`HfkaVVPXk=bqE0?xOkUF({2wu$?bKe@VLedLB2J#4(o89E zRH5yz<*e4Qb8X2n0mam)rw5ikWm%{Td4m2QVdO0&M%RYqr|evTr{3G}y~G3U>K?V$vcw z#TCXC7K>Bc9Ogs#Bq(ze)U2)x8)oY_E_Q={Z8zu5;|>xO1rCz7QfFMKZ(=r5q=)|! z*QK*noEx1hmuh>YVILMJK=_mwXAD=hOXEyKD=C_|zRM%U%AKg8?Po$I7eqCCR@s;@ zHJ4-POs?!pL9*`$pL?t@Ez64Rs?4mi(oZ}P!-2(47-B)Ll_n}@)>aQN`+zlO>yQ`E zs)RA2LsNcUS!LG!P+rgxvRPtXWobIAW8qbUY4z13<2Tdx`x+Hzl7hW=wAuAK>U#cy zqbcK2xhsQ~2+L8ek#$884z%KU@@@n_Us9>C(>}0ZGk9@X_IbaPJfia|0?Xl^q9pIGRn1*Equg^msCCzMVZAxbvtD#&4!r1Gbtb z5eX(bM5$YZXS5_#`bDKY=gb`+%N%5tV!b@EkjJs3Y)q7E3L?SO6(o`6MA@dKg)z6c-7i zza&1NHflLB&hqQWuBT&OFBl6u0N<+kM7Q7b$*Ld9wI{oL+z~~*^TlIZISCm&?z>)> zyM7=roes<={pBcE9csHqG#J%kI7q`%$WT7+uYX5+a`^$0xkipC=nm`3vkSFh(YwWC zw(LFLfXnwR;)jR zoGR@J-!T~+zdm!!-CdnH)|AV7o%x;Y*zA&`G2d_}lw8%-D|bijTT1{`B-IzAjn;6+ z8N^8S@P1VjmYBLknk#8>xsbB2TKy`wtKf2zc{1%?{gc&JmSdd&Pg@@OJ&Ms%pCqh> zp{*}pA~jXVqlV_bTv(+Dv4i&u)$K$IDSnR{q%^DHgvYnk!+(fBDtfT;&aBSrlkzhW zBc&M5d^!NT#;=LNvpWg8y3Z&Ara;HY>ssz^lCfhW-HQ^`-6Lg}Vx!5ggp(MmZDB`x zec#KO2`^221tlQr`m~#Mnz|D|iLxFnc1p(|I3Bs@l?y|k00>qhfAyDH80Og>KTzi0 zOp;}G*=P~9Ptko|-nT-(Q_eL=n^lqZv@BcB-|COF*b`oc+*54p96j~T0vPwokUS9} zG`eLhq1dtH4n4-k2)1h(qHpyakuBPXTv`J+gtEndOOh(CBTVdcXRPW|XD)bl{aC#h zd@|ZQei6hSV+NctB_3>SB)%lsxaJ6MBw;5EEQ_P zl$eG&vyz}Qrx7koM&$7z6$|eSMlcry<+6A}FlC;O|0lemkDEV#ypCzhk~5U!KH%)b zC8c4+5%~zgk9b?3w24*T@)U3UTovbNwK+|u$$IWfTYuFUVcyzb>5n4xbhT@|i zTk0Pp1|~>6#E&zS1e$ZU@Yn{vk19e!Ro9P5Ygkk6AIm}YRwA@{+>L~CRK3qX-S6mtP zd~lwRIqrfFd)CF$`eJdeA&{Jh#tv#9#8WL}fmeaOyf|)c4f}t&*|-dG>lt znI4d*`bdcf)6zzui6cC4^N~jQqAp8<5hpr#z@&a}BvJR9aIp4_IXhx7AT+3q_GeR~ zVW&{S<$}(~7G6;Ypu&zuTH(QIHR9zPYfd;ej-d$GW}*>4=Dp4LqXZGu#QWNuq5*D_ z<{Lp}F~CtL?H5hs27?5H@ix!WwD-nOwFL~{BCj~b%ha_^?ud!9vab)}L8*(Rp2Y}L>I*%)XT4P6>pgEAw4lwn zR|YcMV*{{NMnYM(uq>S5Z<~saKGSLAj3WN!Y8QWM> zi1JOim-2{DGSxTc*0mY-V4zjPcn$Yc?GquQ;?hwwfaDE{dq-5$g!#y|fYR*k1&>3= zBPo`OZKVeQ-Q^I^w{;MO53Db(TiTB1r`cjrSA-K4FdCsRI# zR?^5L-=nTEv4|_tEBb08y+cT-&(E~Jg8tY9P2mZk{&S!&i+#9;+~Ovq2bKm3W_i8x zmT7Ow@j@++yn=O7fWMK z2#aUh)z}}yte1Z=;~rO~edXzqn{X7X`HzdwKAnRKk?HRjCvS=+Xdp zWLb%eRIjM-IUEyhCi{cu3DbT^oS+DYJ$zp2xFB`NYP#s@Q_bS`LUHeq3z(eiP{I$3 zV4xZ+Tbfbb&!m%+)TdQ_XTQY3R-(al#=(hrG@qC%EKiCm2P9*9yzHoiaQegOXMFkH zo!(&YKy`pjL=my5Dm`gl&!-fqYeG-cqq&Q6(2+94a42q&)I$8hSUlO<)~U1rzoz;f z^dP9f$@AUy&Z)TiZ%{Z}m%J=5a*ex>cB_;o8JHMb-+g zB1DYz80)l*W%HiPnk?Gl8V=TzB($Ku#JQgRDQE%k=t1T@hWtJz$aXMJD4XEoBk=Ww zvT{V-UAuW|uDGg~K4eo~P6OHK9Vm~BtUUw6Hn;LqwD}RVatqi#dPK(bDbdatY&oCW z0?3z4(v|u>THW5ETBm>|^bQ0hbHQ~*C0}v3PI+Ca)nECP_X6KPc3`Dk2uY;a?Z=pj zXXb73wrX#C@a%V)b<<<_B-#i+a}-{0cPd02c%>j9ORA+4Z+aE+n1ZkRBV-QfLc7D2 zCA;PJCjQKQwRKGf)}C8`X3N1P!?7SPeSw4cvBc>T%S{9bP&zAe@Dty zn`njGo?ylE>(YRCtdSJ_FwTFRIP(L~74;AglNh#O8SVO+auaOIr1Rgmj$~iNAg;|5oBy1I?OMcDlZG*g{aQ1L1@+n%aF8bmTSa5cIv8~Yb^t|`$ikmTb$9e^)$Q-c+&z--sFp9I=<4Jv( z5iyM~MO2~ZgW3K^ihvHC?lL<(!!Hj{{IF}0>esZYj5lE6gwBxZ(Mq?TC{uxl$Yb)f zhJRG_JSdw;LU~GEhbAAt-QzGbR=O?17b$_`4Jua%$XDYa#4GKp|w07^(GaR z2Z6+u)Q-zaonxrhj(j-*5(9U!tagp+`z+U>S z9$u?QQas02J7h!8&ffs2i6=8J>5b+NJTLUiIv*D7jN8^X?uO(LIB?1P>E|;F4>c?q zA(n`H-C~&3NHAUZHip^DIv(`!w3P+D%rSBUhbH)hDnjCtlOUycUDO3?OS{bLA4&6GxS z4E8(c<#xCg7RKoznYvm@rO)7OI2OBIf!bQ5;?xtBswERS?t~fcmJcy~%JB&vR&zG* zmUNe;uC|f~mG(VKp$?KP=He!gD84LC>R8dLIB0BAjuUewPkoFQQW;p$U;Vc*O*#jkAP`{va!G= zcH@|SYE^?R9@v0DUsjWaHRh(Xd|ENaB8ffWaFtHuefbEWJB9)9qnGK2R1CK%ysg*O%iekpr@%q5 z0dxL>v7#CBpgmC)b~#*u=ceLOZ& zw%j7wGx4+cCId`%>-hxIE#sBw_{Zb(Mxw!=htJJ_Sb1Nfed%n601fzIP)A@(;W@5U z70khGW7C#_D=i1zW|yj{eIp59XE%D5D0-A9!2M~bY!hCniwYiDL@$1dMw2n2Ar5~pLXf8X+_lBBU! z_Co0B_i}kluE(jMVp{qSzn3iBt4`3ePe>u7THa{g{<>rMG*-+En!Wo`<$~tr4FD{)&?-X|IYmGps^C*$5LcdFgRusY?EA{yP- z|7f7#i%pP+@2OFd87;LZ-vr=v(u^ zkdZEqP2R3Z8qcna>Hip{sETk&7)kE&YNGCC;!!x&=h*C0pGQIeoocF)!tr|98iJj4 z7n7%^qbR45zCP!vykZmjKVx*7iphgZU;O2yD7xc2$vCf$cEzh;?P+(4l$OV}Esx>W z=D*i(DV>6ol|l&a;ySDO+NsXtt*p|+skHlM{u8AuQAm8SjM9ARxU_k)XQs)_ai8~I#(c93Fyj2kv+4}mHq>3;Z>|={p9{qo+(P&FXzaWM{&a%?wSDVxWb(R8N(_lW% z7e9Zw+BOxGhZ$D8QM>t5LI&dfZ8J_QxF=W07jB954;o?w_P=|aa-VKL?4gfB~ zYi0ODrM{7!9wCjs^Jz%scG&F*&LAl(C6&IBvehz5UoOJcSqqLsp@0+97q<(YqD-gp z+No((#8%SZ`N4*c4W`57o!dpEKp$GhiTZlyEc)A#DC zSY6bv%BZ(j4F3ZTE*9|yON`T*wc2Vy*lpJt#RA3( zp!-aD+l?2TY~XykGp1^5gJz3{NA_HZR}^(T$NSZN?6yTGaY%e>0bgB0OKwe*^mf~; z&#>Ho@*S0>)38e!w-r5>_ffh#Mg%RDU3$o9^40@-z62Smv#rZ?&eX|a1<~;~= zXZbBpN{4!)h=JpiiPVI?7LE~S$6TOoN)N9fm1-`if_ z=m_&21%&v!q!s50zVXZcxgDzmc$dh-VX)NG_$QbZwsp440tRe~aFoy8YBUArT)AMF zvhuuoS4-x*UPbjElLY@TX7QMi6*fRwyhaUDnmI*k{(GGKN`daKv#i2=Ob1X<*6sT( z9SpeK{h!4gdwN)q7(Dbh;{PEbfG}jVBTbuQ&r!U?qe?#-{OTV>3eeVB=%D$?jCwixLUen%!iit43BMpAxsnc^_OouNG00TqkZu!BHxZy{?=lmm`$t)7ziGKV2 zUtDy!0O{9?%;Q@!Gk$&dHE(XH#HEKrKzzZh9z5i-6jtN8Ja|_&3 zRf~f=U21=ICdz3Z6gm%5-fA0#q6r)lqpDtix*|@MLL^3iSp^FCPh)^|PyH}x^*MzL zRxe*}Y5RA}wc7v|Z%u{?KpaP^S2ar33bu9e|t@O|}<<}ujA z_L5WS(8TJOP9K#0lXRm%kH*PzkbXh>W3gv{7YzqOr_t%8-v>3=1!2f?evpi5Vh%9* zU&BBgGK$PdsTe~h&RzCOhP21{F1^DrWxf@k730&k@o8*bQC?$NrTIRBKO+_YSuq_z z-I^6v+vSupO+Z2c)J^JU)Sd+2L*>)qJVu(Z@Kw`1Jvsm&obI5lW7Fx_Wr zfBp}L`Z!WnFjYGu3r71nQl8;5ab0@>gzUc+DvE*RdAmTvCD~zH$E)LA7*VBn_P=o6 z8W)~a|8W{U0m6VD4&xlAJ-K!l{ZAJbe+M0W0@!?>$*A@t4%$}3yqBs^i6txu!{!IM zeYrs(g$kVp=kSxmp0YzCUf4izppj*nYvRAYYS?hzts<9qg7$CENBh1V`FbJS9N%GW zPPeESk~ACrv8lEmj{$r>XaB8Z1E7BBch)74@U0m4Upqm%cmz){9 zRj+y^#`j?K!`FWZ(^8_-$EWzb#=$wEbI-hH(BJFvrH4{vO; z63iWa@p!LHtoPiv@1Fwo(pSFHyMt2O?WD@h-m&E4ff4j>kNhdwlV|WaR(I&XT|;f+ zc=3`s+4{YX=%|39q+91; zx|Mg?N8j-A|2u}UXl)z0Z;7TND4m2Yq?!sm?B2ayiB)Bj5tAjuXQP=*v8V(|_mb0N zGv0~4&08awBDq-mj>0l%p!s0^D*Gf@aehdDo)R4#zZcw{_z!FVxW)ah4gI+=Si`Ch z*$nLyOt}i_U;XeQ&cr=lQd;xB%SxCEsaX}V3G{4O<@l6z_qEXf69!g;W6e@ztNPwM zo8_(g95@I~G}x9>Y`Oh&i~V$2p;py*agRaXjduKgG79r#_v@V_W_VyG-zm2KbDpQ* zka0CFu9NW2SG24!Ys@3rV42w4G&fo#n^m%v>g*$pVm&N0X(Uq&=*Wr{V!@I0L;Wl4 zwKkQ6#$Pif;!vz#0P#ZB*(6wJZ#cghn1F+9C(_e%U zH3;7F%n3r=Wtee+DUmd1&RR02Gwmg@J~iEfR{wH^xAy;2NJ4T4lrE}Ob9dY~xOZV^}8NH5;>-sPQy z71LY3kj#~!N1WiNKf%oh#i^@hZ#AyRl$S_$&ILF13UitaB5olt4W#Eg8GY+3@#1B( z{5)HI>DOk*yaAub86QL?kxRGwbqD-11v#(f@KO4K`?H|g31#s)Ci{NDi8+;=k7oBd ziTYX3Z!;aju#VvUf|(XwX$->`AG|=znc7mrZ>ja?cd-mfSkSz`Yf44bF$oxuVu;zr8zexq6_-M_PJZ_-?QdUED6Q|CMhJ(q|n*NNrnj zj!tme(uTstTAA$cmy1m(1~JlB^RMlxIZ<=Y|6L0@YJh(HTU6FAO_VXk;qJ62+R1B< zSQ<{~^kWLjw{~eq1ef0iiCJs_sKyXdTYLPihzQCQ6}Ngdn;)mW(?&(I@n-6FA(Tu8 zvT(}3R9Jf*mu0T{&?L+cS$}7wMGeLFxgXpL_yA<{F1&(Z^LOUiL22e{+1hMQU7K!a zJ?PT^;PY*h9`K3OAWlXXSA;tXy`^ZceWp7)V&XMG0mN? zZe;Pfn%V2VQulFc3vJ$qBJjkhcGbs#np-Eq6WPIxC415B>$CL&R(iE?yd@Rd-F$3F zZ*Z%K;-I3j%dV_8!ysb~V;w1E8zF(+W?wiG-zYF=!NBN%^ESw&ql2s7IVxTowuh>o z!Jg2gN&P6>8(7Tb9In3|XjK)`nQx7K|M1ntnJ!TqdQ1A6z`WqWk{amkC#8=w32Np4 zG%-XPa-WxXGAD(Tfc=f@a87J}LF+XK8^?tPS(qKA)va>>Na}#%*d}BxwT2k`vGmrX zTU`iH^fZRl-Ba12`ny4RBP}%?Ox{6JLpk}i2Tt_g=b>6d(oB0oj+>)7RP5Plt*p*} zh5~Q}vRf5ZB1Nh~A}Jtuvc5q=vA6$Sh6(lu&|XuE5bnkCUr z3}qPZYRMoI@tpo-aQYNGp_GekBoauP&=vu0C*fZ~-XDqkdS6TPmww(|4`;EkAb)$O zY~~%QC`4@ZRMglnbfx=P?>)i4#6Q@d^i8PeArTSmfu(e<#-~rK9Ol;_XxP4Qnc^BI zn$8<5sdN6*2nj&7`UDyKr;4PK$Yr`RQT?!lk1cC1s~^_^B_G#gxVWODZgUa%7vP`8 zHR8Y*L7vC_^HiDzvTvE}PcOalnv0}r8y&Xs)YGetZx?BV=w%8=vvNJ}_}$tj#a2Dt zsTW&=lc*0CD=!VOVTXc7>B01>m6WTM#y1fKBoO4CIr-Rlw=}P;ZvwZ`Yf^5#aXYbo zbF8(Emb?c}H`5NMeR~}ZA};xxguvf`lsXPyV58cObn2HY=wB5dbA->HjW?iPBNPPw zK@Ue35;y7_@f);0Rf=!<>AV^-rTZe`@Kab~z3?@MXI2^^F1vPpEc8?5utD^#X^jE? z$*If_ds6wX3O`?oaeufB-8cZflS$5lDP8-~QR({Ob=YRs<;?=Z-xI%#X&``T^YVLr z`J|YO67O}-hbGXu%%KePWk-$gKpj1f4&txqkA`kU5>1M!?0Ll$)>btB<>ckG>zt&j zUP2F4@!O?M5f-MgA9bb+>KS^!>_!@w!1*A&ta zNI{Ygeo+fAQXcH>X`jDzMe2CR4N7|wPkvjQvH0B~I?a27v>zWk_<)5lxuG3w{vzlr zT`TQ=K3a-Vzo{(8d&*KFR(OlaDso4(6zZo_TsQ3z3I644qCn|PKNn2VtC|VAbAj^6 z#QKX?-1Nz)6%y)m^-aCn|MgxS>_8qz{xjuVqpt3~3D>Wi3iDyQuNM;V_fZ&nNlb{< zhVvF|57BQo413a5@^;%SbUJF&=71(AvBw}p=2Vc!?W)BW~>pxt$-2_t;#&k_6?GtXA3l9vQ*m;va_q%M; zezE7b#nSlutf<4Ki8RD^*3}fpjWFaAI_UYYD0!>_m=|24<9y?D%bM$jygcKR=2R6nA-9U#@sflal+l zI`Re<9SGq3_nFj)HpLM4S_wqGghv3FsHq?NqebVWi<(DAD##*phmstgAAdxFw~HoHov!-wamwzoEB3B+=3k#=5- zuG6#HdhAh6=E)bPtBxK+IC3d8))cqkSqh_u!>DM=4Rr{>YekbSY0*mkbNAzC9}>}( zlv&L`@}9z!=hpS3MK$*B?Ec+xr4z52~Up<2|1cX!&te-TKTH1xQ~8O(ZGIRTE*(yqIp6Mlznmyw^=v2*RQa zV&tFvd>;m*@buU>`bZqJkZnq+esLktcg5Sd z%kmk0z zc_16sWV0XFK#u5u>OVs_T8qo@G^VPKwX&ruyeS$tlA}46zE3rOriJ9tNBARF+1xbY zg(K$$l!Q8p$L((h0G|aR&QE1o61uXR`rIX>a^F0uUFE1uqPxsd%$IKFY1_Ycl~Hwud}<(yvhZ?qfL4%ZGXu4L^x2XO$Aa&$AMvT6Rp5!aV!LesKK}jbF0lc z%*04oN-1ky3-s4@7_)9IS}H}9Nf*R0%p{;nq9HmPL4u-P|di@l+v`_idw)22;FJG}x4GL8w zQorNe9`L094I6SP9TFaXJSkk+EH#J;8kYR`3oag-s`I#VhU@ka{a79nug8&_clTZt zE>~XOERVCHRT&BJ&|9hoHd+07>u3KW@kq{Z)`%NYRXY3ZdYT|P9e0y2fcO364l|wdN#L4 zVyw_Jp;^NDw(qMGg3mrZq(^w@D~ELrA6hEp2qcPsm1L5b|CJjW0w(W|O&sVHiVq48 zYz;{Z4tAeBDO@cBJ7G=}TP9>`*|1Wjlcgtwb^bwzzgeGwi44fcF^R=0Jv@}6rF)aJ z=rcLmW}H?m%jeQ;1>^Ok_NS_B_%o{Ld3F&E#uid=>i|@HYWT-dnobR1V`s(ru+vw0 z&-OWv=pW5IG_DYj10$!((FTeq(BCQOHv-X{5x;GhHb*Uz8atAHlJXjS-H;1jpYtJ$GjWXT^%=G*TP}IbIreIe67$kF~US z#rQ^$7EclA+TY~A9DwG7Rl_nekjNE!aJe4~3(6U4}JoPQW={5p< z{x~bNhGJdC5?P`0MZ`ikudlPJb~dhFWp>O$SizQ;J7UV(ZG8$|;*aJ z^3!YRg%1I?YUJ)4qAOlTm>9tm!|PVCLK&Wgrgt0uT-aaqs4>AEAC#9n@lt7U8xCB~ zji&96Efg@=AE@K~;mjlTP$-ZOg3`}P@Jvy57XoM7QXrC`pK`Ne5GcdL>o?vy?l3EG zyqC;e-YSRO3wjRelxYd9Gu}@?UqMgc-}EcX0$`leVHn0)6M)Pph$P6B;k9#W)3V9C zCZ7vC9FCd4*Wx4UEp+dcV!wmAr~D%%2Phh_py1Cs-Nx^u85W6JaR$fLn?2r*&}eD? zVCK&Twv8>>13V(j@XN6zgxUd8NZ2Il=Ai?;{BDv_j}Ga!1YmkR}^!Nz0&Gw>`9~WFe8!CH^!Ja zvlq&A58-3`>x4Yer|p7{>vnD$FRnGyx6@Wcl~v{iGxa;9U;ilGk~XaaTv<_FeEN1# z*Nk4YWAIA~vPg+DeEI$wr<5*)y=wmf{iUrZlC;lx@UDeptK84=4&#U=r0ga2_0WZ>gu%2Do6?mx&Z)x!_&bIi39zBRG&L5+_c+52T{~hV!~1c>Oja44XjQ9?e6eWlwR%#Q z17)#$ab)xPdjT{h4z=i9h+N;UT=`Shz z^iGf__b0(4-bx!R^zF04Yu!QKQTCh%-S1pP$DAE7n+R^Ok)jM~by|>|VGI+WaM%ki zmmj?zp4GAL=S%kQ(&R~fHs+Toux}VAsPp$++YEd{v1HAuTJnd3YC`#=jpWVsD9q>k zNJ|>A$X(j2oqZ^8?vX={l85R}7CN5M1Byz1*<&x=$L$qK5sWO$%3vsUK+>Gn%=JNO zr4?3LrzQA(7!(f9jvv5g%)ud2khtcf%Y>42_{xIv*eTn^pJWsw-t~~!&^P~pK7u;m ztO?!&ba`&I{XF0sx*&szNX;6Wq?K>gWL2P!`n|g*B>;<3-qCS`0c-{Tls`fZB7?!v zV2>XouM3s3)Bt?!UQJX=wZvZOukE<>H))kB_NJ~s~)XRb1 zlZml=rxcBkWT2aAlzpz&`m>GLN>#ct7t1YwNC|UO;2`dT^tC}sD@=QdQCByMlU9h> zlJ7>7=+Jwa;4XKy-i7srZ`a(1{M2JoG02|s_k)Z2qh;01O|SQV=NXj+)XUN{@8_wc zrt`vIfh?ZyV6v?3A&r-=X@etvQOYtzBSEy!ieeW$LXfWHGQD6nozCk{wJ>+}PFr*| zg8{4)7n5Qo@S_R7789@I1pS;`yPCh$23Q{)$??pfLFiTBI_NH~%Cz1{%?CFFPsZ;1 zg>^B0Sn~GPl|%%+dtKCj9Pjse?5=H=DaW!C)--AUbYS)!gZU7i&>h$tw5JVGHE}i& z#>r1>sJ*BmMpge9$3kD3q{*tKe*SUYJ(okRr1hcV;`y0eQjAFOjq!5?A%4M&>l^GJ z-!<&J^f35&AmWVNy{0l$B}(~G=XfnWrW2sQNco7-;hMO7ho`|5{4l|vsgOEE!_ILb z>Y5hfS#vDj(bG)eVH<3AF~)E^?kK1gFN?KSztornY8m zIpc$k53$@SZT7$*p4f%3f&p=vhDXmBNdu0yB_kiB!!gs1>)K?l7>@aJ^~w20Y06;{`V+`U;a3LnWT;Ua&^`19lhv zMJxNfskOQQoIJsvlB|72R+*$o>d0}{{G(0WvHN7NG2rGmLXj!N|hQo@WTjHOfxdhVMWip|hQ9gA= z0K;FCXq(z?rgcQ)uD&>C0gZp|*Pk>=Wl42`d!q-45B@sOgmKH}r*^YZO~iX{l134q zmq+5AkVOx*v&Sr3PGpWq^K@)V3V>~_!zcykuq|9eb&;L0QxLI$5V)C}$6EJk1;#V;yHD+y z?lSITbB8c)5T>=fP7|H`CbhDtu*ez(4=-o}F1%Bb&iDoZLUTba;Rru}7ZLZ(@l7`O zWqb#w^cJkNcbH^6aE>Hk+tbeojVkV#?r;7k)(r3{qT?j>O|c)YNS<$n&+It(-{L_! zZmL%-oW()j&!>s=uiz7|pY)@DOSp)kZ+mLuAga%KYh;-)*&V$;2u=35Ao1NK9As(Z zq0wg9g&7^S0i%C@^c4fXjNP ze+(7p*cXa!!$H>`vcns3wkIqkm@ut4BZ=#GSuF5m(40(yES+Vm1rs`)E#O<&AjigRC#<6cbZY9I5-2WE7h4x4h8W{(Wwp#uq4@FZ}Z zKpHQPN-A|v_wb_gM`-hXg>Qud41pmA;eR%Ca4!hZ`u@;qlP?2|+R_HSKf_9N^5B=R zFk^7{{xQ}3HPF-Z!{vn=AsU3@DpF*2MSZ6ZJjP_*QuD`-X)}1IxQ?!U}3l0({Zuwl%SOn+4aBmk(h?hOwSc|A;996bZ-F zS&|gLxWqXLA&u6NDajG+!uNC&)D3Ku!P`Am%2vgS7s#k7W z8*{s&uq-`v890knxd7rfTtA7rmQa{iylv8hIE@dWk*Y~ydj9@G&Q z?|DzZIDUAGRqN374)fa-k@GrycO!t7Xo;!wSf_5^oGmvv=h#;0bTA!E?%o5vS z_Mm{YUVpxEG7yM-= z^!Eo=O;c8jNl`wHz*YVN=IB1Mv!fW8Vt7ZtaN`5pASX+`pDIsMLa%U|aM8c#0R`YW z`Zx8|BmQ(Oo@Yk+H9z85e$#jJ%MM-4^&C94YP_$cIrgc>C3P*VSd}5+In^g@L*4K5XeK@`(5XMnz-&jHoLb? z<$v6T{=23a{8@%OE9}Fwet+$_r;8Zz2C-ML8tg2_>eNK(&V*>6Yf)gBXy-r615zUB-VKM z`2-2+9Pbh3&7<)sHUNI7=(x3rk(I}pJ7(fxIW9%tc&=V|-_R~oLXjqd#r@WBg(}eac zE*`Zf>^-Y8xjFI6;#djXyr9gs>%ZRqCT)gR3bY7Uf7`HV!p%a(p4NA&?NZ@+@Hyv)gBkD--z<-gmxIZ%8qZ z-Ctp!m!=Q!>57l#8SDqc_U|5evr^Z-ne;Bse5?LCnv)|2G!x$k))r1( zptl_fzi{XWs7Gb-t`#rG53TgS)77WOcA`e#dkXGWS_^>RbL=eZ)`d#7KlRl4EI+GF zyhr}?M2po8@wd?olVbN@F~@i8SMSSU=s#%Vm$Yx`GX_;gj%hj)%2LHI;h|m&S-NdE zn{#4Z4mD1#fOGm7m@Aa9n@k5Dkr}u$5j$9V3m7^|Hc~=ln%1fEkAz)CN_Y_R1O8s5 zLv?@Bkn@KZ&(xx-?^Ipl$`=QK&DGd#6_3$kB3h%_C-;T((w$t*Ay(v^+)Braf{vjF zf!sspv`SN2;Ddj0#>pR*N_PR{&+1oP=GhWxGD}0xt?Bo7j}YDo4vCxfYw*atGuK48 zY^=N68khszq5AV^97!;nq!FA71(d9Q=RQL{sw;%a1Hg@0`xq?$|L_R_+*r##sGDH$S4y4_P= zyz|Pr`lBFr+E7o`$SOD6&#H7ssq!f>!-rQ?7uMiuC~|M^;;U$5@3Yza3dCxkhYCas z`@Q-m#I0`)rF_C$_Ls#Z(b5X@sjyO~g1A2LWkJBNYDf`N1;H6}p?FSc?cJ;_VtG90 z@IZ->B?as-KOp(?=)f9zEL7i==AQd=XLGwuOq|}w1^P-$&BlWw0{11njnDihWk~*k z9KYTxp|$4w&R54)wZ%pLR}` zni~CSP#&P{p$wq0fzHO|DF)SUpQK31 zrxtG01;fK%85|sOl0?C>j-WVu+T;`I+_*X#gXGd?=F^%(mBs5>eEpsS+4-%J1=)$@ zptK1iO&YEtf(H zRue89vE7N0Z3z=2GA*hL+|!zQIb4NW{Mt%k?fJ;yvl}+{G+@gEu(#{M1LHxtL;X2g z=5sYbGs5s~G+52ZpwrpOT)6hfTZm0EH;a}f%a>kFztRE%-M~@m+gZ}qekpruy1(+W z*hAxb>tnUFPDbB8Tyj)>Y$pqq^`~t+6-^SC%0{cfZD%E0>HP)cOm6;oW zk$;V%p-w_Ydg_ivEol-;JqJAV&G9IM3R3)tctsOo+F3DxJ4m+2D^w$)o!BK(Y=Gtk zY7)~J7SHDszD$1DH$sR%sIsazq-fprT1*Nkx0qp4ZyRN~APt8e*JZ5# z)^$nmH+9gW?fJmLKY|Lips@jJaB(Og;^Ja=Pk@!ymr$7OP0=oV!QH62kcOVm{l~n{ zO&d=HyKWXUGXGQ1(oI)ztz`sCYa=!Wm83w^!_o1%!j)Ra-i`DOCRx%iOihY>Oi3Sf zA8$105xYm>_v}^qs0$!5+u(5$5`w*&2NQ6rRP-O-Sb1CIL%i~UylsP|sH2XV z2b5`Nk>*CH1Z4nhbbuba6U47q0%#JhL!sj9)!RrN{Tg)w?5e%;s_d z>OB-4)bS4+_58CUm9#!^t%f(gc-#n|F1t|a~+UW@#v3O^S^r`!zY+=b0>>*d>=IPh3{XD-*WOBOn zJLWZ{6p~P=$)f>S!x&%<@9(uSyEEYB0xyJ0#>&MdL1k>DtU*Fx^b*$4HUNaeMqlA? zZj?_8cW`u($s-Q#Dg+t@5k@D==n*btc7x>YWwGmmIFH$5BXxM`+ ztI7YaDnc9mbjCYz{nj}MU*N|o9&hIU$KFJL^!q8NMn$Jzz1NJ>BQtWqud{Ck`SM%) zDs%JHkhIR%>0JG}K+w3bj)DM&w@R+V?cnNwzEex_-kR^{`aHxP(Dj7`-+LPdo$#x%!k`@hAR%o;Dd*p3 zz;>fh4(U~qbr!^`e?Tnvf0PZHu0;Qr;O7KO&}LqJ600QYdgO-J*HsFAWrpqxml+u|{>5b_dv5gX+4Jwp>;a`-Um@$5Q`qK}BeN z*9)R?>v|}My-yN|l_8QtSbc-!=B^^e9R#DHZR9!kumxSPWk-H)kMdi6o88HGaWid= z**47C@C)+@SYH6JS%?Y7j=+HZH%m5^lil=ou!J3NL3m~nnPYVj$dUm{QjM)=;mevR zTLye{-~~tBaOx&1ti1OFuAEFseLL-b8Wy=A83nL2qHS@4FsbL^TO8Fn!$hynOS+gf zRAVnqQH@)Y*EBWLeFGRyT>z5^tgDpd+xFxcdAlcO*7ppG)UXJ9^0ON6ZpAjWqn4D8i;j5*!4F0SNo4f#GKR+T{lGB1SSlNoI&n^4^cl3 zaylx&b;{E>OE@=p(=^s-ir5HWQdwco1vy+YvOb5u)19ZlwCCnZK2xLnk5dLB+jtG~XrjCw729{j)>ahk318U|J3`82Bb2kC@ z7FNiUzyFWwHd?QhWN{oh(k`jYa5tK2ZR!k7)2iR%-|@nvuHcs@51C3hrry9YyRW*j zYT9cO)bt2g2>8rK)C82BJEkMkb+~eW=X2}B{nGcM+cYKi7;2(k9Y6s3yV8EW;272c z*751V0&I>Tg=6Rb`>y)BRbj1bzhr=&?-2&>9uhWL?jS98ldH3rQAi$kN*7#j3yHfn z)Q6-_Cmt|cBNStoeWWvmlk+&Idl7z}$4SHkDAtQJ0K#-z%{7jI zHXdzA@> = ({ const markAsPlayedStatus = useMarkAsPlayed(item); - if (from === "(home)" || from === "(search)" || from === "(libraries)") + if ( + from === "(home)" || + from === "(search)" || + from === "(libraries)" || + from === "(favorites)" + ) return ( diff --git a/components/home/Favorites.tsx b/components/home/Favorites.tsx new file mode 100644 index 00000000..90e55b1a --- /dev/null +++ b/components/home/Favorites.tsx @@ -0,0 +1,119 @@ +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useAtom } from "jotai"; +import { View } from "react-native"; +import { ScrollingCollectionList } from "./ScrollingCollectionList"; +import { useCallback } from "react"; +import { BaseItemKind } from "@jellyfin/sdk/lib/generated-client"; + +export const Favorites = () => { + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const fetchFavoritesByType = useCallback( + async (itemType: BaseItemKind) => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id!, + sortBy: ["SeriesSortName", "SortName"], + sortOrder: ["Ascending"], + filters: ["IsFavorite"], + recursive: true, + fields: ["PrimaryImageAspectRatio"], + collapseBoxSetItems: false, + excludeLocationTypes: ["Virtual"], + enableTotalRecordCount: false, + limit: 20, + includeItemTypes: [itemType], + }); + return response.data.Items || []; + }, + [api, user] + ); + + const fetchFavoriteSeries = useCallback( + () => fetchFavoritesByType("Series"), + [fetchFavoritesByType] + ); + const fetchFavoriteMovies = useCallback( + () => fetchFavoritesByType("Movie"), + [fetchFavoritesByType] + ); + const fetchFavoriteEpisodes = useCallback( + () => fetchFavoritesByType("Episode"), + [fetchFavoritesByType] + ); + const fetchFavoriteVideos = useCallback( + () => fetchFavoritesByType("Video"), + [fetchFavoritesByType] + ); + const fetchFavoriteBoxsets = useCallback( + () => fetchFavoritesByType("BoxSet"), + [fetchFavoritesByType] + ); + const fetchFavoritePlaylists = useCallback( + () => fetchFavoritesByType("Playlist"), + [fetchFavoritesByType] + ); + const fetchFavoriteMusicAlbum = useCallback( + () => fetchFavoritesByType("MusicAlbum"), + [fetchFavoritesByType] + ); + const fetchFavoriteAudio = useCallback( + () => fetchFavoritesByType("Audio"), + [fetchFavoritesByType] + ); + + return ( + + + + + + + + + + + ); +}; diff --git a/components/home/ScrollingCollectionList.tsx b/components/home/ScrollingCollectionList.tsx index 04dd6004..2fddec7f 100644 --- a/components/home/ScrollingCollectionList.tsx +++ b/components/home/ScrollingCollectionList.tsx @@ -11,6 +11,7 @@ import ContinueWatchingPoster from "../ContinueWatchingPoster"; import { ItemCardText } from "../ItemCardText"; import { TouchableItemRouter } from "../common/TouchableItemRouter"; import SeriesPoster from "../posters/SeriesPoster"; +import { useEffect } from "react"; interface Props extends ViewProps { title?: string | null; @@ -18,6 +19,7 @@ interface Props extends ViewProps { disabled?: boolean; queryKey: QueryKey; queryFn: QueryFunction; + hideIfEmpty?: boolean; } export const ScrollingCollectionList: React.FC = ({ @@ -26,10 +28,9 @@ export const ScrollingCollectionList: React.FC = ({ disabled = false, queryFn, queryKey, + hideIfEmpty = false, ...props }) => { - // console.log(queryKey); - const { data, isLoading } = useQuery({ queryKey: queryKey, queryFn, @@ -41,6 +42,8 @@ export const ScrollingCollectionList: React.FC = ({ if (disabled || !title) return null; + if (hideIfEmpty === true && data?.length === 0) return null; + return ( @@ -86,11 +89,9 @@ export const ScrollingCollectionList: React.FC = ({ {item.Type === "Episode" && orientation === "horizontal" && (