From c7009ff6f7932eadc08c2f0d5dc2a3bebd3f636f Mon Sep 17 00:00:00 2001 From: Gheorghe Pinzaru Date: Mon, 21 Dec 2020 10:24:58 +0300 Subject: [PATCH] Add request/approve communites Signed-off-by: Andrea Maria Piana --- .clj-kondo/config.edn | 1 + .env.e2e | 2 +- .env.jenkins | 2 +- .env.nightly | 1 + resources/images/icons/activity@2x.png | Bin 0 -> 766 bytes resources/images/icons/activity@3x.png | Bin 0 -> 1114 bytes resources/images/icons/animals-nature@2x.png | Bin 0 -> 731 bytes resources/images/icons/animals-nature@3x.png | Bin 0 -> 1102 bytes resources/images/icons/flags@2x.png | Bin 0 -> 547 bytes resources/images/icons/flags@3x.png | Bin 0 -> 747 bytes resources/images/icons/food@2x.png | Bin 0 -> 688 bytes resources/images/icons/food@3x.png | Bin 0 -> 1092 bytes resources/images/icons/objects@2x.png | Bin 0 -> 698 bytes resources/images/icons/objects@3x.png | Bin 0 -> 1064 bytes resources/images/icons/smileys@2x.png | Bin 0 -> 887 bytes resources/images/icons/smileys@3x.png | Bin 0 -> 1347 bytes resources/images/icons/symbols@2x.png | Bin 0 -> 650 bytes resources/images/icons/symbols@3x.png | Bin 0 -> 950 bytes resources/images/icons/travel@2x.png | Bin 0 -> 881 bytes resources/images/icons/travel@3x.png | Bin 0 -> 1363 bytes src/quo/components/list/footer.cljs | 17 +- src/quo/components/list/header.cljs | 18 +- src/quo/components/list/index.cljs | 16 + src/quo/core.cljs | 2 + src/quo/react_native.cljs | 10 +- src/status_im/chat/models/message.cljs | 9 +- src/status_im/communities/core.cljs | 431 +++++++++----- src/status_im/constants.cljs | 13 + src/status_im/contact/core.cljs | 14 +- src/status_im/ethereum/json_rpc.cljs | 9 +- src/status_im/subs.cljs | 67 ++- src/status_im/transport/message/core.cljs | 9 + .../ui/components/unviewed_indicator.cljs | 10 + .../ui/screens/bottom_sheets/views.cljs | 15 +- .../ui/screens/chat/message/message.cljs | 22 +- .../ui/screens/communities/community.cljs | 245 ++++++++ .../ui/screens/communities/create.cljs | 170 ++++++ .../screens/communities/create_channel.cljs | 36 ++ .../ui/screens/communities/edit.cljs | 21 + .../ui/screens/communities/import.cljs | 32 + .../ui/screens/communities/invite.cljs | 79 +++ .../ui/screens/communities/members.cljs | 113 ++++ .../ui/screens/communities/membership.cljs | 50 ++ .../ui/screens/communities/profile.cljs | 96 +++ .../screens/communities/requests_to_join.cljs | 64 ++ .../ui/screens/communities/views.cljs | 548 +++++------------- .../ui/screens/home/sheet/views.cljs | 2 +- src/status_im/ui/screens/home/views.cljs | 45 +- .../ui/screens/routing/chat_stack.cljs | 67 ++- src/status_im/ui/screens/routing/main.cljs | 3 + src/status_im/utils/handlers.cljs | 4 + status-go-version.json | 6 +- translations/en.json | 54 +- 53 files changed, 1648 insertions(+), 655 deletions(-) create mode 100644 resources/images/icons/activity@2x.png create mode 100644 resources/images/icons/activity@3x.png create mode 100644 resources/images/icons/animals-nature@2x.png create mode 100644 resources/images/icons/animals-nature@3x.png create mode 100644 resources/images/icons/flags@2x.png create mode 100644 resources/images/icons/flags@3x.png create mode 100644 resources/images/icons/food@2x.png create mode 100644 resources/images/icons/food@3x.png create mode 100644 resources/images/icons/objects@2x.png create mode 100644 resources/images/icons/objects@3x.png create mode 100644 resources/images/icons/smileys@2x.png create mode 100644 resources/images/icons/smileys@3x.png create mode 100644 resources/images/icons/symbols@2x.png create mode 100644 resources/images/icons/symbols@3x.png create mode 100644 resources/images/icons/travel@2x.png create mode 100644 resources/images/icons/travel@3x.png create mode 100644 src/quo/components/list/index.cljs create mode 100644 src/status_im/ui/components/unviewed_indicator.cljs create mode 100644 src/status_im/ui/screens/communities/community.cljs create mode 100644 src/status_im/ui/screens/communities/create.cljs create mode 100644 src/status_im/ui/screens/communities/create_channel.cljs create mode 100644 src/status_im/ui/screens/communities/edit.cljs create mode 100644 src/status_im/ui/screens/communities/import.cljs create mode 100644 src/status_im/ui/screens/communities/invite.cljs create mode 100644 src/status_im/ui/screens/communities/members.cljs create mode 100644 src/status_im/ui/screens/communities/membership.cljs create mode 100644 src/status_im/ui/screens/communities/profile.cljs create mode 100644 src/status_im/ui/screens/communities/requests_to_join.cljs diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 59029b199d..4e8b32a1e2 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -1,5 +1,6 @@ {:lint-as {status-im.utils.views/defview clojure.core/defn status-im.utils.views/letsubs clojure.core/let + reagent.core/with-let clojkure.core/let status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all quo.react/with-deps-check clojure.core/fn quo.previews.preview/list-comp clojure.core/for diff --git a/.env.e2e b/.env.e2e index 6aa2afec43..0c9ac735e1 100644 --- a/.env.e2e +++ b/.env.e2e @@ -27,5 +27,5 @@ MAX_IMAGES_BATCH=5 APN_TOPIC=im.status.ethereum.pr VERIFY_TRANSACTION_CHAIN_ID=3 COMMUNITIES_ENABLED=1 -COMMUNITIES_MANAGEMENT_ENABLED=0 DATABASE_MANAGEMENT_ENABLED=1 +COMMUNITIES_MANAGEMENT_ENABLED=1 diff --git a/.env.jenkins b/.env.jenkins index a72164606a..885a120516 100644 --- a/.env.jenkins +++ b/.env.jenkins @@ -27,5 +27,5 @@ BLANK_PREVIEW=0 MAX_IMAGES_BATCH=5 GOOGLE_FREE=0 COMMUNITIES_ENABLED=1 -COMMUNITIES_MANAGEMENT_ENABLED=0 DATABASE_MANAGEMENT_ENABLED=1 +COMMUNITIES_MANAGEMENT_ENABLED=1 diff --git a/.env.nightly b/.env.nightly index 6e9693b1c8..2bc1be05f8 100644 --- a/.env.nightly +++ b/.env.nightly @@ -22,3 +22,4 @@ MAX_IMAGES_BATCH=5 BLANK_PREVIEW=0 COMMUNITIES_ENABLED=1 DATABASE_MANAGEMENT_ENABLED=1 +COMMUNITIES_MANAGEMENT_ENABLED=1 diff --git a/resources/images/icons/activity@2x.png b/resources/images/icons/activity@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3e972cb58004f21bd94719bda4f9177779b9a3a8 GIT binary patch literal 766 zcmV_ z+At76Upk~+)e9$R4_wNvkfap;-0w>*fQ zr?Y)R{jI9|RsFta?39#tlRM8H?nc!=I{yjd(RiW|g6*Lzd9m6sZNSivo?ABx%?a(9 zB@?a2c7hLa`n$4_1LZs-PFr*3^oX}C=ums3#Kh8yeV!?|$2K;!!g*=1L#G61;vJ%$ z*OGy5prH)fvzLUj$quZrm$XuwiwOlCMs=aGBeeB;=@XXI50g1y8p*`X`O4rz$jWfI+}Z^8=Yau(MJBWM1GZdM3s<*LYzTk;XY=jtRU z=?UA+lq-_6^n{L`Nz15K>=-C}q0A@@2=)e+wUO1tdp4zP3hpT|d9~9q8M6LDrl)3T z$lSyR+Jpvl$R)+<4BCMW8KmSaLgsQe!Hz;{OH2HQ6?@tWA(UI5{}7Y2xaDf{5XP4X zixJ|kRZc96S>Zv~Dqyb&QzLOA-vD8}6BoZ?bWdsS)#5p`^BwPSwK%ZFf4TeY{8k6r z+4M@fUiW3A#Sy}ZmwW6q->Y7^BmQ@Vsk!wqTJm0IT)9yuhsr{@do{-pQ>SHN?o{%T w&xA-b81}X*ZF&NQ2Im8Y#kdA_{#!MSQ$a6$zQ+i~Il zVUmMGIr@9b8d!>>Y?w(=RILwEqT6bCO-RG+LLQD4B7bF>lkl2h+m$@OCjAt=X32}9 zC*d_q1;X(!-4P7>SfTD`@(71nZWJb6G-fUONY1*;D1WAM9+L9Uob~8NlSQ8Rr7o>V zOSs=Dz>+7>8n5Oe&8)p+Qsy-$q3nqQ8seRAm|ya2(INI3D`jEKPu~D%#YOS2)h~F$?iCbNur4WvZ zWYNJfKg7fKRu!lr|F#$tsYh>N%@G&L*V~N$_k+qwdhKm7cBi>VkKAqQDv@nipHF-a zc&*)_1q}IH)y4aFVJEwcvXWOX=;KHer^=GYrSPIsKCbpvq!ZA`Qw8l0M|+Mp4NIU@6k@ zGF0YFnHQ{3zCoW>sJBA_gFNv|O?0IjfNLX>Z|YD9L;VpwHh*|wuF}+y zUHhcQ8ty4`=4vc**tQ8}!$hsMLo4MSZeDyl z+b|4<|Moh7HYhi!y8$<7M&JhZ2Jr@QC$KkgH&8o)vw^dLy+J};qzeR+5nr;Li@rOY zWtk5U{7FJGu(0sC`GWaqtgSlx(S6cA)%||4_Gs;owZFI#l(n_B*4o+HbEIj^2t~Db z`Z?2fm;sW+1KyAH$=cSM>B=isUyE{ALn9pRbN3s~NHfw-BkE0#!s}G~9FKKc!rPdX zd-$b7j%(yI`2a|_C)9VGm|Q}?F^%IrnzegGMJO^->lC4NLj7KHEyC;KjM26n03LY0!~VVq+Tap%xQ$4)0T=03L~$gvxg| z#IBVdlh|hviWrZi87Y4cH`81poi?G*cQJ^MgmgaTY~W~A+pD;q+}P@w|6LFt^_UKR3D zNIxi};1T}L?i?0Ks>iQt3$Dr=HlOXt=u! z|89_vbbf=0kWQCs)%Q5M$AoD32Vjtnf}3LRaRq#{pc3lyhj`vbQJ$ZI@^o$@>V+In zd4o}n1Ke4lnK2lq%&NN=da2u@@>Utk5&pFhYqLb1lj}GQJm;luG~Xi1D0Y3Z_OrXD z6yg`&{u&~lI{R?A-Kzft|6-s?s>rw;)|ou?lt?swr`O+ z|8OS{h%3$w(v=4Xp}nMRA)f@HJB0R-3iXq*Q?iOmR8lVvqj0vNKP{ z0V|Jvq&$#XoB{)Q4`U{1*K|pci@;Plr$IVqfee@jEgjoGA-^~q;g~+29)&IfNgZVm zB&35Nhch7)z9B})Qv{kwp;aOyW(V#)w`&tK7SgQ}?nl-)$b6nkN>h63j@rryh0I8I z<<3u>AbU^BJ9oA+qQn3wwC9zr@cS0&Ajr2%Dh$L*=umU1ppbV;Ck#_#IjJDKbWS(7 z-GxkDMtGs%Cr+x7yn_8MWVWZIE_}Rbi}-9>j#pqr@F=9K|*#Ry-w0}KJ>jC1$U0yVL~6FxDvJUXoI)e!0+|o zWs;T?pxr>A=?EG#T6EG&$W{|v-m UJDEtQF#rGn07*qoM6N<$g6$snIRF3v literal 0 HcmV?d00001 diff --git a/resources/images/icons/flags@2x.png b/resources/images/icons/flags@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..41426612bdd57491e5a8d80cf03e7baeb492ef33 GIT binary patch literal 547 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=&hKNr(3sA8tMNvo ztOt7*>p`wXq7vE{_!fz_@D;5*P$H0@-p`n%S8$;4=Z5_=%~c-W-@CWfQKm_Um6c453ci#x zPuYI=iOji7EjBrf3N-Sncs zC*5h;1(lA*8*ubv-4V~+>gF_6Pwh2szrC!r_OY#S+sVdnS17Z%H}1gKez$~QO9HsQ zKj37|OIUGnrlNs!mnCoCC$^jyLcYaZG3V1CNJegnS^wsg+yh5><|&WQukdR-R;Srd2KK4%Z*^!=UjV9K=bKkPRz{&RZb;#U*wY|p>GJuxu;Ps_v;7uPRU5&08! zdh1^IOJCU*N=xycs}zopr0EA2GM*si- literal 0 HcmV?d00001 diff --git a/resources/images/icons/flags@3x.png b/resources/images/icons/flags@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5ac420ecfbb4484d6286cc5ba09ff0ba2b4f9566 GIT binary patch literal 747 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!oCO|{#S9FJ79h;%I?XTvD9BhG zOG?(%f`CGHk`sQ|a z0VfW{76GSUpSgZ=NL}1>{mi9*dQ+eIeGcDzN#`SzhDq^Rr_C>CMxV|1?KEEMQggz$ zZH?*u4fkIaDRjsmQFVL5Re8jub5k+9On<`u)bkf6J0DZ<6wf<+Y$jLXoSUi&_YxZO zbWH0nm0f-KL+S2q_9NHsmdx#cVfk&Zqg|efy`cQFcb$*ZH0KM-ui&lxQf{4pwcuS; z)d3fQH|4ws6IsrhCSAYPBjD&C_jpT4$K8Xo{$1ZEb8H_Y`}zgl+k49z!QdX8p>slw5W8{ZT)ev=BB+P8I5 z-&q+u?ZjAtCu>b&5_fdH+PpqXLiJNv$Y!IZj=Nme+?aO4`9P;YN%YRd&{m;Bn@L;# z_T_0iUVB;3{7X3_-daI|(eZb)?uEB%Oxt2lPfpb~e)e*Y>*<|7|5DpObGkWPxRdnE zqDSbj_xbEE@l~4-cz7H>>Mi5$u;yYuTgS;g2Rb;~vUf55bkQwXbTzx_Akocmd| zyDr%uncl1*9IR^_-l6-_>h1Mzr`P$<^G(0s_*b*B;`JmqXM-BA5nq*ZwiIh*W)FzJg z#(pz)Q`p(rY0YhDe}{HqgIFHoeRFJ#J7EByLwjQ*7((%-Rd{2iK^+NaUVpYj>!a&e z9zm~$cfwi!AipBI-Npv}MmxT%&%cNEv(WF4`rNUQoeU3Kbn2S6$Kij|Hgc-J?%OX%iBk2-5=KH-P zO?JOg87lHYUZ`W5-%ECpaXTtw376SK_Z`mzDA`4xYw~;dS=sbhQt=%N*@Z}$UT!nt z8R--MfN5rzn3DH%h#k%>Y!{7X`sR`o?t?Zg#FW{$}hAuJ8O2fqVR%aFHGAu6kfRJR3Nc06j-B+BPJm? zX{fy;e{A5Ki1X&XNOH|H{k_N>Z1EgNu8`gE>P>e_9TC~EK(zeR5EsnnX@-U2IBlqi z77II{EpBRX%V{G8%P@h&dX0Lyk7A!XpC4L(|9I-i@n1Xpy0ah3`49BqJ(tJwI+Q=M zZ`|YI^SV>^_u;RI@WB6506F%Jz1JPZt2yN_Gr{g`iyj6Q`-Iv(UU%vrldl}tLTAHi zqMij;49g!^o$IXd=MmJ|Zl=_Yt&@g-i>Vwt^*>90p^a-3joY#i+ zA^GF8=Nj3s+q7P-J7sG8B3%<_ML2}zTt$YnYPl)xW;JU34UBn#2$ZsZ+mew-8T6Yp zfjSO<<0`_)Xs0b&HIs4|&rMt6p{21VJXcELC{{?Mh700I4=oLYU!;`Ym-8ZY!#D%L zs2KFdU5#`dvHQdMqY#e0cQ&NYxB7J0sA#=5)SX2D-{$?f=C}aba$<~4tI%AjuWbnL zc~8og>0-TRm>}}~B8m*}h6_>=j(5ZP)Wq|OBysULN56I4q!`kAv*LW*#MhsMhIE%d zK8-ks&|Jxq84iEk`$UqF>0FTvx=0%`L%T_LIA;mZWu_1*s~yCO(}v%5%B=8D`^ON; zDcAi-1{m~48&CP#Q@&w1KJl~IB$I||xyJ!k1+JJBG7HZU-mskzq z0l<*~zQo(M>p=bQowBK$SQX*@o?9}&5f>*#BoReCp(c(cyl8zQlbu3??mZ3up1o!` zE8>&T4H@kt)RF7qMFevYywF3hYGaEO$~$Aob#6w7d7WO};7k7L&&?n%#P7Y-(Nwkr0xcWGNI1g+ie)6aE3vhP|Z&-V$5@0000< KMNUMnLSTYKsRNb( literal 0 HcmV?d00001 diff --git a/resources/images/icons/objects@2x.png b/resources/images/icons/objects@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..411c4ab353e85da643fe4152652a99d33b37c469 GIT binary patch literal 698 zcmV;r0!96aP)zz66+}Nv$OINAh1uc>wcpzgv_S@RiDxw2_DL9-KH?c^ zn>%D6oarlGSviCZ=u`81)NO*y$-;FA-7CL`yKT@I8A97l3P+yI;itg6u)@d?+TvX{ zIEN`d`cBIi*rIj{d*wLxdKoLU#cPF;RnA`r&Ns92qqht4ViFDihSgQm^3VH>$oOtRw#D35XiHeg z7L(q9?_w@zk{eU#tATG(?&i@EJBj0kCw^@KWbOMtyee`0g#;9E+o+89Y22=qBBw+G zY}ZgCUcoKiSO4TWS9E0WFIx#h?faFO3-$3w>paOI+~3T}(vg4sWN5V8={_w0=k)mo zl!OBQ7aDmp)Wi;s`~oAR@%LBCEI!CG1oL&VYw(j8tm6#vfZHI{o7l2BrY3%B#wY3> gImw1KF);!B0+nZsgwkS&yZ`_I07*qoM6N<$f&i2|TmS$7 literal 0 HcmV?d00001 diff --git a/resources/images/icons/objects@3x.png b/resources/images/icons/objects@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..21cb98d3fcc671e3240f95de62dc7bc70f88b8cb GIT binary patch literal 1064 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 FV1n7La|r+d literal 0 HcmV?d00001 diff --git a/resources/images/icons/symbols@2x.png b/resources/images/icons/symbols@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..00a39f566773adad4d12399d88b5039ade17317f GIT binary patch literal 650 zcmV;50(Jd~P)Bq@655)p`s4?q;vjHXi2-L#y(ATqZ?wV5dC<%u6>knVq9q^DTx`ArN4ctnIjxy?U%g*-| zM&57L2#*UD;L`KmJ!kHuaI~jX+461*XN?97w2NDH>oJ9sUMn@)gP%;wgs{_Qk|`5{ k;W|szn0U%$GMP*~KPHbnB~Q!2C;$Ke07*qoM6N<$f){=nn*aa+ literal 0 HcmV?d00001 diff --git a/resources/images/icons/symbols@3x.png b/resources/images/icons/symbols@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d6e0a0325064336ec956c25f03f2b5e93339a90e GIT binary patch literal 950 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 V;=hyIxp4ph002ovPDHLkV1kTzf*1e* literal 0 HcmV?d00001 diff --git a/src/quo/components/list/footer.cljs b/src/quo/components/list/footer.cljs index e3ba4a30c5..dd3058ad94 100644 --- a/src/quo/components/list/footer.cljs +++ b/src/quo/components/list/footer.cljs @@ -1,11 +1,14 @@ (ns quo.components.list.footer (:require [quo.react-native :as rn] [quo.design-system.spacing :as spacing] - [quo.components.text :as text])) - -(defn footer [& children] - [rn/view {:style (merge (:base spacing/padding-horizontal) - (:small spacing/padding-vertical))} - (into [text/text {:color :secondary}] - children)]) + [quo.components.text :as text] + [reagent.core :as reagent])) +(defn footer [] + (let [this (reagent/current-component) + {:keys [color] + :or {color :secondary}} (reagent/props this)] + [rn/view {:style (merge (:base spacing/padding-horizontal) + (:small spacing/padding-vertical))} + (into [text/text {:color color}] + (reagent/children this))])) diff --git a/src/quo/components/list/header.cljs b/src/quo/components/list/header.cljs index 522fc3d4c7..05f8a758f1 100644 --- a/src/quo/components/list/header.cljs +++ b/src/quo/components/list/header.cljs @@ -1,11 +1,15 @@ (ns quo.components.list.header - (:require [quo.react-native :as rn] + (:require [reagent.core :as reagent] + [quo.react-native :as rn] [quo.design-system.spacing :as spacing] [quo.components.text :as text])) -(defn header [& children] - [rn/view {:style (merge (:base spacing/padding-horizontal) - (:x-tiny spacing/padding-vertical))} - (into [text/text {:color :secondary - :style {:margin-top 10}}] - children)]) +(defn header [] + (let [this (reagent/current-component) + {:keys [color] + :or {color :secondary}} (reagent/props this)] + [rn/view {:style (merge (:base spacing/padding-horizontal) + (:x-tiny spacing/padding-vertical))} + (into [text/text {:color color + :style {:margin-top 10}}] + (reagent/children this))])) diff --git a/src/quo/components/list/index.cljs b/src/quo/components/list/index.cljs new file mode 100644 index 0000000000..90d99d6da7 --- /dev/null +++ b/src/quo/components/list/index.cljs @@ -0,0 +1,16 @@ +(ns quo.components.list.index + (:require [quo.react-native :as rn] + [quo.components.text :as text] + [quo.design-system.colors :as colors])) + +(defn index [{:keys [title]}] + [rn/view {:style {:padding-right 16}} + [rn/view {:style {:border-top-width 1 + :border-bottom-width 1 + :border-right-width 1 + :border-color (colors/get-color :border-01) + :padding-vertical 3 + :padding-horizontal 16 + :border-top-right-radius 16 + :border-bottom-right-radius 16}} + [text/text title]]]) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index 2f848197ed..5b932daf50 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -9,6 +9,7 @@ [quo.components.list.header :as list-header] [quo.components.list.footer :as list-footer] [quo.components.list.item :as list-item] + [quo.components.list.index :as list-index] [quo.components.controls.view :as controls] [quo.components.bottom-sheet.view :as bottom-sheet] [quo.components.separator :as separator] @@ -23,6 +24,7 @@ (def list-header list-header/header) (def list-footer list-footer/footer) (def list-item list-item/list-item) +(def list-index list-index/index) (def bottom-sheet bottom-sheet/bottom-sheet) (def switch controls/switch) (def radio controls/radio) diff --git a/src/quo/react_native.cljs b/src/quo/react_native.cljs index 1759e0f0f6..eee8912a1b 100644 --- a/src/quo/react_native.cljs +++ b/src/quo/react_native.cljs @@ -15,6 +15,8 @@ (def image (reagent/adapt-react-class (.-Image rn))) (def text (reagent/adapt-react-class (.-Text ^js rn))) +(defn resolve-asset-source [uri] (js->clj (.resolveAssetSource ^js (.-Image ^js rn) uri) :keywordize-keys true)) + (def scroll-view (reagent/adapt-react-class (.-ScrollView ^js rn))) (def modal (reagent/adapt-react-class (.-Modal ^js rn))) (def refresh-control (reagent/adapt-react-class (.-RefreshControl ^js rn))) @@ -90,9 +92,9 @@ ;; Flat-list (def ^:private rn-flat-list (reagent/adapt-react-class (.-FlatList ^js rn))) -(defn- wrap-render-fn [f] +(defn- wrap-render-fn [f render-data] (fn [data] - (reagent/as-element [f (.-item ^js data) (.-index ^js data) (.-separators ^js data)]))) + (reagent/as-element [f (.-item ^js data) (.-index ^js data) (.-separators ^js data) render-data]))) (defn- wrap-key-fn [f] (fn [data index] @@ -100,10 +102,10 @@ (f data index))) (defn base-list-props - [{:keys [key-fn render-fn empty-component header footer separator data] :as props}] + [{:keys [key-fn render-fn empty-component header footer separator data render-data] :as props}] (merge {:data (to-array data)} (when key-fn {:keyExtractor (wrap-key-fn key-fn)}) - (when render-fn {:renderItem (wrap-render-fn render-fn)}) + (when render-fn {:renderItem (wrap-render-fn render-fn render-data)}) (when separator {:ItemSeparatorComponent (fn [] (reagent/as-element separator))}) (when empty-component {:ListEmptyComponent (fn [] (reagent/as-element empty-component))}) (when header {:ListHeaderComponent (reagent/as-element header)}) diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index 5129038ad3..bec7d9c1a9 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -211,10 +211,11 @@ ;;;; Send message (fx/defn update-db-message-status [{:keys [db] :as cofx} chat-id message-id status] - (fx/merge cofx - {:db (assoc-in db - [:messages chat-id message-id :outgoing-status] - status)})) + (when (get-in db [:messages chat-id message-id]) + (fx/merge cofx + {:db (assoc-in db + [:messages chat-id message-id :outgoing-status] + status)}))) (fx/defn update-message-status [{:keys [db] :as cofx} chat-id message-id status] diff --git a/src/status_im/communities/core.cljs b/src/status_im/communities/core.cljs index 1f4de03c8e..ab47c9052a 100644 --- a/src/status_im/communities/core.cljs +++ b/src/status_im/communities/core.cljs @@ -2,6 +2,8 @@ (:require [re-frame.core :as re-frame] [clojure.walk :as walk] + [clojure.string :as string] + [clojure.set :as clojure.set] [taoensso.timbre :as log] [status-im.utils.fx :as fx] [status-im.constants :as constants] @@ -9,35 +11,50 @@ [status-im.transport.filters.core :as models.filters] [status-im.bottom-sheet.core :as bottom-sheet] [status-im.data-store.chats :as data-store.chats] - [status-im.ethereum.json-rpc :as json-rpc])) + [status-im.ethereum.json-rpc :as json-rpc] + [status-im.ui.components.colors :as colors] + [status-im.navigation :as navigation])) + +(def crop-size 1000) (def featured [{:name "Status" :id constants/status-community-id}]) -(def access-no-membership 1) -(def access-invitation-only 2) -(def access-on-request 3) +(defn <-request-to-join-community-rpc [r] + (clojure.set/rename-keys r {:communityId :community-id + :publicKey :public-key + :chatId :chat-id})) + +(defn <-requests-to-join-community-rpc [requests] + (reduce (fn [acc r] + (assoc acc (:id r) (<-request-to-join-community-rpc r))) + {} + requests)) (defn <-chats-rpc [chats] (reduce-kv (fn [acc k v] (assoc acc (name k) (-> v - (update :members walk/stringify-keys) - (assoc :identity {:display-name (get-in v [:identity :display_name]) - :description (get-in v [:identity :description])} - :id (name k))))) + (assoc :can-post? (:canPost v)) + (dissoc :canPost) + (update :members walk/stringify-keys)))) {} chats)) -(defn <-rpc [{:keys [description] :as c}] - (let [identity (:identity description)] - (-> c - (update-in [:description :members] walk/stringify-keys) - (assoc-in [:description :identity] {:display-name (:display_name identity) - :description (:description identity)}) - (update-in [:description :chats] <-chats-rpc)))) +(defn <-rpc [c] + (-> c + (clojure.set/rename-keys {:canRequestAccess :can-request-access? + :canManageUsers :can-manage-users? + :canJoin :can-join? + :requestedToJoinAt :requested-to-join-at + :isMember :is-member?}) + (update :members walk/stringify-keys) + (update :chats <-chats-rpc))) + +(defn fetch-community-id-input [{:keys [db]}] + (:communities/community-id-input db)) (fx/defn handle-chats [cofx chats] (models.chat/ensure-chats cofx chats)) @@ -48,6 +65,10 @@ (fx/defn handle-removed-filters [cofx filters] (models.filters/handle-filters-removed cofx (map models.filters/responses->filters filters))) +(fx/defn handle-request-to-join [{:keys [db]} r] + (let [{:keys [id community-id] :as request} (<-request-to-join-community-rpc r)] + {:db (assoc-in db [:communities/requests-to-join community-id id] request)})) + (fx/defn handle-removed-chats [{:keys [db]} chat-ids] {:db (reduce (fn [db chat-id] (update db :chats dissoc chat-id)) @@ -83,27 +104,30 @@ (handle-response cofx response)) (fx/defn joined - {:events [::joined]} + {:events [::joined ::requested-to-join]} [cofx response] (handle-response cofx response)) (fx/defn export - [cofx community-id on-success] - {::json-rpc/call [{:method "wakuext_exportCommunity" - :params [community-id] - :on-success on-success - :on-error #(do - (log/error "failed to export community" community-id %) - (re-frame/dispatch [::failed-to-export %]))}]}) + {:events [::export-pressed]} + [cofx community-id] + {::json-rpc/call [{:method "wakuext_exportCommunity" + :params [community-id] + :on-success #(re-frame/dispatch [:show-popover {:view :export-community + :community-key %}]) + :on-error #(do + (log/error "failed to export community" community-id %) + (re-frame/dispatch [::failed-to-export %]))}]}) + (fx/defn import-community {:events [::import]} - [cofx community-key on-success] - {::json-rpc/call [{:method "wakuext_importCommunity" - :params [community-key] - :on-success on-success - :on-error #(do - (log/error "failed to import community" %) - (re-frame/dispatch [::failed-to-import %]))}]}) + [cofx community-key] + {::json-rpc/call [{:method "wakuext_importCommunity" + :params [community-key] + :on-success #(re-frame/dispatch [::community-imported %]) + :on-error #(do + (log/error "failed to import community" %) + (re-frame/dispatch [::failed-to-import %]))}]}) (fx/defn join {:events [::join]} @@ -115,15 +139,25 @@ (log/error "failed to join community" community-id %) (re-frame/dispatch [::failed-to-join %]))}]}) +(fx/defn request-to-join + {:events [::request-to-join]} + [cofx community-id] + {::json-rpc/call [{:method "wakuext_requestToJoinCommunity" + :params [{:communityId community-id}] + :on-success #(re-frame/dispatch [::requested-to-join %]) + :on-error #(do + (log/error "failed to request to join community" community-id %) + (re-frame/dispatch [::failed-to-request-to-join %]))}]}) + (fx/defn leave {:events [::leave]} [cofx community-id] - {::json-rpc/call [{:method "wakuext_leaveCommunity" - :params [community-id] + {::json-rpc/call [{:method "wakuext_leaveCommunity" + :params [community-id] :on-success #(re-frame/dispatch [::left %]) - :on-error #(do - (log/error "failed to leave community" community-id %) - (re-frame/dispatch [::failed-to-leave %]))}]}) + :on-error #(do + (log/error "failed to leave community" community-id %) + (re-frame/dispatch [::failed-to-leave %]))}]}) (fx/defn fetch [_] {::json-rpc/call [{:method "wakuext_communities" @@ -136,184 +170,265 @@ (fx/defn chat-created {:events [::chat-created]} [cofx community-id user-pk] - {::json-rpc/call [{:method "wakuext_sendChatMessage" - :params [{:chatId user-pk - :text "Upgrade here to see an invitation to community" - :communityId community-id - :contentType constants/content-type-community}] + {::json-rpc/call [{:method "wakuext_sendChatMessage" + :params [{:chatId user-pk + :text "Upgrade here to see an invitation to community" + :communityId community-id + :contentType constants/content-type-community}] :on-success #(re-frame/dispatch [:transport/message-sent % 1]) :on-failure #(log/error "failed to send a message" %)}]}) -(fx/defn invite-user [cofx - community-id - user-pk - on-success-event - on-failure-event] +(fx/defn invite-users + {:events [::invite-people-confirmation-pressed]} + [cofx user-pk contacts] + (let [community-id (fetch-community-id-input cofx) + pks (if (seq user-pk) + (conj contacts user-pk) + contacts)] + (when (seq pks) + {::json-rpc/call [{:method "wakuext_inviteUsersToCommunity" + :params [{:communityId community-id + :users pks}] + :on-success #(re-frame/dispatch [::people-invited %]) + :on-error #(do + (log/error "failed to invite-user community" %) + (re-frame/dispatch [::failed-to-invite-people %]))}]}))) +(fx/defn share-community + {:events [::share-community-confirmation-pressed]} + [cofx user-pk contacts] + (let [community-id (fetch-community-id-input cofx) + pks (if (seq user-pk) + (conj contacts user-pk) + contacts)] + (when (seq pks) + {::json-rpc/call [{:method "wakuext_shareCommunity" + :params [{:communityId community-id + :users pks}] + :on-success #(re-frame/dispatch [::people-invited %]) + :on-error #(do + (log/error "failed to invite-user community" %) + (re-frame/dispatch [::failed-to-share-community %]))}]}))) - (fx/merge cofx - {::json-rpc/call [{:method "wakuext_inviteUserToCommunity" - :params [community-id - user-pk] - :on-success #(re-frame/dispatch [on-success-event %]) - :on-error #(do - (log/error "failed to invite-user community" %) - (re-frame/dispatch [on-failure-event %]))}]} - (models.chat/upsert-chat {:chat-id user-pk - :active (get-in cofx [:db :chats user-pk :active])} - #(re-frame/dispatch [::chat-created community-id user-pk])))) +(fx/defn create + {:events [::create-confirmation-pressed]} + [{:keys [db]}] + (let [{:keys [name description membership image]} (get db :communities/create) + my-public-key (get-in db [:multiaccount :public-key])] + ;; If access is ENS only, we set the access to require approval and set the rule + ;; of ens only + (let [params (cond-> {:name name + :description description + :membership (or membership constants/community-no-membership-access) + :color (rand-nth colors/chat-colors) + :image (string/replace-first (str image) #"file://" "") + :imageAx 0 + :imageAy 0 + :imageBx crop-size + :imageBy crop-size} + (= membership constants/community-rule-ens-only) + (assoc :membership constants/community-on-request-access + :ens-only true))] -(fx/defn create [{:keys [db]} - community-name - community-description - community-membership - on-success-event - on-failure-event] - (let [membership (js/parseInt community-membership) - my-public-key (get-in db [:multiaccount :public-key])] - {::json-rpc/call [{:method "wakuext_createCommunity" - :params [{:identity {:display_name community-name - :description community-description} - :members {my-public-key {}} - :permissions {:access membership}}] - :on-success #(re-frame/dispatch [on-success-event %]) - :on-error #(do - (log/error "failed to create community" %) - (re-frame/dispatch [on-failure-event %]))}]})) + {::json-rpc/call [{:method "wakuext_createCommunity" + :params [params] + :on-success #(re-frame/dispatch [::community-created %]) + :on-error #(do + (log/error "failed to create community" %) + (re-frame/dispatch [::failed-to-create-community %]))}]}))) -(defn create-channel [community-id - community-channel-name - community-channel-description - on-success-event - on-failure-event] - {::json-rpc/call [{:method "wakuext_createCommunityChat" - :params [community-id - {:identity {:display_name community-channel-name - :description community-channel-description} - :permissions {:access access-no-membership}}] - :on-success #(re-frame/dispatch [on-success-event %]) - :on-error #(do - (log/error "failed to create community channel" %) - (re-frame/dispatch [on-failure-event %]))}]}) +(fx/defn edit + {:events [::edit-confirmation-pressed]} + [{:keys [db]}] + (let [{:keys [name description membership]} (get db :communities/create) + my-public-key (get-in db [:multiaccount :public-key])] + (log/error "Edit community is not yet implemented") + ;; {::json-rpc/call [{:method "wakuext_editCommunity" + ;; :params [{:identity {:display_name name + ;; :description description} + ;; :permissions {:access membership}}] + ;; :on-success #(re-frame/dispatch [::community-edited %]) + ;; :on-error #(do + ;; (log/error "failed to create community" %) + ;; (re-frame/dispatch [::failed-to-edit-community %]))}]} + )) -(def no-membership-access 1) -(def invitation-only-access 2) -(def on-request-access 3) +(fx/defn create-channel + {:events [::create-channel-confirmation-pressed]} + [cofx community-channel-name community-channel-description] + (let [community-id (fetch-community-id-input cofx)] + {::json-rpc/call [{:method "wakuext_createCommunityChat" + :params [community-id + {:identity {:display_name community-channel-name + :color (rand-nth colors/chat-colors) + :description community-channel-description} + :permissions {:access constants/community-channel-access-no-membership}}] + :on-success #(re-frame/dispatch [::community-channel-created %]) + :on-error #(do + (log/error "failed to create community channel" %) + (re-frame/dispatch [::failed-to-create-community-channel %]))}]})) (defn require-membership? [permissions] - (not= no-membership-access (:access permissions))) + (not= constants/community-no-membership-access (:access permissions))) (def community-id-length 68) -;; TODO: test this -(defn can-post? [{:keys [admin] :as community} pk local-chat-id] - (let [chat-id (subs local-chat-id community-id-length) - can-access-community? (or (get-in community [:description :members pk]) - (not (require-membership? (get-in community [:description :permissions]))))] - (or admin - (get-in community [:description :chats chat-id :members pk]) - (and can-access-community? - (not (require-membership? (get-in community [:description :chats chat-id :permissions]))))))) + +(defn can-post? [community _ local-chat-id] + (let [chat-id (subs local-chat-id community-id-length)] + (get-in community [:chats chat-id :can-post?]))) (fx/defn reset-community-id-input [{:keys [db]} id] {:db (assoc db :communities/community-id-input id)}) -(defn fetch-community-id-input [{:keys [db]}] - (:communities/community-id-input db)) - -(fx/defn import-pressed - {:events [::import-pressed]} - [cofx] - (bottom-sheet/show-bottom-sheet cofx {:view :import-community})) - -(fx/defn create-pressed - {:events [::create-pressed]} - [cofx] - (bottom-sheet/show-bottom-sheet cofx {:view :create-community})) - (fx/defn invite-people-pressed {:events [::invite-people-pressed]} [cofx id] (fx/merge cofx (reset-community-id-input id) - (bottom-sheet/show-bottom-sheet {:view :invite-people-community}))) + (bottom-sheet/hide-bottom-sheet) + (navigation/navigate-to :invite-people-community {:invite? true}))) + +(fx/defn share-community-pressed + {:events [::share-community-pressed]} + [cofx id] + (fx/merge cofx + (reset-community-id-input id) + (bottom-sheet/hide-bottom-sheet) + (navigation/navigate-to :invite-people-community {}))) (fx/defn create-channel-pressed {:events [::create-channel-pressed]} [cofx id] (fx/merge cofx (reset-community-id-input id) - (bottom-sheet/show-bottom-sheet {:view :create-community-channel}))) + (navigation/navigate-to :create-community-channel nil))) (fx/defn community-created {:events [::community-created]} [cofx response] (fx/merge cofx - (bottom-sheet/hide-bottom-sheet) + (navigation/navigate-back) (handle-response response))) +(fx/defn community-edited + {:events [::community-edited]} + [cofx response] + (fx/merge cofx + (navigation/navigate-back) + (handle-response response))) + +(fx/defn open-create-community + {:events [::open-create-community]} + [{:keys [db] :as cofx}] + (fx/merge cofx + {:db (assoc db :communities/create {})} + (navigation/navigate-to :community-create nil))) + +(fx/defn open-edit-community + {:events [::open-edit-community]} + [{:keys [db] :as cofx} id] + (let [{:keys [identity permissions]} (get-in db [:communities id :description]) + {:keys [display-name description image]} identity + {:keys [access]} permissions] + (fx/merge cofx + {:db (assoc db :communities/create {:name display-name + :description description + :image image + :membership access})} + (navigation/navigate-to :communities {:screen :community-edit})))) + (fx/defn community-imported {:events [::community-imported]} [cofx response] (fx/merge cofx - (bottom-sheet/hide-bottom-sheet) + (navigation/navigate-back) (handle-response response))) (fx/defn people-invited {:events [::people-invited]} [cofx response] (fx/merge cofx - (bottom-sheet/hide-bottom-sheet) + (navigation/navigate-back) (handle-response response))) (fx/defn community-channel-created {:events [::community-channel-created]} [cofx response] + (fx/merge cofx + (navigation/navigate-back) + (handle-response response))) + +(fx/defn create-field + {:events [::create-field]} + [{:keys [db]} field value] + {:db (assoc-in db [:communities/create field] value)}) + +(fx/defn member-ban + {:events [::member-ban]} + [cofx community-id public-key] + (log/error "Community member ban is not yet implemented")) + +(fx/defn member-kicked + {:events [::member-kicked]} + [cofx response] + (fx/merge cofx (bottom-sheet/hide-bottom-sheet) (handle-response response))) -(fx/defn handle-export-pressed - {:events [::export-pressed]} +(fx/defn member-kick + {:events [::member-kick]} + [cofx community-id public-key] + {::json-rpc/call [{:method "wakuext_removeUserFromCommunity" + :params [community-id public-key] + :on-success #(re-frame/dispatch [::member-kicked %]) + :on-error #(log/error "failed to remove user from community" community-id public-key %)}]}) + +(fx/defn delete-community + {:events [::delete-community]} [cofx community-id] - (export cofx community-id - #(re-frame/dispatch [:show-popover {:view :export-community - :community-key %}]))) + (log/error "Community delete is not yet implemented")) -(fx/defn import-confirmation-pressed - {:events [::import-confirmation-pressed]} - [cofx community-key] - (import-community - cofx - community-key - #(re-frame/dispatch [::community-imported %]))) +(fx/defn requests-to-join-fetched + {:events [::requests-to-join-fetched]} + [{:keys [db]} community-id requests] + {:db (assoc-in db [:communities/requests-to-join community-id] (<-requests-to-join-community-rpc requests))}) -(fx/defn create-confirmation-pressed - {:events [::create-confirmation-pressed]} - [cofx community-name community-description membership] - (create - cofx - community-name - community-description - membership - ::community-created - ::failed-to-create-community)) +(fx/defn fetch-requests-to-join + {:events [::fetch-requests-to-join]} + [cofx community-id] + {::json-rpc/call [{:method "wakuext_pendingRequestsToJoinForCommunity" + :params [community-id] + :on-success #(re-frame/dispatch [::requests-to-join-fetched community-id %]) + :on-error #(log/error "failed to fetch requests-to-join" community-id %)}]}) -(fx/defn create-channel-confirmation-pressed - {:events [::create-channel-confirmation-pressed]} - [cofx community-channel-name community-channel-description] - (create-channel - (fetch-community-id-input cofx) - community-channel-name - community-channel-description - ::community-channel-created - ::failed-to-create-community-channel)) +(defn fetch-requests-to-join! [community-id] + (re-frame/dispatch [::fetch-requests-to-join community-id])) -(fx/defn invite-people-confirmation-pressed - {:events [::invite-people-confirmation-pressed]} - [cofx user-pk] - (invite-user - cofx - (fetch-community-id-input cofx) - user-pk - ::people-invited - ::failed-to-invite-people)) +(fx/defn request-to-join-accepted + {:events [::request-to-join-accepted]} + [{:keys [db] :as cofx} community-id request-id response] + (fx/merge cofx + {:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)} + (handle-response response))) + +(fx/defn request-to-join-declined + {:events [::request-to-join-declined]} + [{:keys [db] :as cofx} community-id request-id] + {:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)}) + +(fx/defn accept-request-to-join-pressed + {:events [:communities.ui/accept-request-to-join-pressed]} + [cofx community-id request-id] + {::json-rpc/call [{:method "wakuext_acceptRequestToJoinCommunity" + :params [{:id request-id}] + :on-success #(re-frame/dispatch [::request-to-join-accepted community-id request-id %]) + :on-error #(log/error "failed to accept requests-to-join" community-id request-id %)}]}) + +(fx/defn decline-request-to-join-pressed + {:events [:communities.ui/decline-request-to-join-pressed]} + [cofx community-id request-id] + {::json-rpc/call [{:method "wakuext_declineRequestToJoinCommunity" + :params [{:id request-id}] + :on-success #(re-frame/dispatch [::request-to-join-declined community-id request-id %]) + :on-error #(log/error "failed to decline requests-to-join" community-id request-id)}]}) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 1d755eb89e..6519faf2d9 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -27,6 +27,8 @@ (def ^:const timeline-chat-type 5) (def ^:const community-chat-type 6) +(def request-to-join-pending-state 1) + (def reactions {emoji-reaction-love (:love resources/reactions) emoji-reaction-thumbs-up (:thumbs-up resources/reactions) emoji-reaction-thumbs-down (:thumbs-down resources/reactions) @@ -79,6 +81,17 @@ (def ^:const status-create-address "status_createaddress") +(def ^:const community-no-membership-access 1) +(def ^:const community-invitation-only-access 2) +(def ^:const community-on-request-access 3) + +;; Community rules for joining +(def ^:const community-rule-ens-only "ens-only") + +(def ^:const community-channel-access-no-membership 1) +(def ^:const community-channel-access-invitation-only 2) +(def ^:const community-channel-access-on-request 3) + ; BIP44 Wallet Root Key, the extended key from which any wallet can be derived (def ^:const path-wallet-root "m/44'/60'/0'/0") ; EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived diff --git a/src/status_im/contact/core.cljs b/src/status_im/contact/core.cljs index dec0935e1c..23051aec36 100644 --- a/src/status_im/contact/core.cljs +++ b/src/status_im/contact/core.cljs @@ -123,13 +123,17 @@ (fx/defn name-verified {:events [:contacts/ens-name-verified]} [{:keys [db now] :as cofx} public-key ens-name] - (let [contact (-> (get-in db [:contacts/contacts public-key] - (build-contact cofx public-key)) + (let [contact (-> (or (get-in db [:contacts/contacts public-key]) + (build-contact cofx public-key)) (assoc :name ens-name - :last-ens-clock-value now - :ens-verified-at now :ens-verified true))] - (upsert-contact cofx contact))) + (fx/merge cofx + {:db (-> db + (update-in [:contacts/contacts public-key] merge contact)) + ::json-rpc/call [{:method "wakuext_ensVerified" + :params [public-key ens-name] + :on-success #(log/debug "ens name verified successuful")}]} + (transport.filters/load-contact contact)))) (fx/defn update-nickname {:events [:contacts/update-nickname]} diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index ade0873da8..328bbae0bf 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -117,12 +117,19 @@ "multiaccounts_deleteIdentityImage" {} "wakuext_createCommunity" {} "wakuext_createCommunityChat" {} - "wakuext_inviteUserToCommunity" {} + "wakuext_inviteUsersToCommunity" {} + "wakuext_shareCommunity" {} + "wakuext_removeUserFromCommunity" {} + "wakuext_requestToJoinCommunity" {} + "wakuext_acceptRequestToJoinCommunity" {} + "wakuext_declineRequestToJoinCommunity" {} + "wakuext_pendingRequestsToJoinForCommunity" {} "wakuext_joinCommunity" {} "wakuext_leaveCommunity" {} "wakuext_communities" {} "wakuext_importCommunity" {} "wakuext_exportCommunity" {} + "wakuext_ensVerified" {} "status_chats" {} "localnotifications_switchWalletNotifications" {} "localnotifications_notificationPreferences" {} diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 78212941ba..f6129ea6e9 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -213,6 +213,11 @@ (reg-root-key-sub :buy-crypto/on-ramps :buy-crypto/on-ramps) ;; communities + +(reg-root-key-sub :communities/create :communities/create) +(reg-root-key-sub :communities/requests-to-join :communities/requests-to-join) +(reg-root-key-sub :communities/community-id-input :communities/community-id-input) + (re-frame/reg-sub :communities (fn [db] @@ -225,6 +230,17 @@ :else []))) +(re-frame/reg-sub + :communities/section-list + :<- [:communities] + (fn [communities] + (->> (vals communities) + (group-by (comp (fnil string/upper-case "") first :name)) + (sort-by (fn [[title]] title)) + (map (fn [[title data]] + {:title title + :data data}))))) + (re-frame/reg-sub :communities/community :<- [:communities] @@ -232,16 +248,25 @@ (get communities id))) (re-frame/reg-sub - :communities/status-community + :communities/communities :<- [:search/home-filter] :<- [:communities] (fn [[search-filter communities]] - (let [status-community (get communities constants/status-community-id)] - (when (and (:joined status-community) - (or (string/blank? search-filter) - (string/includes? (string/lower-case - (get-in status-community [:description :identity :display-name])) search-filter))) - status-community)))) + (filterv + (fn [{:keys [name joined id]}] + (and joined + (or config/communities-management-enabled? + (= id constants/status-community-id)) + (or (empty? search-filter) + (string/includes? (string/lower-case (str name)) search-filter)))) + (vals communities)))) + +(re-frame/reg-sub + :communities/edited-community + :<- [:communities] + :<- [:communities/community-id-input] + (fn [[communities community-id]] + (get communities community-id))) (re-frame/reg-sub :communities/current-community @@ -260,6 +285,16 @@ 0 chats))) +(re-frame/reg-sub + :communities/requests-to-join-for-community + :<- [:communities/requests-to-join] + (fn [requests [_ community-id]] + (->> + (get requests community-id {}) + vals + (filter (fn [{:keys [state]}] + (= state constants/request-to-join-pending-state)))))) + ;;GENERAL ============================================================================================================== @@ -1198,9 +1233,21 @@ :home-items :<- [:search/home-filter] :<- [:search/filtered-chats] - (fn [[search-filter filtered-chats]] - {:search-filter search-filter - :chats filtered-chats})) + :<- [:communities/communities] + (fn [[search-filter filtered-chats communities]] + (let [communities-count (count communities) + chats-count (count filtered-chats) + ;; If we have both communities & chats we want to display + ;; a separator between them + + communities-with-separator (if (and (pos? communities-count) + (pos? chats-count)) + (update communities + (dec communities-count) + assoc :last? true) + communities)] + {:search-filter search-filter + :items (concat communities-with-separator filtered-chats)}))) ;;PAIRING ============================================================================================================== diff --git a/src/status_im/transport/message/core.cljs b/src/status_im/transport/message/core.cljs index bc8c48bdd5..d14fc6a12c 100644 --- a/src/status_im/transport/message/core.cljs +++ b/src/status_im/transport/message/core.cljs @@ -28,6 +28,9 @@ (fx/defn handle-community [cofx community] (models.communities/handle-community cofx community)) +(fx/defn handle-request-to-join-community [cofx request] + (models.communities/handle-request-to-join cofx request)) + (fx/defn handle-reactions [cofx reactions] (models.reactions/receive-signal cofx reactions)) @@ -44,6 +47,7 @@ {:events [::process]} [cofx ^js response-js] (let [^js communities (.-communities response-js) + ^js requests-to-join-community (.-requestsToJoinCommunity response-js) ^js chats (.-chats response-js) ^js contacts (.-contacts response-js) ^js installations (.-installations response-js) @@ -72,6 +76,11 @@ (fx/merge cofx {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} (handle-community (types/js->clj community)))) + (seq requests-to-join-community) + (let [request (.pop requests-to-join-community)] + (fx/merge cofx + {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} + (handle-request-to-join-community (types/js->clj request)))) (seq chats) (let [chats-clj (types/js->clj chats)] (js-delete response-js "chats") diff --git a/src/status_im/ui/components/unviewed_indicator.cljs b/src/status_im/ui/components/unviewed_indicator.cljs new file mode 100644 index 0000000000..bc0c37caa9 --- /dev/null +++ b/src/status_im/ui/components/unviewed_indicator.cljs @@ -0,0 +1,10 @@ +(ns status-im.ui.components.unviewed-indicator + (:require [status-im.ui.components.badge :as badge] + [status-im.ui.components.react :as react])) + +(defn unviewed-indicator [c] + (when (pos? c) + [react/view {:padding-left 16 + :justify-content :flex-end + :align-items :flex-end} + [badge/message-counter c]])) diff --git a/src/status_im/ui/screens/bottom_sheets/views.cljs b/src/status_im/ui/screens/bottom_sheets/views.cljs index f88a5fe6a8..577f5b0148 100644 --- a/src/status_im/ui/screens/bottom_sheets/views.cljs +++ b/src/status_im/ui/screens/bottom_sheets/views.cljs @@ -4,7 +4,6 @@ [status-im.ui.screens.home.sheet.views :as home.sheet] [status-im.ui.screens.keycard.views :as keycard] [status-im.ui.screens.about-app.views :as about-app] - [status-im.ui.screens.communities.views :as communities] [status-im.ui.screens.multiaccounts.recover.views :as recover.views] [quo.core :as quo])) @@ -33,20 +32,8 @@ (= view :learn-more) (merge about-app/learn-more) - (= view :create-community) - (merge communities/create-sheet) - - (= view :import-community) - (merge communities/import-sheet) - - (= view :create-community-channel) - (merge communities/create-channel-sheet) - - (= view :invite-people-community) - (merge communities/invite-people-sheet) - (= view :recover-sheet) (merge recover.views/bottom-sheet))] [quo/bottom-sheet opts (when content - [content])])) \ No newline at end of file + [content])])) diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index b9fa4182dd..d2d1ce844c 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -2,7 +2,6 @@ (:require [re-frame.core :as re-frame] [status-im.constants :as constants] [status-im.i18n.i18n :as i18n] - [status-im.communities.core :as communities] [status-im.utils.config :as config] [status-im.react-native.resources :as resources] [status-im.ui.components.colors :as colors] @@ -216,7 +215,7 @@ (chat.utils/format-author contact-with-names opts))) (defview community-content [{:keys [community-id] :as message}] - (letsubs [{:keys [joined verified] :as community} [:communities/community community-id]] + (letsubs [{:keys [name description verified] :as community} [:communities/community community-id]] (when (and config/communities-enabled? community) @@ -253,25 +252,24 @@ :style {:width 40 :height 40}}] - (let [display-name (get-in community [:description :identity :display-name])] - [chat-icon/chat-icon-view-chat-list - display-name - true - display-name - colors/default-community-color]))] + [chat-icon/chat-icon-view-chat-list + name + true + name + colors/default-community-color])] [react/view {:padding-right 14} [react/text {:style {:font-weight "700" :font-size 17}} - (get-in community [:description :identity :display-name])] - [react/text (get-in community [:description :identity :description])]]] + name] + [react/text description]]] [react/view {:border-width 1 :padding-vertical 8 :border-bottom-left-radius 10 :border-bottom-right-radius 10 :border-color colors/gray-lighter} - [react/touchable-highlight {:on-press #(re-frame/dispatch [(if joined ::communities/leave ::communities/join) (:id community)])} + [react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :community {:community-id (:id community)}])} [react/text {:style {:text-align :center - :color colors/blue}} (if joined (i18n/label :t/leave) (i18n/label :t/join))]]]]))) + :color colors/blue}} (i18n/label :t/view)]]]]))) (defn message-content-wrapper "Author, userpic and delivery wrapper" diff --git a/src/status_im/ui/screens/communities/community.cljs b/src/status_im/ui/screens/communities/community.cljs new file mode 100644 index 0000000000..736259dc1a --- /dev/null +++ b/src/status_im/ui/screens/communities/community.cljs @@ -0,0 +1,245 @@ +(ns status-im.ui.screens.communities.community + (:require [status-im.ui.components.topbar :as topbar] + [quo.react-native :as rn] + [status-im.ui.components.toolbar :as toolbar] + [quo.core :as quo] + [status-im.constants :as constants] + [status-im.utils.handlers :refer [>evt (datetime/timestamp) (+ (* requested-at 1000) request-cooldown-ms))) + +(defn toolbar-content [id display-name color images show-members-count? members] + (let [thumbnail-image (get-in images [:thumbnail :uri])] + [rn/view {:style {:flex 1 + :align-items :center + :flex-direction :row}} + [rn/view {:padding-right 10} + (cond + (= id constants/status-community-id) + [rn/image {:source (resources/get-image :status-logo) + :style {:width 40 + :height 40}}] + (seq thumbnail-image) + [photos/photo thumbnail-image {:size 40}] + + :else + [chat-icon.screen/chat-icon-view-toolbar + id + true + display-name + (or color (rand-nth colors/chat-colors))])] + [rn/view {:style {:flex 1 :justify-content :center}} + [quo/text {:number-of-lines 1 + :accessibility-label :community-name-text} + display-name] + (when show-members-count? + [quo/text {:number-of-lines 1 + :size :small + :color :secondary} + (i18n/label-pluralize members :t/community-members {:count members})])]])) + +(defn hide-sheet-and-dispatch [event] + (>evt [:bottom-sheet/hide]) + (>evt event)) + +(defn community-actions [{:keys [id + permissions + can-manage-users? name images color]}] + (let [can-invite? (and can-manage-users? (not= (:access permissions) constants/community-no-membership-access)) + can-share? (not= (:access permissions) constants/community-invitation-only-access) + thumbnail-image (get-in images [:thumbnail :uri])] + [:<> + [quo/list-item + {:title name + :on-press #(hide-sheet-and-dispatch [:navigate-to :community-management {:community-id id}]) + :chevron true + :icon (cond + (= id constants/status-community-id) + [rn/image {:source (resources/get-image :status-logo) + :style {:width 40 + :height 40}}] + (seq thumbnail-image) + [photos/photo thumbnail-image {:size 40}] + + :else + [chat-icon.screen/chat-icon-view-chat-sheet + name + true + name + (or color (rand-nth colors/chat-colors))])}] + (when (and config/communities-management-enabled? can-manage-users?) + [:<> + [quo/list-item + {:theme :accent + :title (i18n/label :t/export-key) + :accessibility-label :community-export-key + :icon :main-icons/objects + :on-press #(hide-sheet-and-dispatch [::communities/export-pressed id])}] + [quo/list-item + {:theme :accent + :title (i18n/label :t/create-channel) + :accessibility-label :community-create-channel + :icon :main-icons/channel + :on-press #(hide-sheet-and-dispatch [::communities/create-channel-pressed id])}]]) + (when can-invite? + [quo/list-item + {:theme :accent + :title (i18n/label :t/invite-people) + :icon :main-icons/share + :accessibility-label :community-invite-people + :on-press #(>evt [::communities/invite-people-pressed id])}]) + (when can-share? + [quo/list-item + {:theme :accent + :title (i18n/label :t/share) + :icon :main-icons/share + :accessibility-label :community-share + :on-press #(>evt [::communities/share-community-pressed id])}]) + [quo/list-item + {:theme :accent + :title (i18n/label :t/leave-community) + :accessibility-label :leave + :icon :main-icons/arrow-left + :on-press #(do + (>evt [:bottom-sheet/hide]) + (>evt [:navigate-to :home]) + (>evt [::communities/leave id]))}]])) + +(defn welcome-blank-page [] + [rn/view {:style {:padding 16 :flex 1 :flex-direction :row :align-items :center :justify-content :center}} + [quo/text {:align :center + :color :secondary} + (i18n/label :t/welcome-community-blank-message)]]) + +(defn community-chat-item [home-item] + [inner-item/home-list-item home-item]) + +(defn community-chat-list [chats] + (if (empty? chats) + [welcome-blank-page] + [list/flat-list + {:key-fn :chat-id + :content-container-style {:padding-vertical 8} + :keyboard-should-persist-taps :always + :data chats + :render-fn community-chat-item + :footer [rn/view {:height 68}]}])) + +(defn community-channel-list [id] + (let [chats (> chats + (= id constants/status-community-id) + (map #(assoc % :color colors/blue)))] + [community-chat-list chats])) + +(defn channel-preview-item [{:keys [id color name]}] + (let [color (or color (rand-nth colors/chat-colors))] + [quo/list-item + {:icon [chat-icon.screen/chat-icon-view-chat-list + id true name color false false] + :title [rn/view {:flex-direction :row + :flex 1 + :padding-right 16 + :align-items :center} + [icons/icon :main-icons/tiny-group + {:color colors/black + :width 15 + :height 15 + :container-style {:width 15 + :height 15 + :margin-right 2}}] + [quo/text {:weight :medium + :accessibility-label :chat-name-text + :ellipsize-mode :tail + :number-of-lines 1} + (utils/truncate-str name 30)]] + :title-accessibility-label :chat-name-text}])) + +(defn community-channel-preview-list [_ chats-without-id] + (let [chats (reduce-kv + (fn [acc k v] + (conj acc (assoc v :id (name k)))) + [] + chats-without-id)] + [list/flat-list + {:key-fn :id + :content-container-style {:padding-vertical 8} + :keyboard-should-persist-taps :always + :data chats + :render-fn channel-preview-item}])) + +(defn community [route] + (let [{:keys [community-id]} (get-in route [:route :params]) + {:keys [id + chats + name + images + members + permissions + color + joined + can-request-access? + can-join? + requested-to-join-at + admin] + :as community} (evt [:bottom-sheet/show-sheet + {:content (fn [] + [community-actions community]) + :height 256}])}])}] + (if joined + [community-channel-list id] + [community-channel-preview-list id chats]) + (when-not joined + (cond + can-join? + [toolbar/toolbar + {:show-border? true + :center [quo/button {:on-press #(>evt [::communities/join id]) + :type :secondary} + (i18n/label :t/join)]}] + can-request-access? + (if (and (pos? requested-to-join-at) + (not (can-request-access-again? requested-to-join-at))) + [toolbar/toolbar + {:show-border? true + :left [quo/text {:color :secondary} (i18n/label :t/membership-request-pending)]}] + [toolbar/toolbar + {:show-border? true + :center [quo/button {:on-press #(>evt [::communities/request-to-join id]) + :type :secondary} + (i18n/label :t/request-access)]}]) + :else + [toolbar/toolbar + {:show-border? true + :center [quo/button {:on-press #(>evt [::communities/join id]) + :type :secondary} + (i18n/label :t/follow)]}]))])) diff --git a/src/status_im/ui/screens/communities/create.cljs b/src/status_im/ui/screens/communities/create.cljs new file mode 100644 index 0000000000..74f6f969cf --- /dev/null +++ b/src/status_im/ui/screens/communities/create.cljs @@ -0,0 +1,170 @@ +(ns status-im.ui.screens.communities.create + (:require [quo.react-native :as rn] + [status-im.i18n.i18n :as i18n] + [quo.core :as quo] + [clojure.string :as str] + [status-im.utils.handlers :refer [>evt evt [::communities/create-field :image (.-path ^js %)]) + crop-opts)) + +(defn take-pic [] + (react/show-image-picker-camera + #(>evt [::communities/create-field :image (.-path ^js %)]) + crop-opts)) + +(defn bottom-sheet [has-picture] + (fn [] + [:<> + [quo/list-item {:accessibility-label :take-photo + :theme :accent + :icon :main-icons/camera + :title (i18n/label :t/community-image-take) + :on-press #(do + (>evt [:bottom-sheet/hide]) + (take-pic))}] + [quo/list-item {:accessibility-label :pick-photo + :icon :main-icons/gallery + :theme :accent + :title (i18n/label :t/community-image-pick) + :on-press #(do + (>evt [:bottom-sheet/hide]) + (pick-pic))}] + (when has-picture + [quo/list-item {:accessibility-label :remove-photo + :icon :main-icons/delete + :theme :accent + :title (i18n/label :t/community-image-remove) + :on-press #(do + (>evt [:bottom-sheet/hide]))}])])) + +(defn photo-picker [] + (let [{:keys [image]} (evt [:bottom-sheet/show-sheet + {:content (bottom-sheet (boolean image))}])} + [rn/view {:style {:width 128 + :height 128}} + [rn/view {:style {:flex 1 + :border-radius 64 + :background-color (colors/get-color :ui-01) + :justify-content :center + :align-items :center}} + (if image + [rn/image {:source (utils.image/source image) + :style {:width 128 + :height 128 + :border-radius 64} + :resize-mode :cover + :accessibility-label :community-image}] + [:<> + [icons/icon :main-icons/photo {:color (colors/get-color :icon-02)}] + [quo/text {:color :secondary} + (i18n/label :t/community-thumbnail-upload)]])] + [rn/view {:style {:position :absolute + :top 0 + :right 7}} + [rn/view {:style {:width 40 + :height 40 + :background-color (colors/get-color :interactive-01) + :border-radius 20 + :align-items :center + :justify-content :center + :shadow-offset {:width 0 :height 1} + :shadow-radius 6 + :shadow-opacity 1 + :shadow-color (colors/get-color :shadow-01) + :elevation 2}} + [icons/icon :main-icons/add {:color colors/white}]]]]]])) + +(defn countable-label [{:keys [label value max-length]}] + [rn/view {:style {:padding-bottom 10 + :justify-content :space-between + :align-items :flex-end + :flex-direction :row + :flex-wrap :nowrap}} + [quo/text label] + [quo/text {:size :small + :color (if (> (count value) max-length) + :negative + :secondary)} + (str (count value) "/" max-length)]]) + +(defn form [] + (let [{:keys [name description membership]} (evt [::communities/create-field :name %]) + :auto-focus true}]] + [rn/view {:style {:padding-bottom 16 + :padding-top 10 + :padding-horizontal 16}} + [countable-label {:label (i18n/label :t/give-a-short-description-community) + :value description + :max-length max-description-length}] + [quo/text-input + {:placeholder (i18n/label :t/give-a-short-description-community) + :multiline true + :default-value description + :on-change-text #(>evt [::communities/create-field :description %])}]] + [quo/list-header {:color :main} + (i18n/label :t/community-thumbnail-image)] + [photo-picker] + [:<> + [quo/separator {:style {:margin-vertical 10}}] + [quo/list-item {:title (i18n/label :t/membership-button) + :accessory-text (i18n/label (get-in memberships/options [membership :title] :t/membership-none)) + :accessory :text + :on-press #(>evt [:navigate-to :community-membership]) + :chevron true + :size :small}] + [quo/list-footer + (i18n/label (get-in memberships/options [membership :description] :t/membership-none-placeholder))]]])) + +(defn view [] + (let [{:keys [name description]} (evt [::communities/create-confirmation-pressed])} + (i18n/label :t/create)]}]])) diff --git a/src/status_im/ui/screens/communities/create_channel.cljs b/src/status_im/ui/screens/communities/create_channel.cljs new file mode 100644 index 0000000000..b4235c48aa --- /dev/null +++ b/src/status_im/ui/screens/communities/create_channel.cljs @@ -0,0 +1,36 @@ +(ns status-im.ui.screens.communities.create-channel + (:require [clojure.string :as str] + [reagent.core :as reagent] + [quo.react-native :as rn] + [quo.core :as quo] + [status-im.i18n.i18n :as i18n] + [status-im.ui.components.toolbar :as toolbar] + [status-im.utils.handlers :refer [>evt]] + [status-im.communities.core :as communities] + [status-im.ui.components.topbar :as topbar])) + +(defn valid? [community-name] + (not (str/blank? community-name))) + +(defn create-channel [] + (let [channel-name (reagent/atom "")] + (fn [] + [:<> + [topbar/topbar {:title (i18n/label :t/create-channel-title)}] + [rn/scroll-view {:style {:flex 1} + :content-container-style {:padding-vertical 16}} + [rn/view {:style {:padding-bottom 16 + :padding-top 10 + :padding-horizontal 16}} + [quo/text-input + {:label (i18n/label :t/name-your-channel) + :placeholder (i18n/label :t/name-your-channel-placeholder) + :on-change-text #(reset! channel-name %) + :auto-focus true}]]] + [toolbar/toolbar + {:show-border? true + :center + [quo/button {:disabled (not (valid? @channel-name)) + :type :secondary + :on-press #(>evt [::communities/create-channel-confirmation-pressed @channel-name])} + (i18n/label :t/create)]}]]))) diff --git a/src/status_im/ui/screens/communities/edit.cljs b/src/status_im/ui/screens/communities/edit.cljs new file mode 100644 index 0000000000..3d2024ea96 --- /dev/null +++ b/src/status_im/ui/screens/communities/edit.cljs @@ -0,0 +1,21 @@ +(ns status-im.ui.screens.communities.edit + (:require [status-im.ui.components.topbar :as topbar] + [quo.core :as quo] + [status-im.i18n.i18n :as i18n] + [status-im.ui.screens.communities.create :as community.create] + [status-im.utils.handlers :refer [>evt + [topbar/topbar {:title (i18n/label :t/community-edit-title)}] + [community.create/form] + [toolbar/toolbar + {:show-border? true + :center + [quo/button {:disabled (not (community.create/valid? name description)) + :type :secondary + :on-press #(>evt [::communities/edit-confirmation-pressed])} + (i18n/label :t/save)]}]])) diff --git a/src/status_im/ui/screens/communities/import.cljs b/src/status_im/ui/screens/communities/import.cljs new file mode 100644 index 0000000000..604341c7a8 --- /dev/null +++ b/src/status_im/ui/screens/communities/import.cljs @@ -0,0 +1,32 @@ +(ns status-im.ui.screens.communities.import + (:require [quo.react-native :as rn] + [reagent.core :as reagent] + [quo.core :as quo] + [status-im.i18n.i18n :as i18n] + [status-im.utils.handlers :refer [>evt]] + [status-im.communities.core :as communities] + [status-im.ui.components.topbar :as topbar] + [status-im.ui.components.toolbar :as toolbar])) + +(defn view [] + (let [community-key (reagent/atom "")] + (fn [] + [rn/view {:style {:flex 1}} + [topbar/topbar {:title (i18n/label :t/import-community-title)}] + [rn/scroll-view {:style {:flex 1} + :content-container-style {:padding 16}} + [rn/view {:style {:padding-bottom 16 + :padding-top 10}} + [quo/text-input + {:label (i18n/label :t/community-key) + :placeholder (i18n/label :t/community-key-placeholder) + :on-change-text #(reset! community-key %) + :default-value @community-key + :auto-focus true}]]] + + [toolbar/toolbar + {:show-border? true + :center [quo/button {:disabled (= @community-key "") + :type :secondary + :on-press #(>evt [::communities/import @community-key])} + (i18n/label :t/import)]}]]))) diff --git a/src/status_im/ui/screens/communities/invite.cljs b/src/status_im/ui/screens/communities/invite.cljs new file mode 100644 index 0000000000..f6414b5575 --- /dev/null +++ b/src/status_im/ui/screens/communities/invite.cljs @@ -0,0 +1,79 @@ +(ns status-im.ui.screens.communities.invite + (:require [reagent.core :as reagent] + [quo.react-native :as rn] + [quo.core :as quo] + [status-im.i18n.i18n :as i18n] + [status-im.constants :as constants] + [status-im.ui.components.toolbar :as toolbar] + [status-im.utils.handlers :refer [>evt + [rn/view {:style {:padding-horizontal 16 + :padding-vertical 8}} + [quo/text-input + {:label (i18n/label :t/enter-user-pk) + :placeholder (i18n/label :t/enter-user-pk) + :on-change-text #(reset! user-pk %) + :default-value @user-pk + :auto-focus true}]] + [quo/separator {:style {:margin-vertical 8}}] + [quo/list-header (i18n/label :t/contacts)]]) + +(defn contacts-list-item [{:keys [public-key active] :as contact} _ _ {:keys [selected]}] + (let [[first-name second-name] (multiaccounts/contact-two-names contact true)] + [quo/list-item + {:title first-name + :subtitle second-name + :icon [chat-icon.screen/contact-icon-contacts-tab + (multiaccounts/displayed-photo contact)] + :accessory :checkbox + :active active + :on-press (fn [] + (if active + (swap! selected disj public-key) + (swap! selected conj public-key)))}])) + +(defn invite [] + (let [user-pk (reagent/atom "") + contacts-selected (reagent/atom #{})] + (fn [route] + (let [contacts-data ( + [topbar/topbar {:title (i18n/label (if can-invite? + :t/community-invite-title + :t/community-share-title))}] + [rn/flat-list {:style {:flex 1} + :content-container-style {:padding-vertical 16} + :header [header user-pk] + :render-data {:selected contacts-selected} + :render-fn contacts-list-item + :key-fn (fn [{:keys [active public-key]}] + (str public-key active)) + :data contacts}] + [toolbar/toolbar + {:show-border? true + :center + [quo/button {:disabled (and (str/blank? @user-pk) + (zero? (count selected))) + :type :secondary + :on-press #(>evt [(if can-invite? + ::communities/invite-people-confirmation-pressed + ::communities/share-community-confirmation-pressed) @user-pk selected])} + (i18n/label (if can-invite? :t/invite :t/share))]}]])))) diff --git a/src/status_im/ui/screens/communities/members.cljs b/src/status_im/ui/screens/communities/members.cljs new file mode 100644 index 0000000000..b23d65f0f0 --- /dev/null +++ b/src/status_im/ui/screens/communities/members.cljs @@ -0,0 +1,113 @@ +(ns status-im.ui.screens.communities.members + (:require [quo.react-native :as rn] + [quo.core :as quo] + [reagent.core :as reagent] + [status-im.constants :as constants] + [status-im.ui.components.react :as react] + [status-im.utils.handlers :refer [evt]] + [status-im.ui.components.chat-icon.screen :as chat-icon] + [status-im.ui.components.unviewed-indicator :as unviewed-indicator] + [status-im.multiaccounts.core :as multiaccounts] + [status-im.ui.components.topbar :as topbar] + [status-im.i18n.i18n :as i18n] + [status-im.communities.core :as communities])) + +(defn hide-sheet-and-dispatch [event] + (>evt [:bottom-sheet/hide]) + (>evt event)) + +(defn member-sheet [{:keys [public-key] :as member} community-id can-kick-users?] + [:<> + [quo/list-item + {:theme :accent + :icon [chat-icon/contact-icon-contacts-tab + (multiaccounts/displayed-photo member)] + :title (multiaccounts/displayed-name member) + :subtitle (i18n/label :t/view-profile) + :accessibility-label :view-chat-details-button + :chevron true + :on-press #(hide-sheet-and-dispatch [:chat.ui/show-profile public-key])}] + (when can-kick-users? + [:<> + [quo/separator {:style {:margin-vertical 8}}] + [quo/list-item {:theme :negative + :icon :main-icons/arrow-left + :title (i18n/label :t/member-kick) + :on-press #(>evt [::communities/member-kick community-id public-key])}] + ; ban not implemented + #_[quo/list-item {:theme :negative + :icon :main-icons/cancel + :title (i18n/label :t/member-ban) + :on-press #(>evt [::communities/member-ban community-id public-key])}]])]) + +(defn render-member [public-key _ _ {:keys [community-id + my-public-key + can-kick-users?]}] + (let [{:keys [nickname] :as member} (or (evt [:bottom-sheet/show-sheet + {:content (fn [] [member-sheet member community-id can-kick-users?])}]) + :type :icon + :theme :icon + :accessibility-label :menu-option} + :main-icons/more])}])) + +(defn header [community-id] + [:<> + [quo/list-item {:icon :main-icons/share + :title (i18n/label :t/invite-people) + :accessibility-label :community-invite-people + :theme :accent + :on-press #(>evt [::communities/invite-people-pressed community-id])}] + [quo/separator {:style {:margin-vertical 8}}]]) + +(defn requests-to-join [community-id] + (let [requests ( + [quo/list-item {:chevron true + :accessory + [react/view {:flex-direction :row} + (when (pos? requests-count) + [unviewed-indicator/unviewed-indicator requests-count])] + :on-press #(>evt [:navigate-to :community-requests-to-join {:community-id community-id}]) + :title (i18n/label :t/membership-requests)}] + [quo/separator {:style {:margin-vertical 8}}]])) + +(defn members [route] + (let [{:keys [community-id]} (get-in route [:route :params]) + my-public-key ( + [topbar/topbar {:title (i18n/label :t/community-members-title) + + :subtitle (str (count members))}] + [header community-id] + (when (and can-manage-users? (= constants/community-on-request-access (:access permissions))) + [requests-to-join community-id]) + [rn/flat-list {:data (keys members) + :render-data {:community-id community-id + :my-public-key my-public-key + :can-kick-users? (and can-manage-users? + (not= (:access permissions) + constants/community-no-membership-access)) + :can-manage-users? can-manage-users?} + :key-fn identity + :render-fn render-member}]])) + +(defn members-container [route] + (reagent/create-class + {:display-name "community-members-view" + :component-did-mount (fn [] + (communities/fetch-requests-to-join! (get-in route [:route :params :community-id]))) + :reagent-render members})) diff --git a/src/status_im/ui/screens/communities/membership.cljs b/src/status_im/ui/screens/communities/membership.cljs new file mode 100644 index 0000000000..3482b43387 --- /dev/null +++ b/src/status_im/ui/screens/communities/membership.cljs @@ -0,0 +1,50 @@ +(ns status-im.ui.screens.communities.membership + (:require [quo.react-native :as rn] + [status-im.ui.components.topbar :as topbar] + [status-im.ui.components.toolbar :as toolbar] + [quo.core :as quo] + [status-im.i18n.i18n :as i18n] + [status-im.utils.handlers :refer [>evt + [quo/list-item {:title (i18n/label title) + :size :small + :accessory :radio + :active selected + :on-press on-select}] + [quo/list-footer + (i18n/label description)] + [quo/separator {:style {:margin-vertical 8}}]]) + +(defn membership [] + (let [{:keys [membership]} ( + [topbar/topbar {:title (i18n/label :t/membership-title)}] + [rn/scroll-view {} + (doall + (for [[id o] options] + ^{:key (str "option-" id)} + [option o {:selected (= id membership) + :on-select #(>evt [::communities/create-field :membership id])}]))] + [toolbar/toolbar + {:show-border? true + :center [quo/button {:type :secondary + :on-press #(>evt [:navigate-back])} + (i18n/label :t/done)]}]])) diff --git a/src/status_im/ui/screens/communities/profile.cljs b/src/status_im/ui/screens/communities/profile.cljs new file mode 100644 index 0000000000..53538d3999 --- /dev/null +++ b/src/status_im/ui/screens/communities/profile.cljs @@ -0,0 +1,96 @@ +(ns status-im.ui.screens.communities.profile + (:require [quo.core :as quo] + [status-im.utils.handlers :refer [>evt + [quo/animated-header {:left-accessories [{:icon :main-icons/arrow-left + :accessibility-label :back-button + :on-press #(>evt [:navigate-back])}] + :right-accessories [{:icon :main-icons/share + :accessibility-label :invite-button + :on-press #(>evt [::communities/share-community-pressed community-id])}] + :extended-header (profile-header/extended-header + {:title name + :color (or color (rand-nth colors/chat-colors)) + :photo (if (= community-id constants/status-community-id) + (:uri + (rn/resolve-asset-source + (resources/get-image :status-logo))) + (get-in community [:images :large :uri])) + :subtitle (when show-members-count? (i18n/label-pluralize members-count :t/community-members {:count members-count}))}) + :use-insets true} + [:<> + [quo/list-footer {:color :main} + (get-in description [:identity :description])] + [quo/separator {:style {:margin-vertical 8}}] + (when show-members-count? + [quo/list-item {:chevron true + :accessory + [react/view {:flex-direction :row} + (when (pos? members-count) + [quo/text {:color :secondary} (str members-count)]) + [unviewed-indicator/unviewed-indicator (count requests-to-join)]] + :on-press #(>evt [:navigate-to :community-members {:community-id community-id}]) + :title (i18n/label :t/members-label) + :icon :main-icons/group-chat}]) + (when (and admin roles) + [quo/list-item {:chevron true + :title (i18n/label :t/commonuity-role) + :icon :main-icons/objects}]) + (when notifications + [quo/list-item {:chevron true + :title (i18n/label :t/chat-notification-preferences) + :icon :main-icons/notification}]) + (when (or show-members-count? notifications (and admin roles)) + [quo/separator {:style {:margin-vertical 8}}]) + ;; Disable as not implemented yet + (when false + [quo/list-item {:theme :accent + :icon :main-icons/edit + :title (i18n/label :t/edit-community) + :on-press #(>evt [::communities/open-edit-community community-id])}]) + [quo/list-item {:theme :accent + :icon :main-icons/arrow-left + :title (i18n/label :t/leave-community) + :on-press #(>evt [::communities/leave community-id])}] + ;; Disable as not implemented yet + (when false + [quo/list-item {:theme :negative + :icon :main-icons/delete + :title (i18n/label :t/delete) + :on-press #(>evt [::communities/delete-community community-id])}])]]])) + +(defn management-container [route] + (reagent/create-class + {:display-name "community-profile-view" + :component-did-mount (fn [] + (communities/fetch-requests-to-join! (get-in route [:route :params :community-id]))) + :reagent-render management})) + + + diff --git a/src/status_im/ui/screens/communities/requests_to_join.cljs b/src/status_im/ui/screens/communities/requests_to_join.cljs new file mode 100644 index 0000000000..d7610c1d19 --- /dev/null +++ b/src/status_im/ui/screens/communities/requests_to_join.cljs @@ -0,0 +1,64 @@ +(ns status-im.ui.screens.communities.requests-to-join + (:require [quo.react-native :as rn] + [quo.core :as quo] + [reagent.core :as reagent] + [re-frame.core :as re-frame] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.react :as react] + [status-im.utils.handlers :refer [evt]] + [status-im.ui.components.chat-icon.screen :as chat-icon] + [status-im.multiaccounts.core :as multiaccounts] + [status-im.ui.components.icons.icons :as icons] + [status-im.ui.components.topbar :as topbar] + [status-im.i18n.i18n :as i18n] + [status-im.communities.core :as communities])) + +(defn hide-sheet-and-dispatch [event] + (>evt [:bottom-sheet/hide]) + (>evt event)) + +(defn request-actions [community-id request-id] + [react/view {:flex-direction :row} + [react/touchable-highlight {:on-press #(re-frame/dispatch [:communities.ui/accept-request-to-join-pressed community-id request-id])} + [icons/icon :main-icons/checkmark-circle {:width 35 + :height 35 + :color colors/green}]] + [react/touchable-highlight {:on-press #(re-frame/dispatch [:communities.ui/decline-request-to-join-pressed community-id request-id])} + [icons/icon :main-icons/cancel {:width 35 + :height 35 + :container-style {:padding-left 10} + :color colors/red}]]]) + +(defn render-request [{:keys [id public-key]} _ _ {:keys [community-id + can-manage-users?]}] + (let [member (or ( + [topbar/topbar {:title (i18n/label :t/community-requests-to-join-title) + :subtitle (str (count requests))}] + [rn/flat-list {:data requests + :render-data {:community-id community-id + :can-manage-users? can-manage-users?} + :key-fn :id + :render-fn render-request}]]))) + +(defn requests-to-join-container [route] + (reagent/create-class + {:display-name "community-requests-to-join-view" + :component-did-mount (fn [] + (communities/fetch-requests-to-join! (get-in route [:route :params :community-id]))) + :reagent-render requests-to-join})) diff --git a/src/status_im/ui/screens/communities/views.cljs b/src/status_im/ui/screens/communities/views.cljs index aa654781f4..b7818ec42f 100644 --- a/src/status_im/ui/screens/communities/views.cljs +++ b/src/status_im/ui/screens/communities/views.cljs @@ -1,423 +1,189 @@ -(ns status-im.ui.screens.communities.views (:require-macros [status-im.utils.views :as views]) - (:require - [reagent.core :as reagent] - [re-frame.core :as re-frame] - [quo.core :as quo] - [status-im.i18n.i18n :as i18n] - [status-im.utils.core :as utils] - [status-im.utils.config :as config] - [status-im.constants :as constants] - [status-im.communities.core :as communities] - [status-im.ui.screens.home.views.inner-item :as inner-item] - [status-im.ui.screens.home.styles :as home.styles] - [status-im.ui.components.list.views :as list] - [status-im.ui.components.copyable-text :as copyable-text] - [status-im.react-native.resources :as resources] - [status-im.ui.components.topbar :as topbar] - [status-im.ui.components.icons.icons :as icons] - [status-im.ui.components.colors :as colors] - [status-im.ui.components.chat-icon.screen :as chat-icon.screen] - [status-im.ui.components.toolbar :as toolbar] - [status-im.ui.components.react :as react])) +(ns status-im.ui.screens.communities.views + (:require + [quo.core :as quo] + [status-im.i18n.i18n :as i18n] + [status-im.utils.core :as utils] + [status-im.utils.config :as config] + [status-im.constants :as constants] + [status-im.communities.core :as communities] + [status-im.utils.handlers :refer [>evt evt [:bottom-sheet/hide]) + (>evt event)) -(defn community-list-item [{:keys [id description]}] - (let [identity (:identity description)] +(defn community-unviewed-count [id] + (when-not (zero? (evt [:dismiss-keyboard]) + (>evt [:navigate-to :community {:community-id id}])) + ;; TODO: actions + ;; :on-long-press #(>evt [:bottom-sheet/show-sheet + ;; nil]) + }] + (when last? + [quo/separator])]) + +(defn community-list-item [{:keys [id permissions members name description] :as community}] + (let [members-count (count members) + show-members-count? (not= (:access permissions) constants/community-no-membership-access)] [quo/list-item - {:icon (if (= id constants/status-community-id) - [react/image {:source (resources/get-image :status-logo) - :style {:width 40 - :height 40}}] - - [chat-icon.screen/chat-icon-view-chat-list - id - true - (:display-name identity) - ;; TODO: should be derived by id - (or (:color identity) - (rand-nth colors/chat-colors)) - false - false]) + {:icon [community-icon community] :title [react/view {:flex-direction :row - :flex 1} - [react/view {:flex-direction :row - :flex 1 - :padding-right 16 - :align-items :center} - [quo/text {:weight :medium - :accessibility-label :community-name-text - :ellipsize-mode :tail - :number-of-lines 1} - (utils/truncate-str (:display-name identity) 30)]]] + :flex 1 + :padding-right 16 + :align-items :center} + [quo/text {:weight :medium + :accessibility-label :community-name-text + :ellipsize-mode :tail + :number-of-lines 1} + (utils/truncate-str name 30)]] :title-accessibility-label :community-name-text - :subtitle [react/view {:flex-direction :row} - [react/view {:flex 1} - [quo/text - (utils/truncate-str (:description identity) 30)]]] + :subtitle [react/view + [quo/text {:number-of-lines 1} + description] + [quo/text {:number-of-lines 1 + :color :secondary} + (if show-members-count? + (i18n/label-pluralize members-count :t/community-members {:count members-count}) + (i18n/label :t/open-membership))]] :on-press #(do - (re-frame/dispatch [:dismiss-keyboard]) - (re-frame/dispatch [:navigate-to :community id]))}])) + (>evt [:dismiss-keyboard]) + (>evt [:navigate-to :community {:community-id id}]))}])) (defn communities-actions [] - [react/view + [:<> [quo/list-item {:theme :accent :title (i18n/label :t/import-community) :accessibility-label :community-import-community - :icon :main-icons/check - :on-press #(hide-sheet-and-dispatch [::communities/import-pressed])}] + :icon :main-icons/objects + :on-press #(hide-sheet-and-dispatch [:navigate-to :community-import])}] [quo/list-item {:theme :accent :title (i18n/label :t/create-community) :accessibility-label :community-create-community - :icon :main-icons/check - :on-press #(hide-sheet-and-dispatch [::communities/create-pressed])}]]) + :icon :main-icons/add + :on-press #(hide-sheet-and-dispatch [::communities/open-create-community])}]]) -(views/defview communities [] - (views/letsubs [communities [:communities]] +(defn communities-home-list [communities] + [list/flat-list + {:key-fn :id + :keyboard-should-persist-taps :always + :data communities + :render-fn community-home-list-item}]) + +(defn communities-list [communities] + [list/section-list + {:content-container-style {:padding-vertical 8} + :key-fn :id + :keyboard-should-persist-taps :always + :sticky-section-headers-enabled false + :sections communities + :render-section-header-fn quo/list-index + :render-fn community-list-item}]) + +(defn communities [] + (let [communities ( {:title (i18n/label :t/communities)} + [topbar/topbar (cond-> {:title (i18n/label :t/communities) + :modal? true} config/communities-management-enabled? (assoc :right-accessories [{:icon :main-icons/more :accessibility-label :chat-menu-button :on-press - #(re-frame/dispatch [:bottom-sheet/show-sheet - {:content (fn [] - [communities-actions]) - :height 256}])}]))] - [react/scroll-view {:style {:flex 1} - :content-container-style {:padding-vertical 8}} - [list/flat-list - {:key-fn :id - :keyboard-should-persist-taps :always - :data (vals communities) - :render-fn (fn [community] [community-list-item community])}]] + #(>evt [:bottom-sheet/show-sheet + {:content (fn [] + [communities-actions]) + :height 256}])}]))] + [communities-list communities] (when config/communities-management-enabled? [toolbar/toolbar {:show-border? true - :center [quo/button {:on-press #(re-frame/dispatch [::communities/create-pressed])} - (i18n/label :t/create)]}])])) + :center [quo/button {:on-press #(>evt [::communities/open-create-community]) + :type :secondary} + (i18n/label :t/create)]}])])) -(defn valid? [community-name community-description] - (and (not= "" community-name) - (not= "" community-description))) - -(defn import-community [] - (let [community-key (reagent/atom "")] - (fn [] - [react/view {:style {:padding-left 16 - :padding-right 8}} - [react/view {:style {:padding-horizontal 20}} - [quo/text-input - {:label (i18n/label :t/community-key) - :placeholder (i18n/label :t/community-key-placeholder) - :on-change-text #(reset! community-key %) - :auto-focus true}]] - [react/view {:style {:padding-top 20 - :padding-horizontal 20}} - [quo/button {:disabled (= @community-key "") - :on-press #(re-frame/dispatch [::communities/import-confirmation-pressed @community-key])} - (i18n/label :t/import)]]]))) - -(defn create [] - (let [community-name (reagent/atom "") - membership (reagent/atom 1) - community-description (reagent/atom "")] - (fn [] - [react/view {:style {:padding-left 16 - :padding-right 8}} - [react/view {:style {:padding-horizontal 20}} - [quo/text-input - {:label (i18n/label :t/name-your-community) - :placeholder (i18n/label :t/name-your-community-placeholder) - :on-change-text #(reset! community-name %) - :auto-focus true}]] - [react/view {:style {:padding-horizontal 20}} - [quo/text-input - {:label (i18n/label :t/give-a-short-description-community) - :placeholder (i18n/label :t/give-a-short-description-community) - :multiline true - :number-of-lines 4 - :on-change-text #(reset! community-description %)}]] - [react/view {:style {:padding-horizontal 20}} - [quo/text-input - {:label (i18n/label :t/membership-type) - :placeholder (i18n/label :t/membership-type-placeholder) - :on-change-text #(reset! membership %)}]] - - [react/view {:style {:padding-top 20 - :padding-horizontal 20}} - [quo/button {:disabled (not (valid? @community-name @community-description)) - :on-press #(re-frame/dispatch [::communities/create-confirmation-pressed @community-name @community-description @membership])} - (i18n/label :t/create)]]]))) - -(def create-sheet - {:content create}) - -(def import-sheet - {:content import-community}) - -(defn create-channel [] - (let [channel-name (reagent/atom "") - channel-description (reagent/atom "")] - (fn [] - [react/view {:style {:padding-left 16 - :padding-right 8}} - [react/view {:style {:padding-horizontal 20}} - [quo/text-input - {:label (i18n/label :t/name-your-channel) - :placeholder (i18n/label :t/name-your-channel-placeholder) - :on-change-text #(reset! channel-name %) - :auto-focus true}]] - [react/view {:style {:padding-horizontal 20}} - [quo/text-input - {:label (i18n/label :t/give-a-short-description-channel) - :placeholder (i18n/label :t/give-a-short-description-channel) - :multiline true - :number-of-lines 4 - :on-change-text #(reset! channel-description %)}]] - - (when config/communities-management-enabled? - [react/view {:style {:padding-top 20 - :padding-horizontal 20}} - [quo/button {:disabled (not (valid? @channel-name @channel-description)) - :on-press #(re-frame/dispatch [::communities/create-channel-confirmation-pressed @channel-name @channel-description])} - (i18n/label :t/create)]])]))) - -(def create-channel-sheet - {:content create-channel}) - -(defn invite-people [] - (let [user-pk (reagent/atom "")] - (fn [] - [react/view {:style {:padding-left 16 - :padding-right 8}} - [react/view {:style {:padding-horizontal 20}} - [quo/text-input - {:label (i18n/label :t/enter-user-pk) - :placeholder (i18n/label :t/enter-user-pk) - :on-change-text #(reset! user-pk %) - :auto-focus true}]] - [react/view {:style {:padding-top 20 - :padding-horizontal 20}} - [quo/button {:disabled (= "" user-pk) - :on-press #(re-frame/dispatch [::communities/invite-people-confirmation-pressed @user-pk])} - (i18n/label :t/invite)]]]))) - -(def invite-people-sheet - {:content invite-people}) - -(defn community-actions [id admin] - [react/view - (when (and config/communities-management-enabled? admin) - [quo/list-item - {:theme :accent - :title (i18n/label :t/export-key) - :accessibility-label :community-export-key - :icon :main-icons/check - :on-press #(hide-sheet-and-dispatch [::communities/export-pressed id])}]) - (when (and config/communities-management-enabled? admin) - [quo/list-item - {:theme :accent - :title (i18n/label :t/create-channel) - :accessibility-label :community-create-channel - :icon :main-icons/check - :on-press #(hide-sheet-and-dispatch [::communities/create-channel-pressed id])}]) - (when (and config/communities-management-enabled? admin) - [quo/list-item - {:theme :accent - :title (i18n/label :t/invite-people) - :accessibility-label :community-invite-people - :icon :main-icons/close - :on-press #(re-frame/dispatch [::communities/invite-people-pressed id])}]) - [quo/list-item - {:theme :accent - :title (i18n/label :t/leave) - :accessibility-label :leave - :icon :main-icons/close - :on-press #(do - (re-frame/dispatch [:navigate-to :home]) - (re-frame/dispatch [:bottom-sheet/hide]) - (re-frame/dispatch [::communities/leave id]))}]]) - -(defn toolbar-content [id display-name color] - [react/view {:style {:flex 1 - :align-items :center - :flex-direction :row}} - [react/view {:margin-right 10} - (if (= id constants/status-community-id) - [react/image {:source (resources/get-image :status-logo) - :style {:width 40 - :height 40}}] - [chat-icon.screen/chat-icon-view-toolbar - id - true - display-name - (or color - (rand-nth colors/chat-colors))])] - [react/view {:style {:flex 1 :justify-content :center}} - [react/text {:style {:typography :main-medium - :font-size 15 - :line-height 22} - :number-of-lines 1 - :accessibility-label :community-name-text} - display-name]]]) - -(defn topbar [id display-name color admin joined] - [topbar/topbar - {:content [toolbar-content id display-name color] - :navigation {:on-press #(re-frame/dispatch [:navigate-back])} - :right-accessories (when (or admin joined) - [{:icon :main-icons/more - :accessibility-label :community-menu-button - :on-press - #(re-frame/dispatch [:bottom-sheet/show-sheet - {:content (fn [] - [community-actions id admin]) - :height 256}])}])}]) - -(defn welcome-blank-page [] - [react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :center}} - [react/i18n-text {:style home.styles/welcome-blank-text :key :welcome-blank-message}]]) - -(views/defview community-unviewed-count [id] - (views/letsubs [unviewed-count [:communities/unviewed-count id]] - (when-not (zero? unviewed-count) - [react/view {:style {:background-color colors/blue - :border-radius 6 - :margin-right 5 - :margin-top 2 - :width 12 - :height 12} - :accessibility-label :unviewed-messages-public}]))) - -(defn status-community [{:keys [id description]}] - [quo/list-item - {:icon [react/image {:source (resources/get-image :status-logo) - :style {:width 40 - :height 40}}] - :title [react/view {:flex-direction :row - :flex 1} - [react/view {:flex-direction :row - :flex 1 - :padding-right 16 - :align-items :center} - [quo/text {:weight :medium - :accessibility-label :chat-name-text - :font-size 17 - :ellipsize-mode :tail - :number-of-lines 1} - (get-in description [:identity :display-name])]] - [react/view {:flex-direction :row - :flex 1 - :justify-content :flex-end - :align-items :center} - [community-unviewed-count id]]] - - :title-accessibility-label :chat-name-text - :on-press #(do - (re-frame/dispatch [:dismiss-keyboard]) - (re-frame/dispatch [:navigate-to :community id])) - ;; TODO: actions - :on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet - nil])}]) - -(defn channel-preview-item [{:keys [id identity]}] - [quo/list-item - {:icon [chat-icon.screen/chat-icon-view-chat-list - id true (:display-name identity) colors/blue false false] - :title [react/view {:flex-direction :row - :flex 1} - [react/view {:flex-direction :row - :flex 1 - :padding-right 16 - :align-items :center} - [icons/icon :main-icons/tiny-group - {:color colors/black - :width 15 - :height 15 - :container-style {:width 15 - :height 15 - :margin-right 2}}] - [quo/text {:weight :medium - :accessibility-label :chat-name-text - :ellipsize-mode :tail - :number-of-lines 1} - (utils/truncate-str (:display-name identity) 30)]]] - :title-accessibility-label :chat-name-text - :subtitle [react/view {:flex-direction :row} - [react/text-class {:style home.styles/last-message-text - :number-of-lines 1 - :ellipsize-mode :tail - :accessibility-label :chat-message-text} (:description identity)]]}]) - -(defn community-channel-preview-list [_ description] - (let [chats (reduce-kv - (fn [acc k v] - (conj acc (assoc v :id (name k)))) - [] - (get-in description [:chats]))] - [list/flat-list - {:key-fn :id - :keyboard-should-persist-taps :always - :data chats - :render-fn channel-preview-item}])) - -(defn community-chat-list [chats] - (if (empty? chats) - [welcome-blank-page] - [list/flat-list - {:key-fn :chat-id - :keyboard-should-persist-taps :always - :data chats - :render-fn (fn [home-item] [inner-item/home-list-item (assoc home-item :color colors/blue)]) - :footer [react/view {:height 68}]}])) - -(views/defview community-channel-list [id] - (views/letsubs [chats [:chats/by-community-id id]] - [community-chat-list chats])) - -(views/defview community [route] - (views/letsubs [{:keys [id description joined admin]} [:communities/community (get-in route [:route :params])]] - [react/view {:style {:flex 1}} - [topbar - id - (get-in description [:identity :display-name]) - (get-in description [:identity :color]) - admin - joined] - (if joined - [community-channel-list id] - [community-channel-preview-list id description]) - (when-not joined - [react/view {:style {:padding-top 20 - :margin-bottom 10 - :padding-horizontal 20}} - [quo/button {:on-press #(re-frame/dispatch [::communities/join id])} - (i18n/label :t/join)]])])) - -(views/defview export-community [] - (views/letsubs [{:keys [community-key]} [:popover/popover]] - [react/view {} - [react/view {:style {:padding-top 16 :padding-horizontal 16}} - [copyable-text/copyable-text-view - {:label :t/community-key - :container-style {:margin-top 12 :margin-bottom 4} - :copied-text community-key} - [quo/text {:number-of-lines 1 - :ellipsize-mode :middle - :accessibility-label :chat-key - :monospace true} - community-key]]]])) +(defn export-community [] + (let [{:keys [community-key]} (evt [:navigate-to :community {:community-id id}]) :accessibility-label :chat-item} - [react/view {:padding-right 8 :padding-vertical 8} - [react/view {:border-color colors/gray-lighter :border-radius 36 :border-width 1 :padding-horizontal 8 :padding-vertical 5} - [react/text {:style {:color colors/blue :typography :main-medium}} name]]]]) + [react/view {:padding-right 8 + :padding-vertical 8} + [react/view {:border-color colors/gray-lighter + :border-radius 36 + :border-width 1 + :padding-horizontal 8 + :padding-vertical 5} + [quo/text {:color :link} name]]]]) diff --git a/src/status_im/ui/screens/home/sheet/views.cljs b/src/status_im/ui/screens/home/sheet/views.cljs index f82ef5fe4a..a57a29a7b3 100644 --- a/src/status_im/ui/screens/home/sheet/views.cljs +++ b/src/status_im/ui/screens/home/sheet/views.cljs @@ -53,7 +53,7 @@ :title (i18n/label :t/communities-alpha) :accessibility-label :communities-button :icon :main-icons/communities - :on-press #(hide-sheet-and-dispatch [:navigate-to :communities])}]) + :on-press #(hide-sheet-and-dispatch [:navigate-to :communities {:screen :communities}])}]) [invite/list-item {:accessibility-label :chats-menu-invite-friends-button}]]) diff --git a/src/status_im/ui/screens/home/views.cljs b/src/status_im/ui/screens/home/views.cljs index 51eae3abf0..ee86693344 100644 --- a/src/status_im/ui/screens/home/views.cljs +++ b/src/status_im/ui/screens/home/views.cljs @@ -145,15 +145,17 @@ (re-frame/dispatch [:set :public-group-topic nil]) (re-frame/dispatch [:search/home-filter-changed nil]))}])]))) -(defn render-fn [home-item] - [inner-item/home-list-item home-item]) +(defn render-fn [{:keys [chat-id] :as home-item}] + ;; We use `chat-id` to distinguish communities from chats + (if chat-id + [inner-item/home-list-item home-item] + [communities.views/community-home-list-item home-item])) -(defn communities-and-chats [chats status-community loading? search-filter hide-home-tooltip?] +(defn communities-and-chats [items loading? search-filter hide-home-tooltip?] (if loading? [react/view {:flex 1 :align-items :center :justify-content :center} [react/activity-indicator {:animating true}]] - (if (and (empty? chats) - (not status-community) + (if (and (empty? items) (empty? search-filter) hide-home-tooltip? (not @search-active?)) @@ -161,33 +163,26 @@ [list/flat-list {:key-fn :chat-id :keyboard-should-persist-taps :always - :data chats + :data items :render-fn render-fn - :header [:<> - (when (or (seq chats) @search-active? (seq search-filter)) - [search-input-wrapper search-filter chats]) - [referral-item/list-item] - (when (and (empty? chats) - (not status-community) - (or @search-active? (seq search-filter))) - [start-suggestion search-filter]) - (when status-community - ;; We only support one community now, Status - [communities.views/status-community status-community]) - (when (and status-community - (seq chats)) - [quo/separator])] + :header [:<> + (when (or (seq items) @search-active? (seq search-filter)) + [search-input-wrapper search-filter items]) + [referral-item/list-item] + (when + (and (empty? items) + (or @search-active? (seq search-filter))) + [start-suggestion search-filter])] :footer (if (and (not hide-home-tooltip?) (not @search-active?)) [home-tooltip-view] [react/view {:height 68}])}]))) (views/defview chats-list [] - (views/letsubs [status-community [:communities/status-community] - loading? [:chats/loading?] - {:keys [chats search-filter]} [:home-items] + (views/letsubs [loading? [:chats/loading?] + {:keys [items + search-filter]} [:home-items] {:keys [hide-home-tooltip?]} [:multiaccount]] - [react/scroll-view - [communities-and-chats chats status-community loading? search-filter hide-home-tooltip?]])) + [communities-and-chats items loading? search-filter hide-home-tooltip?])) (views/defview plus-button [] (views/letsubs [logging-in? [:multiaccounts/login]] diff --git a/src/status_im/ui/screens/routing/chat_stack.cljs b/src/status_im/ui/screens/routing/chat_stack.cljs index 79283ae34b..32657aaaf0 100644 --- a/src/status_im/ui/screens/routing/chat_stack.cljs +++ b/src/status_im/ui/screens/routing/chat_stack.cljs @@ -5,12 +5,24 @@ [status-im.ui.screens.group.views :as group] [status-im.ui.screens.referrals.public-chat :as referrals.public-chat] [status-im.ui.screens.communities.views :as communities] + [status-im.ui.screens.communities.community :as community] + [status-im.ui.screens.communities.create :as communities.create] + [status-im.ui.screens.communities.import :as communities.import] + [status-im.ui.screens.communities.profile :as community.profile] + [status-im.ui.screens.communities.edit :as community.edit] + [status-im.ui.screens.communities.create-channel :as create-channel] + [status-im.ui.screens.communities.membership :as membership] + [status-im.ui.screens.communities.members :as members] + [status-im.ui.screens.communities.requests-to-join :as requests-to-join] + [status-im.ui.screens.communities.invite :as invite] [status-im.ui.screens.profile.group-chat.views :as profile.group-chat] [status-im.ui.components.tabbar.styles :as tabbar.styles] - [status-im.ui.screens.stickers.views :as stickers])) + [status-im.ui.screens.stickers.views :as stickers] + [status-im.utils.config :as config])) (defonce stack (navigation/create-stack)) (defonce group-stack (navigation/create-stack)) +(defonce communities-stack (navigation/create-stack)) (defn chat-stack [] [stack {:initial-route-name :home @@ -20,14 +32,6 @@ :component home/home} {:name :referral-enclav :component referrals.public-chat/view} - {:name :communities - :transition :presentation-ios - :insets {:bottom true} - :component communities/communities} - {:name :community - :transition :presentation-ios - :insets {:bottom true} - :component communities/community} {:name :chat :component chat/chat} {:name :group-chat-profile @@ -38,7 +42,50 @@ {:name :stickers :component stickers/packs} {:name :stickers-pack - :component stickers/pack}]]) + :component stickers/pack} + ;; Community + {:name :community + :component community/community} + {:name :community-management + :insets {:top false} + :component community.profile/management-container} + {:name :community-members + :component members/members-container} + {:name :community-requests-to-join + :component requests-to-join/requests-to-join-container} + {:name :create-community-channel + :component create-channel/create-channel} + {:name :invite-people-community + :component invite/invite}]]) + +(defn communities [] + [communities-stack {:header-mode :none} + (concat + [{:name :communities + :insets {:bottom true + :top false} + :component communities/communities} + {:name :community-import + :insets {:bottom true + :top false} + :component communities.import/view} + {:name :invite-people-community + :insets {:bottom true + :top false} + :component invite/invite}] + (when config/communities-management-enabled? + [{:name :community-edit + :insets {:bottom true + :top false} + :component community.edit/edit} + {:name :community-create + :insets {:bottom true + :top false} + :component communities.create/view} + {:name :community-membership + :insets {:bottom true + :top false} + :component membership/membership}]))]) (defn new-group-chat [] [group-stack {:header-mode :none diff --git a/src/status_im/ui/screens/routing/main.cljs b/src/status_im/ui/screens/routing/main.cljs index 6af5d24e45..9add1267ac 100644 --- a/src/status_im/ui/screens/routing/main.cljs +++ b/src/status_im/ui/screens/routing/main.cljs @@ -100,6 +100,9 @@ {:name :create-group-chat :transition :presentation-ios :component chat-stack/new-group-chat} + {:name :communities + :transition :presentation-ios + :component chat-stack/communities} {:name :referral-invite :transition :presentation-ios :insets {:bottom true} diff --git a/src/status_im/utils/handlers.cljs b/src/status_im/utils/handlers.cljs index 3be3eb52b8..94ff776548 100644 --- a/src/status_im/utils/handlers.cljs +++ b/src/status_im/utils/handlers.cljs @@ -52,3 +52,7 @@ name [debug-handlers-names (re-frame/inject-cofx :now) interceptors] handler))) + +(def evt re-frame/dispatch) diff --git a/status-go-version.json b/status-go-version.json index a3cc6b31b5..fd6a5415f6 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -2,7 +2,7 @@ "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' instead", "owner": "status-im", "repo": "status-go", - "version": "v0.71.7", - "commit-sha1": "99a304686faadd465a90704b1e228f62ef873df2", - "src-sha256": "1wgwsh140bbha049adrr4jlaxzwmwd7n6a89r2wjpvdydfcpxpiv" + "version": "v0.72.0", + "commit-sha1": "f115b8d289684ac51dec30e5d27f57722c0a6724", + "src-sha256": "0h94ki44by233abwg055nhm4wg7i6f8f8jmndm9813a965x51rgp" } diff --git a/translations/en.json b/translations/en.json index 36c501509d..01db8ee022 100644 --- a/translations/en.json +++ b/translations/en.json @@ -146,17 +146,64 @@ "close-app-title": "Warning!", "command-button-send": "Send", "communities": "Communities", + "community-members": { + "one": "{{count}} member", + "other": "{{count}} members" + }, + "members-label": "Members", + "open-membership": "Open membership", + "member-kick": "Kick member", + "membership-requests": "Membership requests", + "community-members-title": "Members", + "community-requests-to-join-title": "Membership requests", "name-your-channel": "Name your channel", + "name-your-channel-placeholder": "Channel name", "give-a-short-description": "Give a short description", "communities-alpha": "Communities (alpha)", "communities-verified": "✓ Verified Status Community", + "request-access": "Request access", + "membership-request-pending": "Membership request pending", "create-community": "Create a community", + "edit-community": "Edit community", + "community-edit-title": "Edit community", + "community-invite-title": "Invite", + "community-share-title": "Share", + "invite": "Invite", "create-channel": "Create a channel", "import-community": "Import a community", + "import-community-title": "Import a community", "name-your-community": "Name your community", "name-your-community-placeholder": "A catchy name", "give-a-short-description-community": "Give it a short description", + "new-community-title": "New community", + "membership-title": "Membership requirement", + "create-channel-title": "New channel", + "community-thumbnail-image": "Thumbnail image", + "community-thumbnail-upload": "Upload", + "community-image-take": "Take a photo", + "community-image-pick": "Pick an image", + "community-image-delete": "", + "community-color": "Community colour", + "community-color-placeholder": "Pick a colour", + "membership-button": "Membership requirement", + "membership-none": "None", + "membership-none-placeholder": "You can require new members to meet certain criteria before they can join. This can be changed at any time", + "membership-approval": "Require approval", + "membership-approval-description": "Your community is free to join, but new members are required to be approved by the community creator first", + "membership-invite": "Require invite from another member", + "membership-invite-description": "Your community can only be joined by an invitation from existing community members", + "membership-ens": "Require ENS username", + "membership-ens-description": "Your community requires an ENS username to be able to join", + "membership-free": "No requirement", + "membership-free-description": "Your community is free for anyone to join", + "community-roles": "Roles", + "community-key": "Community private key", + "community-key-placeholder": "Type your community private key", + "leave-community": "Leave community", + "enter-user-pk": "Enter user public key", + "import": "Import", "complete-hardwallet-setup": "This card is now linked. You need it to sign transactions and unlock your keys", + "chat-notification-preferences": "Notification settings", "completed": "Completed", "confirm": "Confirm", "confirmation-request": "Confirmation request", @@ -479,7 +526,8 @@ "ethereum-node-started-incorrectly-title": "Ethereum node started incorrectly", "etherscan-lookup": "Look up on Etherscan", "export-account": "Export account", - "export-key": "Export key", + "export-key": "Export private key", + "community-private-key": "Community private key", "failed": "Failed", "faq": "Frequently asked questions", "fetch-messages": "↓ Fetch messages", @@ -490,6 +538,7 @@ "fleet": "Fleet", "fleet-settings": "Fleet settings", "follow-your-interests": "Jump into a public chat and meet new people", + "follow": "Follow", "free": "↓ Free", "from": "From", "gas-limit": "Gas limit", @@ -1177,6 +1226,7 @@ "validation-amount-invalid-number": "Amount is not a valid number", "validation-amount-is-too-precise": "Amount is too precise. Max number of decimals is {{decimals}}.", "version": "App version", + "view": "View", "view-cryptokitties": "View in CryptoKitties", "view-cryptostrikers": "View in CryptoStrikers", "view-etheremon": "View in Etheremon", @@ -1215,6 +1265,8 @@ "welcome-to-status": "Welcome to Status!", "welcome-to-status-description": "Set up your crypto wallet, invite friends to chat and browse decentralized apps", "welcome-blank-message": "Your chats will appear here. To start new chats press the ⊕ button", + "welcome-community-blank-message": "Your chats will appear here. To start new chats click on the 3 dots above and select \"Create a channel\"", + "welcome-blank-community-message": "Your communities will appear here.", "seed-phrase-placeholder": "Seed phrase...", "word-count": "Word count", "word-n": "Word #{{number}}",