From f0aefafbac140a9253eabf412ddd1e6e846cfd43 Mon Sep 17 00:00:00 2001 From: Aditya Kshirsagar Date: Sun, 14 Sep 2025 19:29:48 -0500 Subject: [PATCH 1/2] remove link from description when having link button on event modal --- app/(tabs)/events.tsx | 4 ++-- lib/linkUtils.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/(tabs)/events.tsx b/app/(tabs)/events.tsx index 4b3a217..7f22fb9 100644 --- a/app/(tabs)/events.tsx +++ b/app/(tabs)/events.tsx @@ -27,7 +27,7 @@ import { useAppSelector, useAppDispatch, RootState } from '@/lib/store'; import { triggerIfEnabled } from '@/lib/haptics'; import { toggleFavorite } from '@/lib/slices/favoritesSlice'; import Toast from 'react-native-toast-message'; -import { parseEventLink } from '@/lib/linkUtils'; +import { parseEventLink, stripEventLinks } from '@/lib/linkUtils'; const dayTabs = [ { label: 'TUE', dayNumber: 2, barColor: '#4F0202' }, @@ -373,7 +373,7 @@ const EventsScreen = () => { > {selectedEvent?.description === 'none' ? 'No description available' - : selectedEvent?.description} + : stripEventLinks(selectedEvent?.description || '')} diff --git a/lib/linkUtils.ts b/lib/linkUtils.ts index 5e762fb..33b29ab 100644 --- a/lib/linkUtils.ts +++ b/lib/linkUtils.ts @@ -46,3 +46,13 @@ export function parseEventLink(description: string): ParsedLink | null { return null; } + +export function stripEventLinks(description: string): string { + if (!description) return ''; + + const lines = description.split('\n'); + return lines + .filter(line => !line.trim().startsWith(':link:')) // drop link lines + .join('\n') + .trim(); +} \ No newline at end of file From 872adb0ff82fe8635b3a8163c8ab4cb49680b257 Mon Sep 17 00:00:00 2001 From: Aditya Kshirsagar Date: Tue, 16 Sep 2025 19:07:51 -0500 Subject: [PATCH 2/2] changed stuff for android leaderboard --- android/app/build.gradle | 2 +- .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 3300 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 2048 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 4535 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 7345 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 10108 -> 0 bytes android/app/src/main/res/values/strings.xml | 2 +- app/(tabs)/leaderboard/leaderboard.tsx | 82 +++++++++--------- app/(tabs)/scanner/scanner_user.tsx | 10 +-- components/leaderboard/LeaderboardList.tsx | 62 +++++++++---- components/scanner/QRDisplay.tsx | 41 ++++++++- lib/linkUtils.ts | 7 -- package.json | 2 +- 13 files changed, 132 insertions(+), 76 deletions(-) delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/android/app/build.gradle b/android/app/build.gradle index 1cb0224..bba3770 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -99,7 +99,7 @@ android { applicationId 'com.reflectionsprojections' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 3 + versionCode 5 versionName "1.0.0" } signingConfigs { diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index 7fae0ccbcfa5c1de75a85a4420f0d8a01e2b0744..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3300 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 e9N(PdoBI!RhPIaWKqK)00000L4erCM_f+B z>w072@tP7(_nn_%YNVSx%hHMBK7ThLxcKg9a@6~HVdm~UU5-Uog`IcdJx_(mJ1Av- zJk^{DeRPY)IoD*zDh~1ysr`wY8ugQE{l}1WJV8$oUMuFeP|)$rwrr$S%p!#D9qddKUG2&f`e4_ssy zp0fwMrKr|%ZE=}Vsst~b)0oILl7|TR)meg+6NNTouplCu<3q(zv3-wh?1bPpQ-p27 z24ncET7r5xy7@!yW_fdD3LD}Sw3l(r`*>=Fn;WhkomM0qVT9msrSdw?`>wQI)q+6n zg%Q1-1FkbW3W6=aELIh{@^1!PHr2e>f;?OH?T&V&@}{=Y%}*S+jS!_PRSt{S)H*O| zl_s(n7sdOd9^}9)<=488bmOls5tGNq(O+8+6mvpZpFFE|1=#C#mHIeZa3)T#Q*Ebn z>T04*{&U&(^xW>0@VgtsI%aeCWATz;7NKt_h!1NyyVhKVsT4u95_ zN5c(vILRW#e%b_W$*Smn`!>s5$ z9GWw~rVDFJoExq>;xG6awWV!wn(owE*AtYv%m3F2X5~ouXdtCC2Tm|4eTgpVN0*<{ z?^Cc7dtCrc?syR(K^qDPA(o}M9fOHgw-T2ODkxm!j*OToYGd((K-(Fdbi*yJ10F=p(u z#1ynNsHU628apR$nOSH5Z5v)%qnLXEcU0PpR zQeSOL=Wkzn!C+ym=kMTMs)xg7(D;b8_1}76&wejgfvL2-k2iKblJhFmGdd|L6)fbQ zGt{Lb*Q(44{khMKoE*(b8wiQ7t8!KE(O-qqts++h>W?22LZh1SE}`{aGf28Q&Ew;; zq)<@~RX$YwYL7yR8}49)9I|js3^x7oqfkoc@%8U>l;#QfFlN0MYRQdN9AH)Yt*7To zalchtFT5|f8nhGS;{hQ>N!iqG)g^ZjDu*q%=Nr6hTevolV>EJuOrF><3o9m0L(3c? z!bmY0BxKvyOIyxN{`~migZ!U$J)QCG^=mk2s7HkQ+f(F)t!8f3K z7Z%|2p|b!Z^YcEBxHFazY|0YRma20P&kXSO-g&~;m-qcUgvtx>;lM+gw^<~##CYD& zwBKTeWJ&~$&TbB8i6F(Ydlj*gnWxSsSRVpi<2#F|1!`f%XX$xvWh%t1SySHN$Fh({ z%fCIQkpPs{x}g>k2z%tT)R6gYa~C3}wQ_ivfVHy&vrPnQ5YkBX%^#~r^L7+iEXFJR@rYz~-MSWpMrBuP3l_A1tj z?|=D^tbyM8&&N4$=hcxehVPs6ghXId&3Qu)ZJ)k_Z5B}U?)@99s>5?P>yeYo)p(|0 zb0HDEHpSbYw3i3H7K@9tDve>`JFWbhT1w6~dhdt~1?YR@T~5tu!R+0eOv@8U-lfG} zyh!Hpi~)AcOJAyP3%8h$y|8EcOx<=DL%?W?y5bm;Alrbd(#@fs|oVz6%nuvjE~_c+n;pXA@jvYPWCtok6ZCkwEywn&TfgMPh$N3q@ ze9r5TpN%JJK_EFmo-%ll5-(%3vH=y0)hCnnyFh>j_%f*Lsszz!L?ECf+6L-Y#vjuo zsqf5EXj=?aH`5uAk?s;IfoFfI_N%qUIj!X1rFqsKM*|33K-2bpZPg0lSWPawDM@b* z6KC+GrrU}j>k~2k+t>ogFibhtJ6dk)-jE}+z- zn5>1NrtA-**m&y5aJ89SD}w9u{>ErCiuYSrTHRwyF}vx9HPvv2M?r5*Tp8J=AVcsn;W^uqitNIqyt( zJm$poj13PLArS@;y zLmm`xM3Ocnd*%KpFdgVR6QwLl0c~wDG0W4ngNl(Q8&K&q`3ikqhCV%zX|I$@Hj?_B z%fm!cI;{-UP0T3h6&*19ZzoQAr};i_kS*i0qY-N`g6w97VGRG%5slc~&qZK{qRYAZ#~&fU>T zf%oZCv0#Uk?u?4uI#N$iXEXw>agA>&Ny_n67`v@6eT!Dv;OTX}5)I%hkhVCR z__pVyY0t#O-bd#zT*!HTGAlpd*rL;LcupitO_58ZCwmlE=yz-mlZSzHT{+b#&h3d7 z(`H9n?1=1G-V4$KTSZ0m?ZgDH`pcIu(b4FRl07><$7@)iOJ9bG11wJ@wjF>=e8$Lc ziUg3Tx!APxcH~2M(m-&sD5#WrdTE^3*N1#LuFe$+1BW_3<(tY`lEV|cjA z>=@3hV>51GOJ6>jY-ev49$KvQ`M=X~QDmnT_8}VIh%k?p)^Xq|bFoxl%r#D*z2Fic zNAi})K1_*e;ei;CyQ0tkP}bbn+F6Mm=mKsFSgVo!Wo{a zHpsF~Bv5S1RP>!w&0VH2^6S|GyXEDhsOy&P@iB+Jx?VhKYPtiwoUbL21wzeWVQ;*s zbH!!yc%q+{B^fmKBSbHWrr=?979lyl)7j)%cc8YtCRwA&fpbvPN_*s8Y$R9%Sat&VHOdTf|3$z zwhr((M}z2u1j!JNGa=-JUs5W66{z_Pde5E<pxV7U_Xh6; zVpc%*?-rg*1xGZ?X1N(&HM?O%Q&( zsax3<`j8wG&H;mLRA0SO7_;XQBUq5dDimbsX6Et@e*Hl%U-UhPklX@;QK?o>T1o`` z0hQR;5z=D97>%`+i*Mit9kq9#`SeMxjQ@3R4rT!8+yfNu`|pD_qaL_C2p5m#;Mda_ ztg%804>GQxoz5zp!LXM>yZ{OG)k#x_+6x)!Ut6zm<&#!t<-qX4oM%BU0mv%>WDWKP zyo;P!Ps-}+s|6_iaWVZaYZ^NMqTe(r>9UZ4cvcXcAF{MPA!aU z?7Q@f-L~jnHQa556nocd$pOp*&4YLWd_YT9^s{Z@?33nZ4$#e&C(hZ2ls5(fCVu>Y z0j%=4cs;vr+4VVUwoY2ZDp*u z8Ns6D+rmN-Smok=HPB%)O<^)7!TQ6rAZ=eFMsIRiprGsLh;Sfow51P}|31mI2V)}u zGd*`M!?h}PeB659eodi(8qXkH9y|~ZC=dK09UKj^6ub)w`}_ked`u32w48YEoMRb( z#sL5wE`R3ZpWhl_L7lG=ZGbFwr5{aP9pYE$SC8K3C13}71_y=JHR6D^0$*DFa@sb2 zU76zrg^{%q2}UV5rZFNmQ#w?gq~AZuGZ+!rv;!j;0KMpN<=S+YK~FUKy6zGiwBDPu z_z{HL#zt`}RbHTDM+Ot8D<~c-Qsuz)>9=WV9@6eg?DKkvUSU{ZY8{XkUq5**dN=sL zXF$2kGxq|E(A?kF1u_t@d=iM1aV?z>llLF6%k{9uNg;7iVGt$_h_fF)P}901M#2rs zZzA%bBEPljci2K6j=O+k%wQ;GF!XSGy1M5BWhksn)Qn(yBhci@cE99Db_?~1hBi!V^EX4F zBwmx&`t?3$%-zv{;za#oNIms#rJ20g=_G5LLVq~b^>eo*K4dMJdRvv*RCd9)zFE2J z2Wi3uHtC*j?OK$+pj)cCPm6u zEH?CFNq}@7q9^@~4H}=FwqFVPb64t7MZ6A%*o?D44}Rzl*A6cuR%vukCa5G_PCP1Z z{tm}^s!N-lSlN13`v+EY&z$LA1c%wpaimLkCeXZ$&+3&oSP$jrptm4Z$$ig%EWx?w`2<(rLX>zL*?$q!` zS)d=eXc?9oCGBstNf%^CED8C$cLy2!p|9wwq0pu>>xvZOn`q z&_bAvpC+b#UXh2)iteyLswH!h2LpX(XScoMXQNDlM;xjT5V!*Jr6r-s*wEC@Q*qA~ zyC)}B_3^L9@b<={!wh_ zk3uRdUGC5^xoeEmI;V`dKm7P~gX`DD)V6Vj-u`|w3hr3#aHEX|XGcWL%?VW7?D)8i zBw0gBw5+@wt1T5H+UinPcKPAChIvls-MbFF`32_e)XFTgZgz#fk1ObY6A)S75XQJu zhSRy{BC0MW8q#;C%+TJN_`a$p*YxniAXN;}=OO{SogG!jqfR`P$7!`WX-lDw9xW&> z{i6d%o~katR*;!_=<)FN_Abs28L<2J!MIwG50f>day(r8ov{&z}Ci!KDCozB_O$eI`E zH9r4z%9P2SpTDaknc%MN@)O}+s4vfy&QXh|;6xRu^(V#i)g`yH7CewW2(poTp@c>H zj-+O3-&f6VR5A3pyVjCqk@c*a!qhmoW?S0JW@|!3t({1{APs9 zZ8M#qM8-_jQ6L;MUV22ICa;4Bnc<#ROPe7-z4UxcN#0}&ja(f01VvVSi@yik0v)6B zGZnJA+f5|NKtV*o9gi8xUru0z82g%^e?4=t>!W9mWrbz+(8E*eg39e%&tF^O#K%W{ zyKyc|JvMaaYmk-vT*;AV5xaJ6{zEzo^7unvUt~D4zbJ#7ys|>OYZpMTz}9%3C8t2n zx3WS`q6MZoE`1flkA1Ve4T=xR-U29+NZAc9)zc%FFwV7oQm>r2%HQhhsxa-PCsEl& zD!ZKQ`i8S-pP1!8aVH@^zR%BlAgvY-%$v5au0cK{ShyNz8(4phBHQ^i&$qat;Nc)+ ze6vS=OpLUFJoAp-m)~iqT$?Xp-WhIKRK%O_JIHQh@5|r@ewdMJDCpEOD($B}pW(#H z!IcBVPrbyT-YwoiA3S z4<0^zWyC7M^8V*O21BNM@>yac0R7o>G8YVro3+BrKA1AMRwdNH3puq`dFbZyj^y>` zJ7S3M1B!ENYj;5*4B{d$zu#|vS~X8&%!X+9&XAJV>%6obFz}xhWy=Z?4AF)ndt0UE zu3Z&fXeuIoDuR)^>k77ETSATYp@%`5a6ixRR@QU|w)lE84$l#N!?RV*lVSMW2wfCL z7)$6&q$oGPIo05LvBiBaf+kCj%yN1_`GQw3FoZQu=5#rz9!h^;1O)(T^lnBzJaN1| zuh-6$c|iDef(ZUNx{ie#L8u99P80PllkU-ZfOlbG@$+d53#T&}n@ZjAGCOA1u%#T~Tt&U#rg-_e()ik+C*QStti< zV*dU|janJ5J}6`#MDJ@WAWp&nRL9rL4ip^c=TAulhb)d5U^>>YYopE2M_gfZBVk^^ z*>5I*{GtuX6MJ46oNPG!XO!I2kILq1VrpVSiuHVWV+7)pEaLu{nFQJB=5cFjhov$H zo-jM=uWv$r?%nu%Fzv(5*UkSCH=DS{>uUm!L;4Oqb!~K0R?b{m_HSPkz^R# zA8OX#?JJhac=xpaWYb@^RFCcj4|Hw9dzdyyNfax>Ghw`{OvC!S=~g6kG3o>KV({F)B)K`1A*E zOS>Ss0$MCv+^z8a?0b|i|Fm{~`zE)#c5Gn4VM($wzZDk+?XYF^L@?$0)$oxDy|Vd- zqjfD9;=?S<-5D0;anE0~y~zHCvI_v??g3~GiwY;o@l(0jn(O?hb!-tQ$-%())|N4{QG1R#!ke&5VI)V6l`7<$Cz`MHYmT{1h>E}K|Asqd?XHHP`TQTJU$NA{+g z>38du>ZljlF41~aq1?@hq3zjejbs(f6B^Bz>cK(nyl}y@q~8RJ*x^HGSv%MTh0hA$d7=0nK(aOD% zyyXO-GjV2Kd8W*Z(u6tFBAkxb+1fLHi9f!&c6ihWQvUsOp**o5y5knBSO2} zGjZ(k>w}%-C)H0UTf_W|v%VU1N@nD7-REuei%VuNvPxQBh zr&gjHLxrvJ&$E5()=~0V{FMw=3GW?l;qQ+ka4wzK_hKzd2j(A5N_!hT+-soRGMZ!$ zyvQ-2W$Vg=k-FiQq;GNc%7o6*+9eK1f6WlY(Pji zJoV|1V?>pZigOKfeg?(v#sbq_=ukWzHAWA8%QXozAZcJK$S zC(<1cJzKdi3n+)gmOZq#{f{nPyJk};1EZa|zQ|>o6|`K)k9kwxX>@V~)w-lg1@qcm z{ks<>BL=IxchryS0Z=&{Y%xLyen<5f3^sztnzpHMsu&J#^r)B4?hdKI>DZEZKq5-> z`wo!mTo4e8o9Y=lIqRqd!;YE-22B4f4#(R|uBWNGS}uC#pVpgP)OQ%j?v1UtseF^i z@aa{BWL9&MJp27v)3$JArto^!^!X>JodT{@uUB^4-}33l!a(w<5!vWtsBQBbYiUAk zy-!nGS|g4;+O=qg-cRuL-X$3?-XlWb0x`BW>4pb}VWR<2J6JhdSCOwI zR_%t5-AUdKXFJV)D9e;N+O^>E&X9rYvpxMx53kcb`J~==bie(fK4s=Y zOg%J;y#0@a4{8f*(ktL3l3L%FXV|a353b7Yg~{JZ9(*I+xtmOBJYMKeS7r~>4_P>c z#5!y4i49`-mMl%Bx~mYk$9#De7i*}90^kC>*awLWVXKJ1gZcItF@(UN$9~6IGn{0 zleG`oRYzWPMD{3c8u}_mmRoydXnz2Mtr|L#n6EFRB;M2X`};zF6-;pT5LNI85U=0S z3ojGSQO!GY>jeFUj4#L!gS!Kxdl=|LJnl9My&;^R~8_{Qu~RQ+%;OxY)6}m zXtJaDjhFi$o)XjyZ@R|xX;Q*S09K73l6Sl)$-veQzr`(BkzI1P=Hhfd=NNPgIGdMf zVR*{1B{`>7Rc7S+uN+INq;Bt5KTQ@&cpX7)zW$!Skv6*!7G7pu60kmloYOPC%6!2q zX|0fZ`Cnj7m{U)69bvU9D}%asv!h~leIq!ztP=aI-dk2c*KcW@yTd9`>z3{ls^<$- z`mm&0w|{O~)ee^wwc#vGHKeX`ikr2$>0DHZPgavD)H}q_4*eFFTUFe6gKv^{jOb_T zO7)oNO`tVCYg|t7j&;^8im69AAU3k2MpWz%4dN7)o*6PkcOyL5u(rldHlX+TQEwt+ zNw!UHUM8Mt5MZAyZxc`JCbvHqJV;su-*qZGqJqKxrnEsBW}s@cZS}hwF->m!F%r#h z1W2V5E$(W}NRYX5XmL?|I~ggHa{WML_od%i&ybFUEy@qN-P4e zn4v_f9j)V{K75Z;#%Fo6v-JOoQ|6;S7?&E6v@(?HZeeR)sfL$%>A>2LZf3|!X3_-e z8LSX&O@0gzCmrTP%EZzW9rwcCkDle89Uc5U|6*Vk7Wn?iex7Y#1&C)=r`9i+roTkv z0UF?Lc~b^HdDxmj3M<<9Y#ODI7fg}O@3XM1(C5zOd&=ZgXIc8bo{}KD+~hU03`%v- z_=~oc5BzUV8BBmk@KcbC{~W7Z&j$(&5=3ME5dd+V-0mKI<^d%SkAzfi;0mWxZef|& zaMAoc$SXNQ#SE}QC3++y{7szP>Dn`sBISVWO>VI8Bwq8b3^Q>=(mga%O$9?&{TA0h zuP#YJL?0f8E~Ih3FUAz)&t@e$48o7H^~!Ma!+fJYAqPdh}fv_BS07MjlVaZI|Gm8MtxW)zJMq{#Xe=jxR;ld(1G ziN97V0dawv9$j0?loPX*_FckuOAFmp{C%FS3M;y{wrHzSkU=k`UkYw{WBt2pL;p}# z+H$-Y7{P9`(QxoBV0cJ1NQ~-M+>RfsimPk416o20B)mmQy!rMQo_ey`u`$w@;>Wma z*N%~0ntl_6G`@*j=54a*Wy1PwQl+g5*tMNI(3%G?gmEz7$MTC1taYc^Dcxns+MZ@*hPyKB`1@u z3Kqf%jROfMIzIXsmW-sP-gwV9;y)z__5_lH+Rp;d`~Nu`ZT2iXN?q!zMp0i3DKkxA zR2dYYATKoWlyI@Vlr9uY8XF8u;+sG(6%~%3XtbLD>#s*IS67cgdO)r^f`o|skjv-) z0YZCeZDn?;1ifW^5lYpHUhzMU3Z3xrlew4Oz?Jd4X@EN2+!`Rky_kKK=n-WGiGtga zj|IU|M+tcs_`b$P3lurjLxL7e0crr)!?(Bs`#K6-l8lCSbarm= zjW7=PECQPj|4|n*WOkIS+i)(}HCH9oqtVgz&C7@v>*Z158L)S9a@|POD1-cx=Fn-ta_eQoa#F zHxWQ5cvPrrpC-TEcn{_C~j5q2)#@|3Fp~PrHz7de>-O?N_H!@2s)py<$C@eTJ7-E;K zZ}JYIoBAL*2OoXF*6i<=rX}kO;mOjzBYUcWcxb_UWa&dz(wr%>BPa);TZ1!gk6bpJ z12$p6W0jTLerwZ}D`rsSmDpU%itaXmx_TFU4I3YVHzgS$2@o<^epoC}VNH$L2KExz zdIJ&E2X5Mezdr)$p*bVfr1Qq&o~ur$O%yoU^e&E>*!^7DRgMybg0a;v|%dr(n+79`8?dRNH5Ni;zXL>7vdcw;tSYDQ)&5AbMo+x-cylG&70Cc3bb|3V%10L+D`FS}W??PJI^XD^j z5ELk{)WOe^DH&&Oxy+=`EMzZ+HoVPx94(mMbHKYax zTQ`*ssA%}uP}XSQmOYb*#J6#BW3c=XgwS^XoSZ-A)7S%?9pVTFho=*$ouCA-tu4C( zKAhC(S5V9WlQGz6KQj`1M}MkEvxg6(%eD5de)Wfe9 zuP$>^XJg~jkSrXKz@4S)AVOAhP^ZO{y%Y>!SnjM zXa&03Q1%Q|+IMyEoaG%W0XKU@a9R+1=w26*YRzuiv3*F|Mw@hSDnQ-<+U`vCg_i(^ z?fBcj=B~&e)FAAJCkQlD>@f>$^e+TIn8louJ_kQ$c84Rmnqc=r{lk4%A@`uZ&e zTu9(v-{Ou@WPxQV(b<0F0~iV5GD4&cpm2e$?` zw!sywvsPA`uoB&hUS8Ju@|Oexy`@qzr|ZtYs%mb6i~n*K``>$C7n5M!T*x&L`f#ShPL__Sd zdZ8F$qf5*;qEsclakG2CtA#QZ%*a;QW+K@VRi-n58jTi!4WgeC_qPIJ@M@`O1Md3vt6m2n%YT<0TnlT0p-A4R;HJulv{Gx z^7#MX3xWM_E>jGG^ZnBYzJxuAe9BJ83I%W89H9K+MZZd{uWwi{C)5%~W)buLfg6?Z zp;JDQF=m7Hq8utO62mR3HO--J#a=SHsU^LTLcrkH-#(0G?4{kL5$oa8fcni~JKD{| z+cbr3VYT_#YuMfZF9Vi)mZVxop}0F_O2fUPHo)s(MfZCPlIha$!UNJz34XT93HR{M zjI{r?BF>}sL@#(EJeE!j@XF{}SCdng_awaK^XC^&={R{Y=Pr3YuR8qb3NZ&|wV~i; z=?i>;9(9YX%|$J3s`eUQ>ihbU8_MU`$`ntyc+y*@u|o@t!xg)`Lzef?9&&K~K16cp zZ$s`ywEeqt*2)fy(4T)ztU^2bdCBwHmx-vp7UY4hSNAJ7bCFX%SrYZw%JxX-O~RdU zt8#)<`Jx#gcerBhy36Y>+imEdRaS9Wb;9k;6{GZAfofp2r@hA3MaGZ_?%1|=m2i3j ztJWKJ$U%L6Z8M(L^SAqYi(R`$%(%!7dJmqavn69qiJmUi|Du+Tv+OcK1w+_dhY2*agdHai1nel?MuP$xjEkPzk z(}FAselAB+e^0E4XX)lpGgBr`!1DHzyGHL}xXMW?X>HW`IC!lDzbDrWW2Qcft*#$T zdt8((sn7lyW6J#HqOnyWd2?XR7A{iWLGE*OmT%SGTD-L_B~06<7GI2Kor>6@C2OOb zr9M4R!}g~0jCHdtK1W~5%38{hxPHGua`@$uU#5^u;0~!bhieo(E^@3BKjv)zmXmU* z;>hjpJB8P;8|8H%Gf&(Men#R1>^}B#EXsWMm-W2xgqQI(C_j!}X)64D%E#^0(d5$7 zrqO8clI+1P)#S9Hq1~6Pd!5Qxei`S{bWlT+>3yV4Jl?YJKQfjkEISeTu0(G{a$}DJ z(hh;<55B+YQq^mX+Vb$Y18Q`3*Xf4sFwvNpS%|1_(N9U7D`9nl;u?!X_|O`kFqp%_Ut>1nr++HF;NWO3n*8!ta|?$^)TA=)56e$5=)SpP7NfEBJ$N*{dGAz68Tez;V| zT8Ff=AZmK!ugSNtHXtr)(Qd^5)SI5axR(&Am5zZlMk?_T8oaWu z;B`#2tHb2sdp$b#AvB#$_Bb~B8dlhe&myYZ)A9=ne6n$#^hC1wekIwbw=HS|+>FS^ zy&N@39TM5PqJ#4%N4q>SG6@F{{*fY^{AgqeEX2r2Plv?Oufn$~JT}%&Ibn4YCKHvh zrf2MemPlDfCA_exR^_LKTx)K>3MU&!VL&861mLPXAf!|VswoV8HMJ8y$Kr{NN-)nS z;r`Es!Y6D*)pddXHKN7FJ?8^!*+?wo25OXHv z`ySeO(IcZ~yjqOxWDE@ZUxt~@Z>H%CfBC|IOyN%Sqa8a(?|q0C!-(1~RSEmcvJbG_ zneG)45fQdXJE!uB!yXnk1&Ck(mDEyfjwHBknuJZNg=LY`6L{y~;^r6^6n37EYx*G+H z*zo54S(bZfSlE{Xw1kU8qLI1#E_Hx`h=oUN^dFZr8+~~&_oUY9o59>u=7FZ(5$Dg_ zl)6gJ!t~NDhQ5EN&se^jmUu_T&f9S)+eyAx8yZv~f<%_Pc9nDmPg~vV`RA>Ydgcj) zZNlTC(2UFle=pUA1E?)O&R;uN-ge3yQ`%`u(;4{q(fG&tIoHI?+|xAlKH!3Ef?tPlPdsfnf;guH8Byk2 zg}Yy0zlG}Gs$WDIxMw=@{kwi_B|MhTH`8M#tXP*>J~G5x-#)D&=0G%GD=bt{R`K1| zXKA56@q6zJutL~(JMPf!fxohG5wY`s@T%J*1rlLh5KGap@Lc?!fqEVLi#Wld=9x*azR zpGFFxKC$~`%ro}VhasdyM_BHDyN{{J6vuu4ns}y>JNs<95ZepiPz*R^Z|pU++XXIX zd((bp8T7SK`Ond5fi>K{?_&-4!};b1^LaB}>JL{mRg)b>t~Gss+U7yF@Q2s+*4}onh%;2VOHPyc$25=`{L_|O`4Ny9tC+Q> zrJaXGQ|B_%(%hsW@a|jm0gbJQ*PoN>?^7<}9Oklsk=*M!-9Y|{{v6#E_j564K5k2R z;kySJa80x1P1x!r6<0(}pY6lg$dG`v74oeqdQ?bFtXkX3otiFE_>xG#JfMHs#~We&C7 zlRo(MYf3UC8f;kLN+tp*5;ObMaoF>?e|T&J7g<@=oUda~N$KiZN+({G_e|p?+Mf+OUIr>fDcR;E{A<9^9whQoLH_<4k@vnh)0iRRpRHY;r+X*6zef zik-&RhbC0DkuPBhn0F798hw+KkxKhXrp0%oVqQL1JOn_5A4%dJPY1rXyX4c=K+B6=1=8WxVyrq>@%d8cY;(y|dmq)+) z%y|+{O2?$zx%sYcTX~geQPWpw)7%d{<5SR%Je8!Yccadqr{bs*8qvZJS&e4_$J4Qg zx&kbjK8#gkB4Ae$Ek?BV0B&VAPC6%e0-7I-BKfAw$N5e9q~UC;di^|N89?1jUVP>N zWm?Re!k(!2sqv}oBiXz(X+yT|$roNj*vkBlP?Lh4@QQTe1PTQbx-0$185L^nJ>{-P8&siMMo4Zi^<$&f@qzC)ZG z6>;`8ya}*83ha*AKR&fu^l*tEQYA?x5qMQ48%@A6aKNg#;Ti*}`&>y)=NhiH`L~_S zUj1=q+sv8EA>F(vE0Wm1QOUEax3EwHme!u8EVF4W>d~1->(Ex9il*LB9L;y_g8g97 zhRk8@02qfRvmY0wk;R`%ddG~e8vs``N(XlQ?Ydy4QNeZvErtZgau;<6?AR*Y>45s< zQsqQKItKV9ln5k24`A~63ubo+(CFr^E%c)7mZ-IrV~47G%`GpY`Ce!`Y;=iUV7(9Q zq=m(3Sznu^fYJaG3?(w!mE}Gmblu=WchHnTkqu zajhv#5`qK+)bcP;m5(3uq3CdtP+Q+Lm@Up!?Rha41t62mCa3vUwI2KRecAZiMxb!% zn79SgLAc$-S<&SQhYXb8FBHpw2Y&j~HmI%u0fQh3q8O@`+CEHp;h~_~b)Q1&eH`-x zq3Tpl*bbUwnir>1XDo}1kf-Oyj_r97R;R4;Q%|M%P1sa#^ZDaO9}am|Edy4X1>B9I z{o}lc$e}BJIAZ&!TiqdtQic-rsD1GCIy?&nFw7jRsTe(du(yzis0Xf00kHJq4v>kE>7{c4w{={m-a>ln}#AFG#l<5jsre>7ti|l z$z(l~bw_Lyjs|#fBkIc_-ecv1^Yegb(1>}SbmH!~`$aI>SFb74McB-u0 z4X=nz46EDgSMLM-w?SED-DS(f?vU0dw@(-rXgeI}sZSx*p00P%`=I()s}t@w{nWOn zfs#nx_#7C|lr&iuCtB>0Yk&lB#UE2_15&~r0Hk2$PP_0z=VPY#$ zuR?Z4*ZNe#bfXAfR9rp|uyFqRM#4{Wrhm1bpfKJ=G zk|+U!z@s93)v|j%NgM80UK$d?tO9PJzIj4Pz0)n`?x}maoGr=fQ7+oUZ;$LV=0Rv7C9Y{}adv2qu%2OCBxpvf`R zx5E%$+^7wZV7m4=Ao7#BbpBc{GJ7>B--WX7kdo}<=@1~(xpDoaS9DPL&}g9g&GxkH zb4-z2a6ZIFBJSDUOK5&4-faTgJCQ=?D*~}dB71*YFXr6p4MouTJ2Ts-eOl=~`Dnff z?)IdYTq#puaHuY4qf3u_sj}OYAfe4NhGaAKWNmv_UY^LV^fmxakrodSXERyjU(Pa#O*a7W$6YdyCt2(wAmBFW~ zzulF(Mm1FYj6RfR4`TlHfqtxfe0silHN&@vAB5nlLWJO-RTt1aFnb%H1d=OB9ddLox3E-R|t#E>M6qciR*3Qb^e9~8e3lL>6WFR0&cAh^*>LEVyFRwe#CiVm zkmyqjX*l-aV-ERSj=zgVhSdDRhx)CcGt-G)2vC<8a3kb_2BZ0XW?u9f-;;VK1yP!A zz-H0>7B;$7>0S>_k`G!-8o?1qllyh-pBZH124ge)^r`-uZZ%%}ZC|{;>xu3&OKV%r zQq;Do@seQnx!M9=05y(=)N2kunk)@r4jG`+Mq}%zXS3)0^c6O1W)Zyr^yEuB5d?*W z7Gx`c$BQttYIGV80mQ+fO)VP1lUvTmi8z8L+$W4MQqeoS@NPv&w;5@r9m0u z&Nn5K#YwF0kj+?a6`(&)Q)KOF5iv2s$cBdXpl-EW`ubF~TL}0nvlcp8%$>E0UMsUH zF{Jy16urSl0T{8Tu~(`Tcixio<_}YOBMS_TjkBn9JL8e*Ekfe%V>{Lf^a=~VPM*u; zaXkM*ig%zqkPe#r=%q z2dM35LUVzw>*mE0FXPNqW#kSYF{6cR`gwMj8K!0h?gdt6*fnAf960T z(}UWCzzVZK_^$4adV!xF?NVzx|HFTh_q#X~`zC zq^z{XNZZ*>{<7WmbHLfa35qpH1WsBB#jn@m&AkRTOKYkHT;t~=yrP! zv}nMNa`y^0=tdsTYI>M|9gLapKMZoHLO0OOv*Z1sfJ-|Q=Hn83l7=H%BkBjt9ORZ0 z6ow`hSc=WCVq!%wbil$UO+Eo$V(NVeY~o^2cCb1&zsGneV`^wy)oXgJ>7Vwvrgxt{ zApsfpfqF8@hxvdXBXyBf22iQ85Zqu(cFYIa13kyRFKoVZOo>)&%Sg!IooD@_4I{FDO^BCxCuv0CsgU!Hae>g2Q3FW=$S!F0MI_My4<<7C%YUp=_OAQ6EI6xI5 zoV7W8AMhVpO-}AB_80*~ycr=g)GK)C~9fRA4 z<3Rd$-h#D;Y82Vd?uh$gX#&==qBId-;tN1d7k32{#HxmggUi(w9;CC~1i5!~{I|Z%Li3-n(eRzMt*HC-11)DaX{7QUODNV@A+swOcfWaK z8W4v1%GLt2tx{eHy5?p7Wu7s7Of);00Vsbom{s4rQK~Q32qRc1{*kdKKW0N3-- z2E*+3TvIY8sWWIBe!WsoPrg9+5%SXbS+LyYS6v~hgUFR{^j!gC!jE4}hHvEK-p4+# zJGogCO4!@x_)s)|hc{YvG5xd1Ig zsqL{_^Bp+D|3UfQdR%m3+u9o~0)ZLu_Z2`W!R&Jdg<4So4A%HzgcluzPH2l!$%~Ng z+r6RKgr3)6Gn5{>JqOIUbtTcIJd)vvXbWQH9hAhaxZNEGct!OO3v22OdPPY=^C1M_ zio#~8fP>Y7riT!Utrs10{rS8xygmh+1Kn06G%Jfyv=R zL6HgDp0f)jCAD^;V6?@p>YWKP9Rmj~uqF{%3Y6lLLnwLMS{FzVaBRT@Z)_~@``fMJ zNNfl`w8Nb~+_FYY`VSCgF48u^x9T#Es$;)}9|6^FDNuKy0=%fBuvTZ^$7Wz)L}A-* zXt@Hxvw%fB{c7m!%)*B0W4~rnwNe6V6cUy9~}gWg8c%o0SJP61X>P^fYV)EF+Yun!byrrJ4-~y0SZiY{2#sG{l^Q z{zE_NcPn@Qek(@WV4|Btdt2LjwByw>b+Dm2Un4bd`BqI1WsXmrTSZWiV#>^n2dGQK zBXlD4ICjF7RLmd!vvZTY7;qCcVO_xgf^tk}-hUVeo*!iZQFUSbEI7_~evL}7P-~hX zLZ{$~pw*LDP$qxP--tJczS%V>15o(NZ~P1d%UJ3Y2*9mvowXX+1;_*46g`OR0jMYP zqKB)Y2M3)mkd>2^q_GFm*Vf~+VZ_Z)x1cZelP!>YpU!0P}j679Y>8Be_sFKfiAQ@s+yOa46ZAP zTO~C#UXpqQ0XwcOEI_4$1b~!*?iv_+EcZ2H&YW?8*K_}BaHsC}$t-fiNf3L`*(3me z-)MBhu)gW%MGU0f!}$0NBFcAtfyYzMb}19_@Q}7|AUupl6rqfcGqY(>yQ_KwEsy4Q z25AEq?UR9cJJ$TxwW=5LrmXW;+L&dEZ02L+UYbr|?d!Wfl>v=P(n2GQ1Pq2gUK$a_ zN!TMjTrfZFk^YxM+fZxrN`ja!ZQtNHWIj0VBY-SWKOy`;)_u4o+*q7l(m#FxhHZQc z?wY{A8fZ4y+F;a>fY1Xal{!$Xgptseaf9ZYsM-Jzvf3|A*8Kb{6g9w&!^LcZT_h}pkq2;PY6XEBJd`#_^>pY9KZULl*!g=Q=AcYu=}}Pt?UCW(N#kc* z{&LmO5>CH2jE~(9gow1ywZpJEZg6A-`~?b&jUIwe$Ij2&BHE;#nD}}A7NBq|o{s#9 zV&3fgJ4*r`5~xe<0nY)IemBm+6AlG4VMktgj2SR>c@Ro779i`GGA}&_Lty&S~)c9QFL%zBfY$ zpzfrLy22S~j^_%m!6^dZV(=t@8R!>;)Lu-v>ynO&69m22Ff*XVzni0xM*n?SViD}H>j&X1L)!lw zlZXe`1&GqX`+!GrO-5PmK9Fb`Ym!>!S_hzj1)1CdwH8vlu5xT5o2EmokppH3RqLh6 z(8eR%q&>=e;_Yyt;bn86+xI$>7?RtexgT0SP;E!TKn&T7g_BRJ=1UvM@UZT2^+R|GpG-LW{qm5zLoM-Lf54IJs`y zV6^Y{=|`X1{M#c_hh$i_;N-U$L;o7h_e9gN$>s;0=)-JJbgUBSX~}D_52Qz*l6sP) zKd5ds8Ugz$31in{G-46pWyg@{Di8+L!9K!=u1Tt%P?*A+af9GggrumJi`zaaH?*J{l0^?l{UvO{vR~^z(fE5 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 4842897..aeb672d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - reflectionsprojections + R|P 2025 automatic contain false diff --git a/app/(tabs)/leaderboard/leaderboard.tsx b/app/(tabs)/leaderboard/leaderboard.tsx index 77770ba..47dbe45 100644 --- a/app/(tabs)/leaderboard/leaderboard.tsx +++ b/app/(tabs)/leaderboard/leaderboard.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useCallback } from 'react'; -import { View, PanResponder, Animated, Pressable, Text } from 'react-native'; +import { View, PanResponder, Animated, Pressable, Text, Dimensions, ActivityIndicator } from 'react-native'; import { ThemedText } from '@/components/themed/ThemedText'; import { Header } from '@/components/home/Header'; import { @@ -42,23 +42,28 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) today.getDate(), ).padStart(2, '0')}`; + const dailyLoading = useAppSelector((state) => state.leaderboard.daily.status === 'loading'); + const globalLoading = useAppSelector((state) => state.leaderboard.global.status === 'loading'); + React.useEffect(() => { + if (!attendee?.userId) return; + if (!dailyLeaderboard.day || dailyLeaderboard.day !== dayStr) { dispatch(fetchDailyLeaderboard({ day: dayStr })); } if (globalLeaderboard.leaderboard.length === 0) { dispatch(fetchGlobalLeaderboard({})); } - }, [dayStr]); + }, [dayStr, attendee?.userId]); const dailyUserRank = - dailyLeaderboard.leaderboard.find((x) => x.userId === attendee?.userId)?.rank ?? 0; + dailyLeaderboard?.leaderboard?.find((x) => x.userId === attendee?.userId)?.rank ?? 0; const globalUserRank = - globalLeaderboard.leaderboard.find((x) => x.userId === attendee?.userId)?.rank ?? 0; + globalLeaderboard?.leaderboard?.find((x) => x.userId === attendee?.userId)?.rank ?? 0; const dailyPoints = - dailyLeaderboard.leaderboard.find((x) => x.userId === attendee?.userId)?.points ?? 0; + dailyLeaderboard?.leaderboard?.find((x) => x.userId === attendee?.userId)?.points ?? 0; const globalPoints = - globalLeaderboard.leaderboard.find((x) => x.userId === attendee?.userId)?.points ?? 0; + globalLeaderboard?.leaderboard?.find((x) => x.userId === attendee?.userId)?.points ?? 0; const pan = useRef(new Animated.ValueXY()).current; const listRef = useRef(null); @@ -74,18 +79,12 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) }, []); const handleRankPress = async () => { - await triggerIfEnabled(hapticsEnabled, 'medium'); - listRef.current?.scrollToUser(); - - const userIndex = data.findIndex((p: any) => p.userId === attendee?.userId); - - if (userIndex !== -1 && outerScrollRef.current) { - const ITEM_HEIGHT = 94; - const HEADER_APPROX = 0; - const scrollY = HEADER_APPROX + userIndex * ITEM_HEIGHT; - try { - outerScrollRef.current.scrollTo({ y: scrollY, animated: true }); - } catch {} + try { + await triggerIfEnabled(hapticsEnabled, 'medium'); + // Use the ref to call the method on the child component + listRef.current?.scrollToUser(); + } catch (error) { + console.error('handleRankPress error:', error); } }; const panResponder = useRef( @@ -113,22 +112,27 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) }), ).current; - // No load-more. We'll compute top/user/bottom sections with separators. - const { data, showTopSeparator, topSeparatorIndex, peopleAboveCount, showBottomSeparator, - bottomSeparatorIndex, peopleBelowCount, } = React.useMemo(() => { const src = activeTab === 0 - ? (dailyLeaderboard.leaderboard ?? dailyLeaderboard.leaderboard) - : (globalLeaderboard.leaderboard ?? globalLeaderboard.leaderboard); - if (!src) return { data: [], showSeparator: false, separatorIndex: -1, peopleBetweenCount: 0 }; + ? (dailyLeaderboard?.leaderboard ?? []) + : (globalLeaderboard?.leaderboard ?? []); + if (!src || src.length === 0) + return { + data: [], + showTopSeparator: false, + topSeparatorIndex: -1, + peopleAboveCount: 0, + showBottomSeparator: false, + peopleBelowCount: 0, + }; const mappedData = src.map((p) => ({ rank: p.rank, @@ -146,7 +150,6 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) const CONTEXT_AFTER = 6; const BOTTOM_COUNT = 20; - // If no user found or list small, just show up to TOP_COUNT if (userIndex === -1 || mappedData.length <= TOP_COUNT) { return { data: mappedData.slice(0, Math.min(TOP_COUNT, mappedData.length)), @@ -154,7 +157,6 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) topSeparatorIndex: -1, peopleAboveCount: 0, showBottomSeparator: false, - bottomSeparatorIndex: -1, peopleBelowCount: 0, }; } @@ -164,7 +166,6 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) const contextEnd = Math.min(mappedData.length, userIndex + CONTEXT_AFTER + 1); const userContext = mappedData.slice(contextStart, contextEnd); - // Deduplicate overlaps const seen = new Set(); const pushUnique = (arr: typeof mappedData, into: typeof mappedData) => { for (const item of arr) { @@ -179,17 +180,13 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) pushUnique(top, assembled); pushUnique(userContext, assembled); - // Count everyone before the user's context - const peopleAboveCount = Math.max(0, contextStart); - // Count everyone after the user's context + const peopleAboveCount = Math.max(0, contextStart - TOP_COUNT); const peopleBelowCount = Math.max(0, mappedData.length - contextEnd); const showTopSeparator = peopleAboveCount > 0; const topSeparatorIndex = showTopSeparator ? top.length : -1; const showBottomSeparator = peopleBelowCount > 0; - // Place the bottom separator after the user context (end of assembled array) - const bottomSeparatorIndex = showBottomSeparator ? assembled.length : -1; return { data: assembled, @@ -197,7 +194,6 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) topSeparatorIndex, peopleAboveCount, showBottomSeparator, - bottomSeparatorIndex, peopleBelowCount, }; }, [activeTab, dailyLeaderboard, globalLeaderboard, attendee?.userId]); @@ -208,7 +204,6 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) ref={outerScrollRef} showsVerticalScrollIndicator={false} headerMaxHeight={330} - // No load-more scrolling renderHeaderNavBarComponent={() => (
@@ -348,8 +343,15 @@ const LeaderboardScreen = ({ scrollRef }: { scrollRef?: React.RefObject }) - {activeTab === 0 && - (!dailyLeaderboard || (dailyLeaderboard.leaderboard?.length ?? 0) === 0) ? ( + {activeTab === 0 && dailyLoading && !dailyLeaderboard.leaderboard.length ? ( + + + + ) : activeTab === 1 && globalLoading && !globalLeaderboard.leaderboard.length ? ( + + + + ) : activeTab === 0 && (!dailyLeaderboard || (dailyLeaderboard.leaderboard?.length ?? 0) === 0) ? ( }) No leaderboard for today — check back tomorrow! - ) : activeTab === 1 && - (!globalLeaderboard || (globalLeaderboard.leaderboard?.length ?? 0) === 0) ? ( + ) : activeTab === 1 && (!globalLeaderboard || (globalLeaderboard.leaderboard?.length ?? 0) === 0) ? ( }) topSeparatorIndex={topSeparatorIndex} peopleAboveCount={peopleAboveCount} showBottomSeparator={showBottomSeparator} - bottomSeparatorIndex={bottomSeparatorIndex} peopleBelowCount={peopleBelowCount} + isLoading={activeTab === 0 ? dailyLoading : globalLoading} /> )} - ); }; -export default LeaderboardScreen; +export default LeaderboardScreen; \ No newline at end of file diff --git a/app/(tabs)/scanner/scanner_user.tsx b/app/(tabs)/scanner/scanner_user.tsx index 920e72a..09a9bd1 100644 --- a/app/(tabs)/scanner/scanner_user.tsx +++ b/app/(tabs)/scanner/scanner_user.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { SafeAreaView, View, Dimensions, Text, TouchableOpacity } from 'react-native'; +import { SafeAreaView, View, Dimensions, Text, TouchableOpacity, ActivityIndicator } from 'react-native'; import BackgroundSvg from '@/assets/images/qrbackground.svg'; import { useQRCode } from '@/hooks/useQRCode'; import QRDisplay from '@/components/scanner/QRDisplay'; @@ -8,7 +8,7 @@ import { getWeekday } from '@/lib/utils'; import { Attendee } from '@/api/types'; const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window'); -const QR_SIZE = SCREEN_WIDTH * 0.67; +const QR_SIZE = SCREEN_WIDTH * 0.6; // Slightly smaller to fit better export default function ScannerScreen() { const [weekdayShort, setWeekdayShort] = useState(null); @@ -36,15 +36,13 @@ export default function ScannerScreen() { setIsRefreshing(true); handleManualRefresh(); - // Set cooldown for 3 seconds after a brief delay setTimeout(() => { setIsRefreshing(false); setIsRefreshCooldown(true); - setTimeout(() => { setIsRefreshCooldown(false); }, 3000); - }, 500); // Small delay to prevent flashing + }, 500); }, [isRefreshing, isRefreshCooldown, handleManualRefresh]); const attendee = useAppSelector((state) => state.attendee.attendee); @@ -116,4 +114,4 @@ export default function ScannerScreen() { ); -} +} \ No newline at end of file diff --git a/components/leaderboard/LeaderboardList.tsx b/components/leaderboard/LeaderboardList.tsx index 9c7a248..0f3db0a 100644 --- a/components/leaderboard/LeaderboardList.tsx +++ b/components/leaderboard/LeaderboardList.tsx @@ -16,15 +16,12 @@ interface LeaderboardData { interface LeaderboardListProps { data: LeaderboardData[]; userId: string; - // Top separator showTopSeparator?: boolean; topSeparatorIndex?: number; peopleAboveCount?: number; - - // Bottom separator showBottomSeparator?: boolean; - bottomSeparatorIndex?: number; peopleBelowCount?: number; + isLoading: boolean; } export type LeaderboardListHandle = { @@ -41,6 +38,7 @@ export const LeaderboardList = forwardRef { + if (listRef.current) { + listRef.current.scrollToIndex({ + index: userIndex, + animated: true, + viewPosition: 0.5, + }); + } + }, 100); + } } }; @@ -65,14 +74,30 @@ export const LeaderboardList = forwardRef + + + ); + } + + if (!isLoading && data.length === 0) { + return ( + + No leaderboard data available. + + ); + } + return ( String(item.userId)} - // 👇 Android-only adjustments - scrollEnabled={Platform.OS === 'android' ? true : false} - removeClippedSubviews={Platform.OS === 'android' ? false : true} + scrollEnabled={true} + removeClippedSubviews={Platform.OS === 'android'} initialScrollIndex={ Platform.OS === 'android' ? undefined @@ -94,6 +119,17 @@ export const LeaderboardList = forwardRef { + // This ensures a robust scroll, especially on Android + const wait = new Promise(resolve => setTimeout(resolve, 500)); + wait.then(() => { + try { + listRef.current?.scrollToIndex({ index: info.index, animated: true }); + } catch (e) { + console.log("Scroll to index failed again: ", e); + } + }); + }} renderItem={({ item, index }) => { const shouldShowTopSeparator = showTopSeparator && index === topSeparatorIndex; @@ -123,7 +159,7 @@ export const LeaderboardList = forwardRef - {peopleAboveCount > 0 ? `...` : 'Your Position'} + {peopleAboveCount > 0 ? `...${peopleAboveCount} People Above` : 'Your Position'} )} - {/* Bottom separator moved to ListFooterComponent */} {Platform.OS === 'android' ? ( ); }} - onScrollToIndexFailed={() => { - setTimeout(scrollToUser, 100); - }} ListFooterComponent={() => showBottomSeparator ? ( - {`...`} + {`...${peopleBelowCount} People Below`} ); }, -); +); \ No newline at end of file diff --git a/components/scanner/QRDisplay.tsx b/components/scanner/QRDisplay.tsx index cd0cab8..35783a2 100644 --- a/components/scanner/QRDisplay.tsx +++ b/components/scanner/QRDisplay.tsx @@ -22,6 +22,14 @@ const QRDisplay: React.FC = ({ qrSize, }) => { const MAX_RETRY_ATTEMPTS = 3; + const QR_ROTATION_DEG = '12.5deg'; // Rotation for the QR code and its placement + // These multipliers will need to be fine-tuned to fit YOUR specific SVG flag + // The 'qrSize' prop is typically the ideal square size for the QR code itself. + // The container needs to be slightly larger to account for the flag's visual size. + const containerWidthMultiplier = 1.05; // Adjust to visually fit the flag's width + const containerHeightMultiplier = 0.9; // Adjust to visually fit the flag's height + const qrCodeSizeMultiplier = 0.8; // Adjust QR code size relative to the flag container + const containerPadding = 15; // Padding inside the flag where the QR code sits if (loading) { return ( @@ -55,11 +63,36 @@ const QRDisplay: React.FC = ({ if (qrValue) { return ( + {/* + This View now simply positions the QR code, applying the tilt. + It does NOT have its own background color, allowing the SVG flag + to show through. + The width, height, and padding are crucial for alignment with your SVG. + */} - + ); @@ -68,4 +101,4 @@ const QRDisplay: React.FC = ({ return null; }; -export default QRDisplay; +export default QRDisplay; \ No newline at end of file diff --git a/lib/linkUtils.ts b/lib/linkUtils.ts index 0afdedf..cf145f8 100644 --- a/lib/linkUtils.ts +++ b/lib/linkUtils.ts @@ -52,14 +52,7 @@ export function stripEventLinks(description: string): string { const lines = description.split('\n'); return lines -<<<<<<< HEAD - .filter(line => !line.trim().startsWith(':link:')) // drop link lines - .join('\n') - .trim(); -} -======= .filter((line) => !line.trim().startsWith(':link:')) // drop link lines .join('\n') .trim(); } ->>>>>>> 0d67df498176be230967832360c11e08f3f9a498 diff --git a/package.json b/package.json index 9988007..1a974b6 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@gorhom/bottom-sheet": "^5.2.6", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/blur": "^4.4.1", - "@react-native-firebase/app": "^23.0.1", + "@react-native-firebase/app": "^23.1.2", "@react-native-firebase/messaging": "^23.0.1", "@react-native-google-signin/google-signin": "^15.0.0", "@react-native-picker/picker": "^2.11.1",