From 6737fde9dfcc3342456f30f9965d343cfb03d298 Mon Sep 17 00:00:00 2001 From: "sha.sdk_deployment" Date: Mon, 16 Dec 2024 05:42:56 +0000 Subject: [PATCH] Update uikit changelog for v3.22.0 --- CHANGELOG.md | 2 + gradle.properties | 2 +- settings.gradle | 2 +- .../src/main/res/drawable-hdpi/icon_bad.png | Bin 0 -> 1134 bytes .../src/main/res/drawable-hdpi/icon_good.png | Bin 0 -> 1119 bytes .../src/main/res/drawable-mdpi/icon_bad.png | Bin 0 -> 828 bytes .../src/main/res/drawable-mdpi/icon_good.png | Bin 0 -> 784 bytes .../src/main/res/drawable-xhdpi/icon_bad.png | Bin 0 -> 1498 bytes .../src/main/res/drawable-xhdpi/icon_good.png | Bin 0 -> 1512 bytes .../src/main/res/drawable-xxhdpi/icon_bad.png | Bin 0 -> 2211 bytes .../main/res/drawable-xxhdpi/icon_good.png | Bin 0 -> 2196 bytes .../main/res/drawable-xxxhdpi/icon_bad.png | Bin 0 -> 3040 bytes .../main/res/drawable-xxxhdpi/icon_good.png | Bin 0 -> 3071 bytes uikit/build.gradle | 3 +- uikit/internal.gradle | 1 + .../ChatNotificationChannelFragment.java | 2 +- .../FeedNotificationChannelFragment.java | 2 +- .../adapter/CarouselChildViewAdapter.kt | 88 ---- .../internal/extensions/MessageExtensions.kt | 6 +- .../extensions/MessageTemplateExtensions.kt | 50 ++- .../internal/extensions/ViewExtensions.kt | 52 --- .../interfaces/GetTemplateResultHandler.kt | 5 +- .../model/NotificationDiffCallback.kt | 5 + .../notifications/NotificationChannelTheme.kt | 2 +- .../notifications/NotificationTemplate.kt | 105 ----- .../notifications/NotificationTemplateList.kt | 30 ++ .../internal/model/serializer/Serializers.kt | 31 -- .../internal/model/template_messages/Enums.kt | 120 ------ .../model/template_messages/KeySet.kt | 73 +--- .../model/template_messages/Params.kt | 399 ------------------ .../model/template_messages/Styles.kt | 121 ------ .../model/template_messages/Template.kt | 175 -------- .../template_messages/TemplateViewCreator.kt | 290 +++++++++++++ .../TemplateViewGenerator.kt | 188 --------- .../model/templates/MessageTemplate.kt | 95 ----- .../singleton/MessageTemplateManager.kt | 61 ++- .../singleton/MessageTemplateMapper.kt | 62 +-- .../singleton/MessageTemplateParser.kt | 56 --- .../singleton/MessageTemplateRepository.kt | 8 +- .../singleton/NotificationChannelManager.kt | 119 +++--- .../NotificationTemplateRepository.kt | 76 +++- .../ui/messages/BaseNotificationView.kt | 139 ++---- .../ui/messages/MessageTemplateView.kt | 22 +- .../ui/messages/OtherTemplateMessageView.kt | 20 +- .../NotificationListComponent.kt | 2 +- .../ui/widgets/CarouselViewItemDecoration.kt | 15 - .../ui/widgets/MessageTemplateImageView.kt | 173 -------- .../internal/ui/widgets/TemplateViews.kt | 266 ------------ .../internal/utils/CarouselLeftSnapHelper.kt | 72 ---- .../java/com/sendbird/uikit/model/Action.kt | 7 +- .../com/sendbird/uikit/model/MessageList.kt | 8 +- .../components/MessageListComponent.java | 2 +- .../sendbird/uikit/utils/MessageUtils.java | 1 - .../sendbird/uikit/vm/ChannelViewModel.java | 27 +- .../vm/ChatNotificationChannelViewModel.java | 37 +- .../vm/FeedNotificationChannelViewModel.java | 37 +- 56 files changed, 756 insertions(+), 2303 deletions(-) create mode 100644 uikit-samples/src/main/res/drawable-hdpi/icon_bad.png create mode 100644 uikit-samples/src/main/res/drawable-hdpi/icon_good.png create mode 100644 uikit-samples/src/main/res/drawable-mdpi/icon_bad.png create mode 100644 uikit-samples/src/main/res/drawable-mdpi/icon_good.png create mode 100644 uikit-samples/src/main/res/drawable-xhdpi/icon_bad.png create mode 100644 uikit-samples/src/main/res/drawable-xhdpi/icon_good.png create mode 100644 uikit-samples/src/main/res/drawable-xxhdpi/icon_bad.png create mode 100644 uikit-samples/src/main/res/drawable-xxhdpi/icon_good.png create mode 100644 uikit-samples/src/main/res/drawable-xxxhdpi/icon_bad.png create mode 100644 uikit-samples/src/main/res/drawable-xxxhdpi/icon_good.png delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/adapter/CarouselChildViewAdapter.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplate.kt create mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplateList.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Enums.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Params.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Styles.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Template.kt create mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewCreator.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewGenerator.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/model/templates/MessageTemplate.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateParser.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/CarouselViewItemDecoration.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/MessageTemplateImageView.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/TemplateViews.kt delete mode 100644 uikit/src/main/java/com/sendbird/uikit/internal/utils/CarouselLeftSnapHelper.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index d04e358f..9ec0742a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Changelog +### v3.22.0 (Dec 16, 2024) with Chat SDK `v4.21.1` +* Templated-related code has been moved to a separate module. ### v3.21.1 (Nov 12, 2024) with Chat SDK `v4.20.0` * Fixed thumbs up reaction not working in chat messages. ### v3.21.0 (Sep 12, 2024) with Chat SDK `v4.19.0` diff --git a/gradle.properties b/gradle.properties index 1c1466bd..3eb54b42 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,5 +22,5 @@ android.nonTransitiveRClass=false android.nonFinalResIds=false android.enableR8.fullMode=false -UIKIT_VERSION = 3.21.1 +UIKIT_VERSION = 3.22.0 UIKIT_VERSION_CODE = 1 diff --git a/settings.gradle b/settings.gradle index 87127053..d4d0cff6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,5 +16,5 @@ dependencyResolutionManagement { } include ':uikit' -rootProject.name='sendbird-uikit-android-sources' include ':uikit-samples' +rootProject.name='sendbird-uikit-android-sources' diff --git a/uikit-samples/src/main/res/drawable-hdpi/icon_bad.png b/uikit-samples/src/main/res/drawable-hdpi/icon_bad.png new file mode 100644 index 0000000000000000000000000000000000000000..b3cccd9f144717e32ca2728f821465992a9c0abd GIT binary patch literal 1134 zcmV-!1d;oRP)E0Z@UD3Lq7nJAhO`Zes`x*xJ>RX{D3S03n1BLI@#*5JCtcWE9>YA7?yn1yY3fgB=dG9PB32C}~#@%Iwep9%U*;uCc%2`rom>>6=DvjvWdyLHQe+++N!uEuwZ|{a~LkG7j}_ zNFt%Hc>NkCQ97y!J)TzujU|G^2zUTDw2ihzCT|VL?1OB>L(l4hyfYm0QSl!^&+#4B zscS6PgB{=g-31fq&5RtgKt&|XN`&AA;==nE)?Q^ z(ov7dbd&ajZiuoH7ce6X>^L;!w3aPqgXmh z0n#Xzj#7Yh&J@3CR7?TV=@kEG7@z>@$jhTpI?4zbf#ZNr`T!3F3Xn&q_|@?M1<0dQ z{Mwo-3XqBP0ha;4g8~J}C+j^n1;|62_xS?_3Xq5F=n0K@L(S7aC_p+6>vf|cI>k=` zGSHSIu0R2*AWhC3dWLfZ1D5>(FA0ylrR0*=Pod9ru3+3V(24+ei(6|gO>|f{84lyH zXFaf$CP*s(C+aa~ zB`xy~;DJ%f-J_Dw9vI~Y9^OdOW``Y?e_6Q9PgnxgZ{qO{mBOQ5Ic2kU)%{^(+{89J zL>WBFmaE5lADg`K%6{r&z854)*UVHlA?aL)JUqe?^%aIWn_E`cF{M7NA)&i49_NsU zSN?~`Gs9sW4Y3=q@4|P6`_Nq&&bdGy9t{hS`^mERu(yNPcsgE?bESfva;~JpKahq; zxCtuJ%sp!yY+uQmAPtWf)&$jHihBKl2L&);8FBZBPyIY7fEi22K1UoL6u^vwtv`qv zeR@!UC_HQ_t##7QqhSGBd?nez_Bv0d1FBAdy>^z+qhM*c&OX=#B_U6D4_$;qY3D8df!}hzL%37F0#T8SM-xCQa{A~z4?TiTK};-Qiv+3gCG0b{QxFr@{vgJB zkSH9=e%gKt63rB4Dt`u&%nW&zzW~W5_0{MoD;Qo24(q4c(l0ErgI~R{P~umVT0kPg z5B34~i+sgospA4|e~*+9LI@#*5JCtcguD{|0@9jRq)p>JIsgCw07*qoM6N<$f+3pc A?O}A%qY@2qAZ+(VCq<>R&D@CDLVX61WCY=@cQF|Gg(wtTQOUZ+`u$XBCd z?TT>5>tvX?3ACTIw!$zx$}dI8oa8$SA)za(iz8FQcgzUrxkTRQD*5a|WZcUUx+Q*z z8sk0ZHmflreFZV6USyr56@C7cwIGKs>wC-#DHXqQrLxI(`1yUuhU6^F9UcNh=pv7zFjm;f%$-i6T~jmD27(qR1@(^FSoA02Q_TaDa=wr%K)<P=L3eG6P7RV$~9|*7xBjAExKLXDZ`;<&+B`1M__lc!pIw!E0jAmGe;}3 zZ?*=op=~vf0yvC&SbMJ4pdeD(%}s;`T~##J{T&J;A~Y!L#8j*C&q37)_ph)g9Bh9$ zZ(k-+dcNhkB@&tp_m_KkOaOY)Gt literal 0 HcmV?d00001 diff --git a/uikit-samples/src/main/res/drawable-mdpi/icon_bad.png b/uikit-samples/src/main/res/drawable-mdpi/icon_bad.png new file mode 100644 index 0000000000000000000000000000000000000000..d8325d979d52a0200fc66c1beb120edd8218cd2c GIT binary patch literal 828 zcmV-C1H=4@P)uD@|wpagRV*L=iHl`4fD&S^V_gg^yL#>S|LJMGfgw`U$FgpdX zJz}fj$xDlWCHNREd&lW|H7{PfibpvE*+rSkixkq!kp&}yH` z&yk+@#u}ufL*KK8oWg_D0-JKCIFG9L+dceQGiD9SyzR*3gA5*<|qJ>ro%-E&)1gnwPEN(g+!)cAj*f8M76IF2Y=Kyt znhyuuhz`8wt?xB$F@Rm>9Q}@KC=~Ovu{Q{V_jK?0zC*uN&N2-mQDb^*x0dg&;E#sf zs?iyn1#0vWlzaXsj&f`rs{e8KKexu!pbUS5t#g9~z&SZ29%#(e15&L7+0y5NpYh-p zagWJ7Y1WW?!dP(Cn5hS(hAnLiif4{`8kq<{6zppX+opxak;j02I>_;WaDA|tc)%&r zwrU%4EtK2f$YD#{j?VFcHTofOWhql30~8K>$7m7x5aeT`F%3Ms5^n(3EE6xXM&^?L>t%}q&(0DjO0jBf40T& z10E1Ck_4XoSOc_lD2FBm<6$_#0ZeRdaD$r)$MgPBzi)Fu8iPDnDo<6z zbrOh;=YLm>K*exHigT!U%nH@-FI`UzM8SDXkV0wMGQRCfty!NrSi7y_WFl2dguAw8UDrC;r<2s0dFRg$z(E_Oy)mSVGh#6e7?Z| O0000$!n&T1DUK(NreREP zPyfyp#`I#w_m_5eO1Id6G^Tz`_ba9TbWE){ru~@St@s;Crv7xk0gaI>?H}aJm?J)) zDe3eXF9o^A`R?Gq_WIuq%3N{pPT%!dz`8Y#$3Td1>>A}WC7!-pYMo<+@2`<|a44Wo z0Z2^{(QZ4xnIPDstqj~kx?^;20gT>Ku(hR0`p4V!CF4Kga&BLiyy`~a#Y zBE!hQ+5|`kOfyE6*}T7CBkf$kI;6_{6TSu53-oqOFHw=U(-L6!99Z00lKx3VI|A(U z8C55Vv_CTU@h=sAK${NGpJzfvUIU=z!WVK?Yv(uisK{dgNX7-UuY>-4tML{9k{K_s z4H5eDj;O?20BmhuLfZ^m7lvpVQd1* zuxz?5IvivGlu>Lr$R@xix5Gi!0OhzG4l)4B3DD<9#7t}g{2HL&20nXLlmXzIc4O@o z3SkpaR)juJW(@~f1Ndf{6-=0c0pOby{#)f|BW7R#_-E+{WSdEv_abpWWRZrO+XzH`n(A+U=7d)%W%-t3@`xNU>Od62LlE`JGA|Ph>X|=s3$;| zH>Txb>k~q>nLqa##JP{(y4CZCO+Y)c%&}qL zL{f+M!@D%&7!i5Sb5k|ITC0JFQ<7m~!2HRX0aM#IoH@Q6^A+@@*POeCqQZLEYmzl5 zi4rze1GKg)_$Sz|XsI=Jpo>}yfB{nwSjYs?4d|>E?+duav7aj@ zvXFj2kEge2^hGV-86@)qgW>Z5bvBW>)^Y&IY4`pm%_M!HP2JVDA(zO%ddZ)B>dnpK}WimAs=jfLtnZOIrtWEg@gw zm%K%t7U-~k;U7RvxyE{Zf)3QAfa%JeTkt(N+}nhJDG;^!=K^SgD4h=_=Yh=_=Yh=_=Yh=_>9f2e7%K(MmhbN~PV07*qoM6N<$g5rR) A4*&oF literal 0 HcmV?d00001 diff --git a/uikit-samples/src/main/res/drawable-xhdpi/icon_good.png b/uikit-samples/src/main/res/drawable-xhdpi/icon_good.png new file mode 100644 index 0000000000000000000000000000000000000000..b55aca6ed21027f4343d03fa3e62f0b58b45cafe GIT binary patch literal 1512 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 hm0qs!{|lTd^1u*~sBu+Wnxd}6C z&k-4rNA)C?$n5jG-0*fUrF_=6_Cb5Rrk z5W1)Au!LBUH`z~CvPb>)nMM~yzq_ZL13tZklK~}@@)43!q%@WmBptEO$z$*y@JpT+ z1PvV=VH!ONG)1w%Ec~dcWi(p8r<`Ht^jWsdGuLv~qIKu|@n=b~soN*1h8<;$vuEq$k>08X$ zZEKQq*C1$pIJe7b?jSN}^v8^==J;J>BrE zJ4W(87JoqqnJQB>8@C-BtA>*{wg`0R^<1i5f=3Z?x0$S~1~zl~ghU|3nxz6xtXC$k z@ULnQL&MCG*3YrBbF=i1g3XO7zu>UIc;vWEnZT6_EpUtVdDLJz@ti-8?hmjA* z*iD~Rt6|k`0hW~>pHmbctJ1u{nSiK@Ib~)9H8^jrZm>P&%E|*CENhA|TCP{p=Q@Xv z6JNbYV3b3GHqEvgQ3aQA8o>HfEV?hXRXm*Z6(PbfFqKu1rLD7mM_?{EY%$@<3vAO_ z0D`Aqtn{1f+<FB;U$eDq@ozjAH;P<|SeyPcV8swSyL71?A`#_iy6BN3o(TWV2}Z%l9q% zh|9CGRK9Ewp`f{nR7eCYyDxH64p~7YDZSH*tLH(~j1+Zr|KVfnSR)k>;WZE)ivw?G zBU#3vXil4j!6F5Zib?G`{|$k8cWy^q2qFPm2i8>uOnZ6sHcveYBDRWPJuxq$o@^<@ z81W%fVme(_;;2D3zwRq;n-Sm(rnR~o-noeSEK9{+pHwTu>b#272;oGuKp6-&&{Yj3 zrT$k~Oz601s~NKa4$?ldWq;>;q`i9!6oW>cSw*tc#!EC>|Z0!F|hs87dmpyZfGLNKAo+yeYoxtw^2t~q8eCXr|=1KmWoePX5ym0JJ<^_ z^UHqD0)Z#@YPSg?g9+ZfP?0F})H%d#H75%^7HxSQ6>dp$IK2vZW+Y07H|1%V)ZYBx zT2IA^0Y<}kqwQSep}58Co~ypf^psTZb1iW2;};G1 zcN(XC!M};8Yz~<|j^Jtb4n-D?skdixcT4LCXzP}5z7)hj*b1KpNn=lF(38$>)jBX63~91QF4?RWaQ&pS}y zWBa-@+-L)r#B3XiOY}?_x}mi&#-pd~i1gR4-u|<#XS<+mj2-^?u6De)1j{yg0GIxF niWa7?^XX2*-dpkSHa)Qej|x^bG$A_Y?6FSSyV%v7^iTc=M2zn0 literal 0 HcmV?d00001 diff --git a/uikit-samples/src/main/res/drawable-xxxhdpi/icon_bad.png b/uikit-samples/src/main/res/drawable-xxxhdpi/icon_bad.png new file mode 100644 index 0000000000000000000000000000000000000000..d5c9f46173d10a970a83d0cdd8452316a9c08978 GIT binary patch literal 3040 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 JCTZF|{x=SLh*kgq literal 0 HcmV?d00001 diff --git a/uikit-samples/src/main/res/drawable-xxxhdpi/icon_good.png b/uikit-samples/src/main/res/drawable-xxxhdpi/icon_good.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4022a2a72e5a1f350b13db9de6cda27eb242d5 GIT binary patch literal 3071 zcmbVOi8mB#8-HgQCQFutEYWtuC}izgX2>L!EXl|a#xk;HDdr+GXfd`(X%RCMZ^jDxX*CZNfZK# zl1fYiA%gezQ+tLI2qqsVmP0<)^wx0B_oJ(m(49xRnT@@=jne!^|BDdb;4OS9?p6&; z>+a>Rn;Ms*$0gK2gBY6-u{n=X*tNCK35>e2ZP^248hEOs!d@Mh8TKj_q*_P# z5<^p%8+W(LD~ZJXC0Qs+z$wXgqj6H$9!?sRNqsP*;+w)`=!0kL=U%R?+NP#56u_%N z=F3A^1ev(*Plco4XSOK)zFf+y^3yM+00#a2><>4EE?6`AmM#(v;9RE$X`W1k@#1~p z8d&CPO*CG{;UpP}HibUcKfd&p%ZK#6GpnHgxBEhfb9`Q6+vZ?-P@){U7 zSNwE(fqjFnTV#?6oS)KMcU-sDRskbk`K}!?mzyLVCNn%jliDBg6m}ZY{F*c?8vNLM zb(ZPv>@-L>qr!|^8ju-+c|qL&Exi9aZ?yTKtxpMAVS&SH0W%%nrGTOpf`-~IksRsAE#nx1^ zhBNB-QHj-&H}0vk(xouX^vM0_z@{qlTjmBk%2)^@>M%{QwBNjj)s)#Z3NsM*wAU;# z+2vQ^tUn_#pm(E9emdI(+m`&Sn6HVV>~pNmNA>h{gCQXg zUh8a?5bBg3t&#TFgAmF`ol6={J4jQCjvve z>yz;HP;_w;l%&28I;JnXLr2AK4-A|iyp?B|1`twQ zvTrT;=ffw|0HHKtK(9|+1RPy*2~>c7(<>f3P8wdYcklTQ2~-ei-3@YIeGsfFqNX*tb#*Z z>UCdUQ(f8Dzg2RMD^knL=p(vsoHiK_Uy?UX_M}CNO2zK( z4pZ#*O$*`{!%eGZg_UloYp%%|%6TAvoNUmz<{j8iQOLd(MP@?j5wH*!u`1~(rzx8g zOf``vk>C=*f7Kf%CZ2ozn=Tj2jj5eNz{YE#&){cpEA_$?``k770gnTn8!KxUHgUb( zdY%nE77%B|qI8h}r>{QY%X?uZJ=yH8_yag)V!dd*(5J^4IR~=Be7wV<8SJVVYu|L< zYUBCV{gm6d=RV{SJxA>F((Pftm#uH5(eE5&>Zcp)7N#WV|BA7`f2h3nYyvI~A2i5T zDNH;=yvu7stf}-4Ro_9rKl%NT(|nbja>g!>7k0aZ^W~S*{`w2KkNN4tkAu-rAHI`{%jBK@Y_lx*)-2W#eGs`<>sLHGb@DC|@k8U6BN`BZKE!k5kgbr_ zhwYMLig z8t0IL4BM$XX>CwZAwWua1GSsx6aWTU6O7(DHcGxoY`##40GIYdjFS1G=;3IhiGiKl z^jGSxEKvG46Z|MDU#>Ah^noYgPjSi&F+CJwFTckOY_vSabA;Efwgv z#&+VvP?oH&1TcDg73R^wx@Y(adcjhbl(Vt)L8;mP$0R&@FKO0ieL~(Hex})iYFpq6 zug$F3KNLsG{tR8r+z3?!2c?!A$IB`O)pLu(+b1b-??9dONJCPD>iE<4OMo*B_~1+h z*f^5QHUZ;Qr+^y~s(X_S#)}+~6602D8Z>3N$;C2gWhOFMfOODLc(Tf&wx^RSPDr2& zL~_x_k_6i0JA(PjaJ@ob$Uuk6chJ16FF3V(XRBi^1lix_)pA)FDqi9SBDm}zkS_QY zb@o0jHR2@yE>zw4X=YR_u%5ZSS7Mr>0NUiBw~E+UbcC$6L|QB zZ!gSr7pY^$Um%wczy6;Aj9L`es&U8Q%?HEf_yBRJw)Gtt=?|cqQO2M0&Oe}xm1>U@ z#*z=IH~A}I;h~GvhGEj-T7gi-pk~`^gF+^G%JBEp`%uXxktK7`>0bbvdI^Ky$p5XA zFB4R&>nC6){S`&6&Jsbjnl$S|7#KuQb0#y!{U_R3TWg3!`SL=XAiY%CZ4SQmxj?AO z@b5X5ry|56k+#e&T=W{MWX46he}sk@|7PZFo#>#1yK;Ad^#5F>e=bKqYR;t zjBJaVNZNRgZykYd$fPP$CF1P)FRsg9Yb;^qlHUdU818xSt!MHGDNmUphMw{vO8NHq zLlPO@_=_RfQZ?4@jyuMdwE{$07aWCsJLwlW7{N<0f*%5ZSx)eK zhl{5@)NN6H?^E_SKDHc455MD~hFBfA%)dW8)>=~s)=Y=@4_bbsszHC2>bp;#2hxf% zD>ny{WgYbp)e&91ytrbwMJAi&Y@dDNT$R*K)mNvlIj!2HKAuN8aaD+kA9CSW9{ ztB^58Ro@rSF&ukV@4tBlF;&c&>+T}#j^MDx67MT?knNqQv z`so@tY}|$kqWbVIq>I6tn`A0oJ?jfDiZq{dg?ThYh}`wuIsVk|$pxXT*3K;=CLM{8 z$Zn&@>%*PXi0X^7s=d3L$z_x4r%}9xr+7RsA>R9u0Amij#rsUL#_DheI~9^a zcTGY&IwbFyEU4*aJQLllSsy!87xxu!2FJUVqa#MijWJ9Mw4z zd}Xk1jH9ShW3|vWQ)^WtF5lBAM2=c=iM&#%{-qJVfabcF`t89d=Dto^70to)&CWK- zb#Nw6H<%G{DP@I+Nt?{gvr{RF%u}%#^iPVht*h+$&c7qo6|U?)wpki^(|xo_PMi*4t$P4ztA)5jj9-5&5anTpQ+nbeM}e(-El>A@|`#B10BwL(ue zLCigC@2i)K*i>tGnJWbk>rb2NpJP)?TDur~-ed-8X#B<6vN_vIFjAVrq#{;F-FSbb zF%^ppEwE3_hFv=FNX~>jua}L#SV`pnuKyqL>)}D3+&xh_zvTem=3ArBS(aOP#Qz)6 CN_}_$ literal 0 HcmV?d00001 diff --git a/uikit/build.gradle b/uikit/build.gradle index 0ce49e1e..b6186440 100644 --- a/uikit/build.gradle +++ b/uikit/build.gradle @@ -70,7 +70,8 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Sendbird - api 'com.sendbird.sdk:sendbird-chat:4.20.0' + api 'com.sendbird.sdk:sendbird-chat:4.21.1' + implementation "com.sendbird.sdk:message-template:1.0.0" implementation 'com.github.bumptech.glide:glide:4.16.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' diff --git a/uikit/internal.gradle b/uikit/internal.gradle index 994ee269..d41eb72e 100644 --- a/uikit/internal.gradle +++ b/uikit/internal.gradle @@ -34,6 +34,7 @@ dependencies { androidTestImplementation "androidx.arch.core:core-testing:2.2.0" androidTestImplementation 'io.kotest:kotest-assertions-core:5.3.0' + testImplementation 'com.google.code.gson:gson:2.9.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.10' testImplementation 'androidx.test:core:1.5.0' diff --git a/uikit/src/main/java/com/sendbird/uikit/fragments/ChatNotificationChannelFragment.java b/uikit/src/main/java/com/sendbird/uikit/fragments/ChatNotificationChannelFragment.java index 91427da8..b819f239 100644 --- a/uikit/src/main/java/com/sendbird/uikit/fragments/ChatNotificationChannelFragment.java +++ b/uikit/src/main/java/com/sendbird/uikit/fragments/ChatNotificationChannelFragment.java @@ -366,7 +366,7 @@ public Builder withArguments(@NonNull Bundle args) { /** * Sets the click listener on the message template view clicked. - * Sets the click listener when the view component that has {@link com.sendbird.uikit.model.Action} is clicked + * Sets the click listener when the view component that has {@link Action} is clicked * * @param handler The callback that will run. * @return This Builder object to allow for chaining of calls to set methods. diff --git a/uikit/src/main/java/com/sendbird/uikit/fragments/FeedNotificationChannelFragment.java b/uikit/src/main/java/com/sendbird/uikit/fragments/FeedNotificationChannelFragment.java index ec784dc7..2647555c 100644 --- a/uikit/src/main/java/com/sendbird/uikit/fragments/FeedNotificationChannelFragment.java +++ b/uikit/src/main/java/com/sendbird/uikit/fragments/FeedNotificationChannelFragment.java @@ -396,7 +396,7 @@ public Builder withArguments(@NonNull Bundle args) { /** * Sets the click listener on the message template view clicked. - * Sets the click listener when the view component that has {@link com.sendbird.uikit.model.Action} is clicked + * Sets the click listener when the view component that has {@link Action} is clicked * * @param handler The callback that will run. * @return This Builder object to allow for chaining of calls to set methods. diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/adapter/CarouselChildViewAdapter.kt b/uikit/src/main/java/com/sendbird/uikit/internal/adapter/CarouselChildViewAdapter.kt deleted file mode 100644 index 41cf94ae..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/adapter/CarouselChildViewAdapter.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.sendbird.uikit.internal.adapter - -import android.content.Context -import android.view.ViewGroup -import android.widget.LinearLayout.LayoutParams -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import com.sendbird.uikit.internal.model.template_messages.Params -import com.sendbird.uikit.internal.model.template_messages.SizeType -import com.sendbird.uikit.internal.model.template_messages.ViewLifecycleHandler -import com.sendbird.uikit.internal.ui.messages.MessageTemplateView - -internal class CarouselChildViewAdapter(private val maxChildWidth: Int) : RecyclerView.Adapter() { - private val childTemplateParams: MutableList = mutableListOf() - internal var onChildViewCreated: ViewLifecycleHandler? = null - - fun setChildTemplateParams(newParams: List) { - val oldParams = childTemplateParams.toList() - val diffResult = DiffUtil.calculateDiff( - ParamsDiffCallback(oldParams, newParams) - ) - childTemplateParams.clear() - childTemplateParams.addAll(newParams) - diffResult.dispatchUpdatesTo(this) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CarouselChildItemViewHolder { - return CarouselChildItemViewHolder(parent.context) - } - - override fun getItemCount(): Int { - return childTemplateParams.size - } - - override fun onBindViewHolder(holder: CarouselChildItemViewHolder, position: Int) { - holder.bind(childTemplateParams[position]) - } - - inner class CarouselChildItemViewHolder( - context: Context, - private val contentView: MessageTemplateView = MessageTemplateView( - context, - autoAdjustHeightWhenInvisible = false - ) - ) : RecyclerView.ViewHolder(contentView) { - fun bind(params: Params) { - contentView.maxWidth = maxChildWidth - - // If width is fill, the width of the parent is not fixed, so we can't set the value because there is no value to base it on. Therefore, we set the maximum size of the parent child item. - val hasFillWidth = params.hasFillWidth - val width = if (hasFillWidth) { - maxChildWidth - } else { - LayoutParams.WRAP_CONTENT - } - - contentView.layoutParams = contentView.layoutParams.apply { - this.width = width - } - contentView.draw( - params, - onViewCreated = { view, viewParams -> onChildViewCreated?.invoke(view, viewParams) } - ) - } - - private val Params.hasFillWidth: Boolean - get() { - return this.body.items.any { it.width.type == SizeType.Flex && it.width.value == ViewGroup.LayoutParams.MATCH_PARENT } - } - } - - private class ParamsDiffCallback( - private val oldParams: List, - private val newParams: List - ) : DiffUtil.Callback() { - override fun getOldListSize(): Int = oldParams.size - - override fun getNewListSize(): Int = newParams.size - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldParams[oldItemPosition] == newParams[newItemPosition] - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldParams[oldItemPosition] == newParams[newItemPosition] - } - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageExtensions.kt b/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageExtensions.kt index 59a70f90..c03c8769 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageExtensions.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageExtensions.kt @@ -8,16 +8,16 @@ import com.sendbird.android.message.Emoji import com.sendbird.android.message.FileMessage import com.sendbird.android.message.MessageFormItem import com.sendbird.android.message.MultipleFilesMessage -import com.sendbird.android.shadow.com.google.gson.JsonParser import com.sendbird.uikit.R import com.sendbird.uikit.activities.adapter.MessageFormViewType import com.sendbird.uikit.consts.StringSet import com.sendbird.uikit.internal.singleton.MessageDisplayDataManager -import com.sendbird.uikit.model.MessageList import com.sendbird.uikit.log.Logger import com.sendbird.uikit.model.EmojiManager +import com.sendbird.uikit.model.MessageList import com.sendbird.uikit.model.UserMessageDisplayData import com.sendbird.uikit.utils.MessageUtils +import org.json.JSONObject internal fun BaseMessage.hasParentMessage() = parentMessageId != 0L @@ -165,7 +165,7 @@ internal val BaseMessage.isStreamMessage: Boolean return false } return try { - JsonParser.parseString(data).asJsonObject[StringSet.stream].asBoolean + JSONObject(data).getBoolean(StringSet.stream) } catch (e: Exception) { false } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageTemplateExtensions.kt b/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageTemplateExtensions.kt index 2aa799f4..ba47b322 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageTemplateExtensions.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageTemplateExtensions.kt @@ -2,12 +2,17 @@ package com.sendbird.uikit.internal.extensions import com.sendbird.android.annotation.AIChatBotExperimental import com.sendbird.android.channel.TemplateMessageData +import com.sendbird.android.exception.SendbirdException import com.sendbird.android.message.BaseMessage +import com.sendbird.message.template.consts.MessageTemplateError +import com.sendbird.message.template.consts.TemplateTheme +import com.sendbird.message.template.model.TemplateParams +import com.sendbird.uikit.SendbirdUIKit +import com.sendbird.uikit.SendbirdUIKit.ThemeMode import com.sendbird.uikit.consts.StringSet -import com.sendbird.uikit.internal.model.template_messages.Params +import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode import com.sendbird.uikit.internal.model.templates.MessageTemplateStatus import com.sendbird.uikit.internal.singleton.MessageTemplateManager -import com.sendbird.uikit.internal.singleton.MessageTemplateParser internal fun BaseMessage.isTemplateMessage(): Boolean { return this.templateMessageData != null @@ -25,22 +30,16 @@ internal fun TemplateMessageData?.isValid(): Boolean { internal fun BaseMessage.saveParamsFromTemplate() { val templateMessageData = this.templateMessageData ?: return val key = templateMessageData.key - val template = MessageTemplateManager.getTemplate(key) - if (template != null) { - val syntax = template.getTemplateSyntax( - templateMessageData.variables, - templateMessageData.viewVariables - ) - try { - val params = MessageTemplateParser.parse(syntax) - this.messageTemplateStatus = MessageTemplateStatus.CACHED - this.messageTemplateParams = params - } catch (e: Exception) { - this.messageTemplateStatus = MessageTemplateStatus.FAILED_TO_PARSE + try { + val params = MessageTemplateManager.parseTemplate(key, templateMessageData.variables, templateMessageData.viewVariables) + this.messageTemplateStatus = MessageTemplateStatus.CACHED + this.messageTemplateParams = params + } catch (e: SendbirdException) { + when (e.code) { + MessageTemplateError.ERROR_TEMPLATE_NOT_EXIST -> this.messageTemplateStatus = MessageTemplateStatus.FAILED_TO_FETCH + MessageTemplateError.ERROR_TEMPLATE_PARSE_FAILED -> this.messageTemplateStatus = MessageTemplateStatus.FAILED_TO_PARSE } - } else { - this.messageTemplateStatus = MessageTemplateStatus.FAILED_TO_FETCH } } @@ -67,8 +66,8 @@ internal var BaseMessage.messageTemplateStatus: MessageTemplateStatus? } @OptIn(AIChatBotExperimental::class) -internal var BaseMessage.messageTemplateParams: Params? - get() = extras[StringSet.message_template_params] as? Params +internal var BaseMessage.messageTemplateParams: TemplateParams? + get() = extras[StringSet.message_template_params] as? TemplateParams set(value) { if (value == null) { extras.remove(StringSet.message_template_params) @@ -77,6 +76,21 @@ internal var BaseMessage.messageTemplateParams: Params? } } +internal fun ThemeMode.toTemplateTheme(): TemplateTheme { + return when (this) { + ThemeMode.Light -> TemplateTheme.Light + ThemeMode.Dark -> TemplateTheme.Dark + } +} + +internal fun NotificationThemeMode.toTemplateTheme(): TemplateTheme { + return when (this) { + NotificationThemeMode.Light -> TemplateTheme.Light + NotificationThemeMode.Dark -> TemplateTheme.Dark + NotificationThemeMode.Default -> SendbirdUIKit.getDefaultThemeMode().toTemplateTheme() + } +} + internal enum class MessageTemplateContainerType { UNKNOWN, DEFAULT; diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/extensions/ViewExtensions.kt b/uikit/src/main/java/com/sendbird/uikit/internal/extensions/ViewExtensions.kt index 8ca94e68..528e52c0 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/extensions/ViewExtensions.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/extensions/ViewExtensions.kt @@ -3,20 +3,15 @@ package com.sendbird.uikit.internal.extensions import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList -import android.graphics.Color import android.graphics.Typeface import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.RippleDrawable import android.os.Build import android.util.TypedValue -import android.view.Gravity import android.view.View -import android.view.ViewGroup import android.widget.EditText -import android.widget.FrameLayout import android.widget.ImageView -import android.widget.ProgressBar import android.widget.TextView import androidx.core.content.ContextCompat import com.bumptech.glide.Glide @@ -30,11 +25,6 @@ import com.sendbird.android.message.FeedbackRating import com.sendbird.uikit.R import com.sendbird.uikit.consts.StringSet import com.sendbird.uikit.internal.interfaces.OnFeedbackRatingClickListener -import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode -import com.sendbird.uikit.internal.model.template_messages.Params -import com.sendbird.uikit.internal.model.template_messages.TemplateParamsCreator -import com.sendbird.uikit.internal.model.template_messages.TemplateViewGenerator.spinnerColor -import com.sendbird.uikit.utils.DrawableUtils import com.sendbird.uikit.widgets.FeedbackView @Suppress("DEPRECATION") @@ -155,45 +145,3 @@ internal fun FeedbackView.drawFeedback(message: BaseMessage, listener: OnFeedbac listener?.onFeedbackClicked(message, feedbackRating) } } - -internal fun Context.createTemplateMessageLoadingView(): View { - val height = resources.getDimensionPixelSize(R.dimen.sb_template_message_loading_view_height) - return FrameLayout(this).apply { - layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height) - setBackgroundColor(Color.TRANSPARENT) - addView( - ProgressBar(context).apply { - val size = resources.intToDp(42) - layoutParams = FrameLayout.LayoutParams( - size, size, Gravity.CENTER - ) - val loading = DrawableUtils.setTintList( - context, - R.drawable.sb_progress, - ColorStateList.valueOf(NotificationThemeMode.Default.spinnerColor) - ) - this.indeterminateDrawable = loading - } - ) - } -} - -internal fun Context.createFallbackViewParams(message: BaseMessage): Params { - return createFallbackViewParams(message.message) -} - -internal fun Context.createFallbackViewParams(message: String): Params { - return TemplateParamsCreator.createMessageTemplateDefaultViewParam( - message, - this.getString(R.string.sb_text_template_message_fallback_title), - this.getString(R.string.sb_text_template_message_fallback_description), - ) -} - -internal fun TextView.applyTextAlignment(gravity: Int) { - if ((gravity and Gravity.START) == Gravity.START) { - textAlignment = View.TEXT_ALIGNMENT_VIEW_START - } else if ((gravity and Gravity.END) == Gravity.END) { - textAlignment = View.TEXT_ALIGNMENT_VIEW_END - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/interfaces/GetTemplateResultHandler.kt b/uikit/src/main/java/com/sendbird/uikit/internal/interfaces/GetTemplateResultHandler.kt index 58b47841..a512c88b 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/interfaces/GetTemplateResultHandler.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/interfaces/GetTemplateResultHandler.kt @@ -1,7 +1,8 @@ package com.sendbird.uikit.internal.interfaces import com.sendbird.android.exception.SendbirdException +import com.sendbird.message.template.model.TemplateParams -internal interface GetTemplateResultHandler { - fun onResult(templateKey: String, jsonTemplate: String?, isDataTemplate: Boolean, e: SendbirdException?) +internal fun interface GetTemplateResultHandler { + fun onResult(templateKey: String, templateParams: TemplateParams?, e: SendbirdException?) } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/NotificationDiffCallback.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/NotificationDiffCallback.kt index 2c9c9061..2cd9c4a5 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/NotificationDiffCallback.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/model/NotificationDiffCallback.kt @@ -2,6 +2,7 @@ package com.sendbird.uikit.internal.model import androidx.recyclerview.widget.DiffUtil import com.sendbird.android.message.BaseMessage +import com.sendbird.uikit.internal.extensions.messageTemplateStatus internal class NotificationDiffCallback( private val oldMessageList: List, @@ -75,6 +76,10 @@ internal class NotificationDiffCallback( return false } + if (oldMessage.messageTemplateStatus != newMessage.messageTemplateStatus) { + return false + } + val prevIsNew: Boolean = oldMessage.createdAt > oldLastSeenAt val currentIsNew: Boolean = newMessage.createdAt > newLastSeenAt if (prevIsNew != currentIsNew) { diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationChannelTheme.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationChannelTheme.kt index 5def52bf..98592d18 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationChannelTheme.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationChannelTheme.kt @@ -2,9 +2,9 @@ package com.sendbird.uikit.internal.model.notifications +import com.sendbird.message.template.consts.Weight import com.sendbird.uikit.internal.model.serializer.CSVColorIntAsStringSerializer import com.sendbird.uikit.internal.model.template_messages.KeySet -import com.sendbird.uikit.internal.model.template_messages.Weight import com.sendbird.uikit.internal.singleton.JsonParser import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplate.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplate.kt deleted file mode 100644 index 327ed546..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplate.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.sendbird.uikit.internal.model.notifications - -import com.sendbird.uikit.internal.model.serializer.JsonElementToStringSerializer -import com.sendbird.uikit.internal.model.template_messages.KeySet -import com.sendbird.uikit.internal.singleton.JsonParser -import com.sendbird.uikit.log.Logger -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject - -@Serializable -internal data class NotificationTemplateList constructor( - val templates: List -) { - companion object { - @JvmStatic - fun fromJson(value: String): NotificationTemplateList { - val mutableTemplates = mutableListOf() - JsonParser.toJsonElement(value).jsonObject[KeySet.templates]?.jsonArray?.let { templateList -> - for (element in templateList) { - try { - mutableTemplates.add(JsonParser.fromJsonElement(element)) - } catch (e: Exception) { - e.printStackTrace() - } - } - } - return NotificationTemplateList(mutableTemplates.toList()) - } - } -} - -@Serializable -internal data class NotificationTemplate constructor( - @SerialName(KeySet.key) - val templateKey: String, - @SerialName(KeySet.created_at) - val createdAt: Long, - @SerialName(KeySet.updated_at) - val updatedAt: Long, - val name: String? = null, - @SerialName(KeySet.ui_template) - @Serializable(with = JsonElementToStringSerializer::class) - private val _uiTemplate: String, - @SerialName(KeySet.data_template) - @Serializable(with = JsonElementToStringSerializer::class) - private val _dataTemplate: String = "{}", // for backward compatibility. [since 3.20.1] - @SerialName(KeySet.color_variables) - private val _colorVariables: Map -) { - - companion object { - @JvmStatic - fun fromJson(value: String): NotificationTemplate { - return JsonParser.fromJson(value) - } - } - - /** - * If the data template is empty, it returns the UI template. - */ - private fun validTemplateSyntax(): String { - if (_uiTemplate.length > 2) { - return _uiTemplate - } - return _dataTemplate - } - - /** - * If the data template is not empty, it returns true. - */ - val isDataTemplate: Boolean - get() = _dataTemplate.length > 2 - - fun getTemplateSyntax(variables: Map, themeMode: NotificationThemeMode): String { - val regex = "\\{([^{}]+)\\}".toRegex() - val template = validTemplateSyntax() - return regex.replace(template) { matchResult -> - val variable = matchResult.groups[1]?.value - var converted = false - - // 1. lookup and convert color variables first - var convertedResult = _colorVariables[variable]?.let { - Logger.i("++ color variable key=$variable, value=$it") - converted = true - val csvColor = CSVColor(it) - csvColor.getColorHexString(themeMode) - } ?: matchResult.value - - // 2. If color variables didn't convert, convert data variables then. - if (!converted && variables.isNotEmpty()) { - convertedResult = variables[variable]?.let { - Logger.i("++ data variable key=$variable, value=$it") - it - } ?: convertedResult - } - convertedResult - } - } - - override fun toString(): String { - return JsonParser.toJsonString(this) - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplateList.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplateList.kt new file mode 100644 index 00000000..2f3e2919 --- /dev/null +++ b/uikit/src/main/java/com/sendbird/uikit/internal/model/notifications/NotificationTemplateList.kt @@ -0,0 +1,30 @@ +package com.sendbird.uikit.internal.model.notifications + +import com.sendbird.message.template.model.MessageTemplate +import com.sendbird.uikit.internal.model.template_messages.KeySet +import com.sendbird.uikit.internal.singleton.JsonParser +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject + +@Serializable +internal data class NotificationTemplateList( + val templates: List +) { + companion object { + @JvmStatic + fun fromJson(value: String): NotificationTemplateList { + val mutableTemplates = mutableListOf() + JsonParser.toJsonElement(value).jsonObject[KeySet.templates]?.jsonArray?.let { templateList -> + for (element in templateList) { + try { + mutableTemplates.add(JsonParser.fromJsonElement(element)) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + return NotificationTemplateList(mutableTemplates.toList()) + } + } +} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/serializer/Serializers.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/serializer/Serializers.kt index 287dc7c9..79010141 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/serializer/Serializers.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/model/serializer/Serializers.kt @@ -1,6 +1,5 @@ package com.sendbird.uikit.internal.model.serializer -import android.graphics.Color import com.sendbird.uikit.consts.ReplyType import com.sendbird.uikit.consts.SuggestedRepliesDirection import com.sendbird.uikit.consts.SuggestedRepliesFor @@ -12,24 +11,6 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement - -internal object ColorIntAsStringSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ColorInt", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): Int { - val decoded = decoder.decodeString() - // Logger.i("deserialize hex=$decoded") - return Color.parseColor(decoded) - } - - override fun serialize(encoder: Encoder, value: Int) { - val hex = String.format("#%08X", 0xFFFFFFFF and value.toLong()) - // Logger.i("serialize hex=$hex") - encoder.encodeString(hex) - } -} internal object CSVColorIntAsStringSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("CSVColor class", PrimitiveKind.STRING) @@ -46,18 +27,6 @@ internal object CSVColorIntAsStringSerializer : KSerializer { } } -internal object JsonElementToStringSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("StringJsonSerializer", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: String) { - encoder.encodeSerializableValue(JsonElement.serializer(), Json.parseToJsonElement(value)) - } - - override fun deserialize(decoder: Decoder): String { - return decoder.decodeSerializableValue(JsonElement.serializer()).toString() - } -} - internal object ReplyTypeAsStringSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ReplyType enum class", PrimitiveKind.STRING) diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Enums.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Enums.kt deleted file mode 100644 index 21ed926e..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Enums.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.sendbird.uikit.internal.model.template_messages - -import android.graphics.Typeface -import android.view.Gravity -import android.widget.ImageView -import android.widget.LinearLayout -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -internal enum class ViewType { - @SerialName(KeySet.box) - Box, - - @SerialName(KeySet.image) - Image, - - @SerialName(KeySet.textButton) - Button, - - @SerialName(KeySet.imageButton) - ImageButton, - - @SerialName(KeySet.text) - Text, - - @SerialName(KeySet.carouselView) - CarouselView - ; - - companion object { - @JvmStatic - fun from(value: String): ViewType { - return values().first { it.name == value } - } - } -} - -@Serializable -internal enum class Orientation(val value: Int) { - @SerialName(KeySet.row) - Row(LinearLayout.HORIZONTAL), - - @SerialName(KeySet.column) - Column(LinearLayout.VERTICAL) -} - -@Serializable -internal enum class Weight(val value: Int) { - @SerialName(KeySet.normal) - Normal(Typeface.NORMAL), - - @SerialName(KeySet.bold) - Bold(Typeface.BOLD) -} - -@Serializable -internal enum class ContentMode(val scaleType: ImageView.ScaleType) { - @SerialName(KeySet.aspectFill) - CenterCrop(ImageView.ScaleType.CENTER_CROP), - - @SerialName(KeySet.aspectFit) - FitCenter(ImageView.ScaleType.FIT_CENTER), - - @SerialName(KeySet.scalesToFill) - FitXY(ImageView.ScaleType.FIT_XY); - - fun toValueAsSerialName(): String { - return when (this) { - CenterCrop -> KeySet.aspectFill - FitCenter -> KeySet.aspectFit - FitXY -> KeySet.scalesToFill - } - } -} - -@Serializable -internal enum class ActionType { - @SerialName(KeySet.web) - Web, - - @SerialName(KeySet.custom) - Custom, - - @SerialName(KeySet.uikit) - Uikit -} - -@Serializable -internal enum class SizeType { - @SerialName(KeySet.fixed) - Fixed, - - @SerialName(KeySet.flex) - Flex -} - -@Serializable -internal enum class VerticalAlign(val value: Int) { - @SerialName(KeySet.top) - Top(Gravity.TOP), - - @SerialName(KeySet.bottom) - Bottom(Gravity.BOTTOM), - - @SerialName(KeySet.center) - Center(Gravity.CENTER_VERTICAL) -} - -@Serializable -internal enum class HorizontalAlign(val value: Int) { - @SerialName(KeySet.left) - Left(Gravity.START), - - @SerialName(KeySet.right) - Right(Gravity.END), - - @SerialName(KeySet.center) - Center(Gravity.CENTER_HORIZONTAL) -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/KeySet.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/KeySet.kt index 4865f1e0..17d0f39a 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/KeySet.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/KeySet.kt @@ -1,92 +1,26 @@ package com.sendbird.uikit.internal.model.template_messages +@Suppress("ConstPropertyName") internal object KeySet { - const val version = "version" - const val type = "type" - const val layout = "layout" - const val width = "width" - const val height = "height" - const val value = "value" - const val url = "url" - const val data = "data" - const val alterData = "alterData" - const val align = "align" - const val backgroundColor = "backgroundColor" - const val backgroundImageUrl = "backgroundImageUrl" - const val borderWidth = "borderWidth" - const val borderColor = "borderColor" - const val radius = "radius" - const val items = "items" - const val imageButton = "imageButton" - const val box = "box" - const val image = "image" - const val textButton = "textButton" - const val text = "text" - const val maxTextLines = "maxTextLines" - const val size = "size" - const val color = "color" - const val imageUrl = "imageUrl" - const val weight = "weight" - const val contentMode = "contentMode" - const val viewStyle = "viewStyle" - const val textStyle = "textStyle" - const val buttonStyle = "buttonStyle" - const val imageStyle = "imageStyle" - const val row = "row" - const val column = "column" - const val normal = "normal" - const val bold = "bold" const val action = "action" - const val aspectFill = "aspectFill" - const val contain = "contain" - const val aspectFit = "aspectFit" - const val scalesToFill = "scalesToFill" - const val web = "web" - const val custom = "custom" - const val uikit = "uikit" - const val fixed = "fixed" - const val flex = "flex" - const val padding = "padding" - const val margin = "margin" - const val top = "top" - const val bottom = "bottom" - const val center = "center" - const val left = "left" - const val right = "right" - const val horizontal = "horizontal" - const val vertical = "vertical" - const val sub_data = "sub_data" - const val sub_type = "sub_type" - const val carouselView = "carouselView" - const val templates = "templates" // notifications - const val key = "key" const val template_key = "template_key" const val created_at = "created_at" const val updated_at = "updated_at" - const val ui_template = "ui_template" - const val data_template = "data_template" - const val color_variables = "color_variables" - const val template_variables = "template_variables" - const val variables = "variables" + const val theme_mode = "theme_mode" const val light = "light" const val dark = "dark" const val default = "default" - const val theme = "theme" - const val theme_mode = "theme_mode" - const val channel_type = "channel_type" const val notification = "notification" const val list = "list" const val header = "header" + const val templates = "templates" // Configurations const val group_channel = "group_channel" const val open_channel = "open_channel" - const val group_channel_list = "group_channel_list" - const val group_channel_setting = "group_channel_setting" const val enable_using_default_user_profile = "enable_using_default_user_profile" - const val enable_using_userId_for_nickname = "enable_using_userId_for_nickname" const val photo = "photo" const val video = "video" const val enable_ogtag = "enable_ogtag" @@ -115,7 +49,6 @@ internal object KeySet { const val enable_document = "enable_document" const val camera = "camera" const val gallery = "gallery" - const val label = "label" const val show_suggested_replies_for = "show_suggested_replies_for" const val enable_markdown_for_user_message = "enable_markdown_for_user_message" const val suggested_replies_direction = "suggested_replies_direction" diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Params.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Params.kt deleted file mode 100644 index 493f4138..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Params.kt +++ /dev/null @@ -1,399 +0,0 @@ -package com.sendbird.uikit.internal.model.template_messages - -import android.content.Context -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import com.sendbird.android.message.BaseMessage -import com.sendbird.uikit.interfaces.OnNotificationTemplateActionHandler -import com.sendbird.uikit.internal.extensions.intToDp -import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode -import com.sendbird.uikit.internal.model.template_messages.TemplateViewGenerator.backgroundColor -import com.sendbird.uikit.internal.model.template_messages.TemplateViewGenerator.descTextColor -import com.sendbird.uikit.internal.model.template_messages.TemplateViewGenerator.titleColor -import com.sendbird.uikit.model.Action -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonClassDiscriminator -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import org.json.JSONObject - -const val FILL_PARENT = 0 -const val WRAP_CONTENT = 1 - -@Serializable -internal data class ActionData( - val type: ActionType = ActionType.Web, - val data: String, - val alterData: String? = null -) { - fun register( - view: View, - message: BaseMessage, - onNotificationTemplateActionHandler: OnNotificationTemplateActionHandler? - ) { - onNotificationTemplateActionHandler?.let { callback -> - view.setOnClickListener { - callback.onHandleAction(it, Action.from(this@ActionData), message) - } - } - } - - companion object { - fun create( - type: String = KeySet.web, - data: String, - customData: String? = null - ): JsonObject { - return buildJsonObject { - put(KeySet.type, type) - put(KeySet.data, data) - customData?.let { put(KeySet.alterData, data) } - } - } - } -} - -@Serializable -internal data class SizeSpec( - val type: SizeType, - @SerialName(KeySet.value) - private val _value: Int -) { - internal val value: Int - get() = when (type) { - SizeType.Fixed -> _value - SizeType.Flex -> { - when (_value) { - FILL_PARENT -> ViewGroup.LayoutParams.MATCH_PARENT - WRAP_CONTENT -> ViewGroup.LayoutParams.WRAP_CONTENT - else -> _value - } - } - } - - fun getWeight(): Float { - return when (type) { - SizeType.Fixed -> 0F - SizeType.Flex -> { - when (_value) { - FILL_PARENT -> 1F - WRAP_CONTENT -> 0F - else -> 0F - } - } - } - } -} - -@Serializable -internal data class Align( - private val horizontal: HorizontalAlign = HorizontalAlign.Left, - private val vertical: VerticalAlign = VerticalAlign.Top -) { - val gravity: Int - get() = horizontal.value or vertical.value - - companion object { - fun create( - horizontal: String = KeySet.left, - vertical: String = KeySet.top - ): JsonObject { - return buildJsonObject { - put(KeySet.horizontal, horizontal) - put(KeySet.vertical, vertical) - } - } - } -} - -@Serializable -internal data class MetaData( - val pixelWidth: Int, - val pixelHeight: Int -) - -@Serializable -internal data class Params( - val version: Int, - val body: Body -) - -@Serializable -internal data class Body( - val items: List -) - -@OptIn(ExperimentalSerializationApi::class) -@Serializable -@JsonClassDiscriminator(KeySet.type) -internal sealed class ViewParams { - abstract val type: ViewType - abstract val action: ActionData? - abstract val width: SizeSpec - abstract val height: SizeSpec - abstract val viewStyle: ViewStyle - - private fun getWeight(orientation: Orientation): Float { - return when (orientation) { - Orientation.Row -> { - width.getWeight() - } - Orientation.Column -> { - height.getWeight() - } - } - } - - fun applyLayoutParams( - context: Context, - layoutParams: ViewGroup.LayoutParams, - orientation: Orientation - ): ViewGroup.LayoutParams { - val resources = context.resources - layoutParams.width = if (width.type == SizeType.Fixed) resources.intToDp(width.value) else width.value - layoutParams.height = if (height.type == SizeType.Fixed) resources.intToDp(height.value) else height.value - if (layoutParams is LinearLayout.LayoutParams) { - layoutParams.weight = getWeight(orientation) - } - return layoutParams - } -} - -@Serializable -@SerialName(KeySet.box) -internal data class BoxViewParams( - override val type: ViewType, - override val action: ActionData? = null, - override val width: SizeSpec = SizeSpec(SizeType.Flex, FILL_PARENT), - override val height: SizeSpec = SizeSpec(SizeType.Flex, WRAP_CONTENT), - override val viewStyle: ViewStyle = ViewStyle(), - val align: Align = Align(), - @SerialName(KeySet.layout) - val orientation: Orientation = Orientation.Row, - val items: List? = null -) : ViewParams() - -@Serializable -@SerialName(KeySet.text) -internal data class TextViewParams( - override val type: ViewType, - override val action: ActionData? = null, - override val width: SizeSpec = SizeSpec(SizeType.Flex, FILL_PARENT), - override val height: SizeSpec = SizeSpec(SizeType.Flex, WRAP_CONTENT), - override val viewStyle: ViewStyle = ViewStyle(), - val align: Align = Align(), - val text: String, - val maxTextLines: Int? = null, - val textStyle: TextStyle = TextStyle() -) : ViewParams() - -@Serializable -@SerialName(KeySet.image) -internal data class ImageViewParams( - override val type: ViewType, - override val action: ActionData? = null, - override val width: SizeSpec = SizeSpec(SizeType.Flex, FILL_PARENT), - override val height: SizeSpec = SizeSpec(SizeType.Flex, WRAP_CONTENT), - override val viewStyle: ViewStyle = ViewStyle(), - val imageUrl: String, - val metaData: MetaData? = null, - val imageStyle: ImageStyle = ImageStyle() -) : ViewParams() - -@Serializable -@SerialName(KeySet.textButton) -internal data class ButtonViewParams( - override val type: ViewType, - override val action: ActionData? = null, - override val width: SizeSpec = SizeSpec(SizeType.Flex, FILL_PARENT), - override val height: SizeSpec = SizeSpec(SizeType.Flex, WRAP_CONTENT), - override val viewStyle: ViewStyle = ViewStyle(), - val text: String, - val maxTextLines: Int = 1, - val textStyle: TextStyle? = null -) : ViewParams() - -@Serializable -@SerialName(KeySet.imageButton) -internal data class ImageButtonViewParams( - override val type: ViewType, - override val action: ActionData? = null, - override val width: SizeSpec = SizeSpec(SizeType.Flex, FILL_PARENT), - override val height: SizeSpec = SizeSpec(SizeType.Flex, WRAP_CONTENT), - override val viewStyle: ViewStyle = ViewStyle(), - val imageUrl: String, - val metaData: MetaData? = null, - val imageStyle: ImageStyle = ImageStyle() -) : ViewParams() - -@Serializable -@SerialName(KeySet.carouselView) -internal data class CarouselViewParams( - override val type: ViewType, - override val action: ActionData? = null, - override val width: SizeSpec = SizeSpec(SizeType.Flex, FILL_PARENT), - override val height: SizeSpec = SizeSpec(SizeType.Flex, WRAP_CONTENT), - override val viewStyle: ViewStyle = ViewStyle(), - val items: List, - val carouselStyle: CarouselStyle = CarouselStyle() -) : ViewParams() - -internal object TemplateParamsCreator { - @JvmStatic - @Throws(Exception::class) - internal fun createDataTemplateViewParams( - dataTemplate: String, - themeMode: NotificationThemeMode - ): Params { - val textParams = mutableListOf().apply { - val json = JSONObject(dataTemplate) - json.keys().forEach { key -> - add( - TextViewParams( - type = ViewType.Text, - textStyle = TextStyle( - size = 14, - color = themeMode.descTextColor - ), - text = "$key : ${json.getString(key)}" - ) - ) - } - } - return Params( - version = 1, - body = Body( - items = listOf( - BoxViewParams( - type = ViewType.Box, - orientation = Orientation.Column, - viewStyle = ViewStyle( - backgroundColor = themeMode.backgroundColor, - padding = Padding( - 12, 12, 12, 12 - ), - radius = 8 - ), - items = textParams - ), - ) - ) - ) - } - - @JvmStatic - fun createDefaultViewParam( - message: BaseMessage, - defaultFallbackTitle: String, - defaultFallbackDescription: String, - themeMode: NotificationThemeMode - ): Params { - val hasFallbackMessage = message.message.isNotEmpty() - val textList = mutableListOf( - TextViewParams( - type = ViewType.Text, - textStyle = TextStyle( - size = 14, - color = themeMode.titleColor - ), - text = message.message.takeIf { it.isNotEmpty() } ?: defaultFallbackTitle - ) - ) - - if (!hasFallbackMessage) { - textList.add( - TextViewParams( - type = ViewType.Text, - textStyle = TextStyle( - size = 14, - color = themeMode.descTextColor - ), - text = defaultFallbackDescription - ) - ) - } - return Params( - version = 1, - body = Body( - items = listOf( - BoxViewParams( - type = ViewType.Box, - orientation = Orientation.Column, - viewStyle = ViewStyle( - backgroundColor = themeMode.backgroundColor, - padding = Padding( - 12, 12, 12, 12 - ), - radius = 8 - ), - items = textList - ), - ) - ) - ) - } - - @JvmStatic - fun createMessageTemplateDefaultViewParam( - message: String, - defaultFallbackTitle: String, - defaultFallbackDescription: String - ): Params { - val hasFallbackMessage = message.isNotEmpty() - val textList = mutableListOf( - TextViewParams( - type = ViewType.Text, - width = SizeSpec(SizeType.Flex, WRAP_CONTENT), - height = SizeSpec(SizeType.Flex, WRAP_CONTENT), - textStyle = TextStyle( - size = 14, - color = NotificationThemeMode.Default.titleColor - ), - text = message.takeIf { it.isNotEmpty() } ?: defaultFallbackTitle, - ) - ) - - if (!hasFallbackMessage) { - textList.add( - TextViewParams( - type = ViewType.Text, - width = SizeSpec(SizeType.Flex, WRAP_CONTENT), - height = SizeSpec(SizeType.Flex, WRAP_CONTENT), - textStyle = TextStyle( - size = 14, - color = NotificationThemeMode.Default.descTextColor - ), - text = defaultFallbackDescription - ) - ) - } - return Params( - version = 1, - body = Body( - items = listOf( - BoxViewParams( - type = ViewType.Box, - orientation = Orientation.Column, - width = SizeSpec(SizeType.Flex, WRAP_CONTENT), - height = SizeSpec(SizeType.Flex, WRAP_CONTENT), - viewStyle = ViewStyle( - backgroundColor = NotificationThemeMode.Default.backgroundColor, - padding = Padding( - 6, 6, 12, 12 - ), - margin = Margin( - 0, 0, 50, 0 - ), - radius = 16 - ), - items = textList - ), - ) - ) - ) - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Styles.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Styles.kt deleted file mode 100644 index 7fa78e35..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Styles.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.sendbird.uikit.internal.model.template_messages - -import android.graphics.Color -import android.graphics.PorterDuff -import android.util.TypedValue -import android.view.View -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import androidx.annotation.ColorInt -import com.sendbird.uikit.internal.extensions.intToDp -import com.sendbird.uikit.internal.extensions.loadToBackground -import com.sendbird.uikit.internal.extensions.setTypeface -import com.sendbird.uikit.internal.interfaces.ViewRoundable -import com.sendbird.uikit.internal.model.serializer.ColorIntAsStringSerializer -import kotlinx.serialization.Serializable - -@Serializable -internal data class TextStyle( - val size: Int? = null, - @ColorInt - @Serializable(with = ColorIntAsStringSerializer::class) - val color: Int? = null, - val weight: Weight? = null -) { - fun apply(view: TextView): TextStyle { - size?.let { view.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.toFloat()) } - color?.let { view.setTextColor(it) } - weight?.let { view.setTypeface(it.value) } - return this - } -} - -@Serializable -internal data class ImageStyle( - val contentMode: ContentMode? = null, - @Serializable(with = ColorIntAsStringSerializer::class) - val tintColor: Int? = null, -) { - fun apply(view: ImageView): ImageStyle { - contentMode?.let { view.scaleType = it.scaleType } - tintColor?.let { view.setColorFilter(it, PorterDuff.Mode.SRC_ATOP) } - return this - } -} - -@Serializable -internal data class ViewStyle( - @ColorInt - @Serializable(with = ColorIntAsStringSerializer::class) - val backgroundColor: Int? = null, - val backgroundImageUrl: String? = null, - val borderWidth: Int? = null, - @ColorInt - @Serializable(with = ColorIntAsStringSerializer::class) - val borderColor: Int? = null, - val radius: Int? = null, - val margin: Margin? = null, - val padding: Padding? = null -) { - fun apply(view: View, useRipple: Boolean = false): ViewStyle { - if (backgroundColor != null || (borderWidth != null && borderWidth > 0)) { - view.setBackgroundColor(backgroundColor ?: Color.TRANSPARENT) - } - - // backgroundImageUrl has higher priority than backgroundColor (platform synced) - backgroundImageUrl?.let { view.loadToBackground(it, radius ?: 0, useRipple) } - - margin?.apply(view) - padding?.apply(view) - - if (view is ViewRoundable) { - radius?.let { view.setRadiusIntSize(it) } - borderWidth?.let { view.setBorder(borderWidth, borderColor ?: Color.TRANSPARENT) } - } - return this - } -} - -@Serializable -internal data class CarouselStyle( - val spacing: Int = 10, - val maxChildWidth: Int = 240 // default value -) - -@Serializable -internal data class Margin( - val top: Int = 0, - val bottom: Int = 0, - val left: Int = 0, - val right: Int = 0 -) { - fun apply(view: View) { - val resources = view.context.resources - val layoutParams = view.layoutParams as LinearLayout.LayoutParams - view.layoutParams = layoutParams.also { - it.topMargin = resources.intToDp(top) - it.bottomMargin = resources.intToDp(bottom) - it.marginStart = resources.intToDp(left) - it.marginEnd = resources.intToDp(right) - } - } -} - -@Serializable -internal data class Padding( - val top: Int = 0, - val bottom: Int = 0, - val left: Int = 0, - val right: Int = 0 -) { - fun apply(view: View) { - val resources = view.context.resources - view.setPaddingRelative( - resources.intToDp(left), - resources.intToDp(top), - resources.intToDp(right), - resources.intToDp(bottom) - ) - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Template.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Template.kt deleted file mode 100644 index 6de2463d..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/Template.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.sendbird.uikit.internal.model.template_messages - -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put - -internal object Template { - private fun createImage( - action: JsonObject? = null, - width: JsonObject? = null, - height: JsonObject? = null, - viewStyle: JsonObject? = null, - imageUrl: String, - imageStyle: JsonObject? = null, - ): JsonObject { - return buildJsonObject { - put(KeySet.type, KeySet.image) - action?.let { put(KeySet.action, it) } - width?.let { put(KeySet.width, it) } - height?.let { put(KeySet.height, it) } - viewStyle?.let { put(KeySet.viewStyle, it) } - put(KeySet.imageUrl, imageUrl) - imageStyle?.let { put(KeySet.imageStyle, it) } - } - } - - private fun createButton( - action: JsonObject? = null, - width: JsonObject? = null, - height: JsonObject? = null, - viewStyle: JsonObject? = null, - text: String, - maxTextLines: Int? = null, - textStyle: JsonObject? = null, - ): JsonObject { - return buildJsonObject { - put(KeySet.type, KeySet.textButton) - action?.let { put(KeySet.action, it) } - width?.let { put(KeySet.width, it) } - height?.let { put(KeySet.height, it) } - viewStyle?.let { put(KeySet.viewStyle, it) } - put(KeySet.text, text) - maxTextLines?.let { put(KeySet.maxTextLines, it) } - textStyle?.let { put(KeySet.textStyle, it) } - } - } - - private fun createImageButton( - action: JsonObject? = null, - width: JsonObject? = null, - height: JsonObject? = null, - viewStyle: JsonObject? = null, - imageUrl: String, - imageStyle: JsonObject? = null, - ): JsonObject { - return buildJsonObject { - put(KeySet.type, KeySet.imageButton) - action?.let { put(KeySet.action, it) } - width?.let { put(KeySet.width, it) } - height?.let { put(KeySet.height, it) } - viewStyle?.let { put(KeySet.viewStyle, it) } - put(KeySet.imageUrl, imageUrl) - imageStyle?.let { put(KeySet.imageStyle, it) } - } - } - - private fun createText( - action: JsonObject? = null, - width: JsonObject? = null, - height: JsonObject? = null, - align: JsonObject? = null, - viewStyle: JsonObject? = null, - text: String, - maxTextLines: Int? = null, - textStyle: JsonObject? = null, - ): JsonObject { - return buildJsonObject { - put(KeySet.type, KeySet.text) - action?.let { put(KeySet.action, it) } - width?.let { put(KeySet.width, it) } - height?.let { put(KeySet.height, it) } - align?.let { put(KeySet.align, it) } - viewStyle?.let { put(KeySet.viewStyle, it) } - put(KeySet.text, text) - maxTextLines?.let { put(KeySet.maxTextLines, it) } - textStyle?.let { put(KeySet.textStyle, it) } - } - } - - private fun createBox( - action: JsonObject? = null, - width: JsonObject? = null, - height: JsonObject? = null, - align: JsonObject? = null, - viewStyle: JsonObject? = null, - layout: String? = null, - items: JsonArray - ): JsonObject { - return buildJsonObject { - put(KeySet.type, KeySet.box) - action?.let { put(KeySet.action, it) } - width?.let { put(KeySet.width, it) } - height?.let { put(KeySet.height, it) } - align?.let { put(KeySet.align, it) } - viewStyle?.let { put(KeySet.viewStyle, it) } - layout?.let { put(KeySet.layout, it) } - put(KeySet.items, items) - } - } - - private fun createSize( - type: String, - value: Int - ): JsonObject { - return buildJsonObject { - put(KeySet.type, type) - put(KeySet.value, value) - } - } - - private fun createRect( - top: Int? = null, - bottom: Int? = null, - left: Int? = null, - right: Int? = null, - ): JsonObject { - return buildJsonObject { - top?.let { put(KeySet.top, it) } - bottom?.let { put(KeySet.bottom, it) } - left?.let { put(KeySet.left, it) } - right?.let { put(KeySet.right, it) } - } - } - - private fun createTextStyle( - size: Int? = null, - color: String? = null, - weight: String? = null, - ): JsonObject { - return buildJsonObject { - size?.let { put(KeySet.size, it) } - color?.let { put(KeySet.color, it) } - weight?.let { put(KeySet.weight, it) } - } - } - - private fun createImageStyle( - contentMode: String? = null, - ): JsonObject { - return buildJsonObject { - contentMode?.let { put(KeySet.contentMode, it) } - } - } - - private fun createViewStyle( - backgroundColor: String? = null, - backgroundImageUrl: String? = null, - borderWidth: Int? = null, - borderColor: String? = null, - radius: Int? = null, - margin: JsonObject? = null, - padding: JsonObject? = null - ): JsonObject { - return buildJsonObject { - backgroundColor?.let { put(KeySet.backgroundColor, it) } - backgroundImageUrl?.let { put(KeySet.backgroundImageUrl, it) } - borderWidth?.let { put(KeySet.borderWidth, it) } - borderColor?.let { put(KeySet.borderColor, it) } - radius?.let { put(KeySet.radius, it) } - margin?.let { put(KeySet.margin, it) } - padding?.let { put(KeySet.padding, it) } - } - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewCreator.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewCreator.kt new file mode 100644 index 00000000..81880fd5 --- /dev/null +++ b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewCreator.kt @@ -0,0 +1,290 @@ +package com.sendbird.uikit.internal.model.template_messages + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.FrameLayout.LayoutParams +import android.widget.ProgressBar +import com.sendbird.android.message.BaseMessage +import com.sendbird.message.template.consts.Orientation +import com.sendbird.message.template.consts.SizeType +import com.sendbird.message.template.consts.TemplateTheme +import com.sendbird.message.template.consts.ViewType +import com.sendbird.message.template.model.Body +import com.sendbird.message.template.model.BoxViewParams +import com.sendbird.message.template.model.Margin +import com.sendbird.message.template.model.Padding +import com.sendbird.message.template.model.SizeSpec +import com.sendbird.message.template.model.TemplateParams +import com.sendbird.message.template.model.TextStyle +import com.sendbird.message.template.model.TextViewParams +import com.sendbird.message.template.model.ViewParams +import com.sendbird.message.template.model.ViewStyle +import com.sendbird.message.template.model.WRAP_CONTENT +import com.sendbird.uikit.R +import com.sendbird.uikit.SendbirdUIKit +import com.sendbird.uikit.internal.extensions.intToDp +import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode +import com.sendbird.uikit.utils.DrawableUtils +import org.json.JSONObject + +internal object TemplateParamsCreator { + @JvmStatic + @Throws(Exception::class) + internal fun createDataTemplateViewParams( + dataTemplate: String, + themeMode: TemplateTheme + ): TemplateParams { + val textParams = mutableListOf().apply { + val json = JSONObject(dataTemplate) + json.keys().forEach { key -> + add( + TextViewParams( + type = ViewType.Text, + textStyle = TextStyle( + size = 14, + color = Color.parseColor(if (themeMode == TemplateTheme.Light) "#70000000" else "#70FFFFFF") + ), + text = "$key : ${json.getString(key)}" + ) + ) + } + } + return TemplateParams( + version = 1, + body = Body( + items = listOf( + BoxViewParams( + type = ViewType.Box, + orientation = Orientation.Column, + viewStyle = ViewStyle( + backgroundColor = Color.parseColor(if (themeMode == TemplateTheme.Light) "#EEEEEE" else "#2C2C2C"), + padding = Padding( + 12, 12, 12, 12 + ), + radius = 8 + ), + items = textParams + ), + ) + ) + ) + } + + @JvmStatic + internal fun createDefaultViewParam( + message: BaseMessage, + defaultFallbackTitle: String, + defaultFallbackDescription: String, + themeMode: NotificationThemeMode + ): TemplateParams { + val hasFallbackMessage = message.message.isNotEmpty() + val textList = mutableListOf( + TextViewParams( + type = ViewType.Text, + textStyle = TextStyle( + size = 14, + color = themeMode.titleColor + ), + text = message.message.takeIf { it.isNotEmpty() } ?: defaultFallbackTitle + ) + ) + + if (!hasFallbackMessage) { + textList.add( + TextViewParams( + type = ViewType.Text, + textStyle = TextStyle( + size = 14, + color = themeMode.descTextColor + ), + text = defaultFallbackDescription + ) + ) + } + return TemplateParams( + version = 1, + body = Body( + items = listOf( + BoxViewParams( + type = ViewType.Box, + orientation = Orientation.Column, + viewStyle = ViewStyle( + backgroundColor = themeMode.backgroundColor, + padding = Padding( + 12, 12, 12, 12 + ), + radius = 8 + ), + items = textList + ), + ) + ) + ) + } + + @JvmStatic + internal fun createMessageTemplateDefaultViewParam( + message: String, + defaultFallbackTitle: String, + defaultFallbackDescription: String + ): TemplateParams { + val hasFallbackMessage = message.isNotEmpty() + val textList = mutableListOf( + TextViewParams( + type = ViewType.Text, + width = SizeSpec(SizeType.Flex, WRAP_CONTENT), + height = SizeSpec(SizeType.Flex, WRAP_CONTENT), + textStyle = TextStyle( + size = 14, + color = NotificationThemeMode.Default.titleColor + ), + text = message.takeIf { it.isNotEmpty() } ?: defaultFallbackTitle, + ) + ) + + if (!hasFallbackMessage) { + textList.add( + TextViewParams( + type = ViewType.Text, + width = SizeSpec(SizeType.Flex, WRAP_CONTENT), + height = SizeSpec(SizeType.Flex, WRAP_CONTENT), + textStyle = TextStyle( + size = 14, + color = NotificationThemeMode.Default.descTextColor + ), + text = defaultFallbackDescription + ) + ) + } + return TemplateParams( + version = 1, + body = Body( + items = listOf( + BoxViewParams( + type = ViewType.Box, + orientation = Orientation.Column, + width = SizeSpec(SizeType.Flex, WRAP_CONTENT), + height = SizeSpec(SizeType.Flex, WRAP_CONTENT), + viewStyle = ViewStyle( + backgroundColor = NotificationThemeMode.Default.backgroundColor, + padding = Padding( + 6, 6, 12, 12 + ), + margin = Margin( + 0, 0, 50, 0 + ), + radius = 16 + ), + items = textList + ), + ) + ) + ) + } + + internal fun createTemplateMessageLoadingView(context: Context): View { + val height = context.resources.getDimensionPixelSize(R.dimen.sb_template_message_loading_view_height) + return FrameLayout(context).apply { + layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height) + setBackgroundColor(Color.TRANSPARENT) + addView( + ProgressBar(context).apply { + val size = resources.intToDp(42) + layoutParams = FrameLayout.LayoutParams( + size, size, Gravity.CENTER + ) + val loading = DrawableUtils.setTintList( + context, + R.drawable.sb_progress, + ColorStateList.valueOf(NotificationThemeMode.Default.spinnerColor) + ) + this.indeterminateDrawable = loading + } + ) + } + } + + internal fun createNotificationLoadingView( + context: Context, + isChatNotification: Boolean, + themeMode: NotificationThemeMode, + ): View { + return FrameLayout(context).apply { + layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + resources.intToDp(if (isChatNotification) 274 else 294), + ) + addView( + ProgressBar(context).apply { + val size = resources.intToDp(36) + layoutParams = LayoutParams( + size, size, Gravity.CENTER + ) + val loading = DrawableUtils.setTintList( + context, + R.drawable.sb_progress, + ColorStateList.valueOf(themeMode.spinnerColor) + ) + this.indeterminateDrawable = loading + } + ) + } + } + + internal fun createFallbackViewParams(context: Context, message: BaseMessage): TemplateParams { + return createFallbackViewParams(context, message.message) + } + + internal fun createFallbackViewParams(context: Context, message: String): TemplateParams { + return TemplateParamsCreator.createMessageTemplateDefaultViewParam( + message, + context.getString(R.string.sb_text_template_message_fallback_title), + context.getString(R.string.sb_text_template_message_fallback_description), + ) + } + + private val NotificationThemeMode.backgroundColor: Int + get() { + val color = when (this) { + NotificationThemeMode.Light -> "#EEEEEE" + NotificationThemeMode.Dark -> "#2C2C2C" + NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#EEEEEE" else "#2C2C2C" + } + return Color.parseColor(color) + } + + private val NotificationThemeMode.titleColor: Int + get() { + val color = when (this) { + NotificationThemeMode.Light -> "#E0000000" + NotificationThemeMode.Dark -> "#E0FFFFFF" + NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#E0000000" else "#E0FFFFFF" + } + return Color.parseColor(color) + } + + private val NotificationThemeMode.descTextColor: Int + get() { + val color = when (this) { + NotificationThemeMode.Light -> "#70000000" + NotificationThemeMode.Dark -> "#70FFFFFF" + NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#70000000" else "#70FFFFFF" + } + return Color.parseColor(color) + } + + private val NotificationThemeMode.spinnerColor: Int + get() { + val color = when (this) { + NotificationThemeMode.Light -> "#70000000" + NotificationThemeMode.Dark -> "#70FFFFFF" + NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#70000000" else "#70FFFFFF" + } + return Color.parseColor(color) + } +} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewGenerator.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewGenerator.kt deleted file mode 100644 index 128d40bf..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/template_messages/TemplateViewGenerator.kt +++ /dev/null @@ -1,188 +0,0 @@ -package com.sendbird.uikit.internal.model.template_messages - -import android.content.Context -import android.graphics.Color -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import com.sendbird.uikit.SendbirdUIKit -import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode -import com.sendbird.uikit.internal.ui.widgets.Box -import com.sendbird.uikit.internal.ui.widgets.CarouselView -import com.sendbird.uikit.internal.ui.widgets.Image -import com.sendbird.uikit.internal.ui.widgets.ImageButton -import com.sendbird.uikit.internal.ui.widgets.Text -import com.sendbird.uikit.internal.ui.widgets.TextButton - -internal typealias ViewLifecycleHandler = (view: View, viewParams: ViewParams) -> Unit - -internal object TemplateViewGenerator { - - @Throws(RuntimeException::class) - fun inflateViews( - context: Context, - params: Params, - onViewCreated: ViewLifecycleHandler? = null, - onChildViewCreated: ViewLifecycleHandler? = null - ): View { - when (params.version) { - 1, 2 -> { - return LinearLayout(context).apply { - orientation = LinearLayout.VERTICAL - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - params.body.items.forEach { - addView( - generateView(context, it, Orientation.Column, onViewCreated, onChildViewCreated) - ) - } - } - } - else -> { - throw RuntimeException("unsupported version. current version = ${params.version}") - } - } - } - - private fun generateView( - context: Context, - viewParams: ViewParams, - orientation: Orientation, - onViewCreated: ViewLifecycleHandler? = null, - onChildViewCreated: ViewLifecycleHandler? = null - ): View { - return when (viewParams) { - is BoxViewParams -> createBoxView(context, viewParams, orientation, onViewCreated) - is ImageViewParams -> createImageView(context, viewParams, orientation, onViewCreated) - is TextViewParams -> createTextView(context, viewParams, orientation, onViewCreated) - is ButtonViewParams -> createButtonView(context, viewParams, orientation, onViewCreated) - is ImageButtonViewParams -> createImageButtonView(context, viewParams, orientation, onViewCreated) - is CarouselViewParams -> createCarouselView(context, viewParams, orientation, onViewCreated, onChildViewCreated) - } - } - - private fun createTextView( - context: Context, - params: TextViewParams, - orientation: Orientation, - onViewCreated: ViewLifecycleHandler? = null - ): View { - return Text(context).apply { - onViewCreated?.invoke(this, params) - apply(params, orientation) - } - } - - private fun createImageView( - context: Context, - params: ImageViewParams, - orientation: Orientation, - onViewCreated: ViewLifecycleHandler? = null - ): View { - return Image(context).apply { - onViewCreated?.invoke(this, params) - apply(params, orientation) - } - } - - private fun createButtonView( - context: Context, - params: ButtonViewParams, - orientation: Orientation, - onViewCreated: ViewLifecycleHandler? = null - ): View { - return TextButton(context).apply { - onViewCreated?.invoke(this, params) - apply(params, orientation) - } - } - - private fun createImageButtonView( - context: Context, - params: ImageButtonViewParams, - orientation: Orientation, - onViewCreated: ViewLifecycleHandler? = null - ): View { - return ImageButton(context).apply { - onViewCreated?.invoke(this, params) - apply(params, orientation) - } - } - - private fun createBoxView( - context: Context, - params: BoxViewParams, - orientation: Orientation, - onViewCreated: ViewLifecycleHandler? = null - ): ViewGroup { - return Box(context).apply { - onViewCreated?.invoke(this, params) - apply(params, orientation) - params.items?.forEach { - addView( - generateView( - context, - it, - params.orientation, - onViewCreated - ) - ) - } - } - } - - private fun createCarouselView( - context: Context, - params: CarouselViewParams, - orientation: Orientation, - onViewCreated: ViewLifecycleHandler? = null, - onChildViewCreated: ViewLifecycleHandler? = null - ): ViewGroup { - return CarouselView(context).apply { - onViewCreated?.invoke(this, params) - apply(params, orientation, onChildViewCreated) - } - } - - internal val NotificationThemeMode.backgroundColor: Int - get() { - val color = when (this) { - NotificationThemeMode.Light -> "#EEEEEE" - NotificationThemeMode.Dark -> "#2C2C2C" - NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#EEEEEE" else "#2C2C2C" - } - return Color.parseColor(color) - } - - internal val NotificationThemeMode.titleColor: Int - get() { - val color = when (this) { - NotificationThemeMode.Light -> "#E0000000" - NotificationThemeMode.Dark -> "#E0FFFFFF" - NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#E0000000" else "#E0FFFFFF" - } - return Color.parseColor(color) - } - - internal val NotificationThemeMode.descTextColor: Int - get() { - val color = when (this) { - NotificationThemeMode.Light -> "#70000000" - NotificationThemeMode.Dark -> "#70FFFFFF" - NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#70000000" else "#70FFFFFF" - } - return Color.parseColor(color) - } - - internal val NotificationThemeMode.spinnerColor: Int - get() { - val color = when (this) { - NotificationThemeMode.Light -> "#70000000" - NotificationThemeMode.Dark -> "#70FFFFFF" - NotificationThemeMode.Default -> if (SendbirdUIKit.getDefaultThemeMode() == SendbirdUIKit.ThemeMode.Light) "#70000000" else "#70FFFFFF" - } - return Color.parseColor(color) - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/model/templates/MessageTemplate.kt b/uikit/src/main/java/com/sendbird/uikit/internal/model/templates/MessageTemplate.kt deleted file mode 100644 index 5c2bd144..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/model/templates/MessageTemplate.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.sendbird.uikit.internal.model.templates - -import com.sendbird.android.channel.SimpleTemplateData -import com.sendbird.uikit.internal.model.notifications.CSVColor -import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode -import com.sendbird.uikit.internal.model.serializer.JsonElementToStringSerializer -import com.sendbird.uikit.internal.model.template_messages.KeySet -import com.sendbird.uikit.internal.singleton.JsonParser -import com.sendbird.uikit.internal.singleton.MessageTemplateManager -import com.sendbird.uikit.log.Logger -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import org.json.JSONArray -import org.json.JSONObject - -// TODO : Bind with [NotificationTemplate] after api spec finalize -@Serializable -internal data class MessageTemplate( - @SerialName(KeySet.key) - val templateKey: String, - @SerialName(KeySet.created_at) - val createdAt: Long, - @SerialName(KeySet.updated_at) - val updatedAt: Long, - val name: String? = null, - @SerialName(KeySet.ui_template) - @Serializable(with = JsonElementToStringSerializer::class) - private val _uiTemplate: String, - @SerialName(KeySet.color_variables) - private val _colorVariables: Map -) { - - fun getTemplateSyntax( - variables: Map, - viewVariables: Map> = emptyMap() - ): String { - return _uiTemplate - .replaceVariables(variables) - .replaceViewVariables(viewVariables) - } - - private fun String.replaceVariables(variables: Map): String { - val regex = "\\{([^{}]+)\\}".toRegex() - return regex.replace(this) { matchResult -> - val variable = matchResult.groups[1]?.value - var converted = false - - // 1. lookup and convert color variables first - var convertedResult = _colorVariables[variable]?.let { - Logger.i("++ color variable key=$variable, value=$it") - converted = true - val csvColor = CSVColor(it) - csvColor.getColorHexString(NotificationThemeMode.Default) - } ?: matchResult.value - - // 2. If color variables didn't convert, convert data variables then. - if (!converted && variables.isNotEmpty()) { - convertedResult = variables[variable]?.let { - Logger.i("++ data variable key=$variable, value=$it") - it - } ?: convertedResult - } - convertedResult - } - } - - /** - * If there is problem while replacing view variables, it will return original string and it will be failed to parse to Params. It's intended. - */ - private fun String.replaceViewVariables(viewVariables: Map>): String { - val regex = """\"\{@([^{}]+)\}\"""".toRegex() // find `"{@variable}"` pattern including `"` - return regex.replace(this) { matchResult -> - val variable = matchResult.groups[1]?.value ?: return@replace matchResult.value - val variableDataList = viewVariables[variable] ?: return@replace matchResult.value - val jsonArray = JSONArray() - variableDataList.forEach { childTemplateData -> - val template = MessageTemplateManager.getTemplate(childTemplateData.key) ?: return@replace matchResult.value - jsonArray.put(JSONObject(template.getTemplateSyntax(childTemplateData.variables))) - } - - jsonArray.toString() - } - } - - override fun toString(): String { - return JsonParser.toJsonString(this) - } - - companion object { - @JvmStatic - fun fromJson(value: String): MessageTemplate { - return JsonParser.fromJson(value) - } - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateManager.kt b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateManager.kt index 10e8b93b..20ff6991 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateManager.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateManager.kt @@ -3,11 +3,19 @@ package com.sendbird.uikit.internal.singleton import android.content.Context import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.sendbird.android.channel.SimpleTemplateData import com.sendbird.android.exception.SendbirdException +import com.sendbird.android.message.BaseMessage import com.sendbird.android.params.MessageTemplateListParams -import com.sendbird.uikit.internal.model.templates.MessageTemplate +import com.sendbird.message.template.TemplateParser +import com.sendbird.message.template.model.MessageTemplate +import com.sendbird.message.template.model.TemplateParams +import com.sendbird.uikit.SendbirdUIKit +import com.sendbird.uikit.internal.extensions.childTemplateKeys +import com.sendbird.uikit.internal.extensions.isTemplateMessage +import com.sendbird.uikit.internal.extensions.isValid +import com.sendbird.uikit.internal.extensions.toTemplateTheme import com.sendbird.uikit.log.Logger -import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean /** @@ -16,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean */ internal object MessageTemplateManager { internal lateinit var instance: MessageTemplateManagerImpl + private lateinit var templateParser: TemplateParser @VisibleForTesting internal val isInitialized: AtomicBoolean = AtomicBoolean() @@ -30,9 +39,16 @@ internal object MessageTemplateManager { fun init(context: Context) { val messageTemplateRepository = MessageTemplateRepository(context.applicationContext) instance = MessageTemplateManagerImpl(messageTemplateRepository) + templateParser = TemplateParser(messageTemplateRepository) isInitialized.set(true) } + @Throws(SendbirdException::class) + @JvmStatic + fun parseTemplate(key: String, dataVariables: Map, viewVariables: Map> = emptyMap()): TemplateParams { + return templateParser.parse(key, SendbirdUIKit.getDefaultThemeMode().toTemplateTheme(), dataVariables, viewVariables) + } + @JvmStatic fun hasTemplate(key: String?): Boolean { key ?: return false @@ -61,11 +77,16 @@ internal object MessageTemplateManager { @VisibleForTesting internal fun isInstanceInitialized() = this::instance.isInitialized + + @JvmStatic + val mapper: TemplateMapperDataProvider + get() = instance } -internal class MessageTemplateManagerImpl(private val messageTemplateRepository: MessageTemplateRepository) { - private val worker = Executors.newSingleThreadExecutor() - fun hasTemplate(key: String): Boolean = messageTemplateRepository.getTemplate(key) != null +internal class MessageTemplateManagerImpl( + private val messageTemplateRepository: MessageTemplateRepository +) : TemplateMapperDataProvider { + override fun hasTemplate(key: String): Boolean = messageTemplateRepository.getTemplate(key) != null @WorkerThread @Throws(SendbirdException::class) @@ -112,4 +133,34 @@ internal class MessageTemplateManagerImpl(private val messageTemplateRepository: Logger.d("MessageTemplateManager::clearAll()") messageTemplateRepository.clearAll() } + + override fun isValid(message: BaseMessage): Boolean { + return message.templateMessageData.isValid() + } + + override fun isTemplateMessage(message: BaseMessage): Boolean { + return message.isTemplateMessage() + } + + override fun hasAllTemplates(message: BaseMessage): Boolean { + val templateMessageData = message.templateMessageData ?: return false + val hasParentTemplate = hasTemplate(templateMessageData.key) + return hasParentTemplate && templateMessageData.childTemplateKeys().all { key -> + hasTemplate(key) + } + } + + override fun getTemplateKey(message: BaseMessage): String? { + return message.templateMessageData?.key + } + + override fun childTemplateKeys(message: BaseMessage): List { + return message.templateMessageData?.childTemplateKeys() ?: emptyList() + } + + @WorkerThread + @Throws(SendbirdException::class) + override fun requestTemplateListBlocking(keys: List): List { + return getMessageTemplatesBlocking(keys) + } } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateMapper.kt b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateMapper.kt index 86e9e863..f6263a35 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateMapper.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateMapper.kt @@ -1,24 +1,38 @@ package com.sendbird.uikit.internal.singleton +import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread import com.sendbird.android.message.BaseMessage -import com.sendbird.uikit.internal.extensions.childTemplateKeys -import com.sendbird.uikit.internal.extensions.isTemplateMessage -import com.sendbird.uikit.internal.extensions.isValid +import com.sendbird.message.template.model.MessageTemplate import com.sendbird.uikit.internal.extensions.messageTemplateStatus import com.sendbird.uikit.internal.model.templates.MessageTemplateStatus import com.sendbird.uikit.log.Logger import java.util.concurrent.ExecutorService import java.util.concurrent.Executors +internal interface TemplateMapperDataProvider { + fun isValid(message: BaseMessage): Boolean + fun isTemplateMessage(message: BaseMessage): Boolean + fun hasAllTemplates(message: BaseMessage): Boolean + fun hasTemplate(key: String): Boolean + fun getTemplateKey(message: BaseMessage): String? + fun childTemplateKeys(message: BaseMessage): List + + @WorkerThread + fun requestTemplateListBlocking(keys: List): List +} + /** - * Map [BaseMessage] and [com.sendbird.uikit.internal.model.templates.MessageTemplate]. + * Map [BaseMessage] and [MessageTemplate]. */ -internal class MessageTemplateMapper( +internal class MessageTemplateMapper @JvmOverloads constructor( + @get:VisibleForTesting + internal val dataProvider: TemplateMapperDataProvider, private val worker: ExecutorService = Executors.newCachedThreadPool() ) { /** * Returns updated messages immediately. - * Then, it requests uncached MessageTemplates and will call onFetchCompleteHandler with fetched messages. + * Then, it requests un-cached MessageTemplates and will call onFetchCompleteHandler with fetched messages. */ fun mapTemplate(messages: List, onFetchCompleteHandler: (updatedMessages: List) -> Unit): List { val startedAt = System.currentTimeMillis() @@ -34,7 +48,7 @@ internal class MessageTemplateMapper( } // 2. filter template message - val (templateMessages, notTemplateMessage) = mutableTemplateMessages.partition { it.isTemplateMessage() } + val (templateMessages, notTemplateMessage) = mutableTemplateMessages.partition { dataProvider.isTemplateMessage(it) } templateMessages.forEach { it.messageTemplateStatus = MessageTemplateStatus.LOADING } notTemplateMessage.forEach { it.messageTemplateStatus = MessageTemplateStatus.NOT_APPLICABLE } @@ -45,18 +59,14 @@ internal class MessageTemplateMapper( // 3. filter not cached template keys val (cachedTemplateMessages, notCachedTemplateMessages) = templateMessages.partition { - val templateMessageData = it.templateMessageData ?: return@partition false - val hasParentTemplate = MessageTemplateManager.hasTemplate(templateMessageData.key) - hasParentTemplate && templateMessageData.childTemplateKeys().all { key -> - MessageTemplateManager.hasTemplate(key) - } + dataProvider.hasAllTemplates(it) } cachedTemplateMessages.forEach { - it.messageTemplateStatus = if (it.templateMessageData.isValid()) { + it.messageTemplateStatus = if (dataProvider.isValid(it)) { MessageTemplateStatus.CACHED } else { - Logger.i("This template message is not supported. key=${it.templateMessageData}") + Logger.i("This template message is not supported. key=${dataProvider.getTemplateKey(it)}") MessageTemplateStatus.NOT_APPLICABLE } } @@ -67,26 +77,28 @@ internal class MessageTemplateMapper( return mutableTemplateMessages } + println(">> filter mutable template message status result >> total[${messages.size}], mutable[${mutableTemplateMessages.size}]") // 4. fetch not cached templates worker.submit { val parentTemplateKeys = notCachedTemplateMessages.mapNotNull { - it.templateMessageData?.key - }.filter { key -> MessageTemplateManager.hasTemplate(key).not() } + dataProvider.getTemplateKey(it) + }.filter { key -> dataProvider.hasTemplate(key).not() } - val childTemplateKeys = notCachedTemplateMessages.mapNotNull { - it.templateMessageData?.childTemplateKeys() - }.flatten().filter { key -> MessageTemplateManager.hasTemplate(key).not() } + val childTemplateKeys = notCachedTemplateMessages.map { + dataProvider.childTemplateKeys(it) + }.flatten().filter { key -> dataProvider.hasTemplate(key).not() } val notCachedTemplateKeys = (parentTemplateKeys + childTemplateKeys).distinct() try { - MessageTemplateManager.getMessageTemplatesBlocking(notCachedTemplateKeys) + println("notCachedTemplateKeys: ${notCachedTemplateKeys.size}") + Logger.d("notCachedTemplateKeys: ${notCachedTemplateKeys.size}") + if (notCachedTemplateKeys.isNotEmpty()) { + dataProvider.requestTemplateListBlocking(notCachedTemplateKeys) + } val (fetchedMessages, notFetchedMessages) = notCachedTemplateMessages.partition { message -> - val templateMessageData = message.templateMessageData ?: return@partition false - val hasParentTemplate = MessageTemplateManager.hasTemplate(templateMessageData.key) - hasParentTemplate && templateMessageData.childTemplateKeys().all { key -> - MessageTemplateManager.hasTemplate(key) - } + dataProvider.hasAllTemplates(message) } + println("4. fetch not cached templates result >> fetched messages[${fetchedMessages.size}], not fetched messages[${notFetchedMessages.size}]") Logger.d("4. fetch not cached templates result >> fetched messages[${fetchedMessages.size}], not fetched messages[${notFetchedMessages.size}]") fetchedMessages.forEach { it.messageTemplateStatus = MessageTemplateStatus.CACHED } notFetchedMessages.forEach { it.messageTemplateStatus = MessageTemplateStatus.FAILED_TO_FETCH } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateParser.kt b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateParser.kt deleted file mode 100644 index bf341a00..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateParser.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.sendbird.uikit.internal.singleton - -import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode -import com.sendbird.uikit.internal.model.template_messages.KeySet -import com.sendbird.uikit.internal.model.template_messages.Params -import com.sendbird.uikit.internal.model.template_messages.TemplateParamsCreator -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.decodeFromJsonElement -import org.json.JSONObject - -internal object MessageTemplateParser { - private val json by lazy { - Json { - prettyPrint = true - ignoreUnknownKeys = true - - // https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#coercing-input-values - // coerceInputValues = true - - // https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#encoding-defaults - // encodeDefaults = true - } - } - - @JvmStatic - fun parseParams(el: JsonElement): Params { - return json.decodeFromJsonElement(el) - } - - @JvmStatic - fun parseParams(jsonStr: String): Params { - return json.decodeFromString(jsonStr) - } - - @JvmStatic - fun parseToMap(jsonStr: String): Map { - return json.decodeFromString(jsonStr) - } - - @JvmStatic - @Throws(Exception::class) - fun parse(template: String): Params { - return when (val version = JSONObject(template).getInt(KeySet.version)) { - 1, 2 -> parseParams(template) - else -> throw RuntimeException("unsupported version. current version = $version") - } - } - - @JvmStatic - @Throws(Exception::class) - fun parseDataTemplate(template: String): Params { - return TemplateParamsCreator.createDataTemplateViewParams(template, NotificationThemeMode.Default) - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateRepository.kt b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateRepository.kt index 27c797fe..6f3c71fb 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateRepository.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/MessageTemplateRepository.kt @@ -5,7 +5,8 @@ import androidx.annotation.WorkerThread import com.sendbird.android.SendbirdChat import com.sendbird.android.exception.SendbirdException import com.sendbird.android.params.MessageTemplateListParams -import com.sendbird.uikit.internal.model.templates.MessageTemplate +import com.sendbird.message.template.model.MessageTemplate +import com.sendbird.message.template.providers.MessageTemplateProvider import com.sendbird.uikit.log.Logger import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CountDownLatch @@ -21,7 +22,7 @@ private const val PREFERENCE_FILE_NAME = "com.sendbird.message.templates" * This class is used to store templates which is used for [com.sendbird.android.channel.GroupChannel]. * It doesn't manage the templates for Notification. For Notification, use [NotificationTemplateRepository]. */ -internal class MessageTemplateRepository(context: Context) { +internal class MessageTemplateRepository(context: Context) : MessageTemplateProvider { private val templateCache: MutableMap = ConcurrentHashMap() private val preferences = BaseSharedPreference(context.applicationContext, PREFERENCE_FILE_NAME) internal var lastCachedToken: String = "" @@ -117,4 +118,7 @@ internal class MessageTemplateRepository(context: Context) { } private fun String.toMessageTemplateKey() = MESSAGE_TEMPLATE_KEY_PREFIX + this + override fun provide(key: String): MessageTemplate? { + return getTemplate(key) + } } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationChannelManager.kt b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationChannelManager.kt index d8896303..31e27ae6 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationChannelManager.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationChannelManager.kt @@ -3,12 +3,17 @@ package com.sendbird.uikit.internal.singleton import android.content.Context import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.sendbird.android.channel.SimpleTemplateData import com.sendbird.android.exception.SendbirdException +import com.sendbird.message.template.TemplateParser +import com.sendbird.message.template.consts.MessageTemplateError +import com.sendbird.message.template.model.TemplateParams import com.sendbird.uikit.internal.extensions.runOnUiThread +import com.sendbird.uikit.internal.extensions.toTemplateTheme import com.sendbird.uikit.internal.interfaces.GetTemplateResultHandler import com.sendbird.uikit.internal.model.notifications.NotificationChannelSettings -import com.sendbird.uikit.internal.model.notifications.NotificationTemplate import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode +import com.sendbird.uikit.internal.model.template_messages.TemplateParamsCreator import com.sendbird.uikit.log.Logger import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors @@ -34,6 +39,10 @@ internal object NotificationChannelManager { internal lateinit var templateRepository: NotificationTemplateRepository @VisibleForTesting internal lateinit var channelSettingsRepository: NotificationChannelRepository + private lateinit var templateParser: TemplateParser + @JvmStatic + val mapper: TemplateMapperDataProvider + get() = templateRepository /** * To avoid sending an unintended exception, if the NotificationChannelManager hasn't been initialized it tries to initialize automatically. @@ -55,6 +64,7 @@ internal object NotificationChannelManager { worker.submit { channelSettingsRepository = NotificationChannelRepository(context.applicationContext) templateRepository = NotificationTemplateRepository(context.applicationContext) + templateParser = TemplateParser(templateRepository) isInitialized.set(true) }.get() } @@ -62,82 +72,87 @@ internal object NotificationChannelManager { @JvmStatic fun hasTemplate(key: String): Boolean = templateRepository.getTemplate(key) != null + @Throws(SendbirdException::class) @JvmStatic - fun makeTemplate( + fun parseTemplate(key: String, themeMode: NotificationThemeMode, dataVariables: Map, viewVariables: Map> = emptyMap()): TemplateParams { + val template = templateRepository.getTemplate(key) ?: throw SendbirdException("dataTemplate is empty", MessageTemplateError.ERROR_TEMPLATE_NOT_EXIST) + return if (template.isDataTemplate) { + TemplateParamsCreator.createDataTemplateViewParams(template.dataTemplate, NotificationThemeMode.Default.toTemplateTheme()) + } else { + templateParser.parse(key, themeMode.toTemplateTheme(), dataVariables, viewVariables) + } + } + + @JvmStatic + fun requestTemplate( key: String, variables: Map, themeMode: NotificationThemeMode, - callback: GetTemplateResultHandler - ) { - Logger.d(">> NotificationChannelManager::makeTemplate(), key=$key, handler=$callback") - - templateRepository.getTemplate(key)?.let { - val jsonTemplate = it.getTemplateSyntax(variables, themeMode) - Logger.d("++ template[$key]=$jsonTemplate") - callback.onResult(key, jsonTemplate, it.isDataTemplate, null) - return - } - - // Apply a retry count to prevent infinite requests in case of failure. - val retryCount = templateRequestCount[key] ?: 0 - if (retryCount >= MAX_REQUEST_TEMPLATE_RETRY_COUNT) { - notifyError(key, SendbirdException("Too many template requests have been made.[key=$key]")) - return - } - templateRequestCount[key] = retryCount + 1 - + callback: GetTemplateResultHandler) { synchronized(templateRequestDatas) { val request = TemplateRequestData(key, variables, themeMode, callback) - templateRequestDatas[key]?.let { - it.add(request) + val isRequesting = templateRequestDatas[key] != null + if (isRequesting.not()) { + templateRequestDatas[key] = mutableSetOf() + } + templateRequestDatas[key]?.add(request) + + // Apply a retry count to prevent infinite requests in case of failure. + val retryCount = templateRequestCount[key] ?: 0 + if (retryCount >= MAX_REQUEST_TEMPLATE_RETRY_COUNT) { + notifyError(key, SendbirdException("Too many template requests have been made.[key=$key]"), true) + return + } + templateRequestCount[key] = retryCount + 1 + + if (isRequesting) { Logger.i("-- return (fetching template request already exists), key=$key, handler count=${templateRequestDatas.size}") return - } ?: run { - templateRequestDatas[key] = mutableSetOf().apply { - add(request) - } } } Logger.d("++ templateRequestHandlers size=${templateRequestDatas.size}, templateRequestHandlers[key].size=${templateRequestDatas[key]?.size}") worker.submit { try { - val rawTemplate = templateRepository.requestTemplateBlocking(key) - makeAndNotifyTemplate(key, rawTemplate) + // 1. get template + templateRepository.requestTemplateBlocking(key) + + // 2. parse template to templateParams + val templateParams = parseTemplate(key, themeMode, variables) + + // 3. notify + notifyTemplateFetched(key, templateParams) } catch (e: Throwable) { - templateRequestCount[key] = 1 notifyError(key, SendbirdException(e)) } } } - private fun makeAndNotifyTemplate(key: String, rawTemplate: NotificationTemplate) { + private fun notifyTemplateFetched(key: String, params: TemplateParams) { + Logger.d("NotificationChannelManager::makeAndNotifyTemplate()") runOnUiThread { - synchronized(templateRequestDatas) { - try { - Logger.d("NotificationChannelManager::makeAndNotifyTemplate()") - templateRequestDatas[key]?.forEach { requestData -> - // The template may be the same but variable may be a different message.(NOTI-1027) - val template = rawTemplate.getTemplateSyntax(requestData.variables, requestData.themeMode) - requestData.handler.onResult(key, template, rawTemplate.isDataTemplate, null) - } - } finally { - templateRequestDatas.remove(key) - } - } + dispatchResults(key, params, null) } } - private fun notifyError(key: String, e: SendbirdException) { + private fun notifyError(key: String, e: SendbirdException, runOnCurrentThread: Boolean = false) { + if (runOnCurrentThread) { + dispatchResults(key, null, e) + return + } runOnUiThread { - synchronized(templateRequestDatas) { - try { - Logger.d("NotificationChannelManager::notifyError()") - templateRequestDatas[key]?.forEach { requestData -> - requestData.handler.onResult(key, null, false, e) - } - } finally { - templateRequestDatas.remove(key) + dispatchResults(key, null, e) + } + } + + private fun dispatchResults(key: String, params: TemplateParams?, e: SendbirdException? = null) { + synchronized(templateRequestDatas) { + try { + Logger.d("NotificationChannelManager::dispatchResults()") + templateRequestDatas[key]?.forEach { requestData -> + requestData.handler.onResult(key, params, e) } + } finally { + templateRequestDatas.remove(key) } } } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationTemplateRepository.kt b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationTemplateRepository.kt index 5901edf7..a2a90f68 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationTemplateRepository.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/singleton/NotificationTemplateRepository.kt @@ -4,8 +4,10 @@ import android.content.Context import androidx.annotation.WorkerThread import com.sendbird.android.SendbirdChat import com.sendbird.android.exception.SendbirdException +import com.sendbird.android.message.BaseMessage import com.sendbird.android.params.NotificationTemplateListParams -import com.sendbird.uikit.internal.model.notifications.NotificationTemplate +import com.sendbird.message.template.model.MessageTemplate +import com.sendbird.message.template.providers.MessageTemplateProvider import com.sendbird.uikit.internal.model.notifications.NotificationTemplateList import com.sendbird.uikit.log.Logger import java.util.concurrent.ConcurrentHashMap @@ -18,8 +20,8 @@ private const val TEMPLATE_COUNT = "TEMPLATE_COUNT" private const val PREFERENCE_FILE_NAME = "com.sendbird.notifications.templates" private const val MAX_CACHED_TEMPLATE_COUNT = 1000 -internal class NotificationTemplateRepository(context: Context) { - private val templateCache: MutableMap = ConcurrentHashMap() +internal class NotificationTemplateRepository(context: Context) : MessageTemplateProvider, TemplateMapperDataProvider { + private val templateCache: MutableMap = ConcurrentHashMap() private val preferences = BaseSharedPreference(context.applicationContext, PREFERENCE_FILE_NAME) private var lastCacheToken: String = "" get() { @@ -40,7 +42,7 @@ internal class NotificationTemplateRepository(context: Context) { preferences.loadAll({ key -> key.startsWith(TEMPLATE_KEY_PREFIX) }, { key, value -> - templateCache[key] = NotificationTemplate.fromJson(value.toString()) + templateCache[key] = MessageTemplate.fromJson(value.toString()) }).also { preferences.putInt(TEMPLATE_COUNT, templateCache.size) } @@ -54,46 +56,54 @@ internal class NotificationTemplateRepository(context: Context) { } } - private fun getTemplateKey(key: String) = "${TEMPLATE_KEY_PREFIX}$key" + private fun getCacheKey(key: String) = "${TEMPLATE_KEY_PREFIX}$key" @WorkerThread @Synchronized - private fun saveToCache(template: NotificationTemplate) { + private fun saveToCache(template: MessageTemplate) { Logger.d(">> NotificationTemplateRepository::saveToCache() key=${template.templateKey}") - val key = getTemplateKey(template.templateKey) + val key = getCacheKey(template.templateKey) templateCache[key] = template preferences.putString(key, template.toString()) preferences.putInt(TEMPLATE_COUNT, templateCache.size) } - fun needToUpdateTemplateList(latestUpdatedToken: String?): Boolean { + internal fun needToUpdateTemplateList(latestUpdatedToken: String?): Boolean { return lastCacheToken.isEmpty() || lastCacheToken != latestUpdatedToken } - fun getTemplate(key: String): NotificationTemplate? { + internal fun getTemplate(key: String): MessageTemplate? { Logger.d(">> NotificationTemplateRepository::getTemplate() key=$key") - return templateCache[getTemplateKey(key)] + return templateCache[getCacheKey(key)] } @WorkerThread @Throws(SendbirdException::class) - fun requestTemplateListBlocking(): NotificationTemplateList { + internal fun requestTemplateListBlocking(): NotificationTemplateList { + return requestTemplateListBlocking( + NotificationTemplateListParams().apply { limit = 100 }, + lastCacheToken + ) + } + + @WorkerThread + @Throws(SendbirdException::class) + internal fun requestTemplateListBlocking(params: NotificationTemplateListParams, cachedToken: String? = null): NotificationTemplateList { Logger.d(">> NotificationTemplateRepository::requestTemplateList()") val latch = CountDownLatch(1) var error: SendbirdException? = null val result: AtomicReference = AtomicReference() + val shouldUpdateToken = cachedToken != null SendbirdChat.getNotificationTemplateListByToken( - lastCacheToken, - NotificationTemplateListParams().apply { - limit = 100 - } + cachedToken, + params ) { notificationTemplateList, _, token, e -> error = e try { val templateList = notificationTemplateList?.let { NotificationTemplateList.fromJson(it.jsonPayload) } - if (!token.isNullOrEmpty()) lastCacheToken = token + if (shouldUpdateToken && !token.isNullOrEmpty()) lastCacheToken = token result.set(templateList) } catch (e: Throwable) { error = SendbirdException("notification template list data is not valid", e) @@ -114,17 +124,17 @@ internal class NotificationTemplateRepository(context: Context) { @WorkerThread @Throws(SendbirdException::class) - fun requestTemplateBlocking(key: String): NotificationTemplate { + internal fun requestTemplateBlocking(key: String): MessageTemplate { Logger.d(">> NotificationTemplateRepository::requestTemplate() key=$key") val latch = CountDownLatch(1) var error: SendbirdException? = null - val result: AtomicReference = AtomicReference() + val result: AtomicReference = AtomicReference() SendbirdChat.getNotificationTemplate(key) { template, e -> error = e try { template?.let { Logger.i("++ request response template key=$key : ${it.jsonPayload}") - result.set(NotificationTemplate.fromJson(it.jsonPayload)) + result.set(MessageTemplate.fromJson(it.jsonPayload)) } } catch (e: Throwable) { error = SendbirdException("notification template response data is not valid", e) @@ -139,9 +149,35 @@ internal class NotificationTemplateRepository(context: Context) { return result.get().also { saveToCache(it) } } - fun clearAll() { + internal fun clearAll() { lastCacheToken = "" templateCache.clear() preferences.clearAll() } + + override fun provide(key: String): MessageTemplate? { + return getTemplate(key) + } + + override fun isValid(message: BaseMessage): Boolean = message.notificationData != null + override fun isTemplateMessage(message: BaseMessage): Boolean = message.notificationData != null + override fun hasAllTemplates(message: BaseMessage): Boolean { + // if the sbm message supports the CarouselView later, it should also check the child templates. + return message.notificationData?.let { + return hasTemplate(it.templateKey) + } ?: false + } + + override fun hasTemplate(key: String): Boolean = getTemplate(key) != null + override fun getTemplateKey(message: BaseMessage): String? = message.notificationData?.templateKey + override fun childTemplateKeys(message: BaseMessage): List = emptyList() + + @WorkerThread + @Throws(SendbirdException::class) + override fun requestTemplateListBlocking(keys: List): List { + return requestTemplateListBlocking(NotificationTemplateListParams().apply { + this.limit = 100 + this.keys = keys + }).templates + } } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/BaseNotificationView.kt b/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/BaseNotificationView.kt index 4ca5fa58..04a50bda 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/BaseNotificationView.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/BaseNotificationView.kt @@ -1,31 +1,25 @@ package com.sendbird.uikit.internal.ui.messages import android.content.Context -import android.content.res.ColorStateList import android.util.AttributeSet -import android.view.Gravity import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ProgressBar import com.sendbird.android.channel.NotificationData -import com.sendbird.android.exception.SendbirdException import com.sendbird.android.internal.SendbirdStatistics import com.sendbird.android.message.BaseMessage +import com.sendbird.message.template.ViewGenerator import com.sendbird.uikit.R import com.sendbird.uikit.interfaces.OnNotificationTemplateActionHandler -import com.sendbird.uikit.internal.extensions.intToDp import com.sendbird.uikit.internal.extensions.isContentDisplayed -import com.sendbird.uikit.internal.interfaces.GetTemplateResultHandler +import com.sendbird.uikit.internal.extensions.messageTemplateStatus +import com.sendbird.uikit.internal.extensions.toTemplateTheme import com.sendbird.uikit.internal.model.notifications.NotificationThemeMode import com.sendbird.uikit.internal.model.template_messages.KeySet import com.sendbird.uikit.internal.model.template_messages.TemplateParamsCreator -import com.sendbird.uikit.internal.model.template_messages.TemplateViewGenerator -import com.sendbird.uikit.internal.model.template_messages.TemplateViewGenerator.spinnerColor -import com.sendbird.uikit.internal.singleton.MessageTemplateParser +import com.sendbird.uikit.internal.model.templates.MessageTemplateStatus import com.sendbird.uikit.internal.singleton.NotificationChannelManager import com.sendbird.uikit.log.Logger -import com.sendbird.uikit.utils.DrawableUtils +import com.sendbird.uikit.model.Action internal abstract class BaseNotificationView @JvmOverloads internal constructor( context: Context, @@ -39,73 +33,51 @@ internal abstract class BaseNotificationView @JvmOverloads internal constructor( themeMode: NotificationThemeMode, onNotificationTemplateActionHandler: OnNotificationTemplateActionHandler? = null ) { - val handler = object : GetTemplateResultHandler { - override fun onResult(templateKey: String, jsonTemplate: String?, isDataTemplate: Boolean, e: SendbirdException?) { - Logger.d("++ get template has been succeed, matched=${parentView.tag == message.messageId}") - if (parentView.tag != message.messageId) return - val layout = try { - e?.let { throw e } - - jsonTemplate?.let { - val viewParams = if (isDataTemplate) { - MessageTemplateParser.parseDataTemplate(jsonTemplate) - } else { - MessageTemplateParser.parse(jsonTemplate) - } - - TemplateViewGenerator.inflateViews( - context, - viewParams, - onViewCreated = { view, params -> - params.action?.register( - view, - message - ) { v, action, message -> - sendNotificationStats(templateKey, message) - onNotificationTemplateActionHandler?.onHandleAction( - v, action, message - ) - } - } - ).also { - message.isContentDisplayed = true - } - } - } catch (e: Throwable) { - Logger.w("${e.printStackTrace()}") - message.isContentDisplayed = false - createFallbackNotification(message, themeMode, onNotificationTemplateActionHandler) - } - parentView.removeAllViews() - parentView.addView(layout) - } - } - val notificationData: NotificationData? = message.notificationData val templateKey: String = notificationData?.templateKey ?: "" val templateVariables = notificationData?.templateVariables ?: mapOf() - Logger.d("++ message notificationData=$notificationData") - try { + val layout: View = try { parentView.removeAllViews() - parentView.tag = message.messageId + if (templateKey.isEmpty()) { throw IllegalArgumentException("this message must have template key.") } - if (!NotificationChannelManager.hasTemplate(templateKey)) { - message.isContentDisplayed = false - val layout = createLoadingView(!message.isFeedChannel, themeMode) - parentView.addView(layout) + message.isContentDisplayed = true + when (message.messageTemplateStatus) { + MessageTemplateStatus.CACHED -> { + val templateParams = NotificationChannelManager.parseTemplate(templateKey, themeMode, templateVariables) + ViewGenerator.inflateViews( + context, + themeMode.toTemplateTheme(), + templateParams, + onViewCreated = { view, params -> + params.action?.register( + view, + message + ) { v, action, message -> + sendNotificationStats(templateKey, message) + onNotificationTemplateActionHandler?.onHandleAction( + v, Action.from(action), message + ) + } + } + ).also { + message.isContentDisplayed = true + } + } + MessageTemplateStatus.LOADING -> TemplateParamsCreator.createNotificationLoadingView(context, !message.isFeedChannel, themeMode) + null, MessageTemplateStatus.NOT_APPLICABLE, + MessageTemplateStatus.FAILED_TO_PARSE, + MessageTemplateStatus.FAILED_TO_FETCH -> throw IllegalArgumentException("fail to load this template message. id=${message.messageId}, status=${message.messageTemplateStatus}") } - - NotificationChannelManager.makeTemplate( - templateKey, templateVariables, themeMode, handler - ) } catch (e: Throwable) { - handler.onResult(templateKey, null, false, SendbirdException(e)) + message.isContentDisplayed = false + createFallbackNotification(message, themeMode, onNotificationTemplateActionHandler) } + parentView.addView(layout) } - internal fun createFallbackNotification( + private fun createFallbackNotification( message: BaseMessage, themeMode: NotificationThemeMode, onNotificationTemplateActionHandler: OnNotificationTemplateActionHandler? = null @@ -116,11 +88,16 @@ internal abstract class BaseNotificationView @JvmOverloads internal constructor( context.getString(R.string.sb_text_notification_fallback_description), themeMode ).run { - TemplateViewGenerator.inflateViews( + ViewGenerator.inflateViews( context, + themeMode.toTemplateTheme(), this, onViewCreated = { view, params -> - params.action?.register(view, message, onNotificationTemplateActionHandler) + params.action?.register(view, message) { v, action, message -> + onNotificationTemplateActionHandler?.onHandleAction( + v, Action.from(action), message + ) + } } ) } @@ -151,30 +128,4 @@ internal abstract class BaseNotificationView @JvmOverloads internal constructor( Logger.w(e) } } - - private fun createLoadingView( - isChatNotification: Boolean, - themeMode: NotificationThemeMode, - ): View { - return FrameLayout(context).apply { - layoutParams = LayoutParams( - LayoutParams.MATCH_PARENT, - resources.intToDp(if (isChatNotification) 274 else 294), - ) - addView( - ProgressBar(context).apply { - val size = resources.intToDp(36) - layoutParams = LayoutParams( - size, size, Gravity.CENTER - ) - val loading = DrawableUtils.setTintList( - context, - R.drawable.sb_progress, - ColorStateList.valueOf(themeMode.spinnerColor) - ) - this.indeterminateDrawable = loading - } - ) - } - } } diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/MessageTemplateView.kt b/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/MessageTemplateView.kt index 90f6aaa4..43b6038b 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/MessageTemplateView.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/MessageTemplateView.kt @@ -3,12 +3,13 @@ package com.sendbird.uikit.internal.ui.messages import android.content.Context import android.util.AttributeSet import androidx.core.content.ContextCompat +import com.sendbird.message.template.ViewGenerator +import com.sendbird.message.template.interfaces.OnViewLifecycleHandler +import com.sendbird.message.template.consts.TemplateTheme +import com.sendbird.message.template.model.TemplateParams import com.sendbird.uikit.R import com.sendbird.uikit.internal.extensions.ERR_MESSAGE_TEMPLATE_UNKNOWN -import com.sendbird.uikit.internal.extensions.createFallbackViewParams -import com.sendbird.uikit.internal.model.template_messages.Params -import com.sendbird.uikit.internal.model.template_messages.TemplateViewGenerator -import com.sendbird.uikit.internal.model.template_messages.ViewLifecycleHandler +import com.sendbird.uikit.internal.model.template_messages.TemplateParamsCreator import com.sendbird.uikit.internal.ui.widgets.RoundCornerLayout import com.sendbird.uikit.internal.utils.TemplateViewCachePool @@ -31,11 +32,12 @@ internal class MessageTemplateView @JvmOverloads internal constructor( @Synchronized internal fun draw( - params: Params, + params: TemplateParams, + theme: TemplateTheme, cacheKey: String? = null, viewCachePool: TemplateViewCachePool? = null, - onViewCreated: ViewLifecycleHandler? = null, - onChildViewCreated: ViewLifecycleHandler? = null + onViewCreated: OnViewLifecycleHandler? = null, + onChildViewCreated: OnViewLifecycleHandler? = null ) { if (this.childCount > 0) { this.removeAllViews() @@ -50,11 +52,11 @@ internal class MessageTemplateView @JvmOverloads internal constructor( } val view = try { - TemplateViewGenerator.inflateViews(context, params, onViewCreated, onChildViewCreated) + ViewGenerator.inflateViews(context, theme, params, onViewCreated, onChildViewCreated) } catch (e: Exception) { val errorMessage = context.getString(R.string.sb_text_template_message_fallback_error).format(ERR_MESSAGE_TEMPLATE_UNKNOWN) - val fallbackParams = context.createFallbackViewParams(errorMessage) - TemplateViewGenerator.inflateViews(context, fallbackParams, onViewCreated, onChildViewCreated) + val fallbackParams = TemplateParamsCreator.createFallbackViewParams(context, errorMessage) + ViewGenerator.inflateViews(context, theme, fallbackParams, onViewCreated, onChildViewCreated) } if (cacheKey != null) viewCachePool?.cacheView(cacheKey, view) this.addView(view) diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/OtherTemplateMessageView.kt b/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/OtherTemplateMessageView.kt index 3b02745c..b815cb04 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/OtherTemplateMessageView.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/ui/messages/OtherTemplateMessageView.kt @@ -9,13 +9,12 @@ import com.sendbird.android.channel.TemplateContainerOptions import com.sendbird.android.message.BaseMessage import com.sendbird.android.message.FeedbackStatus import com.sendbird.uikit.R +import com.sendbird.uikit.SendbirdUIKit import com.sendbird.uikit.consts.ReplyType import com.sendbird.uikit.databinding.SbViewOtherTemplateMessageComponentBinding import com.sendbird.uikit.interfaces.OnItemClickListener import com.sendbird.uikit.interfaces.OnMessageTemplateActionHandler import com.sendbird.uikit.internal.extensions.ERR_MESSAGE_TEMPLATE_NOT_APPLICABLE -import com.sendbird.uikit.internal.extensions.createFallbackViewParams -import com.sendbird.uikit.internal.extensions.createTemplateMessageLoadingView import com.sendbird.uikit.internal.extensions.drawFeedback import com.sendbird.uikit.internal.extensions.hasParentMessage import com.sendbird.uikit.internal.extensions.isSuggestedRepliesVisible @@ -24,10 +23,13 @@ import com.sendbird.uikit.internal.extensions.messageTemplateStatus import com.sendbird.uikit.internal.extensions.saveParamsFromTemplate import com.sendbird.uikit.internal.extensions.shouldShowSuggestedReplies import com.sendbird.uikit.internal.extensions.toContextThemeWrapper +import com.sendbird.uikit.internal.extensions.toTemplateTheme import com.sendbird.uikit.internal.interfaces.OnFeedbackRatingClickListener +import com.sendbird.uikit.internal.model.template_messages.TemplateParamsCreator import com.sendbird.uikit.internal.model.templates.MessageTemplateStatus import com.sendbird.uikit.internal.utils.TemplateViewCachePool import com.sendbird.uikit.log.Logger +import com.sendbird.uikit.model.Action import com.sendbird.uikit.model.MessageListUIParams import com.sendbird.uikit.utils.ViewUtils @@ -127,13 +129,13 @@ internal class OtherTemplateMessageView @JvmOverloads internal constructor( val errorMessage = context.getString(R.string.sb_text_template_message_fallback_error).format( ERR_MESSAGE_TEMPLATE_NOT_APPLICABLE ) - context.createFallbackViewParams(errorMessage) + TemplateParamsCreator.createFallbackViewParams(context, errorMessage) } MessageTemplateStatus.FAILED_TO_PARSE, MessageTemplateStatus.FAILED_TO_FETCH -> { - context.createFallbackViewParams(message) + TemplateParamsCreator.createFallbackViewParams(context, message) } MessageTemplateStatus.LOADING -> { - val loadingView = context.createTemplateMessageLoadingView() + val loadingView = TemplateParamsCreator.createTemplateMessageLoadingView(context) binding.messageTemplateView.removeAllViews() binding.messageTemplateView.addView(loadingView) return @@ -145,23 +147,25 @@ internal class OtherTemplateMessageView @JvmOverloads internal constructor( message.messageTemplateParams } - params ?: context.createFallbackViewParams(message) + params ?: TemplateParamsCreator.createFallbackViewParams(context, message) } } + val theme = SendbirdUIKit.getDefaultThemeMode().toTemplateTheme() val cacheKey = "${message.messageId}_${message.messageTemplateStatus}" binding.messageTemplateView.draw( params, + theme, cacheKey, viewCachePool, onViewCreated = { v, p -> p.action?.register(v, message) { view, action, message -> - handler?.onHandleAction(view, action, message) + handler?.onHandleAction(view, Action.from(action), message) } }, onChildViewCreated = { v, p -> p.action?.register(v, message) { view, action, message -> - handler?.onHandleAction(view, action, message) + handler?.onHandleAction(view, Action.from(action), message) } } ) diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/ui/notifications/NotificationListComponent.kt b/uikit/src/main/java/com/sendbird/uikit/internal/ui/notifications/NotificationListComponent.kt index 61dde58d..6d4591ba 100644 --- a/uikit/src/main/java/com/sendbird/uikit/internal/ui/notifications/NotificationListComponent.kt +++ b/uikit/src/main/java/com/sendbird/uikit/internal/ui/notifications/NotificationListComponent.kt @@ -106,7 +106,7 @@ internal open class NotificationListComponent @JvmOverloads constructor( } /** - * Called when the view that has an [com.sendbird.uikit.model.Action] data is clicked. + * Called when the view that has an [Action] data is clicked. * * @param view the view that was clicked. * @param action the registered Action data diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/CarouselViewItemDecoration.kt b/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/CarouselViewItemDecoration.kt deleted file mode 100644 index c786ea6d..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/CarouselViewItemDecoration.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.sendbird.uikit.internal.ui.widgets - -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -class CarouselViewItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() { - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - super.getItemOffsets(outRect, view, parent, state) - - if (parent.getChildAdapterPosition(view) != parent.adapter?.itemCount?.minus(1)) { - outRect.right = space - } - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/MessageTemplateImageView.kt b/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/MessageTemplateImageView.kt deleted file mode 100644 index cef5e489..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/MessageTemplateImageView.kt +++ /dev/null @@ -1,173 +0,0 @@ -package com.sendbird.uikit.internal.ui.widgets - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.Path -import android.graphics.RectF -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import androidx.annotation.ColorInt -import androidx.appcompat.widget.AppCompatImageView -import com.bumptech.glide.Glide -import com.bumptech.glide.load.resource.gif.GifDrawable -import com.bumptech.glide.request.target.CustomViewTarget -import com.bumptech.glide.request.transition.Transition -import com.sendbird.uikit.internal.extensions.intToDp -import com.sendbird.uikit.internal.interfaces.ViewRoundable -import com.sendbird.uikit.internal.model.template_messages.SizeType -import com.sendbird.uikit.internal.model.template_messages.ViewParams -import com.sendbird.uikit.log.Logger -import com.sendbird.uikit.utils.MetricsUtils - -internal open class MessageTemplateImageView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : AppCompatImageView(context, attrs, defStyleAttr), ViewRoundable { - private val path: Path = Path() - private val rectF = RectF() - override var radius: Float = 0F - private var imageRatio: Float = 0F - private var targetWidth: Int = 0 - private var targetHeight: Int = 0 - var viewParams: ViewParams? = null - - private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - style = Paint.Style.STROKE - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - setWillNotDraw(false) - } - - fun setSize(width: Int, height: Int) { - if (width > 0 && height > 0) { - this.targetWidth = width - this.targetHeight = height - this.imageRatio = width.toFloat() / height.toFloat() - requestLayout() - } - } - - override fun setRadiusIntSize(radius: Int) { - this.radius = context.resources.intToDp(radius).toFloat() - invalidate() - } - - final override fun setBorder(borderWidth: Int, @ColorInt borderColor: Int) { - borderPaint.color = borderColor - borderPaint.strokeWidth = context.resources.intToDp(borderWidth).toFloat() - invalidate() - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - // In the Android platform, even if a view is not drawn on the screen due to left and right views, its height value exists. - // In the template message syntax, the views that are not drawn have to hide. - // onSizeChanged() and onLayout() do not update the view even if the visibility changes, so the status of the view must be updated once again. - // Logger.i("-- parent view's width=${(parent as View).width}, x=$x, measureWidth=$width, visible=$visibility") - val visibility = if (x <= -width || x >= (parent as View).width) GONE else VISIBLE - post { - this.visibility = visibility - } - } - - override fun draw(canvas: Canvas) { - rectF.set(0f, 0f, width.toFloat(), height.toFloat()) - - // clip the imageview with round corner - path.reset() - path.addRoundRect(rectF, radius, radius, Path.Direction.CW) - val save = canvas.save() - canvas.clipPath(path) - - // draw the buffer and restore canvas settings. - super.draw(canvas) - canvas.restoreToCount(save) - - // draw border - val hasBorder = borderPaint.strokeWidth > 0 - if (hasBorder) { - val halfBorder: Float = borderPaint.strokeWidth / 2 - rectF.set(0f, 0f, width.toFloat(), height.toFloat()) - rectF.inset(halfBorder, halfBorder) - canvas.drawRoundRect(rectF, radius - halfBorder, radius - halfBorder, borderPaint) - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - val width = MeasureSpec.getSize(widthMeasureSpec) - if (width == 0) { - return - } - - // auto image resolution applies only the height value is flexible - viewParams?.let { - if (it.height.type == SizeType.Fixed) return - if (it.height.type == SizeType.Flex && it.height.value == ViewGroup.LayoutParams.MATCH_PARENT) return - } ?: return - - // if imageRatio doesn't know it should skip - if (imageRatio == 0f) { - return - } - - val layoutParams = layoutParams as ViewGroup.MarginLayoutParams - val newHeight = (width / imageRatio).toInt() - layoutParams.height = newHeight - setMeasuredDimension(width, newHeight) - } - - fun load(url: String) { - var glide = Glide.with(this).load(url) - if (targetWidth > 0 && targetHeight > 0) { - val deviceWidth = MetricsUtils.getDeviceWidth(context) - if (targetWidth > deviceWidth) { - val height = (deviceWidth / imageRatio).toInt() - glide = glide.override(deviceWidth, height) - Logger.i("++ override width=$deviceWidth, height=$height, url=$url") - } - } - - if (imageRatio > 0) { - // if the ratio exist no need to get the original image size to resize image. - glide.into(this) - } else { - // if the image ratio not exist it need to find the size of image to make imageview size. - // After obtaining the proportion of the image, onMeasure is used to determine the size of the view to match the proportion. - glide.into(object : CustomViewTarget(this) { - override fun onLoadFailed(errorDrawable: Drawable?) {} - override fun onResourceCleared(placeholder: Drawable?) {} - - override fun onResourceReady( - resource: Drawable, - transition: Transition? - ) { - val width = when (resource) { - is BitmapDrawable -> resource.bitmap.width - is GifDrawable -> resource.intrinsicWidth - else -> 0 - } - - val height = when (resource) { - is BitmapDrawable -> resource.bitmap.height - is GifDrawable -> resource.intrinsicHeight - else -> 0 - } - - Logger.i("++ width=$width, height=$height, url=$url") - setSize(width, height) - setImageDrawable(resource) - } - }) - } - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/TemplateViews.kt b/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/TemplateViews.kt deleted file mode 100644 index c46b96e1..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/ui/widgets/TemplateViews.kt +++ /dev/null @@ -1,266 +0,0 @@ -package com.sendbird.uikit.internal.ui.widgets - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Color -import android.graphics.Typeface -import android.text.TextUtils -import android.util.AttributeSet -import android.view.Gravity -import android.view.MotionEvent -import android.widget.LinearLayout -import android.widget.TextView -import androidx.appcompat.widget.AppCompatTextView -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.sendbird.uikit.R -import com.sendbird.uikit.SendbirdUIKit -import com.sendbird.uikit.internal.adapter.CarouselChildViewAdapter -import com.sendbird.uikit.internal.extensions.addRipple -import com.sendbird.uikit.internal.extensions.applyTextAlignment -import com.sendbird.uikit.internal.extensions.intToDp -import com.sendbird.uikit.internal.extensions.setAppearance -import com.sendbird.uikit.internal.extensions.setTypeface -import com.sendbird.uikit.internal.model.template_messages.BoxViewParams -import com.sendbird.uikit.internal.model.template_messages.ButtonViewParams -import com.sendbird.uikit.internal.model.template_messages.CarouselViewParams -import com.sendbird.uikit.internal.model.template_messages.ImageButtonViewParams -import com.sendbird.uikit.internal.model.template_messages.ImageViewParams -import com.sendbird.uikit.internal.model.template_messages.Orientation -import com.sendbird.uikit.internal.model.template_messages.TextViewParams -import com.sendbird.uikit.internal.model.template_messages.ViewLifecycleHandler -import com.sendbird.uikit.internal.utils.CarouselLeftSnapHelper - -@SuppressLint("ViewConstructor") -internal open class Text @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RoundCornerLayout(context, attrs, defStyleAttr) { - private val textView: TextView - - init { - layoutParams = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT - ) - textView = AppCompatTextView(context).apply { - ellipsize = TextUtils.TruncateAt.END - layoutParams = LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT - ) - } - this.addView(textView) - } - - fun apply(params: TextViewParams, orientation: Orientation) { - params.applyLayoutParams(context, layoutParams, orientation) - params.viewStyle.apply(this) - params.textStyle.apply(textView) - - textView.gravity = params.align.gravity - textView.applyTextAlignment(params.align.gravity) - params.maxTextLines?.let { textView.maxLines = it } - textView.text = params.text - } - - fun setTextAppearance(textAppearance: Int) { - textView.setAppearance(context, textAppearance) - } -} - -@SuppressLint("ViewConstructor") -internal open class Image @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : MessageTemplateImageView(context, attrs, defStyleAttr) { - init { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - scaleType = ScaleType.FIT_CENTER - } - - fun apply(params: ImageViewParams, orientation: Orientation) { - this.viewParams = params - params.applyLayoutParams(context, layoutParams, orientation) - params.metaData?.let { - setSize(it.pixelWidth, it.pixelHeight) - } - params.imageStyle.apply(this) - params.viewStyle.apply(this) - load(params.imageUrl) - } -} - -@SuppressLint("ViewConstructor") -internal open class TextButton @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RoundCornerLayout(context, attrs, defStyleAttr) { - private val textView: TextView - - init { - // Even if action doesn't exist click ripple effect should show. (UIKit spec) - layoutParams = LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT - ) - isClickable = true - gravity = Gravity.CENTER - // default button padding. - val padding = resources.intToDp(10) - this.setPaddingRelative(padding, padding, padding, padding) - this.setBackgroundResource(R.drawable.sb_shape_round_rect_background_200) - setRadiusIntSize(6) - addRipple(background) - - textView = AppCompatTextView(context).apply { - ellipsize = TextUtils.TruncateAt.END - maxLines = 1 - layoutParams = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT - ) - setTypeface(Typeface.BOLD) - setTextColor(ContextCompat.getColor(context, when (SendbirdUIKit.getDefaultThemeMode()) { - SendbirdUIKit.ThemeMode.Light -> R.color.primary_300 - SendbirdUIKit.ThemeMode.Dark -> R.color.primary_200 - })) - } - this.addView(textView) - } - - fun apply(params: ButtonViewParams, orientation: Orientation) { - params.applyLayoutParams(context, layoutParams, orientation) - params.textStyle?.apply(textView) - params.viewStyle.apply(this, true) - textView.maxLines = params.maxTextLines - textView.text = params.text - addRipple(background) - } - - fun setTextAppearance(textAppearance: Int) { - textView.setAppearance(context, textAppearance) - } -} - -@SuppressLint("ViewConstructor") -internal open class ImageButton @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : MessageTemplateImageView(context, attrs, defStyleAttr) { - init { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - scaleType = ScaleType.FIT_CENTER - addRipple(background) - } - - fun apply(params: ImageButtonViewParams, orientation: Orientation) { - this.viewParams = params - params.applyLayoutParams(context, layoutParams, orientation) - params.metaData?.let { - setSize(it.pixelWidth, it.pixelHeight) - } - params.imageStyle.apply(this) - params.viewStyle.apply(this, true) - load(params.imageUrl) - addRipple(background) - } -} - -@SuppressLint("ViewConstructor") -internal open class Box @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RoundCornerLayout(context, attrs, defStyleAttr) { - init { - layoutParams = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT - ) - } - - fun apply(params: BoxViewParams, orientation: Orientation) { - this.orientation = params.orientation.value - params.applyLayoutParams(context, layoutParams, orientation) - gravity = params.align.gravity - params.viewStyle.apply(this) - } -} - -@SuppressLint("ClickableViewAccessibility") -internal class CarouselView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RoundCornerLayout(context, attrs, defStyleAttr) { - val recyclerView: RecyclerView - private var itemDecoration: CarouselViewItemDecoration? = null - - init { - layoutParams = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT - ) - - recyclerView = object : RecyclerView(context) { - // If padding is touched, the event should be dispatched to the parent view. - override fun onTouchEvent(e: MotionEvent): Boolean { - val layoutManager = this.layoutManager as? LinearLayoutManager - layoutManager?.let { - val firstVisibleItemPosition = it.findFirstVisibleItemPosition() - val lastVisibleItemPosition = it.findLastVisibleItemPosition() - val isTouchEventInPadding = (e.x < paddingStart) || (e.x > width - paddingEnd) - if (isTouchEventInPadding && (firstVisibleItemPosition == 0 || lastVisibleItemPosition == it.itemCount - 1)) { - return false - } - } - - return super.onTouchEvent(e) - } - }.apply { - layoutParams = LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT - ) - layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) - clipToPadding = false - - CarouselLeftSnapHelper().attachToRecyclerView(this) - } - - this.addView(recyclerView) - } - - fun apply(params: CarouselViewParams, orientation: Orientation, onChildViewCreated: ViewLifecycleHandler?) { - val spaceInPixel = context.resources.intToDp(params.carouselStyle.spacing) - itemDecoration?.let { recyclerView.removeItemDecoration(it) } - itemDecoration = CarouselViewItemDecoration(spaceInPixel).also { - recyclerView.addItemDecoration(it) - } - - CarouselChildViewAdapter(context.resources.intToDp(params.carouselStyle.maxChildWidth)).apply { - recyclerView.adapter = this - this.onChildViewCreated = onChildViewCreated - this.setChildTemplateParams(params.items) - } - params.applyLayoutParams(context, layoutParams, orientation) - - // apply ViewStyle to RecyclerView but Recycler is not a ViewRoundable instance. - // So, we need to apply the style to parent view directly. (workaround) - params.viewStyle.apply(recyclerView) - setRadiusIntSize(params.viewStyle.radius ?: 0) - setBorder(params.viewStyle.borderWidth ?: 0, params.viewStyle.borderColor ?: Color.TRANSPARENT) - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/internal/utils/CarouselLeftSnapHelper.kt b/uikit/src/main/java/com/sendbird/uikit/internal/utils/CarouselLeftSnapHelper.kt deleted file mode 100644 index 6e18a9af..00000000 --- a/uikit/src/main/java/com/sendbird/uikit/internal/utils/CarouselLeftSnapHelper.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.sendbird.uikit.internal.utils - -import android.view.View -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.LinearSnapHelper -import androidx.recyclerview.widget.OrientationHelper -import androidx.recyclerview.widget.RecyclerView -import kotlin.math.abs - -internal class CarouselLeftSnapHelper : LinearSnapHelper() { - private var horizontalHelper: OrientationHelper? = null - private fun getHorizontalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper { - val helper = horizontalHelper - return if (helper == null) { - OrientationHelper.createHorizontalHelper(layoutManager) - } else { - if (helper.layoutManager !== layoutManager) { - OrientationHelper.createHorizontalHelper(layoutManager).also { - horizontalHelper = it - } - } else { - helper - } - } - } - - override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? { - if (layoutManager !is LinearLayoutManager) return null - if (layoutManager.findLastVisibleItemPosition() == layoutManager.itemCount - 1) { - return null - } - - return findLeftClosestView(layoutManager, getHorizontalHelper(layoutManager)) - } - - override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? { - if (layoutManager.canScrollVertically()) return super.calculateDistanceToFinalSnap(layoutManager, targetView) - val out = IntArray(2) - out[0] = distanceToLeft(targetView, getHorizontalHelper(layoutManager)) - out[1] = 0 // vertical position always zero - return out - } - - private fun distanceToLeft(targetView: View, orientationHelper: OrientationHelper): Int { - val childLeft: Int = orientationHelper.getDecoratedStart(targetView) - val containerLeft: Int = orientationHelper.startAfterPadding - return childLeft - containerLeft - } - - private fun findLeftClosestView( - layoutManager: RecyclerView.LayoutManager, - helper: OrientationHelper - ): View? { - val childCount = layoutManager.childCount - if (childCount == 0) { - return null - } - var closestChild: View? = null - var absClosest = Int.MAX_VALUE - for (i in 0 until childCount) { - val child = layoutManager.getChildAt(i) ?: continue - val distanceToLeft = distanceToLeft(child, helper) - val absDistance = abs(distanceToLeft) - if (absDistance < absClosest) { - absClosest = absDistance - closestChild = child - } - } - - return closestChild - } -} diff --git a/uikit/src/main/java/com/sendbird/uikit/model/Action.kt b/uikit/src/main/java/com/sendbird/uikit/model/Action.kt index 11d5231a..6403fa77 100644 --- a/uikit/src/main/java/com/sendbird/uikit/model/Action.kt +++ b/uikit/src/main/java/com/sendbird/uikit/model/Action.kt @@ -1,6 +1,5 @@ package com.sendbird.uikit.model -import com.sendbird.uikit.internal.model.template_messages.ActionData import java.util.Locale /** @@ -47,13 +46,13 @@ internal constructor( /** * Convert ActionData to Action class. This is used only for internal. * - * @param actionData The data from the given custom data filed. + * @param action The data from the given custom data filed. * @return Action data. * @since 3.5.0 */ @JvmStatic - internal fun from(actionData: ActionData): Action { - return Action(actionData.type.name.lowercase(Locale.getDefault()), actionData.data, actionData.alterData) + internal fun from(action: com.sendbird.message.template.model.Action): Action { + return Action(action.type.name.lowercase(Locale.getDefault()), action.data, action.alterData) } } } diff --git a/uikit/src/main/java/com/sendbird/uikit/model/MessageList.kt b/uikit/src/main/java/com/sendbird/uikit/model/MessageList.kt index 5b9e23a6..cabcf350 100644 --- a/uikit/src/main/java/com/sendbird/uikit/model/MessageList.kt +++ b/uikit/src/main/java/com/sendbird/uikit/model/MessageList.kt @@ -7,7 +7,7 @@ import com.sendbird.uikit.utils.DateUtils import java.util.TreeSet import java.util.concurrent.ConcurrentHashMap -internal class MessageList @JvmOverloads constructor(private val order: Order = Order.DESC) { +internal class MessageList @JvmOverloads constructor(private val order: Order = Order.DESC, private val useTimeline: Boolean = true) { enum class Order { ASC, DESC } @@ -58,6 +58,10 @@ internal class MessageList @JvmOverloads constructor(private val order: Order = @Synchronized fun add(message: BaseMessage) { Logger.d(">> MessageList::addAll()") + if (!useTimeline) { + BaseMessage.clone(message)?.let { messages.add(it) } + return + } val createdAt = message.createdAt val dateStr = DateUtils.getDateString(createdAt) var timeline = timelineMap[dateStr] @@ -93,7 +97,7 @@ internal class MessageList @JvmOverloads constructor(private val order: Order = fun delete(message: BaseMessage): Boolean { Logger.d(">> MessageList::deleteMessage()") val removed = messages.remove(message) - if (removed) { + if (removed && useTimeline) { val createdAt = message.createdAt val dateStr = DateUtils.getDateString(createdAt) val timeline = timelineMap[dateStr] ?: return true diff --git a/uikit/src/main/java/com/sendbird/uikit/modules/components/MessageListComponent.java b/uikit/src/main/java/com/sendbird/uikit/modules/components/MessageListComponent.java index 12e1d4d5..c784ad49 100644 --- a/uikit/src/main/java/com/sendbird/uikit/modules/components/MessageListComponent.java +++ b/uikit/src/main/java/com/sendbird/uikit/modules/components/MessageListComponent.java @@ -11,9 +11,9 @@ import com.sendbird.android.message.SendingStatus; import com.sendbird.uikit.activities.adapter.MessageListAdapter; import com.sendbird.uikit.consts.StringSet; +import com.sendbird.uikit.interfaces.FormSubmitButtonClickListener; import com.sendbird.uikit.interfaces.OnItemClickListener; import com.sendbird.uikit.interfaces.OnItemLongClickListener; -import com.sendbird.uikit.interfaces.FormSubmitButtonClickListener; import com.sendbird.uikit.interfaces.OnMessageTemplateActionHandler; import com.sendbird.uikit.model.Action; import com.sendbird.uikit.model.MessageListUIParams; diff --git a/uikit/src/main/java/com/sendbird/uikit/utils/MessageUtils.java b/uikit/src/main/java/com/sendbird/uikit/utils/MessageUtils.java index 68cd3e3c..cbb7b495 100644 --- a/uikit/src/main/java/com/sendbird/uikit/utils/MessageUtils.java +++ b/uikit/src/main/java/com/sendbird/uikit/utils/MessageUtils.java @@ -12,7 +12,6 @@ import com.sendbird.android.message.MessageMetaArray; import com.sendbird.android.message.SendingStatus; import com.sendbird.android.message.UserMessage; -import com.sendbird.android.shadow.com.google.gson.JsonParser; import com.sendbird.android.user.User; import com.sendbird.uikit.activities.viewholder.MessageType; import com.sendbird.uikit.activities.viewholder.MessageViewHolderFactory; diff --git a/uikit/src/main/java/com/sendbird/uikit/vm/ChannelViewModel.java b/uikit/src/main/java/com/sendbird/uikit/vm/ChannelViewModel.java index 49d3bb22..22af350d 100644 --- a/uikit/src/main/java/com/sendbird/uikit/vm/ChannelViewModel.java +++ b/uikit/src/main/java/com/sendbird/uikit/vm/ChannelViewModel.java @@ -48,6 +48,7 @@ import com.sendbird.uikit.internal.contracts.SendbirdUIKitImpl; import com.sendbird.uikit.internal.extensions.ChannelExtensionsKt; import com.sendbird.uikit.internal.extensions.MessageExtensionsKt; +import com.sendbird.uikit.internal.singleton.MessageTemplateManager; import com.sendbird.uikit.internal.singleton.MessageTemplateMapper; import com.sendbird.uikit.log.Logger; import com.sendbird.uikit.model.MessageList; @@ -112,10 +113,8 @@ public class ChannelViewModel extends BaseMessageListViewModel { private final ChannelConfig channelConfig; @NonNull - private final MessageTemplateMapper messageTemplateMapper = new MessageTemplateMapper(); - - // The code associated with this flag will be deleted in bulk when the server-side value is activated. - Boolean TEMPORARY_DISABLE_CHAT_INPUT = true; + @VisibleForTesting + MessageTemplateMapper messageTemplateMapper = new MessageTemplateMapper(MessageTemplateManager.getMapper()); /** * Class that holds message data in a channel. @@ -567,15 +566,15 @@ synchronized void notifyDataSetChanged(@NonNull String traceName) { statusFrame.setValue(StatusFrameView.Status.EMPTY); } else { statusFrame.setValue(StatusFrameView.Status.NONE); - if (TEMPORARY_DISABLE_CHAT_INPUT) { + if (channel != null) { BaseMessage lastMessage = cachedMessages.getLatestMessage(); - if (channel != null && lastMessage != null) { - if (MessageExtensionsKt.getDisableChatInput(lastMessage)) { - ChannelExtensionsKt.clearDisabledChatInputMessages(channel); - MessageList.Order order = (messageListParams == null || messageListParams.getReverse()) ? MessageList.Order.DESC : MessageList.Order.ASC; - List messageList = activeDisableInputMessageList(cachedMessages, order); - ChannelExtensionsKt.saveDisabledChatInputMessages(channel, messageList); - } + if (lastMessage != null && MessageExtensionsKt.getDisableChatInput(lastMessage)) { + ChannelExtensionsKt.clearDisabledChatInputMessages(channel); + MessageList.Order order = (messageListParams == null || messageListParams.getReverse()) ? MessageList.Order.DESC : MessageList.Order.ASC; + List messageList = activeDisableInputMessageList(cachedMessages, order); + ChannelExtensionsKt.saveDisabledChatInputMessages(channel, messageList); + } else { + ChannelExtensionsKt.clearDisabledChatInputMessages(channel); } } } @@ -781,7 +780,7 @@ public synchronized boolean loadInitial(final long startingPoint) { collection.initialize(MessageCollectionInitPolicy.CACHE_AND_REPLACE_BY_API, new MessageCollectionInitHandler() { @Override public void onCacheResult(@Nullable List cachedList, @Nullable SendbirdException e) { - if (e == null && cachedList != null && cachedList.size() > 0) { + if (e == null && cachedList != null && !cachedList.isEmpty()) { cachedMessages.addAll(cachedList); notifyDataSetChanged(StringSet.ACTION_INIT_FROM_CACHE); } @@ -793,7 +792,7 @@ public void onApiResult(@Nullable List apiResultList, @Nullable Sen cachedMessages.clear(); cachedMessages.addAll(apiResultList); notifyDataSetChanged(StringSet.ACTION_INIT_FROM_REMOTE); - if (apiResultList.size() > 0) { + if (!apiResultList.isEmpty()) { markAsRead(); } } diff --git a/uikit/src/main/java/com/sendbird/uikit/vm/ChatNotificationChannelViewModel.java b/uikit/src/main/java/com/sendbird/uikit/vm/ChatNotificationChannelViewModel.java index 7a53a8f7..18dc9a6d 100644 --- a/uikit/src/main/java/com/sendbird/uikit/vm/ChatNotificationChannelViewModel.java +++ b/uikit/src/main/java/com/sendbird/uikit/vm/ChatNotificationChannelViewModel.java @@ -23,10 +23,13 @@ import com.sendbird.android.message.BaseMessage; import com.sendbird.android.params.MessageCollectionCreateParams; import com.sendbird.android.params.MessageListParams; +import com.sendbird.uikit.SendbirdUIKit; import com.sendbird.uikit.consts.StringSet; import com.sendbird.uikit.interfaces.AuthenticateHandler; import com.sendbird.uikit.interfaces.OnCompleteHandler; import com.sendbird.uikit.interfaces.OnPagedDataLoader; +import com.sendbird.uikit.internal.singleton.MessageTemplateMapper; +import com.sendbird.uikit.internal.singleton.NotificationChannelManager; import com.sendbird.uikit.log.Logger; import com.sendbird.uikit.model.LiveDataEx; import com.sendbird.uikit.model.MessageData; @@ -37,11 +40,13 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; +import kotlin.Unit; + /** * ViewModel preparing and managing data related with the notification channel. - * * since 3.5.0 */ public class ChatNotificationChannelViewModel extends BaseViewModel implements OnPagedDataLoader>, LifecycleEventObserver { @@ -65,6 +70,9 @@ public class ChatNotificationChannelViewModel extends BaseViewModel implements O private GroupChannel channel; private boolean isVisible = false; + private final MessageTemplateMapper messageTemplateMapper = new MessageTemplateMapper(NotificationChannelManager.getMapper(), Executors.newSingleThreadExecutor()); + @NonNull + private final MessageList cachedMessages = new MessageList(MessageList.Order.DESC, false); /** * Constructor @@ -204,10 +212,12 @@ public synchronized boolean loadInitial(final long startingPoint) { return false; } + cachedMessages.clear(); collection.initialize(MessageCollectionInitPolicy.CACHE_AND_REPLACE_BY_API, new MessageCollectionInitHandler() { @Override public void onCacheResult(@Nullable List cachedList, @Nullable SendbirdException e) { - if (e == null && cachedList != null && cachedList.size() > 0) { + if (e == null && cachedList != null && !cachedList.isEmpty()) { + cachedMessages.addAll(cachedList); notifyDataSetChanged(StringSet.ACTION_INIT_FROM_CACHE); } } @@ -215,8 +225,10 @@ public void onCacheResult(@Nullable List cachedList, @Nullable Send @Override public void onApiResult(@Nullable List apiResultList, @Nullable SendbirdException e) { if (e == null && apiResultList != null) { + cachedMessages.clear(); + cachedMessages.addAll(apiResultList); notifyDataSetChanged(StringSet.ACTION_INIT_FROM_REMOTE); - if (apiResultList.size() > 0) { + if (!apiResultList.isEmpty()) { markAsRead(); } } @@ -249,6 +261,7 @@ public List loadPrevious() throws Exception { try { if (e == null) { messages = messages == null ? Collections.emptyList() : messages; + cachedMessages.addAll(messages); result.set(messages); notifyDataSetChanged(StringSet.ACTION_PREVIOUS); } @@ -304,8 +317,18 @@ private synchronized void notifyChannelDataChanged() { private synchronized void notifyDataSetChanged(@NonNull String traceName) { Logger.d(">> ChatNotificationChannelViewModel::notifyDataSetChanged()"); if (collection == null) return; - final List copiedList = collection.getSucceededMessages(); - if (copiedList.size() == 0) { + final List updatedTemplateMessages = messageTemplateMapper.mapTemplate(cachedMessages.toList(), notCachedMessages -> { + cachedMessages.updateAll(notCachedMessages); + SendbirdUIKit.runOnUIThread(() -> notifyDataSetChanged(StringSet.EVENT_MESSAGE_TEMPLATE_UPDATED)); + return Unit.INSTANCE; + }); + + if (!updatedTemplateMessages.isEmpty()) { + cachedMessages.updateAll(updatedTemplateMessages); + } + + final List copiedList = cachedMessages.toList(); + if (copiedList.isEmpty()) { statusFrame.setValue(StatusFrameView.Status.EMPTY); } else { statusFrame.setValue(StatusFrameView.Status.NONE); @@ -343,6 +366,7 @@ public void onMessagesAdded(@NonNull MessageContext context, @NonNull GroupChann Logger.d(">> ChatNotificationChannelViewModel::onMessagesAdded() from=%s", context.getCollectionEventSource()); if (messages.isEmpty()) return; + cachedMessages.addAll(messages); switch (context.getCollectionEventSource()) { case EVENT_MESSAGE_RECEIVED: case EVENT_MESSAGE_SENT: @@ -357,6 +381,7 @@ public void onMessagesAdded(@NonNull MessageContext context, @NonNull GroupChann @Override public void onMessagesUpdated(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List messages) { Logger.d(">> ChatNotificationChannelViewModel::onMessagesUpdated() from=%s", context.getCollectionEventSource()); + cachedMessages.updateAll(messages); notifyDataSetChanged(context); } @@ -364,7 +389,9 @@ public void onMessagesUpdated(@NonNull MessageContext context, @NonNull GroupCha @Override public void onMessagesDeleted(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List messages) { Logger.d(">> ChatNotificationChannelViewModel::onMessagesDeleted() from=%s", context.getCollectionEventSource()); + // Remove the succeeded message from the succeeded message datasource. + cachedMessages.deleteAll(messages); notifyMessagesDeleted(messages); notifyDataSetChanged(context); } diff --git a/uikit/src/main/java/com/sendbird/uikit/vm/FeedNotificationChannelViewModel.java b/uikit/src/main/java/com/sendbird/uikit/vm/FeedNotificationChannelViewModel.java index 30577fad..046b5d62 100644 --- a/uikit/src/main/java/com/sendbird/uikit/vm/FeedNotificationChannelViewModel.java +++ b/uikit/src/main/java/com/sendbird/uikit/vm/FeedNotificationChannelViewModel.java @@ -25,17 +25,24 @@ import com.sendbird.uikit.consts.StringSet; import com.sendbird.uikit.interfaces.AuthenticateHandler; import com.sendbird.uikit.interfaces.OnPagedDataLoader; +import com.sendbird.uikit.internal.singleton.MessageTemplateMapper; +import com.sendbird.uikit.internal.singleton.NotificationChannelManager; import com.sendbird.uikit.log.Logger; import com.sendbird.uikit.model.LiveDataEx; import com.sendbird.uikit.model.MessageData; +import com.sendbird.uikit.model.MessageList; import com.sendbird.uikit.model.MutableLiveDataEx; import com.sendbird.uikit.widgets.StatusFrameView; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; +import kotlin.Unit; + /** * ViewModel preparing and managing data related with the notification channel. * since 3.5.0 @@ -60,6 +67,9 @@ public class FeedNotificationChannelViewModel extends BaseViewModel implements O @Nullable private FeedChannel channel; private boolean isVisible = false; + private final MessageTemplateMapper messageTemplateMapper = new MessageTemplateMapper(NotificationChannelManager.getMapper(), Executors.newSingleThreadExecutor()); + @NonNull + private final MessageList cachedMessages = new MessageList(MessageList.Order.DESC, false); /** * Constructor @@ -203,11 +213,13 @@ public synchronized boolean loadInitial(final long startingPoint, @Nullable List return false; } + cachedMessages.clear(); collection.initialize(MessageCollectionInitPolicy.CACHE_AND_REPLACE_BY_API, new MessageCollectionInitHandler() { @Override public void onCacheResult(@Nullable List cachedList, @Nullable SendbirdException e) { - if (e == null && cachedList != null && cachedList.size() > 0) { + if (e == null && cachedList != null && !cachedList.isEmpty()) { Logger.d("++ loadInitial from cache cachedList.size=%s", cachedList.size()); + cachedMessages.addAll(cachedList); notifyDataSetChanged(StringSet.ACTION_INIT_FROM_CACHE); } } @@ -216,8 +228,10 @@ public void onCacheResult(@Nullable List cachedList, @Nullable Send public void onApiResult(@Nullable List apiResultList, @Nullable SendbirdException e) { if (e == null && apiResultList != null) { Logger.d("++ loadInitial from remote apiResultList.size=%s", apiResultList.size()); + cachedMessages.clear(); + cachedMessages.addAll(apiResultList); notifyDataSetChanged(StringSet.ACTION_INIT_FROM_REMOTE); - if (apiResultList.size() > 0) { + if (!apiResultList.isEmpty()) { if (isVisible) markAsRead(); } } @@ -250,6 +264,7 @@ public List loadPrevious() throws Exception { try { if (e == null) { messages = messages == null ? Collections.emptyList() : messages; + cachedMessages.addAll(messages); result.set(messages); notifyDataSetChanged(StringSet.ACTION_PREVIOUS); } @@ -305,8 +320,19 @@ private synchronized void notifyChannelDataChanged() { private synchronized void notifyDataSetChanged(@NonNull String traceName) { Logger.d(">> FeedNotificationChannelViewModel::notifyDataSetChanged()"); if (collection == null) return; - final List copiedList = collection.getSucceededMessages(); - if (copiedList.size() == 0) { + + final List updatedTemplateMessages = messageTemplateMapper.mapTemplate(cachedMessages.toList(), notCachedMessages -> { + cachedMessages.updateAll(notCachedMessages); + SendbirdUIKit.runOnUIThread(() -> notifyDataSetChanged(StringSet.EVENT_MESSAGE_TEMPLATE_UPDATED)); + return Unit.INSTANCE; + }); + + if (!updatedTemplateMessages.isEmpty()) { + cachedMessages.updateAll(updatedTemplateMessages); + } + + final List copiedList = cachedMessages.toList(); + if (copiedList.isEmpty()) { statusFrame.setValue(StatusFrameView.Status.EMPTY); } else { statusFrame.setValue(StatusFrameView.Status.NONE); @@ -347,6 +373,7 @@ public void onMessagesAdded(@NonNull NotificationContext context, @NonNull FeedC Logger.d(">> FeedNotificationChannelViewModel::onMessagesAdded() from=%s", context.getCollectionEventSource()); if (messages.isEmpty()) return; + cachedMessages.addAll(messages); switch (context.getCollectionEventSource()) { case EVENT_MESSAGE_RECEIVED: case EVENT_MESSAGE_SENT: @@ -361,6 +388,7 @@ public void onMessagesAdded(@NonNull NotificationContext context, @NonNull FeedC @Override public void onMessagesUpdated(@NonNull NotificationContext context, @NonNull FeedChannel channel, @NonNull List messages) { Logger.d(">> FeedNotificationChannelViewModel::onMessagesUpdated() from=%s", context.getCollectionEventSource()); + cachedMessages.updateAll(messages); notifyDataSetChanged(context); } @@ -369,6 +397,7 @@ public void onMessagesUpdated(@NonNull NotificationContext context, @NonNull Fee public void onMessagesDeleted(@NonNull NotificationContext context, @NonNull FeedChannel channel, @NonNull List messages) { Logger.d(">> FeedNotificationChannelViewModel::onMessagesDeleted() from=%s", context.getCollectionEventSource()); // Remove the succeeded message from the succeeded message datasource. + cachedMessages.deleteAll(messages); notifyMessagesDeleted(messages); notifyDataSetChanged(context); }