From 59c6be8eb789a5b652808c0e99420055f29a7607 Mon Sep 17 00:00:00 2001 From: Andy Schwob Date: Tue, 11 Dec 2018 19:37:49 -0500 Subject: [PATCH] feat(fsengagement): add fsengagement package --- .../src/lib/modules/react-native-fcm.ts | 75 +++++ packages/flagship/src/types.ts | 6 + .../fsengagement/assets/images/backArrow.png | Bin 0 -> 349 bytes .../assets/images/backArrow@2x.png | Bin 0 -> 723 bytes .../assets/images/backArrow@3x.png | Bin 0 -> 1132 bytes .../assets/images/closeBronze.png | Bin 0 -> 381 bytes .../assets/images/closeBronze@2x.png | Bin 0 -> 531 bytes .../assets/images/closeBronze@3x.png | Bin 0 -> 715 bytes .../fsengagement/assets/images/gradient.png | Bin 0 -> 6615 bytes .../assets/images/gradient@2x.png | Bin 0 -> 19613 bytes .../assets/images/gradient@3x.png | Bin 0 -> 40730 bytes .../fsengagement/assets/images/rightArrow.png | Bin 0 -> 332 bytes .../assets/images/rightArrow@2x.png | Bin 0 -> 789 bytes .../assets/images/rightArrow@3x.png | Bin 0 -> 1141 bytes packages/fsengagement/assets/images/share.png | Bin 0 -> 601 bytes .../fsengagement/assets/images/share@2x.png | Bin 0 -> 1115 bytes .../fsengagement/assets/images/share@3x.png | Bin 0 -> 1599 bytes .../fsengagement/assets/images/whenIcon.png | Bin 0 -> 417 bytes .../assets/images/whenIcon@2x.png | Bin 0 -> 748 bytes .../assets/images/whenIcon@3x.png | Bin 0 -> 983 bytes .../fsengagement/assets/images/whereIcon.png | Bin 0 -> 1360 bytes .../assets/images/whereIcon@2x.png | Bin 0 -> 1955 bytes .../assets/images/whereIcon@3x.png | Bin 0 -> 2581 bytes .../fsengagement/assets/images/whyIcon.png | Bin 0 -> 481 bytes .../fsengagement/assets/images/whyIcon@2x.png | Bin 0 -> 916 bytes .../fsengagement/assets/images/whyIcon@3x.png | Bin 0 -> 1228 bytes packages/fsengagement/package.json | 27 ++ packages/fsengagement/src/EngagementComp.tsx | 305 ++++++++++++++++++ .../fsengagement/src/EngagementService.ts | 278 ++++++++++++++++ packages/fsengagement/src/WebView.tsx | 78 +++++ .../fsengagement/src/inboxblocks/CTABlock.tsx | 134 ++++++++ .../fsengagement/src/inboxblocks/Card.tsx | 111 +++++++ .../src/inboxblocks/EventBlock.tsx | 136 ++++++++ .../src/inboxblocks/EventCard.tsx | 145 +++++++++ .../src/inboxblocks/FeaturedTopCard.tsx | 96 ++++++ .../src/inboxblocks/ImageBlock.tsx | 87 +++++ .../src/inboxblocks/ShareBlock.tsx | 87 +++++ .../src/inboxblocks/SimpleCard.tsx | 101 ++++++ .../fsengagement/src/inboxblocks/Story.tsx | 20 ++ .../src/inboxblocks/TextBlock.tsx | 34 ++ .../src/inboxblocks/TwinCTABlock.tsx | 59 ++++ .../src/inboxblocks/VideoBlock.tsx | 154 +++++++++ .../fsengagement/src/inboxblocks/index.tsx | 28 ++ packages/fsengagement/src/index.ts | 25 ++ packages/fsengagement/src/types.ts | 145 +++++++++ packages/fsengagement/tsconfig.json | 1 + yarn.lock | 34 ++ 47 files changed, 2166 insertions(+) create mode 100644 packages/flagship/src/lib/modules/react-native-fcm.ts create mode 100644 packages/fsengagement/assets/images/backArrow.png create mode 100644 packages/fsengagement/assets/images/backArrow@2x.png create mode 100644 packages/fsengagement/assets/images/backArrow@3x.png create mode 100644 packages/fsengagement/assets/images/closeBronze.png create mode 100644 packages/fsengagement/assets/images/closeBronze@2x.png create mode 100644 packages/fsengagement/assets/images/closeBronze@3x.png create mode 100644 packages/fsengagement/assets/images/gradient.png create mode 100644 packages/fsengagement/assets/images/gradient@2x.png create mode 100644 packages/fsengagement/assets/images/gradient@3x.png create mode 100644 packages/fsengagement/assets/images/rightArrow.png create mode 100644 packages/fsengagement/assets/images/rightArrow@2x.png create mode 100644 packages/fsengagement/assets/images/rightArrow@3x.png create mode 100644 packages/fsengagement/assets/images/share.png create mode 100644 packages/fsengagement/assets/images/share@2x.png create mode 100644 packages/fsengagement/assets/images/share@3x.png create mode 100644 packages/fsengagement/assets/images/whenIcon.png create mode 100644 packages/fsengagement/assets/images/whenIcon@2x.png create mode 100644 packages/fsengagement/assets/images/whenIcon@3x.png create mode 100644 packages/fsengagement/assets/images/whereIcon.png create mode 100644 packages/fsengagement/assets/images/whereIcon@2x.png create mode 100644 packages/fsengagement/assets/images/whereIcon@3x.png create mode 100644 packages/fsengagement/assets/images/whyIcon.png create mode 100644 packages/fsengagement/assets/images/whyIcon@2x.png create mode 100644 packages/fsengagement/assets/images/whyIcon@3x.png create mode 100644 packages/fsengagement/package.json create mode 100644 packages/fsengagement/src/EngagementComp.tsx create mode 100644 packages/fsengagement/src/EngagementService.ts create mode 100644 packages/fsengagement/src/WebView.tsx create mode 100644 packages/fsengagement/src/inboxblocks/CTABlock.tsx create mode 100644 packages/fsengagement/src/inboxblocks/Card.tsx create mode 100644 packages/fsengagement/src/inboxblocks/EventBlock.tsx create mode 100644 packages/fsengagement/src/inboxblocks/EventCard.tsx create mode 100644 packages/fsengagement/src/inboxblocks/FeaturedTopCard.tsx create mode 100644 packages/fsengagement/src/inboxblocks/ImageBlock.tsx create mode 100644 packages/fsengagement/src/inboxblocks/ShareBlock.tsx create mode 100644 packages/fsengagement/src/inboxblocks/SimpleCard.tsx create mode 100644 packages/fsengagement/src/inboxblocks/Story.tsx create mode 100644 packages/fsengagement/src/inboxblocks/TextBlock.tsx create mode 100644 packages/fsengagement/src/inboxblocks/TwinCTABlock.tsx create mode 100644 packages/fsengagement/src/inboxblocks/VideoBlock.tsx create mode 100644 packages/fsengagement/src/inboxblocks/index.tsx create mode 100644 packages/fsengagement/src/index.ts create mode 100644 packages/fsengagement/src/types.ts create mode 120000 packages/fsengagement/tsconfig.json diff --git a/packages/flagship/src/lib/modules/react-native-fcm.ts b/packages/flagship/src/lib/modules/react-native-fcm.ts new file mode 100644 index 0000000000..35736ce31c --- /dev/null +++ b/packages/flagship/src/lib/modules/react-native-fcm.ts @@ -0,0 +1,75 @@ +/* tslint:disable:max-line-length ter-max-len */ +import * as path from '../path'; +import * as fs from '../fs'; +import { logInfo } from '../../helpers'; +import { Config } from '../../types'; + +/** + * Patches Android for the module. + * + * @param {object} configuration The project configuration. + */ +exports.android = function installAndroid(configuration: Config): void { + logInfo('patching Android for react-native-fcm'); + + const globalGradle = path.resolve('android', 'build.gradle'); + const appGradle = path.android.gradlePath(); + + // create google-services.json + if (!configuration.firebaseGoogleServices) { + throw new Error('Google Services config is missing.'); + } + + console.log(path.resolve('android', 'app', 'google-services.json')); + fs.writeFileSync(path.resolve('android', 'app', 'google-services.json'), JSON.stringify(configuration.firebaseGoogleServices, null, 2)); + + // add google-services to global gradle + fs.update(globalGradle, '// [Init Script Deps]', `// [Init Script Deps] + classpath 'com.google.gms:google-services:3.1.2'`); + + // add google-services to app gradle + fs.update(appGradle, '// [Init Script EOF]', `// [Init Script EOF] +apply plugin: 'com.google.gms.google-services'`); + // update sdk version + fs.update(appGradle, 'compileSdkVersion 26', 'compileSdkVersion 27'); + fs.update(appGradle, 'targetSdkVersion 24', 'targetSdkVersion 27'); + fs.update(appGradle, 'buildToolsVersion "26.0.2"', 'buildToolsVersion "27.0.3"'); + // add fcm and firebase deps + fs.update(appGradle, "compile project(':react-native-fcm')", `compile project(':react-native-fcm') + compile 'com.google.firebase:firebase-core:15.+' + compile 'com.google.firebase:firebase-messaging:15.+'`); + + // add intent filters and icons to manifest + let icon = ''; + if (configuration.pushIcons && configuration.pushIcons.android) { + icon = ``; + } + fs.update(path.android.manifestPath(), '', ` + + ${icon} + + + + + + + + + + + + + + `); + + // intent bugfix + fs.update(path.android.mainActivityPath(configuration), 'import android.os.Bundle;', `import android.os.Bundle; +import android.content.Intent;`); + fs.update(path.android.mainActivityPath(configuration), 'public static Activity getActivity(){', ` +@Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + public static Activity getActivity(){`); +}; diff --git a/packages/flagship/src/types.ts b/packages/flagship/src/types.ts index 128faedfec..20ef108b86 100644 --- a/packages/flagship/src/types.ts +++ b/packages/flagship/src/types.ts @@ -22,6 +22,12 @@ export interface Config { ios: CodepushConfig; }; + pushIcons?: { + android?: string; + ios?: string; + }; + firebaseGoogleServices?: any; + zendeskChat?: { accountKey: string; }; diff --git a/packages/fsengagement/assets/images/backArrow.png b/packages/fsengagement/assets/images/backArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..de313294067f630d7be3abf9e4a6c209083a3711 GIT binary patch literal 349 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 bh6WY@p|}9!Lb1Jz00000NkvXXu0mjfh9so3 literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/closeBronze@2x.png b/packages/fsengagement/assets/images/closeBronze@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1157fc3572f26d8bc7de5d45a0e54607c8f78bd0 GIT binary patch literal 531 zcmV+u0_^>XP)Px$%}GQ-R9Fec*uP4{KpY3~-z5eYamnHX_z3y{f`gzGT$}~>LI$C6%48gbfX;<% zj!KJ*2tI&5g5V^$WD&QvUhfx3Nz*j1FIlH_ae>sSvK>Ih@`;bPF(}fFBFe4*EU}xfO{G!3hdkP?>0Owuc3KRg% zz;zF<;>QeO1Q*y-If~)fj&$JfU)Xr9RaBUaKGB1f!*Ihkt_igSvx(!OR$w-8BB)we zn>bO_T38!68B`<8Bu*B^g_*#qpoCyLI8~G|Oa-Tdl7h+LbWzeU5u6E>608Yl3Z)FI z!?B?90E9jjDWN5RZ3h&)XFvNy=r3|a|R?437h@~hW zX0x|A=qC;g4pp~`93)SoE@#R*b6)0SX$@6PDm0X9XKJIr|>f9ng?rhD8VWZV5aFoK2H<@nZ@i~^T4c literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/closeBronze@3x.png b/packages/fsengagement/assets/images/closeBronze@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..823295ee751d3dc58a5254d1bd0a8b4147c29700 GIT binary patch literal 715 zcmV;+0yO=JP)Px%g-Jv~RA>d=+RtkfK@T>6Nz-JL?99CPcBTW#B-u^ge)jDW_%hJCxp+Cx z;KsC4kM6bCe`W~}tu?fo7akhId_&D#zukWS3pombnbv1Jd1tfLTt1Vku?t*!sde_m zJkIUw->0obVFv-SJ+om}>(1vCp$9;{QosPdo}4?e(L>@T3NUp(RcIOT*zvi=Yin=! zEfVYt>>zd$JBnb0KX|eJ&2Eoz9tFJn{M}yv@%E7TVM@@dKy)2KrUG>VmLg+^x&lWc zV}<$v$0B2c`T{2+V}XVMDw;1MosiN{Dxfn`3Q7s|LUKc?f!;_iC07;RqP$?ievMN*($by^%l?JjQM?vqmKYnjDF*ZC% z)i87#%w+KUlw&CVp#1T{%*doIUkvbKQNzTB5WE{|5n$l3x zPF4Zd0VPAa0HvWMNFSgiG#D8Il!68!DS#4CZzLs<8|sDR0CGZ|k(@v-s1s5G$N{a4 zlmt>kDjY9k$0B0@Qb0!{V*-XkOOY`GL!d**n1Q~~L&y|>KF}UAC7>&` zK&A+EfvTssmQSU&1k?g?HtC->Dd6g03ixL2YQyfQ{^j}Ol-9V~1Mi>9pxe7UOLm`W xQ1DvkSvHmULVJXov1%6DZyDTt17?0$G7MFaf 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 D)ox@x literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/gradient@3x.png b/packages/fsengagement/assets/images/gradient@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1225c91e0761625e1115afc629ceb84ce345dfe1 GIT binary patch literal 40730 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 literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/rightArrow.png b/packages/fsengagement/assets/images/rightArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..89b91b86a2e63b8b273e37e48f5c793d30b2f98a GIT binary patch literal 332 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 literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/share@3x.png b/packages/fsengagement/assets/images/share@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..49044c23e1d1300ea50f6936ef722cd33ab81ed7 GIT binary patch literal 1599 zcmV-F2Eh4=P)Px)`AI}URA>e5TU%@tRTw`1%q(rW*a}oz8$u+Jm=MFQ?e1!Vksu*N?FAR25+X0C z53M(>>FzeE^n$5PHDFCJV2oNGTD#pg=z|0`MbaxY8f%okK!u1(E4J9nUgr3(>9(`8 zXLo03cI2O=ug$VeEHWyeO+M*kjbMX#Sy>l1Kb zcRMUc)yhYbOGuEmre8C>Q`g;y;Lb>fl+ppG{55$tQhPNM1{pJ^OiO>fV9O(?anY$96cR-|R@V(|hr zm%ZQu&zl~>c2`+#5b1|KQxg|TYcH*3@#CSX3ewilpEEV>`xXstW*P(dA}cTFiBI3y z_z#mmM{Kv}|IC5OiZh_^ne8ZfP$Zt7;=2A#Ouj;5+(8!A_OJ2IirqMhZeZEKN3{Lk zY@tMHe{wnUe_NJGwgr%9>BGS+mr{TCo|=IjEWQ$2TtV(RIMv#c{Jw7NE6hH^ z0XezZVL8yY7yL17GTCi=H+VXn77PO3VKqQRO=$ZQHGwJ)(ksp$#!)YiwFvQ%KgUdDbJ{+`5T8wT3Q@*x^cagc<cTG|QW0xSF<(C82c{C>B?{Fc;#M3-I8!fsq#n=w!q_SIp+LGwye*1>q~;DDy@ zg*yXbk1@}aBA4CTrLW}$?TV3H5J|m~5oGFN#`f4ilAV{-quvon3PD^->jrai_L3Q_ z$GUSoPOwZlqk8Uy2lkDw-5y`d5nd4(ROF^z&MjBvz6&MweXB%~y8NPdo17W79+-xE zuK94nd;~Y2YNEDP!7QomDIcA<*?{xqG?9c)Vl=}bI z0$U}pqTnwS1*LM8J&5~4zN)&zTV)@BDy2G=) zQv4R6G?s9x@b8d5NfUEqom8meSJcZ*>GMbNxS2-ICY6H;a0U<0d!+wK5%6C)6_Yd5 x)A&KjPx$TS-JgR5%gsR6R??KoETs?mUGPj6Xub)=IFmOmhD~EUj)2Y%D|&gv2id@gLY& zSa@hB2o|=sg8l(T%b=J$&A1a;3CR|B;p`k(X6C&&kIimaz!H%PHo}hAvQQZ$oM>!3 z3Ao#7%{oBC2tJwD7`U#F;TC-_K(c;x@j#LCx(u2ExMxT( zW^MeQ{nmkWL`jjZG40+)UKvPzK|hMhp^L-tGT@PL~cHq-Ib#7S<3pSMidZrLIm7_#2-z|#d`*!IsyYN&to zh0z-79se5^uB}4*H)kkMPle5tMo5gJw!f#D6>X`XpXwoIOmy-MVC`p*5uBz}00000 LNkvXXu0mjfB&NF< literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/whenIcon@2x.png b/packages/fsengagement/assets/images/whenIcon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..893f9e97df37ecffe302fb8569b41a31ab85ff73 GIT binary patch literal 748 zcmVPx%rb$FWR9Fe^Sj}q_K@@*)b|Xe@6{^^y;7Oqf6;Z*9f*wp$1>2+72*S2j>PJ14 zpwJZBN^C{Q-%v!;;=z-8tCSYJDELZUnc1U)JjhkVcp6FN@+)2GH@ez<>s1?i+Kp`#g?A0#`2wrS_W;VR0bL0z-= zZ6sDk=ZbGVJLejh7)pQhx4TO1AeG`y-|PzKirIQC$1nVr8Q5cO1lFXlfq{FEY=1{C@RP!8wuB=lBZ%-4wXwc zhc>{G{L`->#GtSew{??;{c5qCqn0adY$9v#Br2XK1}{g^%dPyAcO0!A?V`<~cE&As zG#;Se#rH=BlNGmonHS%>Ep3YlX(655r<{F&RjQmJf&%b%VN&q{9^cdZ{PpLRiww^Y z27lA!1N?JTWStd)!s8 e0xTp^T=@lFvd3P@Zc6$90000Px&kx4{BRA>e5o6TzzK^Vs8oorhQiqz_%A`}#a_M)JI)N?_MMG1J&ORZlOp{Z4a zsb1=(saV^l2ce~4q2kSeP4yoT6j~AL(SrvOJlKyz6pPolnS9kGyA3kC1KFL= zXZAPG?Cd6;0<#&0f@j+VCSNel-`tPJZ`zbA*nF_J<0%v3Gza5ic>LA7f|W|lk+Ky0JQx!=kwN(GzvJ)?eC++b(Ybk9*T8`R{%GaHln|ne?iEKu5|eucvb%P~NMnZD zX93ef(JB~gEl-!Il8i-q{L|sa<5vuV?GcD_rPVqR_NB=3!lFTuPLmWs0E2D=z_h+p z`lLt=l7?Tczo%jM=cUz^!)ji+ajB8p1_0&^;16>1i=|UO0g!@YDa1*J$Z4(bqwPhc&uPrN=P`LSOSwh`{Rl?EuS1Pz%u+X8* zaCG)LNy(4xVB2BaS5RdE54g$$UiI!==WyPlg^2JZc&-IG=9N|54u?8S3bBJ;FHN;P zD0U(c;qLCq1pU-=itGA1y=Hl-80k2g(D^84&G~u5&nV(bCRR0vf&(Xig^n1UbU2D}WP;#6x<6>=b|( zTrs7QiGAkGMb_jBVD&cjfYs5^G4466qpK>D=dd~&I>tSRb#zr_@*Gx2L&v!1u#T>( zOrFE)Xy_RC9M;iQmC18h9St4hp2Ir2sxo;FtD~V~T<#(MA;D<-@WGM3^Yywx$}Jqk zbim{yRv7H@zobM!X3z%lWd!_a7HV>!&CT@xPV7(yCuV@bLM#RCG!i@MFaM3S_oH1C zRujY8%uT!L*O(7Oynpod7m6IA(wH^9bpp;X#U!I-{R@(t{T;dXX>b4l002ovPDHLk FV1fbC=`H{O literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/whereIcon.png b/packages/fsengagement/assets/images/whereIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..e07d432be442be209463402e30bea6df53884871 GIT binary patch literal 1360 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRr!3HExu9B$%Qj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07??FOLn2Bde0{8v^K*7iAWdWaj57fJ{tG z$}cUkRRX#c;)UD-xUqS~&|m@vn0`fKfxe-h0mw@*g}%P{mFDKcRTq~8r6Sym)!^cg z%7Rq=pw#00(xPNw#HA^NtSYc_E=o--$uA1Y&(DE{Vn9ZINq%ugeu09sGbq%|6*PPk zlQZ)`f|_7mzP?tTdBr7(dC94sF1AWQbM!JZQ>@I)UCiAaO^wVgEG-NTT`ioPjV+y> zEe*^p3@wdKjg4V?UGkGlb5rw5V0u#!dW~`F1to~w0-((!6x}c(U>X83;fWW>fhYgeJYbqH z0w!+ZpXSdP7#R0?x;TbZ-0HdFukj>N;=sq5@yZhqd+5yxC~{aGI!R@%i`hb#+kJD) zrdTS7&rFG~4%}A8cSNp3<>ui@-OY#N%0#`qG?O+-7cSVg^uiCjuReSCKI>m@_jUI7 z?|a_Auf1pgSnl)w(#XITy%2@y+kGE|9p}tC+4pfyPo_>HckhW1S&l?u`G?!sJq~K+ z1@uI|73g%XaaH{EQTLL$(Sh_g$qfAKZhVw~|G>51q+-i6t35?~b>uGg-Va)L=0H2= zoyctA3rDRFuxC%;)>-VD7SPY8y--}WqBo@Gc>Eek%hw;Qcb!tqZb;*K7-ikJ@Z>2= zw`s|For2*fOzWO!%$7{l4E3_O{3QADQ}wyqbPA+y1!nRFDAdZ>%9vg|9UJmk?zzV0 zpfeT9wW(~y5myd>%utz=U~?>N+3Pi4sch9Zu6_Q0iQ@&kzU?i8j`$lFlBM;RY>E20 zt8&UxKYOtWBFi`X)Uv%V%)Wn2KI&A)9L8k^E4%-^vGV@%(sW(drJcf(-ws&?=FRpO zKasuVd7{gI@k1ZkCfqnE{`kQOr}K;D>))jK_e3=x`@G@aiK!W#^H`$8`nH{z=E|*p kkRd2v``=No3ZVoB&4W>QZ4do83M%6~UHx3vIVCg!0A5+}ZvX%Q literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/whereIcon@2x.png b/packages/fsengagement/assets/images/whereIcon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..18362b9b677eecf07aca32e27ccb88b543060c6e GIT binary patch literal 1955 zcmaJ?X;c$e7!5)YSyT{hqqGb`6av`@OOg@=5|&V+Gz85N#DvU1B1|SuMiLOI2}+e{ z6_8yKJyozSQ~@`@4RI+d2vrNM78g|NhEhu_D4kfb{ZTsS%)Ix$bMO7ucV<8k|ia1kKlL-gt@(s6ZZhvA>8R z((oarDl!v=Vlx*=6q%_C4oIFq4+zq8aRC^@?cB62TCy643yBzPW0FfTu{V zP^ryEuXikwh$~cUF}Yd+2?abdu0vC)K(23i0K?BWToB0Su$jy-b||04;qy6+05+4u z@@KKexB^765{A^+7#IA98#XT2JP@!3_bh-=RXPNQqX-PVOPs43KbFvO_1<&A@nhkS z%cbMN(9P}rSG&hd_zao1ABGn{d>DU7jn6!a5BFr(UK)PA9EE}qiN2?)RFx+BV(Nw3 z$JfR2ipBB!rw^mI#B3ActKfO1*0+rk zhLax=4sm{T_0ZGPpl0i=c!xy8Y~(TfO-H_6yX7*ap;E@XX!%P})8J$<$Ew59#lqo? zS~GcVKLtoxRZ_T(a4Wd{S(!MhW`rJ5Cl%|5#`mG8EL zL~?Q~tNMDW0k%KjJkVpaeVf%$()V3dV)+_UG^M|MT5C>hFXt1VF7={y`}?$pqDgbR zSW}Eqt!;ODOLy}we4Z7-dKU~K{1RqZ!+Gw8hD2@QoF9%Cn_%6#LhOM5qBv_wSyQ9C zpYyTylwCPz59?hXuG&-Dw&eLfAzbkyduUEZ)3w}}nJ_dA+wdA4?=>x9E~dAMcLiIF zGNmoHb{Wm*>pHzaNzmPIB1lBap&JxN*-VG>g&qaeuMDDupu7cH6;U3_Wd5b;r##X` zGxEi0(H5`#6K7Q4vbFAZ$_E?T%1sfh=&*jYko07Nvclh`y(F z*Ev)2ji8IZx~;_CD$Z&T~4myN;1zya^6MEe^f zoTea_`;Q$nwyal9Irw^Dsawqq&0uWhPn*xyhrhL95c)R$vM$>B RsH^$6DGXa6I2bB3{ssQZ0OkMy literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/whereIcon@3x.png b/packages/fsengagement/assets/images/whereIcon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0f5ec576b63bef2d1b81d9e480f024a9af026169 GIT binary patch literal 2581 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 X*O;58+H_BA00000NkvXXu0mjfZ5-Va literal 0 HcmV?d00001 diff --git a/packages/fsengagement/assets/images/whyIcon@2x.png b/packages/fsengagement/assets/images/whyIcon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..50e6cc0e52c04f341f611b190f3334eb4462c8b4 GIT binary patch literal 916 zcmV;F18e+=P)Px&PDw;TR9Fe^SY1d{VHkejbIwwdEQ1KEiwI4r3j>2P`XRcVlf1}_8qyD~ur@8R z%_xo+x{YXVvmm_*>N3LIg08YIyhtJ<0wINvgdpq>af!}zzTWRF$Ke>x;aN>MFC4!2 z=lgl~obT*B=L1+}h8*t;S(aY?d~=gAb_N)1&8OdhfS(D^{f)azJ6x{5q{zWlR26ps zV>iex=5e0cI>K{!=IgZhe0X$F;^2^&Wtt<@wMSwx?&gaHTI{u|0vyw6x~>Zx7z4J2 zxA_K^WAb)CjRT@eJD`-wvY-Lxh1Fsf;9TD+i(!jl0|VBUFc4yTXfbR$rG^PLEn&zl z8Fn?>Yk*-RGSHMBHAx9|lO z^)&8cdK@{Fo1|J+fV1AizLM%oA_|qFVB5tnsX!|8hT}aPI11vtk9gT=7Luz>+8#6I8W^%V{N4|L!2%Uh0d#?fe8`t zh-f20N|pjFh;Gt)<+N}m<;wJnQ4Ua5xQ1=i=Ywta+x#BK1A={2pEu2?b(4zvmgegv z6_^%V8em`e$#^t+c~9cwL=CCb^l>XwfyYfG!Pe-3_&xRpWj1+_z-}NJ_C-MKpgvRj zJ}dP}_)YIvU--;8Pl{p6y4^jq)V$Pp2)G>zY00LATvR?;ocKO_yVdF}$}d!_2+eO&Rt~K5kk2^)0000Px(hDk(0RA>e5TmNfYRTMwxy|i%!#Z8>wK?>R__{&g1>wZvwlf7;O4Nj+6r( z1iQ7VKUI{ZZkkC0EHnwkIMC)=O zfSJ7qr+@IeTp&Ika@uCIlmLR)y#dYIwh#~5EjRDkH*(}pnPWgDI-7+t24Sb_H@g-@ z-H`uHrunM;Q)0wO-sBfq~l?SShmZYidc}jpS5I%5blz?Bt$=|X0vK9+eP`N zPy(V^6JAbU28c3R1_%R${>3qjFpMya&=D|<(D5*9gkgkXgpPn=gpP+uyLHX>{sU9L_js2?;#Qu(qd=9FdA2xL zva=7f|0_932BD8jW$R_TLJ)e`Rstdi^fX65$u-6bVbdLAA@>wJCMujY80rRu)e7Tc z0cBydf)V`recRp{{2G#KhS&p0I}uQ41i)Vw(Wo00#5ByqM_k$MP&TC*h#8drI!WDt z7}%e>%yW8W(RsNb01PHmQyeE>OA3$q2M4^dWKf-)<~gDaqyfSJp?{|gBMc)9BXk4| zBXm5>8ete=7@;Fz7@^}~)(FE0!w4M#!w4M@vql(3RAYnySzRg#A>Oar&8w|TDmO&> zNw!O+IXPzsp=Txv?qebTj66q6ud$s<7z1!9o^2HEWvfHZpSHngq z2yHtP{b2f(yxzdtjT6IjXWGyLyZ!OtAxOxocv<4%&Es5vep&VZP4mIjXZc|2@ulKo z;jS(q@hQjLx>Ve%_z((c1v>^uk6)~IJ-2vk1+AXqJO#KM|3bCE3R{)E1{EU@xX;bO zC?EITrELGVW=7hU$os42?zCJW$`#`m_eY$ALf|H0i96k>asvrB@qWZ(e!I8N$9o@{ z%hKmuZog02nRY(|T1O>Cm2FkEyf9f`4kV{%7Q3zsgbL_Sq|b0$zA7Z%>%7Otsz6Lm zK4)Jzm5VY^L%g@)85bv^?Z(}M!xPzx0UOBRnQ-m1ibuBIdm)oM!XWTJytwkah4!9k za{4pZ(!h=X3`8vAyhNh#A?J9LnO1Y7H^f7ssqzik^)_^)W*`KJx4fD0f_R?q#yg_P^yz>0IK2^#-LA`T4c^TCB+$Bc zBR^N1ZtQdypIyz2C=dB7-xgovP2JSum$*JW%L4-OJ9YknpDa(V0ql+>Q&+B!RO>v; qSlHPd>)rB9org8U83Y;(fxiJ_6+(KmtZY^Q0000 EngagementWebView); + +const win = Dimensions.get('window'); +const imageAspectRatio = 0.344; + +const styles = StyleSheet.create({ + backButton: { + position: 'absolute', + zIndex: 10, + top: 50, + left: 8, + padding: 12 + }, + backIcon: { + width: 14, + height: 25 + }, + emptyMessage: { + textAlign: 'center', + padding: 20 + }, + container: { + backgroundColor: '#ffffff', + flex: 1 + }, + growStretch: { + alignSelf: 'stretch', + flexGrow: 1 + }, + header: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + overflow: 'hidden', + zIndex: 10 + }, + headerImage: { + width: win.width, + height: win.width * imageAspectRatio + } +}); + +const gradientImage = require('../assets/images/gradient.png'); +const backArrow = require('../assets/images/backArrow.png'); + +export interface EngagementScreenProps extends ScreenProps, EmitterProps { + json: JSON; + backButton?: boolean; + noScrollView?: boolean; + refreshControl?: () => void; + isLoading: boolean; +} +export interface EngagementState { + scrollY: Animated.Value; +} + +export default function( + api: EngagementService, + layoutComponents: ComponentList +): ComponentClass { + return class EngagementComp extends Component { + static childContextTypes: any = { + handleAction: PropTypes.func + }; + + state: any = {}; + constructor(props: EngagementScreenProps) { + super(props); + this.state = { + scrollY: new Animated.Value(0) + }; + } + + getChildContext = () => ({ + handleAction: this.handleAction + }) + + // tslint:disable-next-line:cyclomatic-complexity + handleAction = (actions: Action) => { + if (!(actions && actions.type && actions.value)) { + return false; + } + DeviceEventEmitter.emit('viewLink', { + title: actions.name, + id: actions.id, + type: actions.type, + value: actions.value + }); + api.logEvent('clickInboxCta', { + messageId: actions.id, + ctaType: actions.type, + ctaValue: actions.value + }); + switch (actions.type) { + case 'blog-url': + this.props.navigator.push({ + screen: 'EngagementWebView', + navigatorStyle: { + navBarHidden: true + }, + passProps: { + actions, + isBlog: true, + backButton: true + } + }); + break; + case 'web-url': + this.props.navigator.showModal({ + screen: 'EngagementWebView', + passProps: { actions }, + navigatorStyle: { + navBarBackgroundColor: '#f5f2ee', + navBarButtonColor: '#866d4b', + statusBarTextColorScheme: 'dark' + }, + navigatorButtons: { + rightButtons: [ + { + icon: require('../assets/images/closeBronze.png'), + id: 'close' + } + ] + } + }); + break; + case 'deep-link': + Linking.canOpenURL(actions.value).then(supported => { + if (!supported) { + alert('An error occurred: can\'t handle url ' + actions.value); + return false; + } else { + return Linking.openURL(actions.value); + } + }).catch(err => alert('An error occurred: ' + err)); + break; + case 'phone': + Linking.openURL('tel:' + actions.value).catch(err => alert('An error occurred: ' + err)); + break; + case 'email': + let mailUrl = 'mailto:' + actions.value; + if (actions.subject) { + const subject = actions.subject.replace(/ /g, '%20'); + mailUrl += '?subject=' + subject; + } + if (actions.body) { + const separator = ~mailUrl.indexOf('?') ? '&' : '?'; + const body = actions.body.replace(/ /g, '%20'); + mailUrl += separator + 'body=' + body; + } + Linking.openURL(mailUrl).catch(err => + alert('An error occurred when trying to send email to ' + actions.value + ': ' + err)); + break; + default: + break; + } + return; + } + + onBackPress = (): void => { + this.props.navigator.pop(); + } + + renderBlock = (item: BlockItem): JSX.Element | undefined => { + const { + private_blocks, + private_type, + ...restProps } = item; + const { json, id, name } = this.props; + const props = { + id, + name, + ...restProps + }; + + if (!layoutComponents[private_type]) { + return; + } + props.navigator = this.props.navigator; + + return React.createElement( + layoutComponents[private_type], + { + ...props, + storyGradient: props.story ? json.storyGradient : null, + api, + key: Math.floor(Math.random() * 1000000) + }, + private_blocks && private_blocks.map(this.renderBlock) + ); + } + + renderBlocks(): JSX.Element { + const { json, json: { empty } } = this.props; + return ( + + {(json.private_blocks || []).map(this.renderBlock)} + {empty && !(json.private_blocks && json.private_blocks.length) && + + {empty.message || 'No content found.'}} + + ); + } + + renderScrollView(): JSX.Element { + const { json: { storyGradient } } = this.props; + const { scrollY } = this.state; + if (this.props.noScrollView) { + return ( + + {this.renderBlocks()} + + ); + } else if (this.props.backButton && storyGradient && storyGradient.enabled) { + const { + startFadePosition = 0, + endFadePosition = 250 + } = storyGradient; + const headerOpacity = scrollY.interpolate({ + inputRange: [startFadePosition, endFadePosition], + outputRange: [0, 1], + extrapolate: 'clamp' + }); + return ( + + + {this.renderBlocks()} + + + + + + ); + } + return ( + + } + > + {this.renderBlocks()} + + ); + } + + render(): JSX.Element { + const { json } = this.props; + return ( + + {this.renderScrollView()} + {this.props.backButton && + + + } + + ); + } + }; +} diff --git a/packages/fsengagement/src/EngagementService.ts b/packages/fsengagement/src/EngagementService.ts new file mode 100644 index 0000000000..2caa3c3511 --- /dev/null +++ b/packages/fsengagement/src/EngagementService.ts @@ -0,0 +1,278 @@ +import FCM, { FCMEvent } from 'react-native-fcm'; +import FSNetwork from '@brandingbrand/fsnetwork'; +import * as DeviceInfo from 'react-native-device-info'; +import { + EngagementMessage, + EngagementProfile, + EngagmentEvent, + EngagmentNotification +} from './types'; +const uuid = require('uuid-js'); + +export interface EngagementServiceConfig { + appId: string; + apiKey: string; + baseURL: string; + cacheTTL?: number; // default = 10 mins +} + +export interface Attribute { + key: string; + value: string; +} + +export class EngagementService { + appId: string; + networkClient: FSNetwork; + events: EngagmentEvent[] = []; + profileId?: string; + profileData?: EngagementProfile; + messages: EngagementMessage[] = []; + messageCache: number = 0; + cacheTTL: number = 1000 * 60 * 10; + + constructor(config: EngagementServiceConfig) { + this.appId = config.appId; + if (typeof config.cacheTTL === 'number') { + this.cacheTTL = config.cacheTTL; + } + + this.networkClient = new FSNetwork({ + baseURL: config.baseURL, + headers: { + apikey: config.apiKey, + 'Content-Type': 'application/json' + } + }); + } + + logEvent(type: string, data: any): void { + const event = { + type, + id: uuid.create().toString(), + data: JSON.stringify(data), + fired: new Date() + }; + this.events.push(event); + + if (this.profileId) { + // @TODO: can throttle here to save up events and send all at a threshold + + this.networkClient.post(`/Profiles/${this.profileId}/trackEvents`, { + events: this.events + }) + .then((response: any) => { + // clear the event queue on successful submit + if (response.status === 204) { + this.events = []; + } + }) + .catch((e: any) => console.warn('Unable to log events', e)); + } + } + + async setProfileAttributes(attributes: Attribute[]): Promise { + return this.networkClient + .post(`/Profiles/${this.profileId}/setAttributes`, JSON.stringify(attributes)) + .then((response: any) => { + if (response.status === 204) { + return true; + } + return false; + }) + .catch((e: any) => { + console.warn('Unable to set profile attribute', e); + return false; + }); + } + + async setProfileAttribute(key: string, value: string): Promise { + const data = { + key, + value, + appId: this.appId + }; + + return this.networkClient + .post(`/Profiles/${this.profileId}/setAttribute`, data) + .then((response: any) => { + if (response.status === 204) { + return true; + } + return false; + }) + .catch((e: any) => { + console.warn('Unable to set profile attribute', e); + return false; + }); + } + + setNotification(): void { + // debugging local notifications + // FCM.cancelAllLocalNotifications() + // FCM.getScheduledLocalNotifications() + // .then(notif => console.log('scheduled local push notifications', notif)); + + // get and store push token if available + FCM.getFCMToken() + .then(token => this.setPushToken(token)) + .catch(e => console.log('getFCMToken error: ', e)); + + FCM.on(FCMEvent.RefreshToken, token => this.setPushToken(token)); + // listen to notifications and handle them + FCM.on(FCMEvent.Notification, this.onNotification.bind(this)); + // check if the app was opened from a notification and log it + // @TODO: follow the notifications link? + FCM.getInitialNotification() + .then(notif => { + if (notif && notif.messageId) { + this.logEvent('pushopen', { message: notif.messageId }); + } + }) + .catch(); + } + + // @TODO: does the profile need to be resynced anytime during a session? + async getProfile(accountId?: string): Promise { + if (this.profileId && this.profileData) { + return Promise.resolve(this.profileId); + } + + return this.networkClient.post(`/App/${this.appId}/getProfile`, { + locale: DeviceInfo.getDeviceLocale(), + country: DeviceInfo.getDeviceCountry(), + timezone: DeviceInfo.getTimezone(), + deviceIdentifier: DeviceInfo.getUniqueID(), + accountId, + deviceInfo: JSON.stringify({ + model: DeviceInfo.getModel(), + appName: DeviceInfo.getBundleId(), + appVersion: DeviceInfo.getReadableVersion(), + osName: DeviceInfo.getSystemName(), + osVersion: DeviceInfo.getSystemVersion() + }) + }) + .then((r: any) => r.data) + .then((data: any) => { + this.profileId = data.id; + this.profileData = data; + + return data.id; + }) + .catch((e: any) => { + console.log(e.response); + console.error(e); + }); + } + + setPushToken(pushToken: string): void { + const device = this.profileData && this.profileData.devices && + this.profileData.devices[DeviceInfo.getUniqueID()]; + if (device) { + if (!device.pushToken || device.pushToken !== pushToken) { + this.networkClient + .patch(`/Devices/${device.id}`, { pushToken }) + .catch((e: any) => console.log('failed to set push token', e)); + } + } + } + + async requestPushPermissions(): Promise { + return FCM.requestPermissions(); + } + + /** + * Get inbox messages for the current user + * + * @returns {EngagementMessage[]} inbox messages + */ + async getMessages(): Promise { + // check we have a user profile + if (!this.profileId || !this.profileData) { + throw new Error('Profile not loaded.'); + } + + // cache + if (this.messages.length) { + if (+new Date() - this.messageCache < this.cacheTTL) { + + return Promise.resolve(this.messages); + } + } + + return this.networkClient.get(`/PublishedMessages/getForProfile/${this.profileId}`) + .then((r: any) => r.data) + .then((list: any) => list.map((data: any) => ({ + id: data.id, + published: new Date(data.published), + message: JSON.parse(data.message), + title: data.title, + inbox: data.inbox, + attributes: data.attributes + }))) + .then((messages: EngagementMessage[]) => { + this.messages = messages; + this.messageCache = +new Date(); + return messages; + }) + .catch(async (e: any) => { + console.log('Unable to fetch inbox messages', e); + + let ret: EngagementMessage[] = []; + + // respond with stale cache if we have it + if (this.messages.length) { + ret = this.messages; + } + + return Promise.resolve(ret); + }); + } + + async onNotification(notif: EngagmentNotification): Promise { + console.log('onNotification', notif); + + if (notif.local_notification) { + // this is a local notification + console.log('got local notification', notif); + } + + if (notif.opened_from_tray) { + console.log('notif.opened_from_tray: true'); + + // app resumed from push + if (notif.id) { + this.logEvent('pushopen', { + notificationId: notif.id, + messageId: notif.messageId + }); + } + + // app was backgrounded and now foregrounded + } else { + console.log('notif.opened_from_tray: false'); + // app was open while the message came in + + if (notif.future && notif.on && notif.title && notif.body && notif.messageId) { + const fireDate = new Date(parseInt(notif.on, 10)); + FCM.scheduleLocalNotification({ + fire_date: fireDate.getTime(), + id: notif.messageId, + body: notif.body, + title: notif.title, + messageId: notif.messageId, + show_in_foreground: true + }); + console.log('schedule local notif', fireDate); + } else { + if (notif.id) { + this.logEvent('pushreceive', { + notificationId: notif.id, + messageId: notif.messageId + }); + } + } + } + } +} + diff --git a/packages/fsengagement/src/WebView.tsx b/packages/fsengagement/src/WebView.tsx new file mode 100644 index 0000000000..6908277d34 --- /dev/null +++ b/packages/fsengagement/src/WebView.tsx @@ -0,0 +1,78 @@ +import React, { PureComponent } from 'react'; +import { Image, StyleSheet, TouchableOpacity, View, WebView } from 'react-native'; + +import { + Action, + ScreenProps +} from './types'; + +const styles = StyleSheet.create({ + growStretch: { + alignSelf: 'stretch', + flexGrow: 1 + }, + backButton: { + position: 'absolute', + zIndex: 10, + top: 50, + left: 8, + padding: 12 + }, + backIcon: { + width: 14, + height: 25 + } +}); + +const backArrow = require('../assets/images/backArrow.png'); + +export interface WebViewProps extends ScreenProps { + actions: Action; + isBlog?: boolean; + backButton?: boolean; +} + +export default class EngagementWebView extends PureComponent { + constructor(props: WebViewProps) { + super(props); + this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); + } + + onNavigatorEvent = (event: any) => { + if (event.type === 'NavBarButtonPress') { + if (event.id === 'close') { + this.props.navigator.dismissModal(); + } + } + } + + onBackPress = (): void => { + this.props.navigator.pop(); + } + + injectBlogJS(): string { + return this.props.isBlog ? `var els = document.querySelectorAll( + ".site-header, .notice-bar, .site-footer, .site-footer__navigation"); + for (i=0;i + + {this.props.backButton && + + + } + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/CTABlock.tsx b/packages/fsengagement/src/inboxblocks/CTABlock.tsx new file mode 100644 index 0000000000..46db39b1a6 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/CTABlock.tsx @@ -0,0 +1,134 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Image, + StyleProp, + StyleSheet, + Text, + TextStyle, + TouchableOpacity, + View, + ViewStyle +} from 'react-native'; + +import { + Action, + EmitterProps, + Icon, + JSON, + ScreenProps +} from '../types'; + +const images: any = { + rightArrow: require('../../assets/images/rightArrow.png') +}; + +const styles = StyleSheet.create({ + buttonContainer: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center' + }, + backIcon: { + width: 8, + height: 13, + marginLeft: 10 + }, + buttonContents: { + flexDirection: 'row', + alignItems: 'center' + } +}); + +export interface CTABlockProps extends ScreenProps, EmitterProps { + action: string; + text: string; + icon: Icon; + buttonStyle?: StyleProp; + textStyle?: StyleProp; + containerStyle?: StyleProp; + actions: Action; +} + +export default class CTABlock extends Component { + static contextTypes: any = { + story: PropTypes.object, + cardActions: PropTypes.object, + handleAction: PropTypes.func, + handleStoryAction: PropTypes.func, + name: PropTypes.string, + id: PropTypes.string + }; + + handleActionWithStory = (action: string, actions: Action, story: JSON) => { + const { handleAction, handleStoryAction } = this.context; + if (story.html) { + return handleAction({ + type: 'blog-url', + value: story.html.link + }); + } else if (action === 'story' || (story && actions && + (actions.type === null || actions.type === 'story'))) { + // go to story card + return handleStoryAction(story); + } + return null; + } + + handleActionNoStory = (actions: Action) => { + const { handleAction, cardActions } = this.context; + if (actions && actions.type) { + return handleAction({ + ...actions, + name: this.props.name, + id: this.props.id + }); + } + // tappable card with no story - CTAs use actions of container card + return handleAction({ + ...cardActions, + name: this.props.name, + id: this.props.id + }); + } + + takeAction = (action: string, actions: Action): void => { + const { story } = this.context; + if (action === 'story' || story) { + return this.handleActionWithStory(action, actions, story); + } + return this.handleActionNoStory(actions); + } + + onButtonPress = () => { + this.takeAction(this.props.action, this.props.actions); + } + + render(): JSX.Element { + const { + buttonStyle, + textStyle, + containerStyle, + text, + icon + } = this.props; + + return ( + + + + {text} + {icon && } + + + + + ); + + } +} diff --git a/packages/fsengagement/src/inboxblocks/Card.tsx b/packages/fsengagement/src/inboxblocks/Card.tsx new file mode 100644 index 0000000000..f2a89f876d --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/Card.tsx @@ -0,0 +1,111 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + DeviceEventEmitter, + TouchableOpacity, + View +} from 'react-native'; + +import { + Action, + CardProps, + JSON +} from '../types'; + +export interface ActionsCard extends CardProps { + actions?: Action; +} + +export default class Card extends Component { + static childContextTypes: any = { + story: PropTypes.object, + handleStoryAction: PropTypes.func, + cardActions: PropTypes.object, + id: PropTypes.string, + name: PropTypes.string + }; + static contextTypes: any = { + handleAction: PropTypes.func + }; + + getChildContext = () => ({ + story: this.props.story, + handleStoryAction: this.handleStoryAction, + cardActions: this.props.actions, + id: this.props.id, + name: this.props.name + }) + + handleStoryAction = (json: JSON) => { + DeviceEventEmitter.emit('viewStory', { + title: this.props.name, + id: this.props.id + }); + this.props.api.logEvent('viewInboxStory', { + messageId: this.props.id + }); + this.props.navigator.push({ + screen: 'EngagementComp', + navigatorStyle: { + navBarHidden: true + }, + passProps: { + json, + backButton: true, + name: this.props.name, + id: this.props.id + } + }); + } + + onCardPress = (): void => { + const { handleAction } = this.context; + const { actions, story, storyGradient } = this.props; + + // if there is a story attached and either + // 1) no actions object (Related) + // 2) actions.type is null or 'story' (new default tappable cards) + if (story && + (!actions || (actions && (actions.type === null || actions.type === 'story'))) + ) { + if (story.html) { + handleAction({ + type: 'blog-url', + value: story.html.link + }); + } else { + this.handleStoryAction({ + ...story, + storyGradient + }); + } + } else if (actions && actions.type) { + handleAction(actions); + } + } + + render(): JSX.Element { + const { + containerStyle, + plainCard, + children + } = this.props; + + if (plainCard) { + return ( + + {children} + + ); + } + return ( + + {children} + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/EventBlock.tsx b/packages/fsengagement/src/inboxblocks/EventBlock.tsx new file mode 100644 index 0000000000..b37ea1e7a5 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/EventBlock.tsx @@ -0,0 +1,136 @@ +import React, { PureComponent } from 'react'; +import { + StyleProp, + StyleSheet, + TextStyle, + View, + ViewStyle +} from 'react-native'; + +import TextBlock from './TextBlock'; +import ImageBlock from './ImageBlock'; + +const styles = StyleSheet.create({ + eventTitle: { + fontSize: 13, + color: '#000', + fontWeight: 'bold', + marginBottom: 3 + }, + imageContainer: { + width: 25, + alignItems: 'center', + paddingTop: 2 + }, + whenIcon: { + width: 20, + height: 20 + }, + whereIcon: { + width: 16, + height: 22 + }, + whyIcon: { + width: 22, + height: 20 + }, + eventType: { + flexDirection: 'row', + marginBottom: 20 + }, + eventText: { + flex: 1, + marginLeft: 20 + } +}); +export interface EventWhen { + date: string; + time: string; +} +export interface EventInfo { + when: EventWhen; + where: string; + why: string; +} + +export interface EventBlockProps { + textStyle?: StyleProp; + titleStyle?: StyleProp; + containerStyle?: StyleProp; + eventInfo: EventInfo; +} + +const whenIcon = require('../../assets/images/whenIcon.png'); +const whereIcon = require('../../assets/images/whereIcon.png'); +const whyIcon = require('../../assets/images/whyIcon.png'); + +export default class EventBlock extends PureComponent { + render(): JSX.Element { + const { + textStyle, + titleStyle, + containerStyle, + eventInfo: { + when, + where, + why + } + } = this.props; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/EventCard.tsx b/packages/fsengagement/src/inboxblocks/EventCard.tsx new file mode 100644 index 0000000000..955db599b8 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/EventCard.tsx @@ -0,0 +1,145 @@ +import React, { Component } from 'react'; +import { + DeviceEventEmitter, + StyleSheet, + TouchableOpacity, + View +} from 'react-native'; +import PropTypes from 'prop-types'; + +import { + CardProps, + JSON +} from '../types'; + +import TextBlock from './TextBlock'; +import CTABlock from './CTABlock'; +import ImageBlock from './ImageBlock'; + +const styles = StyleSheet.create({ + whenIcon: { + width: 10, + height: 10 + }, + whereIcon: { + width: 8, + height: 11 + }, + eventContainer: { + marginLeft: 50, + paddingLeft: 100 + }, + eventType: { + flexDirection: 'row', + marginVertical: 5 + }, + imageContainer: { + position: 'absolute', + left: 30, + top: 40 + }, + dateRow: { + width: 12, + paddingTop: 2, + alignItems: 'center', + marginRight: 5 + } + +}); + +export interface ComponentProps extends CardProps { + contents: any; +} + +const whenIcon = require('../../assets/images/whenIcon.png'); +const whereIcon = require('../../assets/images/whereIcon.png'); + +export default class EventCard extends Component { + + static childContextTypes: any = { + story: PropTypes.object, + handleStoryAction: PropTypes.func + }; + + getChildContext = () => ({ + story: this.props.story, + handleStoryAction: this.handleStoryAction + }) + + handleStoryAction = (json: JSON) => { + DeviceEventEmitter.emit('viewStory', { + title: this.props.name, + id: this.props.id + }); + this.props.api.logEvent('viewInboxStory', { + messageId: this.props.id + }); + this.props.navigator.push({ + screen: 'LayoutBuilder', + navigatorStyle: { + navBarHidden: true + }, + passProps: { + json, + backButton: true, + name: this.props.name, + id: this.props.id + } + }); + } + onCardPress = (): void => { + const { story, storyGradient } = this.props; + const actionPayload: any = storyGradient ? + { ...story, storyGradient } : { ...story }; + this.handleStoryAction(actionPayload); + } + + render(): JSX.Element { + const { + containerStyle, + contents + } = this.props; + + return ( + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/FeaturedTopCard.tsx b/packages/fsengagement/src/inboxblocks/FeaturedTopCard.tsx new file mode 100644 index 0000000000..db493577cb --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/FeaturedTopCard.tsx @@ -0,0 +1,96 @@ +import React, { Component } from 'react'; +import { + DeviceEventEmitter, + StyleProp, + TextStyle, + TouchableOpacity +} from 'react-native'; +import PropTypes from 'prop-types'; + +import { + EmitterProps, + JSON, + ScreenProps, + StoryGradient +} from '../types'; + +import TextBlock from './TextBlock'; +import CTABlock from './CTABlock'; +import ImageBlock from './ImageBlock'; + +export interface ComponentProps extends ScreenProps, EmitterProps { + containerStyle?: StyleProp; + story?: JSON; + contents: any; + api?: any; + storyGradient?: StoryGradient; +} + +export default class Card extends Component { + + static childContextTypes: any = { + story: PropTypes.object, + handleStoryAction: PropTypes.func + }; + + getChildContext = () => ({ + story: this.props.story, + handleStoryAction: this.handleStoryAction + }) + + handleStoryAction = (json: JSON) => { + DeviceEventEmitter.emit('viewStory', { + title: this.props.name, + id: this.props.id + }); + this.props.api.logEvent('viewInboxStory', { + messageId: this.props.id + }); + this.props.navigator.push({ + screen: 'LayoutBuilder', + navigatorStyle: { + navBarHidden: true + }, + passProps: { + json, + backButton: true, + name: this.props.name, + id: this.props.id + } + }); + } + + onCardPress = (): void => { + const { story, storyGradient } = this.props; + const actionPayload: any = storyGradient ? + { ...story, storyGradient } : { ...story }; + this.handleStoryAction(actionPayload); + } + + render(): JSX.Element { + const { + containerStyle, + contents + } = this.props; + + return ( + + + + + + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/ImageBlock.tsx b/packages/fsengagement/src/inboxblocks/ImageBlock.tsx new file mode 100644 index 0000000000..ee5f118a55 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/ImageBlock.tsx @@ -0,0 +1,87 @@ +import React, { Component } from 'react'; +import { + Dimensions, + Image, + ImageStyle, + ImageURISource, + LayoutChangeEvent, + StyleProp, + View +} from 'react-native'; + +export interface ImageBlockProps { + source: ImageURISource; + resizeMode?: any; + resizeMethod?: any; + ratio?: string; + useRatio?: boolean; + imageStyle?: StyleProp; + containerStyle?: any; +} + +export default class ImageBlock extends Component { + constructor(props: ImageBlockProps) { + super(props); + this.state = { + ratioImageStyle: {} + }; + } + componentDidMount(): void { + this.setState({ + ratioImageStyle: this.findImageRatio() + }); + } + _onLayout = (event: LayoutChangeEvent) => { + const { ratio, useRatio } = this.props; + if (useRatio && ratio) { + this.setState({ + ratioImageStyle: this.findImageRatio() + }); + } + } + findImageRatio = () => { + const { containerStyle, ratio, useRatio } = this.props; + if (!useRatio) { + return {}; + } + const win = Dimensions.get('window'); + const ratioImageStyle: StyleProp = {}; + ratioImageStyle.width = win.width; + if (containerStyle.paddingLeft) { + ratioImageStyle.width = ratioImageStyle.width - containerStyle.paddingLeft; + } + if (containerStyle.marginLeft) { + ratioImageStyle.width = ratioImageStyle.width - containerStyle.marginLeft; + } + if (containerStyle.paddingRight) { + ratioImageStyle.width = ratioImageStyle.width - containerStyle.paddingRight; + } + if (containerStyle.marginRight) { + ratioImageStyle.width = ratioImageStyle.width - containerStyle.marginRight; + } + if (ratio) { + ratioImageStyle.height = ratioImageStyle.width / parseFloat(ratio); + } + return ratioImageStyle; + } + render(): JSX.Element { + const { + imageStyle = {}, + containerStyle, + resizeMode = 'cover', + resizeMethod = 'resize', + source + } = this.props; + + return ( + + + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/ShareBlock.tsx b/packages/fsengagement/src/inboxblocks/ShareBlock.tsx new file mode 100644 index 0000000000..2b9fc3f63d --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/ShareBlock.tsx @@ -0,0 +1,87 @@ +import React, { Component } from 'react'; +import { + Image, + ImageStyle, + Share, + StyleProp, + StyleSheet, + TouchableHighlight, + View, + ViewStyle +} from 'react-native'; + + +export interface ShareBlockProps { + dialogTitle?: string; + message: string; + shareTitle: string; + url: string; + imageSrc?: string; + underlayColor?: string; + imageStyle?: StyleProp; + containerStyle?: StyleProp; +} +const shareIcon = require('../../assets/images/share.png'); +const styles = StyleSheet.create({ + buttonContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + height: 40, + width: 60, + paddingTop: 8, + paddingBottom: 9, + paddingLeft: 20, + paddingRight: 20 + }, + imageStyle: { + width: 19, + height: 23, + resizeMode: 'cover' + } +}); + +export default class ShareBlock extends Component { + onButtonPress = () => { + const { + url, + shareTitle, + message, + dialogTitle + } = this.props; + + Share.share({ + url, + message, + title: shareTitle + }, { + dialogTitle: dialogTitle || '' + }).catch(error => { + if (error) { + console.warn('Error opening sharing: ', error); + } + }); + } + + render(): JSX.Element { + const { + imageSrc, + imageStyle, + underlayColor, + containerStyle + } = this.props; + + const image = imageSrc ? imageSrc : shareIcon; + + return ( + + + + + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/SimpleCard.tsx b/packages/fsengagement/src/inboxblocks/SimpleCard.tsx new file mode 100644 index 0000000000..114bea111b --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/SimpleCard.tsx @@ -0,0 +1,101 @@ +import React, { Component } from 'react'; +import { + StyleProp, + StyleSheet, + TextStyle, + View +} from 'react-native'; + +import { + InboxBlock +} from '../types'; + +import TextBlock from './TextBlock'; +import CTABlock from './CTABlock'; +import ImageBlock from './ImageBlock'; +import ShareBlock from './ShareBlock'; + +const styles = StyleSheet.create({ + eventContainer: { + flexDirection: 'row', + marginLeft: -50 + }, + eventType: { + flexDirection: 'row', + marginVertical: 5 + }, + eventText: { + flex: 1, + marginLeft: 15 + }, + imageContainer: { + width: 12, + alignItems: 'center' + }, + dateRow: { + width: 12, + paddingTop: 2, + alignItems: 'center', + marginRight: 5 + }, + header: { + flexDirection: 'row', + justifyContent: 'flex-start' + }, + headerInfo: { + marginLeft: 10 + }, + buttonRow: { + flexDirection: 'row' + } +}); + +export interface ComponentProps { + containerStyle?: StyleProp; + story?: InboxBlock; + contents: any; +} + +export default class SimpleCard extends Component { + render(): JSX.Element { + const { + containerStyle, + contents + } = this.props; + + return ( + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/Story.tsx b/packages/fsengagement/src/inboxblocks/Story.tsx new file mode 100644 index 0000000000..d5d20c093d --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/Story.tsx @@ -0,0 +1,20 @@ +import React, { Component, Fragment } from 'react'; +import { + StyleProp, TextStyle +} from 'react-native'; +import { InjectedProps } from '../types'; + +export interface CardProps extends InjectedProps { + containerStyle?: StyleProp; +} + +export default class Story extends Component { + + render(): JSX.Element { + return ( + + {this.props.children} + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/TextBlock.tsx b/packages/fsengagement/src/inboxblocks/TextBlock.tsx new file mode 100644 index 0000000000..259ed3e005 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/TextBlock.tsx @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import { + StyleProp, + StyleSheet, + Text, + TextStyle, + View +} from 'react-native'; + +const styles = StyleSheet.create({ + default: { + color: '#000' + } +}); +export interface TextBlockProps { + text: string; + textStyle?: StyleProp; + containerStyle?: StyleProp; +} +export default class TextBlock extends Component { + render(): JSX.Element { + const { + textStyle, + containerStyle, + text + } = this.props; + + return ( + + {text} + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/TwinCTABlock.tsx b/packages/fsengagement/src/inboxblocks/TwinCTABlock.tsx new file mode 100644 index 0000000000..6fd59c0b09 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/TwinCTABlock.tsx @@ -0,0 +1,59 @@ +import React, { Component } from 'react'; +import { + StyleProp, + StyleSheet, + View, + ViewStyle +} from 'react-native'; + +import { + JSON, + ScreenProps +} from '../types'; + +import CTABlock from './CTABlock'; + +export interface CTABlockProps extends ScreenProps { + story?: JSON; + contents: any; + containerStyle?: StyleProp; + buttonSpacing?: StyleProp; +} + +const styles = StyleSheet.create({ + row: { + flexDirection: 'row', + alignItems: 'center' + }, + item: { + flex: 1 + } +}); +export default class TwinCTABlock extends Component { + render(): JSX.Element { + const { + containerStyle, + contents, + buttonSpacing, + story + } = this.props; + + return ( + + + + + + + + + ); + + } +} diff --git a/packages/fsengagement/src/inboxblocks/VideoBlock.tsx b/packages/fsengagement/src/inboxblocks/VideoBlock.tsx new file mode 100644 index 0000000000..e76c344cf8 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/VideoBlock.tsx @@ -0,0 +1,154 @@ +import React, { Component } from 'react'; +import { + Dimensions, + StyleProp, + StyleSheet, + TextStyle, + TouchableOpacity, + View, + WebView +} from 'react-native'; +import VideoPlayer from 'react-native-video'; +import * as _ from 'lodash-es'; + +export interface VideoSource { + src: string; + ratio?: number; +} +export interface VideoBlockProps { + source: VideoSource; + autoPlay?: boolean; + repeat?: boolean; + resizeMode?: string; + style?: any; + containerStyle?: StyleProp; +} + +export interface StateType { + videoPaused: boolean; + facebookSrc: string; +} + +const styles = StyleSheet.create({ + VideoButton: { + position: 'absolute', + top: 0, + justifyContent: 'center', + alignItems: 'center' + }, + VideoButtonWrapper: { + width: 60, + height: 60, + borderRadius: 40, + backgroundColor: 'rgba(0, 0, 0, 0.66)', + justifyContent: 'center', + alignItems: 'center' + }, + VideoButtonInner: { + borderTopWidth: 12, + borderRightWidth: 0, + borderBottomWidth: 12, + borderLeftWidth: 18, + borderTopColor: 'transparent', + borderRightColor: 'transparent', + borderBottomColor: 'transparent', + borderLeftColor: 'white', + marginLeft: 3 + } +}); + +const DEFAULT_WIDTH = Dimensions.get('window').width; + +export default class VideoBlock extends Component { + constructor(props: any) { + super(props); + this.state = { + videoPaused: false, + facebookSrc: '' + }; + } + + renderVideo = (src: string, size: any) => { + if (~src.indexOf('youtube://') || ~src.indexOf('facebook://')) { + const type = ~src.indexOf('youtube://') ? 'youtube' : 'facebook'; + return this.renderSocial(src, size, type); + } else if (~src.indexOf('http://') || ~src.indexOf('https://')) { + return this.renderHttp(src, size); + } + return false; + } + + renderSocial = (src: string, { width, height }: any, type: string) => { + const socialID = src.replace(`${type}://`, ''); + const iframeUri = type === 'youtube' ? + `https://www.youtube.com/embed/${socialID}` : + `https://www.facebook.com/video/embed?video_id=${socialID}`; + + return ; + } + + checkAutoPlay = (autoPlay: boolean) => () => { + this.setState({ videoPaused: !autoPlay }); + } + + renderHttp = (src: string, { width, height }: any) => { + const { + resizeMode = 'cover', + autoPlay = false, + repeat = false + } = this.props; + + return ( + + + + {this.state.videoPaused && + + + } + + + ); + } + + toggleVideo = () => { + this.setState({ + videoPaused: !this.state.videoPaused + }); + } + + render(): JSX.Element { + const { + source, + style = {}, + containerStyle + } = this.props; + + let height = style.height || 200; + const width = DEFAULT_WIDTH; + + if (source.ratio) { + height = width / source.ratio; + } + + const blockStyle = _.cloneDeep(style); + blockStyle.height = height; + blockStyle.width = width; + + return ( + + {height > 0 ? this.renderVideo(source.src, { width, height }) : null} + + ); + } +} diff --git a/packages/fsengagement/src/inboxblocks/index.tsx b/packages/fsengagement/src/inboxblocks/index.tsx new file mode 100644 index 0000000000..1e0b32ae90 --- /dev/null +++ b/packages/fsengagement/src/inboxblocks/index.tsx @@ -0,0 +1,28 @@ + +import TextBlock from './TextBlock'; +import CTABlock from './CTABlock'; +import ImageBlock from './ImageBlock'; +import Card from './Card'; +import Story from './Story'; + +import TwinCTABlock from './TwinCTABlock'; +import EventBlock from './EventBlock'; +import ShareBlock from './ShareBlock'; +import FeaturedTopCard from './FeaturedTopCard'; +import EventCard from './EventCard'; +import SimpleCard from './SimpleCard'; + + +export default { + Text: TextBlock, + Image: ImageBlock, + CTA: CTABlock, + TwinCTA: TwinCTABlock, + Event: EventBlock, + Card, + FeaturedTopCard, + EventCard, + SimpleCard, + Share: ShareBlock, + story: Story +}; diff --git a/packages/fsengagement/src/index.ts b/packages/fsengagement/src/index.ts new file mode 100644 index 0000000000..a4f923caa0 --- /dev/null +++ b/packages/fsengagement/src/index.ts @@ -0,0 +1,25 @@ +import { ComponentClass } from 'react'; +import { EngagementService, EngagementServiceConfig } from './EngagementService'; +import EngagementComp, { EngagementScreenProps } from './EngagementComp'; +import { ComponentList, InboxBlock, InjectedProps } from './types'; +import layoutComponents from './inboxblocks'; + +export interface EngagementSettings extends EngagementServiceConfig { + components: ComponentList; +} + +export interface EngagementUtilities { + engagementService: EngagementService; + EngagementComp: ComponentClass; +} + +export { InboxBlock, InjectedProps }; + +export default function(params: EngagementSettings): EngagementUtilities { + const api = new EngagementService(params); + + return { + engagementService: api, + EngagementComp: EngagementComp(api, {...layoutComponents, ...params.components}) + }; +} diff --git a/packages/fsengagement/src/types.ts b/packages/fsengagement/src/types.ts new file mode 100644 index 0000000000..b26fb6ef42 --- /dev/null +++ b/packages/fsengagement/src/types.ts @@ -0,0 +1,145 @@ +import { ComponentClass } from 'react'; +import { Notification } from 'react-native-fcm'; +import { + ImageStyle, + StyleProp, + TextStyle +} from 'react-native'; +import { Navigator } from 'react-native-navigation'; + +export interface ScreenProps { + navigator: Navigator; +} + +export interface Action { + type: string; + value: string; + subject?: string; + body?: string; + name?: string; + id?: string; +} + +export interface EmitterProps { + id?: string; + name?: string; +} + +export interface ComponentList { + [key: string]: ComponentClass; +} + +export interface Icon { + type: string; + tintColor?: string; + iconStyle?: StyleProp; +} + +export interface CardProps { + containerStyle?: StyleProp; + private_blocks: BlockItem[]; + story?: JSON; + api?: any; + plainCard?: boolean; + storyGradient?: StoryGradient; + navigator: Navigator; + name?: string; + id?: string; +} + +export interface Empty { + message: string; + textStyle?: StyleProp; +} + +export interface StoryGradient { + enabled: boolean; + startFadePosition?: number; + endFadePosition?: number; +} + +export interface HTML { + body: string; + link: string; + iframe: string; + image: JSON; + title: JSON; +} + +export interface JSON { + isBlog?: boolean; + backArrow?: StyleProp; + private_blocks?: BlockItem[]; + private_type: string; + empty?: Empty; + storyGradient?: StoryGradient; + html?: HTML; +} + +export interface BlockItem extends ScreenProps, JSON { + story?: JSON; +} + +export interface InjectedProps { + messageId: string; + clickHandler: (id: string, story?: any) => void; + key?: any; +} + +export interface InboxBlock extends InjectedProps { + private_type: string; + private_blocks: InboxBlock[]; + containerStyle?: StyleProp; + story?: InboxBlock; +} + +export interface EngagmentEvent { + type: string; + id: string; + data: any; + fired: Date; +} + +export interface EngagmentDevice { + id: string; + identifier: string; + model: string; + appName: string; + appVersion: string; + osName: string; + osVersion: string; + profileId: string; + appId: string; + pushToken?: string; +} + +export interface EngagementMessage { + id: string; + published: Date; + message: any; + title: string; + inbox: string; + attributes: any; +} + +export interface EngagmentNotification extends Notification { + messageId?: string; + future?: boolean; + on?: string; + body?: string; + title?: string; +} + +export interface EngagementProfile { + id: string; + created: Date; + modified: Date; + + attributes: { + [key: string]: string; + }; + + devices: { + [deviceID: string]: EngagmentDevice; + }; +} diff --git a/packages/fsengagement/tsconfig.json b/packages/fsengagement/tsconfig.json new file mode 120000 index 0000000000..6fdd525f36 --- /dev/null +++ b/packages/fsengagement/tsconfig.json @@ -0,0 +1 @@ +../../tsconfig/tsconfig.build.json \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 31a41ac0f8..7e4b40f1dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1601,6 +1601,10 @@ version "0.0.29" resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c" +"@types/prop-types@15.5.3": + version "15.5.3" + resolved "http://npm.in.bbhosted.com/@types%2fprop-types/-/prop-types-15.5.3.tgz#bef071852dca2a2dbb65fecdb7bfb30cedae2de2" + "@types/qs@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.1.tgz#a38f69c62528d56ba7bd1f91335a8004988d72f7" @@ -1636,6 +1640,13 @@ version "4.0.0" resolved "https://registry.yarnpkg.com/@types/react-native-touch-id/-/react-native-touch-id-4.0.0.tgz#ddabbac27d40eabe0fe6b798b5a4333e7be294ba" +"@types/react-native-video@^2.0.8": + version "2.0.8" + resolved "http://npm.in.bbhosted.com/@types%2freact-native-video/-/react-native-video-2.0.8.tgz#cbb9fe015f6573d2bd2aef2090803dde76fb8e4c" + dependencies: + "@types/react" "*" + "@types/react-native" "*" + "@types/react-native@*", "@types/react-native@^0.55.17": version "0.55.26" resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.55.26.tgz#a7150ca15e0de7e435cc66a1ca44f6b506c99e40" @@ -7932,6 +7943,10 @@ keygrip@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91" +keymirror@0.1.1: + version "0.1.1" + resolved "http://npm.in.bbhosted.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" + killable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" @@ -10601,10 +10616,18 @@ react-native-cookies@^3.3.0: dependencies: invariant "^2.1.0" +react-native-device-info@^0.21.5: + version "0.21.5" + resolved "http://npm.in.bbhosted.com/react-native-device-info/-/react-native-device-info-0.21.5.tgz#99478a2d68182e012297f2d63f2bd1b788106dee" + react-native-device-info@^0.22.0: version "0.22.2" resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-0.22.2.tgz#ff2829ebe40d75be1d3b6560f0cd48e794a5a287" +react-native-fcm@16.0.0: + version "16.0.0" + resolved "http://npm.in.bbhosted.com/react-native-fcm/-/react-native-fcm-16.0.0.tgz#22ff9de21a2fe5cb30f8d50b1a3b4bf3bccd6434" + react-native-geocoder@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/react-native-geocoder/-/react-native-geocoder-0.5.0.tgz#ff4b9c55d5768a4784eefccb411761c82e067579" @@ -10694,6 +10717,13 @@ react-native-touch-id@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/react-native-touch-id/-/react-native-touch-id-4.0.2.tgz#80957198c43c4fcbfcaf4097bc6d409d42c154c2" +react-native-video@^3.2.1: + version "3.2.1" + resolved "http://npm.in.bbhosted.com/react-native-video/-/react-native-video-3.2.1.tgz#7ba3cbdfc5c747b5b00452811e98193824b16ce3" + dependencies: + keymirror "0.1.1" + prop-types "^15.5.10" + react-native-web-image-loader@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/react-native-web-image-loader/-/react-native-web-image-loader-0.0.6.tgz#4c8a6d100838b7b975de05bd27f90fc09e71c247" @@ -12899,6 +12929,10 @@ utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" +uuid-js@0.7.5: + version "0.7.5" + resolved "http://npm.in.bbhosted.com/uuid-js/-/uuid-js-0.7.5.tgz#6c886d02a53d2d40dcf25d91a170b4a7b25b94d0" + uuid@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"