diff options
author | Ivan Chen <ivan@tagg.id> | 2021-05-07 16:01:47 -0400 |
---|---|---|
committer | Ivan Chen <ivan@tagg.id> | 2021-05-07 16:01:47 -0400 |
commit | 76bc8c5825f39257be6e7648d12b858f1e805569 (patch) | |
tree | b94d9570439ebfa42e6664144f124abe5d4113e3 | |
parent | 65c7411f4609edac3d4d5f23fc031ed274fc5872 (diff) | |
parent | c9d32e68fbb9d1bc175722bfda49454a6f627eae (diff) |
Merge branch 'master' into tma821-load-badges-faster-ft
# Conflicts:
# src/utils/users.ts
46 files changed, 1001 insertions, 650 deletions
diff --git a/ios/Frontend.xcodeproj/project.pbxproj b/ios/Frontend.xcodeproj/project.pbxproj index 4cd4db33..aa86fb3d 100644 --- a/ios/Frontend.xcodeproj/project.pbxproj +++ b/ios/Frontend.xcodeproj/project.pbxproj @@ -12,16 +12,16 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 1E7AD3FE86DF4BD0A9C17980 /* libPods-Frontend-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F8E4573A72CA8220DAA9AF5 /* libPods-Frontend-tvOSTests.a */; }; - 28153F30DD8A96545B6DDE3D /* libPods-Frontend-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CAFA98ACCF23BEB39AD0A53 /* libPods-Frontend-tvOS.a */; }; + 162320D6BACFC1F441EC22C1 /* libPods-Frontend.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 032770E196D0B343FBF58097 /* libPods-Frontend.a */; }; 2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 2DCD954D1E0B4F2C00145EB5 /* FrontendTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* FrontendTests.m */; }; - 493A42D1B1923CF79BDBA497 /* libPods-Frontend-FrontendTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F9583A4B15B8BC744CC480BD /* libPods-Frontend-FrontendTests.a */; }; + 8CFABFFB75409DE440B1F946 /* libPods-Frontend-FrontendTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C8C01B1DA113E97C596E47C /* libPods-Frontend-FrontendTests.a */; }; + B6D41FC4D095A2EF7C3277A9 /* libPods-Frontend-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F8C4537388D0708785A4770E /* libPods-Frontend-tvOS.a */; }; B7B3F698256E07C400EF4575 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B7B3F697256E07C400EF4575 /* GoogleService-Info.plist */; }; B7B3F699256E07C400EF4575 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B7B3F697256E07C400EF4575 /* GoogleService-Info.plist */; }; - FE86B674D4BDCF5024DDFC4A /* libPods-Frontend.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A25FA993A8B7865AC123945B /* libPods-Frontend.a */; }; + E6C87B035E8221C50D635E8F /* libPods-Frontend-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1AC3226BEAF24D06C41E5569 /* libPods-Frontend-tvOSTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -46,6 +46,7 @@ 00E356EE1AD99517003FC87E /* FrontendTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FrontendTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 00E356F21AD99517003FC87E /* FrontendTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FrontendTests.m; sourceTree = "<group>"; }; + 032770E196D0B343FBF58097 /* libPods-Frontend.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* Frontend.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Frontend.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Frontend/AppDelegate.h; sourceTree = "<group>"; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Frontend/AppDelegate.m; sourceTree = "<group>"; }; @@ -53,25 +54,24 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Frontend/Images.xcassets; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Frontend/Info.plist; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Frontend/main.m; sourceTree = "<group>"; }; + 1AC3226BEAF24D06C41E5569 /* libPods-Frontend-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E47B1E0B4A5D006451C7 /* Frontend-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Frontend-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E4901E0B4A5D006451C7 /* Frontend-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Frontend-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 545F2AEA87DB571518CCFA32 /* Pods-Frontend.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend.debug.xcconfig"; path = "Target Support Files/Pods-Frontend/Pods-Frontend.debug.xcconfig"; sourceTree = "<group>"; }; - 567A4E39A63C7DE8FD446FC2 /* Pods-Frontend-FrontendTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-FrontendTests.release.xcconfig"; path = "Target Support Files/Pods-Frontend-FrontendTests/Pods-Frontend-FrontendTests.release.xcconfig"; sourceTree = "<group>"; }; - 602415FF24B9FF3F87744F70 /* Pods-Frontend-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOSTests/Pods-Frontend-tvOSTests.debug.xcconfig"; sourceTree = "<group>"; }; + 4633B8D52C20A00C958034E6 /* Pods-Frontend-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOSTests/Pods-Frontend-tvOSTests.release.xcconfig"; sourceTree = "<group>"; }; + 4C153C13952A77003E919731 /* Pods-Frontend-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOS.release.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOS/Pods-Frontend-tvOS.release.xcconfig"; sourceTree = "<group>"; }; 69C3C9D324E1B851003075D8 /* Feather.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = "<group>"; }; - 6CAFA98ACCF23BEB39AD0A53 /* libPods-Frontend-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 9F8E4573A72CA8220DAA9AF5 /* libPods-Frontend-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - A25FA993A8B7865AC123945B /* libPods-Frontend.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - A967DF75AA8AD56D8D15B597 /* Pods-Frontend-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOS.release.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOS/Pods-Frontend-tvOS.release.xcconfig"; sourceTree = "<group>"; }; + 6C8C01B1DA113E97C596E47C /* libPods-Frontend-FrontendTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend-FrontendTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 82C6ED904DE229CD9C824BE9 /* Pods-Frontend.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend.release.xcconfig"; path = "Target Support Files/Pods-Frontend/Pods-Frontend.release.xcconfig"; sourceTree = "<group>"; }; + 9441D0AF6446AB2B76D53150 /* Pods-Frontend-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOSTests/Pods-Frontend-tvOSTests.debug.xcconfig"; sourceTree = "<group>"; }; + A8F8B1059699DFE5975BAB3B /* Pods-Frontend-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOS/Pods-Frontend-tvOS.debug.xcconfig"; sourceTree = "<group>"; }; B7B3F697256E07C400EF4575 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; B7D7DF1925880C7800C18E93 /* FrontendDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = FrontendDebug.entitlements; path = Frontend/FrontendDebug.entitlements; sourceTree = "<group>"; }; - C43E63ACF638693AFCCCEFD3 /* Pods-Frontend-FrontendTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-FrontendTests.debug.xcconfig"; path = "Target Support Files/Pods-Frontend-FrontendTests/Pods-Frontend-FrontendTests.debug.xcconfig"; sourceTree = "<group>"; }; - C7E3BB8A63613F3856CDAFF6 /* Pods-Frontend-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOSTests/Pods-Frontend-tvOSTests.release.xcconfig"; sourceTree = "<group>"; }; - CCC6F52EEBC98F07FC072D7F /* Pods-Frontend.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend.release.xcconfig"; path = "Target Support Files/Pods-Frontend/Pods-Frontend.release.xcconfig"; sourceTree = "<group>"; }; + B848841B31EAA032CEAC624B /* Pods-Frontend-FrontendTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-FrontendTests.release.xcconfig"; path = "Target Support Files/Pods-Frontend-FrontendTests/Pods-Frontend-FrontendTests.release.xcconfig"; sourceTree = "<group>"; }; + BA3B1C20B549B4C3DD9BDEFF /* Pods-Frontend.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend.debug.xcconfig"; path = "Target Support Files/Pods-Frontend/Pods-Frontend.debug.xcconfig"; sourceTree = "<group>"; }; + D6233E676C26104C225F994F /* Pods-Frontend-FrontendTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-FrontendTests.debug.xcconfig"; path = "Target Support Files/Pods-Frontend-FrontendTests/Pods-Frontend-FrontendTests.debug.xcconfig"; sourceTree = "<group>"; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; - F275760F243CFBFE245CDDC8 /* Pods-Frontend-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Frontend-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-Frontend-tvOS/Pods-Frontend-tvOS.debug.xcconfig"; sourceTree = "<group>"; }; - F9583A4B15B8BC744CC480BD /* libPods-Frontend-FrontendTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend-FrontendTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F8C4537388D0708785A4770E /* libPods-Frontend-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Frontend-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -79,7 +79,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 493A42D1B1923CF79BDBA497 /* libPods-Frontend-FrontendTests.a in Frameworks */, + 8CFABFFB75409DE440B1F946 /* libPods-Frontend-FrontendTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -87,7 +87,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FE86B674D4BDCF5024DDFC4A /* libPods-Frontend.a in Frameworks */, + 162320D6BACFC1F441EC22C1 /* libPods-Frontend.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -95,7 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 28153F30DD8A96545B6DDE3D /* libPods-Frontend-tvOS.a in Frameworks */, + B6D41FC4D095A2EF7C3277A9 /* libPods-Frontend-tvOS.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -103,7 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1E7AD3FE86DF4BD0A9C17980 /* libPods-Frontend-tvOSTests.a in Frameworks */, + E6C87B035E8221C50D635E8F /* libPods-Frontend-tvOSTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -149,10 +149,10 @@ children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, - A25FA993A8B7865AC123945B /* libPods-Frontend.a */, - F9583A4B15B8BC744CC480BD /* libPods-Frontend-FrontendTests.a */, - 6CAFA98ACCF23BEB39AD0A53 /* libPods-Frontend-tvOS.a */, - 9F8E4573A72CA8220DAA9AF5 /* libPods-Frontend-tvOSTests.a */, + 032770E196D0B343FBF58097 /* libPods-Frontend.a */, + 6C8C01B1DA113E97C596E47C /* libPods-Frontend-FrontendTests.a */, + F8C4537388D0708785A4770E /* libPods-Frontend-tvOS.a */, + 1AC3226BEAF24D06C41E5569 /* libPods-Frontend-tvOSTests.a */, ); name = Frameworks; sourceTree = "<group>"; @@ -201,14 +201,14 @@ DC1ED7ABEFF4D056945504EA /* Pods */ = { isa = PBXGroup; children = ( - 545F2AEA87DB571518CCFA32 /* Pods-Frontend.debug.xcconfig */, - CCC6F52EEBC98F07FC072D7F /* Pods-Frontend.release.xcconfig */, - C43E63ACF638693AFCCCEFD3 /* Pods-Frontend-FrontendTests.debug.xcconfig */, - 567A4E39A63C7DE8FD446FC2 /* Pods-Frontend-FrontendTests.release.xcconfig */, - F275760F243CFBFE245CDDC8 /* Pods-Frontend-tvOS.debug.xcconfig */, - A967DF75AA8AD56D8D15B597 /* Pods-Frontend-tvOS.release.xcconfig */, - 602415FF24B9FF3F87744F70 /* Pods-Frontend-tvOSTests.debug.xcconfig */, - C7E3BB8A63613F3856CDAFF6 /* Pods-Frontend-tvOSTests.release.xcconfig */, + BA3B1C20B549B4C3DD9BDEFF /* Pods-Frontend.debug.xcconfig */, + 82C6ED904DE229CD9C824BE9 /* Pods-Frontend.release.xcconfig */, + D6233E676C26104C225F994F /* Pods-Frontend-FrontendTests.debug.xcconfig */, + B848841B31EAA032CEAC624B /* Pods-Frontend-FrontendTests.release.xcconfig */, + A8F8B1059699DFE5975BAB3B /* Pods-Frontend-tvOS.debug.xcconfig */, + 4C153C13952A77003E919731 /* Pods-Frontend-tvOS.release.xcconfig */, + 9441D0AF6446AB2B76D53150 /* Pods-Frontend-tvOSTests.debug.xcconfig */, + 4633B8D52C20A00C958034E6 /* Pods-Frontend-tvOSTests.release.xcconfig */, ); path = Pods; sourceTree = "<group>"; @@ -220,11 +220,12 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "FrontendTests" */; buildPhases = ( - 28B2F0B3018A6952F17878CA /* [CP] Check Pods Manifest.lock */, + A79A7EF8439F90A801249EBE /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - 6052DFFE4C25A50B464D7EDD /* [CP] Copy Pods Resources */, + AD3F50D8788DFD331D4AEF7D /* [CP] Embed Pods Frameworks */, + 8246E283BC1298AD621D00BC /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -240,14 +241,15 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Frontend" */; buildPhases = ( - 55E11A8968CEA26E8DB0C49C /* [CP] Check Pods Manifest.lock */, + 0BFB58C1355276095725E674 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 5F19A5045F2557A3A7A71AA2 /* [CP] Copy Pods Resources */, - 4136E2305F8E25C7C69F8EEF /* [CP-User] [RNFB] Core Configuration */, + 901AC37769F9C0ACE97F2ECF /* [CP] Embed Pods Frameworks */, + 40BC5C221AB5093182A8B9DE /* [CP] Copy Pods Resources */, + 5A5C99B7D995797D0BBC1D92 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -262,7 +264,7 @@ isa = PBXNativeTarget; buildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "Frontend-tvOS" */; buildPhases = ( - 7BCEB0E5F3DB51ADCE257331 /* [CP] Check Pods Manifest.lock */, + 8933A0166DD24089BC76D98F /* [CP] Check Pods Manifest.lock */, FD10A7F122414F3F0027D42C /* Start Packager */, 2D02E4771E0B4A5D006451C7 /* Sources */, 2D02E4781E0B4A5D006451C7 /* Frameworks */, @@ -282,7 +284,7 @@ isa = PBXNativeTarget; buildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "Frontend-tvOSTests" */; buildPhases = ( - 5369A53AD507805BDB117490 /* [CP] Check Pods Manifest.lock */, + C2CBCAC2303D8CE511B1BD24 /* [CP] Check Pods Manifest.lock */, 2D02E48C1E0B4A5D006451C7 /* Sources */, 2D02E48D1E0B4A5D006451C7 /* Frameworks */, 2D02E48E1E0B4A5D006451C7 /* Resources */, @@ -398,7 +400,7 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 28B2F0B3018A6952F17878CA /* [CP] Check Pods Manifest.lock */ = { + 0BFB58C1355276095725E674 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -413,7 +415,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Frontend-FrontendTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Frontend-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -434,61 +436,7 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 4136E2305F8E25C7C69F8EEF /* [CP-User] [RNFB] Core Configuration */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - name = "[CP-User] [RNFB] Core Configuration"; - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\n\n # config.admob_delay_app_measurement_init\n _ADMOB_DELAY_APP_MEASUREMENT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_delay_app_measurement_init\")\n if [[ $_ADMOB_DELAY_APP_MEASUREMENT == \"true\" ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADDelayAppMeasurementInit\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"YES\")\n fi\n\n # config.admob_ios_app_id\n _ADMOB_IOS_APP_ID=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_ios_app_id\")\n if [[ $_ADMOB_IOS_APP_ID ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADApplicationIdentifier\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_ADMOB_IOS_APP_ID\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; - }; - 5369A53AD507805BDB117490 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Frontend-tvOSTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 55E11A8968CEA26E8DB0C49C /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Frontend-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 5F19A5045F2557A3A7A71AA2 /* [CP] Copy Pods Resources */ = { + 40BC5C221AB5093182A8B9DE /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -542,7 +490,17 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Frontend/Pods-Frontend-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 6052DFFE4C25A50B464D7EDD /* [CP] Copy Pods Resources */ = { + 5A5C99B7D995797D0BBC1D92 /* [CP-User] [RNFB] Core Configuration */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] [RNFB] Core Configuration"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\n\n # config.admob_delay_app_measurement_init\n _ADMOB_DELAY_APP_MEASUREMENT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_delay_app_measurement_init\")\n if [[ $_ADMOB_DELAY_APP_MEASUREMENT == \"true\" ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADDelayAppMeasurementInit\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"YES\")\n fi\n\n # config.admob_ios_app_id\n _ADMOB_IOS_APP_ID=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_ios_app_id\")\n if [[ $_ADMOB_IOS_APP_ID ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADApplicationIdentifier\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_ADMOB_IOS_APP_ID\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + }; + 8246E283BC1298AD621D00BC /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -596,7 +554,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Frontend-FrontendTests/Pods-Frontend-FrontendTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 7BCEB0E5F3DB51ADCE257331 /* [CP] Check Pods Manifest.lock */ = { + 8933A0166DD24089BC76D98F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -618,6 +576,86 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 901AC37769F9C0ACE97F2ECF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Frontend/Pods-Frontend-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Frontend/Pods-Frontend-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A79A7EF8439F90A801249EBE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Frontend-FrontendTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + AD3F50D8788DFD331D4AEF7D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Frontend-FrontendTests/Pods-Frontend-FrontendTests-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Frontend-FrontendTests/Pods-Frontend-FrontendTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C2CBCAC2303D8CE511B1BD24 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Frontend-tvOSTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -723,7 +761,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C43E63ACF638693AFCCCEFD3 /* Pods-Frontend-FrontendTests.debug.xcconfig */; + baseConfigurationReference = D6233E676C26104C225F994F /* Pods-Frontend-FrontendTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -748,7 +786,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 567A4E39A63C7DE8FD446FC2 /* Pods-Frontend-FrontendTests.release.xcconfig */; + baseConfigurationReference = B848841B31EAA032CEAC624B /* Pods-Frontend-FrontendTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -770,7 +808,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 545F2AEA87DB571518CCFA32 /* Pods-Frontend.debug.xcconfig */; + baseConfigurationReference = BA3B1C20B549B4C3DD9BDEFF /* Pods-Frontend.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -803,7 +841,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CCC6F52EEBC98F07FC072D7F /* Pods-Frontend.release.xcconfig */; + baseConfigurationReference = 82C6ED904DE229CD9C824BE9 /* Pods-Frontend.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -829,7 +867,7 @@ }; 2D02E4971E0B4A5E006451C7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F275760F243CFBFE245CDDC8 /* Pods-Frontend-tvOS.debug.xcconfig */; + baseConfigurationReference = A8F8B1059699DFE5975BAB3B /* Pods-Frontend-tvOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; @@ -857,7 +895,7 @@ }; 2D02E4981E0B4A5E006451C7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A967DF75AA8AD56D8D15B597 /* Pods-Frontend-tvOS.release.xcconfig */; + baseConfigurationReference = 4C153C13952A77003E919731 /* Pods-Frontend-tvOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; @@ -885,7 +923,7 @@ }; 2D02E4991E0B4A5E006451C7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 602415FF24B9FF3F87744F70 /* Pods-Frontend-tvOSTests.debug.xcconfig */; + baseConfigurationReference = 9441D0AF6446AB2B76D53150 /* Pods-Frontend-tvOSTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -912,7 +950,7 @@ }; 2D02E49A1E0B4A5E006451C7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C7E3BB8A63613F3856CDAFF6 /* Pods-Frontend-tvOSTests.release.xcconfig */; + baseConfigurationReference = 4633B8D52C20A00C958034E6 /* Pods-Frontend-tvOSTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; diff --git a/ios/Podfile b/ios/Podfile index 73187053..4eca4100 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -15,7 +15,7 @@ target 'Frontend' do # An issue in flipper-folly podspec caused failure in building. # Using an older version of flipper folly for that purpose. # https://github.com/facebook/react-native/issues/30836 - use_flipper!({ 'Flipper-Folly' => '2.3.0' }) + use_flipper!({ 'Flipper-Folly' => '2.5.3', 'Flipper' => '0.87.0', 'Flipper-RSocket' => '1.3.1' }) post_install do |installer| flipper_post_install(installer) end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 961d650a..883e3552 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,7 +3,6 @@ PODS: - BVLinearGradient (2.5.6): - React - CocoaAsyncSocket (7.6.5) - - CocoaLibEvent (1.0.0) - DoubleConversion (1.1.6) - FBLazyVector (0.63.3) - FBReactNativeSpec (0.63.3): @@ -36,17 +35,17 @@ PODS: - FirebaseCoreDiagnostics (~> 7.4) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/Logger (~> 7.0) - - FirebaseCoreDiagnostics (7.7.0): + - FirebaseCoreDiagnostics (7.9.0): - GoogleDataTransport (~> 8.0) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/Logger (~> 7.0) - nanopb (~> 2.30907.0) - - FirebaseInstallations (7.7.0): + - FirebaseInstallations (7.11.0): - FirebaseCore (~> 7.0) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/UserDefaults (~> 7.0) - PromisesObjC (~> 1.2) - - FirebaseInstanceID (7.7.0): + - FirebaseInstanceID (7.11.0): - FirebaseCore (~> 7.0) - FirebaseInstallations (~> 7.0) - GoogleUtilities/Environment (~> 7.0) @@ -58,50 +57,61 @@ PODS: - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/Reachability (~> 7.0) - GoogleUtilities/UserDefaults (~> 7.0) - - Flipper (0.54.0): - - Flipper-Folly (~> 2.2) - - Flipper-RSocket (~> 1.1) + - Flipper (0.87.0): + - Flipper-Folly (~> 2.5) + - Flipper-RSocket (~> 1.3) - Flipper-DoubleConversion (1.1.7) - - Flipper-Folly (2.3.0): + - Flipper-Folly (2.5.3): - boost-for-react-native - - CocoaLibEvent (~> 1.0) - Flipper-DoubleConversion - Flipper-Glog - - OpenSSL-Universal (= 1.0.2.20) + - libevent (~> 2.1.12) + - OpenSSL-Universal (= 1.1.180) - Flipper-Glog (0.3.6) - Flipper-PeerTalk (0.0.4) - - Flipper-RSocket (1.1.0): - - Flipper-Folly (~> 2.2) - - FlipperKit (0.54.0): - - FlipperKit/Core (= 0.54.0) - - FlipperKit/Core (0.54.0): - - Flipper (~> 0.54.0) + - Flipper-RSocket (1.3.1): + - Flipper-Folly (~> 2.5) + - FlipperKit (0.87.0): + - FlipperKit/Core (= 0.87.0) + - FlipperKit/Core (0.87.0): + - Flipper (~> 0.87.0) - FlipperKit/CppBridge - FlipperKit/FBCxxFollyDynamicConvert - FlipperKit/FBDefines - FlipperKit/FKPortForwarding - - FlipperKit/CppBridge (0.54.0): - - Flipper (~> 0.54.0) - - FlipperKit/FBCxxFollyDynamicConvert (0.54.0): - - Flipper-Folly (~> 2.2) - - FlipperKit/FBDefines (0.54.0) - - FlipperKit/FKPortForwarding (0.54.0): + - FlipperKit/CppBridge (0.87.0): + - Flipper (~> 0.87.0) + - FlipperKit/FBCxxFollyDynamicConvert (0.87.0): + - Flipper-Folly (~> 2.5) + - FlipperKit/FBDefines (0.87.0) + - FlipperKit/FKPortForwarding (0.87.0): - CocoaAsyncSocket (~> 7.6) - Flipper-PeerTalk (~> 0.0.4) - - FlipperKit/FlipperKitHighlightOverlay (0.54.0) - - FlipperKit/FlipperKitLayoutPlugin (0.54.0): + - FlipperKit/FlipperKitHighlightOverlay (0.87.0) + - FlipperKit/FlipperKitLayoutHelpers (0.87.0): - FlipperKit/Core - FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitLayoutTextSearchable + - FlipperKit/FlipperKitLayoutIOSDescriptors (0.87.0): + - FlipperKit/Core + - FlipperKit/FlipperKitHighlightOverlay + - FlipperKit/FlipperKitLayoutHelpers - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutTextSearchable (0.54.0) - - FlipperKit/FlipperKitNetworkPlugin (0.54.0): + - FlipperKit/FlipperKitLayoutPlugin (0.87.0): - FlipperKit/Core - - FlipperKit/FlipperKitReactPlugin (0.54.0): + - FlipperKit/FlipperKitHighlightOverlay + - FlipperKit/FlipperKitLayoutHelpers + - FlipperKit/FlipperKitLayoutIOSDescriptors + - FlipperKit/FlipperKitLayoutTextSearchable + - YogaKit (~> 1.18) + - FlipperKit/FlipperKitLayoutTextSearchable (0.87.0) + - FlipperKit/FlipperKitNetworkPlugin (0.87.0): + - FlipperKit/Core + - FlipperKit/FlipperKitReactPlugin (0.87.0): - FlipperKit/Core - - FlipperKit/FlipperKitUserDefaultsPlugin (0.54.0): + - FlipperKit/FlipperKitUserDefaultsPlugin (0.87.0): - FlipperKit/Core - - FlipperKit/SKIOSNetworkPlugin (0.54.0): + - FlipperKit/SKIOSNetworkPlugin (0.87.0): - FlipperKit/Core - FlipperKit/FlipperKitNetworkPlugin - Folly (2020.01.13.00): @@ -120,35 +130,36 @@ PODS: - GoogleUtilities/Network (~> 7.0) - "GoogleUtilities/NSData+zlib (~> 7.0)" - nanopb (~> 2.30907.0) - - GoogleDataTransport (8.2.0): + - GoogleDataTransport (8.3.1): + - GoogleUtilities/Environment (~> 7.2) - nanopb (~> 2.30907.0) - - GoogleUtilities/AppDelegateSwizzler (7.2.2): + - PromisesObjC (~> 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.4.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.2.2): + - GoogleUtilities/Environment (7.4.0): - PromisesObjC (~> 1.2) - - GoogleUtilities/Logger (7.2.2): + - GoogleUtilities/Logger (7.4.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.2.2): + - GoogleUtilities/MethodSwizzler (7.4.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.2.2): + - GoogleUtilities/Network (7.4.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.2.2)" - - GoogleUtilities/Reachability (7.2.2): + - "GoogleUtilities/NSData+zlib (7.4.0)" + - GoogleUtilities/Reachability (7.4.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.2.2): + - GoogleUtilities/UserDefaults (7.4.0): - GoogleUtilities/Logger + - libevent (2.1.12) - nanopb (2.30907.0): - nanopb/decode (= 2.30907.0) - nanopb/encode (= 2.30907.0) - nanopb/decode (2.30907.0) - nanopb/encode (2.30907.0) - - OpenSSL-Universal (1.0.2.20): - - OpenSSL-Universal/Static (= 1.0.2.20) - - OpenSSL-Universal/Static (1.0.2.20) + - OpenSSL-Universal (1.1.180) - PromisesObjC (1.2.12) - RCTRequired (0.63.3) - RCTTypeSafety (0.63.3): @@ -467,7 +478,7 @@ PODS: - React - RNVectorIcons (7.1.0): - React - - TOCropViewController (2.5.5) + - TOCropViewController (2.6.0) - Yoga (1.14.0) - YogaKit (1.18.1): - Yoga (~> 1.14) @@ -478,25 +489,25 @@ DEPENDENCIES: - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`) - Firebase/Analytics - - Flipper (~> 0.54.0) + - Flipper (= 0.87.0) - Flipper-DoubleConversion (= 1.1.7) - - Flipper-Folly (= 2.3.0) + - Flipper-Folly (= 2.5.3) - Flipper-Glog (= 0.3.6) - Flipper-PeerTalk (~> 0.0.4) - - Flipper-RSocket (~> 1.1) - - FlipperKit (~> 0.54.0) - - FlipperKit/Core (~> 0.54.0) - - FlipperKit/CppBridge (~> 0.54.0) - - FlipperKit/FBCxxFollyDynamicConvert (~> 0.54.0) - - FlipperKit/FBDefines (~> 0.54.0) - - FlipperKit/FKPortForwarding (~> 0.54.0) - - FlipperKit/FlipperKitHighlightOverlay (~> 0.54.0) - - FlipperKit/FlipperKitLayoutPlugin (~> 0.54.0) - - FlipperKit/FlipperKitLayoutTextSearchable (~> 0.54.0) - - FlipperKit/FlipperKitNetworkPlugin (~> 0.54.0) - - FlipperKit/FlipperKitReactPlugin (~> 0.54.0) - - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.54.0) - - FlipperKit/SKIOSNetworkPlugin (~> 0.54.0) + - Flipper-RSocket (= 1.3.1) + - FlipperKit (= 0.87.0) + - FlipperKit/Core (= 0.87.0) + - FlipperKit/CppBridge (= 0.87.0) + - FlipperKit/FBCxxFollyDynamicConvert (= 0.87.0) + - FlipperKit/FBDefines (= 0.87.0) + - FlipperKit/FKPortForwarding (= 0.87.0) + - FlipperKit/FlipperKitHighlightOverlay (= 0.87.0) + - FlipperKit/FlipperKitLayoutPlugin (= 0.87.0) + - FlipperKit/FlipperKitLayoutTextSearchable (= 0.87.0) + - FlipperKit/FlipperKitNetworkPlugin (= 0.87.0) + - FlipperKit/FlipperKitReactPlugin (= 0.87.0) + - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.87.0) + - FlipperKit/SKIOSNetworkPlugin (= 0.87.0) - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) @@ -553,7 +564,6 @@ SPEC REPOS: trunk: - boost-for-react-native - CocoaAsyncSocket - - CocoaLibEvent - Firebase - FirebaseAnalytics - FirebaseCore @@ -571,6 +581,7 @@ SPEC REPOS: - GoogleAppMeasurement - GoogleDataTransport - GoogleUtilities + - libevent - nanopb - OpenSSL-Universal - PromisesObjC @@ -689,31 +700,31 @@ SPEC CHECKSUMS: boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f DoubleConversion: cde416483dac037923206447da6e1454df403714 FBLazyVector: 878b59e31113e289e275165efbe4b54fa614d43d FBReactNativeSpec: 7da9338acfb98d4ef9e5536805a0704572d33c2f Firebase: e1e089d9aac215a52442583f818ab61de3c4581b FirebaseAnalytics: 9f8f4feab1f3fddf4e4515f8f022fe6aa9e51043 FirebaseCore: 0a43b7f1c5b36f3358cd703011ca4c7e0df55870 - FirebaseCoreDiagnostics: 179bf3831213451c8addd036aca7fcf5492ec154 - FirebaseInstallations: 42c86e7b02ff75b7f27f85833bf5dcb5f38a9774 - FirebaseInstanceID: cf940324a20ac14a27ad1e931d15ac9d335526db + FirebaseCoreDiagnostics: 3d36e05da74cb8b7ce30e6594a8f201b982c725c + FirebaseInstallations: a58d4f72ec5861840b84df489f2668d970df558a + FirebaseInstanceID: ad5135045a498d7775903efd39762d2cdfa1be27 FirebaseMessaging: 4b9b2850fcfcaac2820097ee3703ba6cfff3df84 - Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365 + Flipper: 1bd2db48dcc31e4b167b9a33ec1df01c2ded4893 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 - Flipper-Folly: e4493b013c02d9347d5e0cb4d128680239f6c78a + Flipper-Folly: 755929a4f851b2fb2c347d533a23f191b008554c Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 - Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7 - FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d + Flipper-RSocket: 127954abe8b162fcaf68d2134d34dc2bd7076154 + FlipperKit: 651f50a42eb95c01b3e89a60996dd6aded529eeb Folly: b73c3869541e86821df3c387eb0af5f65addfab4 glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3 GoogleAppMeasurement: c542a2feaac9ab98fd074e8f1a02c3585bbfbd47 - GoogleDataTransport: 1024b1a4dfbd7a0e92cb20d7e0a6f1fb66b449a4 - GoogleUtilities: 31c5b01f978a70c6cff2afc6272b3f1921614b43 + GoogleDataTransport: 8b0e733ea77c9218778e5a9e34ba9508b8328939 + GoogleUtilities: 284cddc7fffc14ae1907efb6f78ab95c1fccaedc + libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 nanopb: 59221d7f958fb711001e6a449489542d92ae113e - OpenSSL-Universal: ff34003318d5e1163e9529b08470708e389ffcdd + OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 RCTRequired: 48884c74035a0b5b76dbb7a998bd93bcfc5f2047 RCTTypeSafety: edf4b618033c2f1c5b7bc3d90d8e085ed95ba2ab @@ -761,10 +772,10 @@ SPEC CHECKSUMS: RNShare: 503c37af86611beadccba46156fcc42170c9fb42 RNSVG: ce9d996113475209013317e48b05c21ee988d42e RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59 - TOCropViewController: da59f531f8ac8a94ef6d6c0fc34009350f9e8bfe + TOCropViewController: 3105367e808b7d3d886a74ff59bf4804e7d3ab38 Yoga: 7d13633d129fd179e01b8953d38d47be90db185a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: f676a6cdf5f98f7fdbfb1eec2ba55b39b1887a2c +PODFILE CHECKSUM: e24412577971b52c81c348785bf01cb915155d6d COCOAPODS: 1.10.1 diff --git a/package.json b/package.json index 2612e67f..16932be4 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-native-animatable": "^1.3.3", "react-native-confirmation-code-field": "^6.5.0", "react-native-contacts": "^6.0.4", + "react-native-controlled-mentions": "^2.2.5", "react-native-date-picker": "^3.2.5", "react-native-device-info": "^7.3.1", "react-native-document-picker": "^5.0.3", @@ -101,4 +102,4 @@ "./node_modules/react-native-gesture-handler/jestSetup.js" ] } -} +}
\ No newline at end of file diff --git a/src/assets/images/no_chats.png b/src/assets/images/no_chats.png Binary files differnew file mode 100644 index 00000000..4e321f17 --- /dev/null +++ b/src/assets/images/no_chats.png diff --git a/src/assets/images/no_chats@2x.png b/src/assets/images/no_chats@2x.png Binary files differnew file mode 100644 index 00000000..d906df09 --- /dev/null +++ b/src/assets/images/no_chats@2x.png diff --git a/src/assets/images/no_chats@3x.png b/src/assets/images/no_chats@3x.png Binary files differnew file mode 100644 index 00000000..f0f05c7b --- /dev/null +++ b/src/assets/images/no_chats@3x.png diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 3b195a2b..dd016109 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -1,45 +1,50 @@ -import React, {useEffect, useRef} from 'react'; +import React, {useContext, useEffect, useRef, useState} from 'react'; import { - Image, Keyboard, KeyboardAvoidingView, Platform, StyleSheet, + TextInput, View, } from 'react-native'; -import {TextInput, TouchableOpacity} from 'react-native-gesture-handler'; +import {MentionInput} from 'react-native-controlled-mentions'; +import {TouchableOpacity} from 'react-native-gesture-handler'; import {useDispatch, useSelector} from 'react-redux'; import UpArrowIcon from '../../assets/icons/up_arrow.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; +import {CommentContext} from '../../screens/profile/MomentCommentsScreen'; import {postComment} from '../../services'; import {updateReplyPosted} from '../../store/actions'; import {RootState} from '../../store/rootreducer'; +import {CommentThreadType, CommentType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; - -/** - * This file provides the add comment view for a user. - * Displays the logged in user's profile picture to the left and then provides space to add a comment. - * Comment is posted when enter is pressed as requested by product team. - */ +import {mentionPartTypes} from '../../utils/comments'; +import {Avatar} from '../common'; export interface AddCommentProps { - setNewCommentsAvailable: Function; - objectId: string; + momentId: string; placeholderText: string; - isCommentInFocus: boolean; } -const AddComment: React.FC<AddCommentProps> = ({ - setNewCommentsAvailable, - objectId, - placeholderText, - isCommentInFocus, -}) => { - const [comment, setComment] = React.useState(''); - const [keyboardVisible, setKeyboardVisible] = React.useState(false); - +const AddComment: React.FC<AddCommentProps> = ({momentId, placeholderText}) => { + const {setShouldUpdateAllComments, commentTapped} = useContext( + CommentContext, + ); + const [inReplyToMention, setInReplyToMention] = useState(''); + const [comment, setComment] = useState(''); + const [keyboardVisible, setKeyboardVisible] = useState(false); const {avatar} = useSelector((state: RootState) => state.user); const dispatch = useDispatch(); + const ref = useRef<TextInput>(null); + const isReplyingToComment = + commentTapped !== undefined && !('parent_comment' in commentTapped); + const isReplyingToReply = + commentTapped !== undefined && 'parent_comment' in commentTapped; + const objectId: string = commentTapped + ? 'parent_comment' in commentTapped + ? (commentTapped as CommentThreadType).parent_comment.comment_id + : (commentTapped as CommentType).comment_id + : momentId; const addComment = async () => { const trimmed = comment.trim(); @@ -47,18 +52,19 @@ const AddComment: React.FC<AddCommentProps> = ({ return; } const postedComment = await postComment( - trimmed, + inReplyToMention + trimmed, objectId, - isCommentInFocus, + isReplyingToComment || isReplyingToReply, ); if (postedComment) { setComment(''); + setInReplyToMention(''); //Set new reply posted object //This helps us show the latest reply on top //Data set is kind of stale but it works - if (isCommentInFocus) { + if (isReplyingToComment || isReplyingToReply) { dispatch( updateReplyPosted({ comment_id: postedComment.comment_id, @@ -66,7 +72,7 @@ const AddComment: React.FC<AddCommentProps> = ({ }), ); } - setNewCommentsAvailable(true); + setShouldUpdateAllComments(true); } }; @@ -82,14 +88,18 @@ const AddComment: React.FC<AddCommentProps> = ({ return () => Keyboard.removeListener('keyboardWillHide', hideKeyboard); }, []); - const ref = useRef<TextInput>(null); - - //If a comment is in Focus, bring the keyboard up so user is able to type in a reply useEffect(() => { - if (isCommentInFocus) { + if (isReplyingToComment || isReplyingToReply) { + // bring up keyboard ref.current?.focus(); } - }, [isCommentInFocus]); + if (commentTapped && isReplyingToReply) { + const commenter = (commentTapped as CommentThreadType).commenter; + setInReplyToMention(`@[${commenter.username}](${commenter.id}) `); + } else { + setInReplyToMention(''); + } + }, [isReplyingToComment, isReplyingToReply, commentTapped]); return ( <KeyboardAvoidingView @@ -101,22 +111,19 @@ const AddComment: React.FC<AddCommentProps> = ({ keyboardVisible ? styles.whiteBackround : {}, ]}> <View style={styles.textContainer}> - <Image - style={styles.avatar} - source={ - avatar - ? {uri: avatar} - : require('../../assets/images/avatar-placeholder.png') - } - /> - <TextInput - style={styles.text} + <Avatar style={styles.avatar} uri={avatar} /> + <MentionInput + containerStyle={styles.text} placeholder={placeholderText} - placeholderTextColor="grey" - onChangeText={setComment} - value={comment} - multiline={true} - ref={ref} + value={inReplyToMention + comment} + onChange={(newText: string) => { + // skipping the `inReplyToMention` text + setComment( + newText.substring(inReplyToMention.length, newText.length), + ); + }} + inputRef={ref} + partTypes={mentionPartTypes} /> <View style={styles.submitButton}> <TouchableOpacity style={styles.submitButton} onPress={addComment}> @@ -148,6 +155,7 @@ const styles = StyleSheet.create({ flex: 1, padding: '1%', marginHorizontal: '1%', + maxHeight: 100, }, avatar: { height: 35, diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index 34eef418..ce346af5 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -1,119 +1,114 @@ -/* eslint-disable radix */ -import React, {Fragment, useEffect, useRef, useState} from 'react'; -import {Text, View} from 'react-native-animatable'; -import {ProfilePreview} from '../profile'; -import {CommentType, ScreenType, TypeOfComment} from '../../types'; +import {useNavigation} from '@react-navigation/native'; +import React, {Fragment, useContext, useEffect, useRef, useState} from 'react'; import {Alert, Animated, StyleSheet} from 'react-native'; -import ClockIcon from '../../assets/icons/clock-icon-01.svg'; -import {TAGG_LIGHT_BLUE} from '../../constants'; +import {Text, View} from 'react-native-animatable'; import {RectButton, TouchableOpacity} from 'react-native-gesture-handler'; -import {getTimePosted, normalize, SCREEN_WIDTH} from '../../utils'; +import Swipeable from 'react-native-gesture-handler/Swipeable'; +import {useDispatch, useSelector, useStore} from 'react-redux'; import Arrow from '../../assets/icons/back-arrow-colored.svg'; +import ClockIcon from '../../assets/icons/clock-icon-01.svg'; import Trash from '../../assets/ionicons/trash-outline.svg'; -import CommentsContainer from './CommentsContainer'; -import Swipeable from 'react-native-gesture-handler/Swipeable'; -import {deleteComment, getCommentsCount} from '../../services'; +import {TAGG_LIGHT_BLUE} from '../../constants'; import {ERROR_FAILED_TO_DELETE_COMMENT} from '../../constants/strings'; -import {useSelector} from 'react-redux'; +import {CommentContext} from '../../screens/profile/MomentCommentsScreen'; +import {deleteComment, getCommentsCount} from '../../services'; import {RootState} from '../../store/rootReducer'; +import { + CommentThreadType, + CommentType, + ScreenType, + UserType, +} from '../../types'; +import { + getTimePosted, + navigateToProfile, + normalize, + SCREEN_WIDTH, +} from '../../utils'; +import {renderTextWithMentions} from '../../utils/comments'; +import {ProfilePreview} from '../profile'; +import CommentsContainer from './CommentsContainer'; /** * Displays users's profile picture, comment posted by them and the time difference between now and when a comment was posted. */ interface CommentTileProps { - comment_object: CommentType; + commentObject: CommentType | CommentThreadType; screenType: ScreenType; - typeOfComment: TypeOfComment; - setCommentObjectInFocus?: (comment: CommentType | undefined) => void; - newCommentsAvailable: boolean; - setNewCommentsAvailable: (available: boolean) => void; + isThread: boolean; + shouldUpdateParent: boolean; + setShouldUpdateParent: (update: boolean) => void; canDelete: boolean; } const CommentTile: React.FC<CommentTileProps> = ({ - comment_object, + commentObject, screenType, - typeOfComment, - setCommentObjectInFocus, - newCommentsAvailable, - setNewCommentsAvailable, + setShouldUpdateParent, + shouldUpdateParent, canDelete, + isThread, }) => { - const timePosted = getTimePosted(comment_object.date_created); + const {setCommentTapped} = useContext(CommentContext); + const timePosted = getTimePosted(commentObject.date_created); const [showReplies, setShowReplies] = useState<boolean>(false); const [showKeyboard, setShowKeyboard] = useState<boolean>(false); - const [newThreadAvailable, setNewThreadAvailable] = useState(true); + const [shouldUpdateChild, setShouldUpdateChild] = useState(true); const swipeRef = useRef<Swipeable>(null); - const isThread = typeOfComment === 'Thread'; - const {replyPosted} = useSelector((state: RootState) => state.user); + const state: RootState = useStore().getState(); + const navigation = useNavigation(); + const dispatch = useDispatch(); - /** - * Bubbling up, for handling a new comment in a thread. - */ useEffect(() => { - if (newCommentsAvailable) { - setNewThreadAvailable(true); + if (shouldUpdateParent) { + setShouldUpdateChild(true); } - }, [newCommentsAvailable]); + }, [shouldUpdateParent]); useEffect(() => { - if (replyPosted && typeOfComment === 'Comment') { - if (replyPosted.parent_comment.comment_id === comment_object.comment_id) { + if (replyPosted && !isThread) { + if (replyPosted.parent_comment.comment_id === commentObject.comment_id) { setShowReplies(true); } } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [replyPosted]); - /** - * Case : A COMMENT IS IN FOCUS && REPLY SECTION IS HIDDEN - * Bring the current comment to focus - * Case : No COMMENT IS IN FOCUS && REPLY SECTION IS SHOWN - * Unfocus comment in focus - */ const toggleAddComment = () => { - //Do not allow user to reply to a thread - if (!isThread) { - if (setCommentObjectInFocus) { - if (!showKeyboard) { - setCommentObjectInFocus(comment_object); - } else { - setCommentObjectInFocus(undefined); - } - } - setShowKeyboard(!showKeyboard); - } + setCommentTapped(commentObject); + setShowKeyboard(!showKeyboard); }; const toggleReplies = async () => { - if (showReplies) { + if (showReplies && isThread) { + const comment = (commentObject as CommentThreadType).parent_comment; //To update count of replies in case we deleted a reply - comment_object.replies_count = parseInt( - await getCommentsCount(comment_object.comment_id, true), + comment.replies_count = parseInt( + await getCommentsCount(comment.comment_id, true), + 10, ); } - setNewThreadAvailable(true); + setShouldUpdateChild(true); setShowReplies(!showReplies); }; /** * Method to compute text to be shown for replies button */ - const getRepliesText = () => + const getRepliesText = (comment: CommentType) => showReplies ? 'Hide' - : comment_object.replies_count > 0 - ? `Replies (${comment_object.replies_count})` + : comment.replies_count > 0 + ? `Replies (${comment.replies_count})` : 'Replies'; const renderRightAction = (text: string, color: string) => { const pressHandler = async () => { swipeRef.current?.close(); - const success = await deleteComment(comment_object.comment_id, isThread); + const success = await deleteComment(commentObject.comment_id, isThread); if (success) { - setNewCommentsAvailable(true); + setShouldUpdateParent(true); } else { Alert.alert(ERROR_FAILED_TO_DELETE_COMMENT); } @@ -149,12 +144,17 @@ const CommentTile: React.FC<CommentTileProps> = ({ <View style={[styles.container, isThread ? styles.moreMarginWithThread : {}]}> <ProfilePreview - profilePreview={comment_object.commenter} + profilePreview={commentObject.commenter} previewType={'Comment'} screenType={screenType} /> <TouchableOpacity style={styles.body} onPress={toggleAddComment}> - <Text style={styles.comment}>{comment_object.comment}</Text> + {renderTextWithMentions({ + value: commentObject.comment, + styles: styles.comment, + onPress: (user: UserType) => + navigateToProfile(state, dispatch, navigation, screenType, user), + })} <View style={styles.clockIconAndTime}> <ClockIcon style={styles.clockIcon} /> <Text style={styles.date_time}>{' ' + timePosted}</Text> @@ -162,11 +162,13 @@ const CommentTile: React.FC<CommentTileProps> = ({ </View> </TouchableOpacity> {/*** Show replies text only if there are some replies present */} - {typeOfComment === 'Comment' && comment_object.replies_count > 0 && ( + {!isThread && (commentObject as CommentType).replies_count > 0 && ( <TouchableOpacity style={styles.repliesTextAndIconContainer} onPress={toggleReplies}> - <Text style={styles.repliesText}>{getRepliesText()}</Text> + <Text style={styles.repliesText}> + {getRepliesText(commentObject as CommentType)} + </Text> <Arrow width={12} height={11} @@ -183,12 +185,11 @@ const CommentTile: React.FC<CommentTileProps> = ({ {showReplies && ( <View> <CommentsContainer - objectId={comment_object.comment_id} + objectId={commentObject.comment_id} screenType={screenType} - setNewCommentsAvailable={setNewThreadAvailable} - newCommentsAvailable={newThreadAvailable} - typeOfComment={'Thread'} - commentId={replyPosted?.comment_id} + shouldUpdate={shouldUpdateChild} + setShouldUpdate={setShouldUpdateChild} + isThread={true} /> </View> )} diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx index 3dc8a71c..d5d02a92 100644 --- a/src/components/comments/CommentsContainer.tsx +++ b/src/components/comments/CommentsContainer.tsx @@ -1,24 +1,23 @@ -import React, {useEffect, useRef, useState} from 'react'; +import moment from 'moment'; +import React, {useContext, useEffect, useRef, useState} from 'react'; import {StyleSheet} from 'react-native'; import {FlatList} from 'react-native-gesture-handler'; import {useDispatch, useSelector} from 'react-redux'; -import CommentTile from './CommentTile'; +import {CommentContext} from '../../screens/profile/MomentCommentsScreen'; import {getComments} from '../../services'; import {updateReplyPosted} from '../../store/actions'; import {RootState} from '../../store/rootReducer'; -import {CommentType, ScreenType, TypeOfComment} from '../../types'; +import {CommentThreadType, CommentType, ScreenType} from '../../types'; import {SCREEN_HEIGHT} from '../../utils'; +import CommentTile from './CommentTile'; + export type CommentsContainerProps = { screenType: ScreenType; - //objectId can be either moment_id or comment_id objectId: string; commentId?: string; - setCommentsLength?: (count: number) => void; - newCommentsAvailable: boolean; - setNewCommentsAvailable: (value: boolean) => void; - typeOfComment: TypeOfComment; - setCommentObjectInFocus?: (comment: CommentType | undefined) => void; - commentObjectInFocus?: CommentType; + shouldUpdate: boolean; + setShouldUpdate: (update: boolean) => void; + isThread: boolean; }; /** @@ -28,14 +27,12 @@ export type CommentsContainerProps = { const CommentsContainer: React.FC<CommentsContainerProps> = ({ screenType, objectId, - setCommentsLength, - newCommentsAvailable, - setNewCommentsAvailable, - typeOfComment, - setCommentObjectInFocus, - commentObjectInFocus, + isThread, + shouldUpdate, + setShouldUpdate, commentId, }) => { + const {setCommentsLength, commentTapped} = useContext(CommentContext); const {username: loggedInUsername} = useSelector( (state: RootState) => state.user.user, ); @@ -45,57 +42,30 @@ const CommentsContainer: React.FC<CommentsContainerProps> = ({ useEffect(() => { const loadComments = async () => { - await getComments(objectId, typeOfComment === 'Thread').then( - (comments) => { - if (comments && subscribedToLoadComments) { - setCommentsList(comments); - if (setCommentsLength) { - setCommentsLength(comments.length); - } - setNewCommentsAvailable(false); + await getComments(objectId, isThread).then((comments) => { + if (comments && subscribedToLoadComments) { + setCommentsList(comments); + if (setCommentsLength) { + setCommentsLength(comments.length); } - }, - ); + setShouldUpdate(false); + } + }); }; let subscribedToLoadComments = true; - if (newCommentsAvailable) { + if (shouldUpdate) { loadComments(); } return () => { subscribedToLoadComments = false; }; - }, [ - dispatch, - objectId, - newCommentsAvailable, - setNewCommentsAvailable, - setCommentsLength, - typeOfComment, - ]); - - // eslint-disable-next-line no-shadow - const swapCommentTo = (commentId: string, toIndex: number) => { - const index = commentsList.findIndex( - (item) => item.comment_id === commentId, - ); - if (index > 0) { - let comments = [...commentsList]; - const temp = comments[index]; - comments[index] = comments[toIndex]; - comments[toIndex] = temp; - setCommentsList(comments); - } - }; + }, [shouldUpdate]); useEffect(() => { - //Scroll only if a new comment and not a reply was posted - const shouldScroll = () => - typeOfComment === 'Comment' && !commentObjectInFocus; - const performAction = () => { if (commentId) { swapCommentTo(commentId, 0); - } else if (shouldScroll()) { + } else if (!isThread && !commentTapped) { setTimeout(() => { ref.current?.scrollToEnd({animated: true}); }, 500); @@ -108,41 +78,47 @@ const CommentsContainer: React.FC<CommentsContainerProps> = ({ //Clean up the reply id present in store return () => { - if (commentId && typeOfComment === 'Thread') { + if (commentId && isThread) { setTimeout(() => { dispatch(updateReplyPosted(undefined)); }, 200); } }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [commentsList, commentId]); - //WIP : TODO : Bring the comment in focus above the keyboard - // useEffect(() => { - // if (commentObjectInFocus && commentsList.length >= 3) { - // swapCommentTo(commentObjectInFocus.comment_id, 2); - // } - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [commentObjectInFocus]); + // eslint-disable-next-line no-shadow + const swapCommentTo = (commentId: string, toIndex: number) => { + const index = commentsList.findIndex( + (item) => item.comment_id === commentId, + ); + if (index > 0) { + let comments = [...commentsList]; + const temp = comments[index]; + comments[index] = comments[toIndex]; + comments[toIndex] = temp; + setCommentsList(comments); + } + }; const ITEM_HEIGHT = SCREEN_HEIGHT / 7.0; - const renderComment = ({item}: {item: CommentType}) => ( + const renderComment = ({item}: {item: CommentType | CommentThreadType}) => ( <CommentTile key={item.comment_id} - comment_object={item} + commentObject={item} screenType={screenType} - typeOfComment={typeOfComment} - setCommentObjectInFocus={setCommentObjectInFocus} - newCommentsAvailable={newCommentsAvailable} - setNewCommentsAvailable={setNewCommentsAvailable} + isThread={isThread} + shouldUpdateParent={shouldUpdate} + setShouldUpdateParent={setShouldUpdate} canDelete={item.commenter.username === loggedInUsername} /> ); return ( <FlatList - data={commentsList} + data={commentsList.sort( + (a, b) => moment(a.date_created).unix() - moment(b.date_created).unix(), + )} ref={ref} keyExtractor={(item, index) => index.toString()} decelerationRate={'fast'} diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx new file mode 100644 index 00000000..831cf906 --- /dev/null +++ b/src/components/common/Avatar.tsx @@ -0,0 +1,18 @@ +import React, {FC} from 'react'; +import {Image, ImageStyle, StyleProp} from 'react-native'; + +type AvatarProps = { + style: StyleProp<ImageStyle>; + uri: string | undefined; +}; +const Avatar: FC<AvatarProps> = ({style, uri}) => { + return ( + <Image + style={style} + defaultSource={require('../../assets/images/avatar-placeholder.png')} + source={{uri, cache: 'reload'}} + /> + ); +}; + +export default Avatar; diff --git a/src/components/common/AvatarTitle.tsx b/src/components/common/AvatarTitle.tsx index 81351327..a2a7c0aa 100644 --- a/src/components/common/AvatarTitle.tsx +++ b/src/components/common/AvatarTitle.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import {Image, StyleSheet, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {TAGGS_GRADIENT} from '../../constants'; +import Avatar from './Avatar'; type AvatarTitleProps = { - avatar: string | null; + avatar: string | undefined; }; const AvatarTitle: React.FC<AvatarTitleProps> = ({avatar}) => { return ( @@ -16,14 +17,7 @@ const AvatarTitle: React.FC<AvatarTitleProps> = ({avatar}) => { angleCenter={{x: 0.5, y: 0.5}} style={styles.gradient} /> - <Image - style={styles.avatar} - source={ - avatar - ? {uri: avatar} - : require('../../assets/images/avatar-placeholder.png') - } - /> + <Avatar style={styles.avatar} uri={avatar} /> </View> ); }; diff --git a/src/components/common/EmptyContentView.tsx b/src/components/common/EmptyContentView.tsx new file mode 100644 index 00000000..14ad4af1 --- /dev/null +++ b/src/components/common/EmptyContentView.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import {Image, Text, StyleSheet, View} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import { + UP_TO_DATE, + NO_NEW_NOTIFICATIONS, + FIRST_MESSAGE, + START_CHATTING, +} from '../../constants/strings'; +import {NOTIFICATION_GRADIENT} from '../../constants/constants'; +import {SCREEN_HEIGHT, normalize, SCREEN_WIDTH} from '../../utils'; +import {EmptyViewProps} from '../../types/index'; + +const EmptyContentView: React.FC<EmptyViewProps> = ({viewType}) => { + const _getNotificationImage = () => { + return ( + <LinearGradient + style={styles.backgroundLinearView} + useAngle={true} + angle={180} + colors={NOTIFICATION_GRADIENT}> + <Image + source={require('../../assets/images/empty_notifications.png')} + /> + </LinearGradient> + ); + }; + + const _getChatImage = () => { + return ( + <LinearGradient + style={styles.backgroundLinearView} + useAngle={true} + angle={180} + colors={NOTIFICATION_GRADIENT}> + <Image + style={styles.imageStyles} + source={require('../../assets/images/no_chats.png')} + /> + </LinearGradient> + ); + }; + + const _getImageForType = () => { + switch (viewType) { + case 'Notification': + return _getNotificationImage(); + case 'ChatList': + return _getChatImage(); + } + }; + + const _getTextForNotification = () => { + return ( + <> + <View style={styles.topMargin}> + <Text style={styles.upperTextStyle}>{UP_TO_DATE}</Text> + </View> + <View> + <Text style={styles.bottomTextStyle}>{NO_NEW_NOTIFICATIONS}</Text> + </View> + </> + ); + }; + + const _getTextForChat = () => { + return ( + <View style={styles.chatTextStyles}> + <View style={styles.topMargin}> + <Text style={styles.upperTextStyle}>{START_CHATTING}</Text> + </View> + <View> + <Text style={styles.bottomTextStyle}>{FIRST_MESSAGE}</Text> + </View> + </View> + ); + }; + + const _getTextForType = () => { + switch (viewType) { + case 'Notification': + return _getTextForNotification(); + case 'ChatList': + return _getTextForChat(); + } + }; + + return ( + <View style={styles.container}> + {_getImageForType()} + {_getTextForType()} + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + topMargin: { + marginTop: SCREEN_HEIGHT * 0.025, + paddingBottom: '5%', + }, + upperTextStyle: { + textAlign: 'center', + fontWeight: '700', + fontSize: normalize(23), + lineHeight: normalize(40), + }, + chatTextStyles: { + width: '85%', + }, + bottomTextStyle: { + textAlign: 'center', + color: '#808080', + fontWeight: '600', + fontSize: normalize(20), + lineHeight: normalize(30), + }, + imageStyles: { + width: SCREEN_WIDTH * 0.72, + height: SCREEN_WIDTH * 0.72, + }, + backgroundLinearView: { + borderRadius: (SCREEN_WIDTH * 0.72) / 2, + }, +}); + +export default EmptyContentView; diff --git a/src/components/common/TaggTypeahead.tsx b/src/components/common/TaggTypeahead.tsx new file mode 100644 index 00000000..7cd99278 --- /dev/null +++ b/src/components/common/TaggTypeahead.tsx @@ -0,0 +1,75 @@ +import React, {Fragment, useEffect, useState} from 'react'; +import {ScrollView, StyleSheet} from 'react-native'; +import {MentionSuggestionsProps} from 'react-native-controlled-mentions'; +import {SEARCH_ENDPOINT_MESSAGES} from '../../constants'; +import {loadSearchResults} from '../../services'; +import {ProfilePreviewType} from '../../types'; +import {SCREEN_WIDTH} from '../../utils'; +import TaggUserRowCell from './TaggUserRowCell'; + +const TaggTypeahead: React.FC<MentionSuggestionsProps> = ({ + keyword, + onSuggestionPress, +}) => { + const [results, setResults] = useState<ProfilePreviewType[]>([]); + const [height, setHeight] = useState(0); + + useEffect(() => { + getQuerySuggested(); + }, [keyword]); + + const getQuerySuggested = async () => { + if (!keyword || keyword.length < 3) { + setResults([]); + return; + } + const searchResults = await loadSearchResults( + `${SEARCH_ENDPOINT_MESSAGES}?query=${keyword}`, + ); + if (searchResults && searchResults.users) { + setResults(searchResults.users); + } + }; + + if (results.length === 0) { + return <Fragment />; + } + + return ( + <ScrollView + style={[styles.container, {top: -(height + 30)}]} + showsVerticalScrollIndicator={false} + onLayout={(event) => { + setHeight(event.nativeEvent.layout.height); + }}> + {results.map((user) => ( + <TaggUserRowCell + onPress={() => { + onSuggestionPress({ + id: user.id, + name: user.username, + }); + setResults([]); + }} + user={user} + /> + ))} + </ScrollView> + ); +}; + +const styles = StyleSheet.create({ + container: { + marginLeft: SCREEN_WIDTH * 0.05, + width: SCREEN_WIDTH * 0.9, + maxHeight: 264, + borderRadius: 10, + backgroundColor: 'white', + position: 'absolute', + alignSelf: 'center', + zIndex: 1, + borderWidth: 1, + }, +}); + +export default TaggTypeahead; diff --git a/src/components/common/TaggUserRowCell.tsx b/src/components/common/TaggUserRowCell.tsx new file mode 100644 index 00000000..446dedc9 --- /dev/null +++ b/src/components/common/TaggUserRowCell.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; +import {ProfilePreviewType} from '../../types'; +import {normalize} from '../../utils'; +import Avatar from './Avatar'; + +type TaggUserRowCellProps = { + onPress: () => void; + user: ProfilePreviewType; +}; +const TaggUserRowCell: React.FC<TaggUserRowCellProps> = ({onPress, user}) => { + return ( + <TouchableOpacity onPress={onPress} style={styles.container}> + <Avatar style={styles.image} uri={user.thumbnail_url} /> + <View style={styles.textContent}> + <Text style={styles.username}>{`@${user.username}`}</Text> + <Text style={styles.name}> + {user.first_name} {user.last_name} + </Text> + </View> + </TouchableOpacity> + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + paddingHorizontal: 25, + paddingVertical: 15, + width: '100%', + }, + image: { + width: normalize(30), + height: normalize(30), + borderRadius: 30, + }, + textContent: { + flexDirection: 'column', + justifyContent: 'space-between', + marginLeft: 20, + }, + username: { + fontWeight: '500', + fontSize: normalize(14), + }, + name: { + fontWeight: '500', + fontSize: normalize(12), + color: '#828282', + }, +}); +export default TaggUserRowCell; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 5a601f1d..b38056c6 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -23,3 +23,6 @@ export {default as FriendsButton} from './FriendsButton'; export {default as TaggSquareButton} from './TaggSquareButton'; export {default as GradientBorderButton} from './GradientBorderButton'; export {default as BasicButton} from './BasicButton'; +export {default as Avatar} from './Avatar'; +export {default as TaggTypeahead} from './TaggTypeahead'; +export {default as TaggUserRowCell} from './TaggUserRowCell'; diff --git a/src/components/messages/ChannelPreview.tsx b/src/components/messages/ChannelPreview.tsx index 3d31d42a..878e5a6b 100644 --- a/src/components/messages/ChannelPreview.tsx +++ b/src/components/messages/ChannelPreview.tsx @@ -22,6 +22,7 @@ import { } from '../../types'; import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import {getMember, isOnline} from '../../utils/messages'; +import {Avatar} from '../common'; const ChannelPreview: React.FC< ChannelPreviewMessengerProps< @@ -87,13 +88,9 @@ const ChannelPreview: React.FC< navigation.navigate('Chat'); }}> <View> - <Image + <Avatar style={styles.avatar} - source={ - member - ? {uri: member.user?.thumbnail_url} - : require('../../assets/images/avatar-placeholder.png') - } + uri={member?.user?.thumbnail_url as string} /> {online && <View style={styles.online} />} </View> diff --git a/src/components/messages/ChatHeader.tsx b/src/components/messages/ChatHeader.tsx index 7ddaa7e6..37dab0fd 100644 --- a/src/components/messages/ChatHeader.tsx +++ b/src/components/messages/ChatHeader.tsx @@ -1,6 +1,6 @@ import {useNavigation} from '@react-navigation/native'; import React, {useContext} from 'react'; -import {Image, StyleSheet, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {useDispatch, useStore} from 'react-redux'; @@ -15,6 +15,7 @@ import { userXInStore, } from '../../utils'; import {formatLastSeenText, getMember, isOnline} from '../../utils/messages'; +import {Avatar} from '../common'; type ChatHeaderProps = { screenType: ScreenType; @@ -50,13 +51,9 @@ const ChatHeader: React.FC<ChatHeaderProps> = (props) => { <View style={styles.container}> <TouchableOpacity style={styles.tappables} onPress={toProfile}> <View> - <Image + <Avatar style={styles.avatar} - source={ - member - ? {uri: member.user?.thumbnail_url} - : require('../../assets/images/avatar-placeholder.png') - } + uri={member?.user?.thumbnail_url as string} /> {online && <View style={styles.online} />} </View> diff --git a/src/components/messages/ChatInput.tsx b/src/components/messages/ChatInput.tsx index a6e5c458..5585d246 100644 --- a/src/components/messages/ChatInput.tsx +++ b/src/components/messages/ChatInput.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Image, StyleSheet, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {useStore} from 'react-redux'; import { AutoCompleteInput, @@ -17,6 +17,7 @@ import { LocalUserType, } from '../../types'; import {normalize} from '../../utils'; +import {Avatar} from '../common'; import {ChatInputSubmit} from '../messages'; const ChatInput: React.FC< @@ -65,14 +66,7 @@ const ChatInput: React.FC< return ( <View style={styles.container}> <View style={styles.textContainer}> - <Image - style={styles.avatar} - source={ - avatar - ? {uri: avatar} - : require('../../assets/images/avatar-placeholder.png') - } - /> + <Avatar style={styles.avatar} uri={avatar} /> <AutoCompleteInput /> <View style={styles.actionButtons}> {/* TODO: Not working, toggled off for now */} diff --git a/src/components/messages/MessageAvatar.tsx b/src/components/messages/MessageAvatar.tsx index d275eae5..bcd4e7a8 100644 --- a/src/components/messages/MessageAvatar.tsx +++ b/src/components/messages/MessageAvatar.tsx @@ -1,9 +1,10 @@ import React, {useContext} from 'react'; -import {Image, StyleSheet, View} from 'react-native'; -import {getMember, normalize} from '../../utils'; -import {useMessageContext} from 'stream-chat-react-native-core'; +import {StyleSheet, View} from 'react-native'; import {useStore} from 'react-redux'; +import {useMessageContext} from 'stream-chat-react-native-core'; import {ChatContext} from '../../App'; +import {getMember, normalize} from '../../utils'; +import {Avatar} from '../common'; const MessageAvatar: React.FC = () => { const {channel} = useContext(ChatContext); @@ -14,13 +15,9 @@ const MessageAvatar: React.FC = () => { return ( <View style={styles.messageAvatarContainer}> {message.lastGroupMessage === true && ( - <Image + <Avatar style={styles.messageAvatar} - source={ - member - ? {uri: member.user?.thumbnail_url} - : require('../../assets/images/avatar-placeholder.png') - } + uri={member?.user?.thumbnail_url as string} /> )} </View> diff --git a/src/components/moments/MomentPostContent.tsx b/src/components/moments/MomentPostContent.tsx index d68ceaa3..03034925 100644 --- a/src/components/moments/MomentPostContent.tsx +++ b/src/components/moments/MomentPostContent.tsx @@ -1,8 +1,17 @@ +import {useNavigation} from '@react-navigation/native'; import React, {useEffect} from 'react'; import {Image, StyleSheet, Text, View, ViewProps} from 'react-native'; +import {useDispatch, useStore} from 'react-redux'; import {getCommentsCount} from '../../services'; -import {ScreenType} from '../../types'; -import {getTimePosted, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {RootState} from '../../store/rootReducer'; +import {ScreenType, UserType} from '../../types'; +import { + getTimePosted, + navigateToProfile, + SCREEN_HEIGHT, + SCREEN_WIDTH, +} from '../../utils'; +import {renderTextWithMentions} from '../../utils/comments'; import {CommentsCount} from '../comments'; interface MomentPostContentProps extends ViewProps { @@ -22,6 +31,9 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({ }) => { const [elapsedTime, setElapsedTime] = React.useState<string>(); const [comments_count, setCommentsCount] = React.useState(''); + const state: RootState = useStore().getState(); + const navigation = useNavigation(); + const dispatch = useDispatch(); useEffect(() => { const fetchCommentsCount = async () => { @@ -47,7 +59,12 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({ /> <Text style={styles.text}>{elapsedTime}</Text> </View> - <Text style={styles.captionText}>{caption}</Text> + {renderTextWithMentions({ + value: caption, + styles: styles.captionText, + onPress: (user: UserType) => + navigateToProfile(state, dispatch, navigation, screenType, user), + })} </View> ); }; diff --git a/src/components/moments/MomentPostHeader.tsx b/src/components/moments/MomentPostHeader.tsx index 8cf509ab..20d9150a 100644 --- a/src/components/moments/MomentPostHeader.tsx +++ b/src/components/moments/MomentPostHeader.tsx @@ -10,7 +10,7 @@ import {MomentMoreInfoDrawer} from '../profile'; import {loadUserMoments} from '../../store/actions'; import {useDispatch, useSelector, useStore} from 'react-redux'; import {ScreenType} from '../../types'; -import Avatar from '../profile/Avatar'; +import TaggAvatar from '../profile/TaggAvatar'; import {useNavigation} from '@react-navigation/native'; import {RootState} from '../../store/rootReducer'; import {fetchUserX, userXInStore} from '../../utils'; @@ -54,7 +54,7 @@ const MomentPostHeader: React.FC<MomentPostHeaderProps> = ({ return ( <View style={[styles.container, style]}> <TouchableOpacity onPress={navigateToProfile} style={styles.header}> - <Avatar + <TaggAvatar style={styles.avatar} userXId={userXId} screenType={screenType} diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx index 3cc1c7f1..a74480b4 100644 --- a/src/components/notifications/Notification.tsx +++ b/src/components/notifications/Notification.tsx @@ -30,6 +30,7 @@ import { SCREEN_WIDTH, userXInStore, } from '../../utils'; +import {Avatar} from '../common'; import AcceptDeclineButtons from '../common/AcceptDeclineButtons'; interface NotificationProps { @@ -225,14 +226,7 @@ const Notification: React.FC<NotificationProps> = (props) => { <TouchableWithoutFeedback onPress={navigateToProfile} style={styles.avatarContainer}> - <Image - style={styles.avatar} - source={ - avatar - ? {uri: avatar, cache: 'reload'} - : require('../../assets/images/avatar-placeholder.png') - } - /> + <Avatar style={styles.avatar} uri={avatar} /> </TouchableWithoutFeedback> {notification_type === 'SYSTEM_MSG' ? ( // Only verbage diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx index 35ec0ea9..14f7dc71 100644 --- a/src/components/profile/ProfileHeader.tsx +++ b/src/components/profile/ProfileHeader.tsx @@ -8,7 +8,7 @@ import {RootState} from '../../store/rootreducer'; import {ScreenType} from '../../types'; import {hasSeenBadgeTutorial, normalize} from '../../utils'; import BadgeDetailView from '../common/BadgeDetailView'; -import Avatar from './Avatar'; +import TaggAvatar from './TaggAvatar'; import BadgeTutorial from './BadgeTutorial'; import FriendsCount from './FriendsCount'; import ProfileMoreInfoDrawer from './ProfileMoreInfoDrawer'; @@ -45,7 +45,6 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ const [drawerVisible, setDrawerVisible] = useState(false); const [showBadgeView, setBadgeViewVisible] = useState(false); - const [firstName, lastName] = [...name.split(' ')]; const containerRef = useRef(null); const childRef = useRef(null); @@ -86,6 +85,10 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ } }; + useEffect(() => { + setDrawerVisible(drawerVisible); + }, [drawerVisible]); + return ( <View ref={containerRef} style={styles.container}> <ProfileMoreInfoDrawer @@ -96,6 +99,7 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ userXName={userXName} setIsOpen={setDrawerVisible} /> + {userId === loggedInUserId && measure && ( <BadgeTutorial uniIconProps={{ @@ -107,21 +111,15 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ /> )} <View style={styles.row}> - <Avatar + <TaggAvatar style={styles.avatar} userXId={userXId} screenType={screenType} /> <View style={styles.header}> - {name.length <= 16 ? ( - <Text style={styles.name}>{name}</Text> - ) : ( - <View> - <Text style={styles.name}>{firstName}</Text> - <Text style={styles.name}>{lastName}</Text> - </View> - )} - + <Text style={styles.name} numberOfLines={2}> + {name} + </Text> <View style={styles.friendsAndUniversity}> <FriendsCount screenType={screenType} userXId={userXId} /> <TouchableOpacity diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index bea989d9..66d68d8f 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -2,7 +2,6 @@ import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; import { Alert, - Image, StyleSheet, Text, TouchableOpacity, @@ -23,6 +22,7 @@ import { userXInStore, } from '../../utils'; import {addUserToRecentlySearched} from '../../utils/search'; +import {Avatar} from '../common'; /** * This component returns user's profile picture friended by username as a touchable component. @@ -48,7 +48,7 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ }) => { const navigation = useNavigation(); const {user: loggedInUser} = useSelector((state: RootState) => state.user); - const [avatar, setAvatar] = useState<string | null>(null); + const [avatar, setAvatar] = useState<string>(); const dispatch = useDispatch(); useEffect(() => { @@ -187,14 +187,7 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ <TouchableOpacity onPress={addToRecentlyStoredAndNavigateToProfile} style={containerStyle}> - <Image - style={avatarStyle} - source={ - avatar - ? {uri: avatar} - : require('../../assets/images/avatar-placeholder.png') - } - /> + <Avatar style={avatarStyle} uri={avatar} /> <View style={nameContainerStyle}> {(previewType === 'Search' || previewType === 'Recent') && ( <> diff --git a/src/components/profile/Avatar.tsx b/src/components/profile/TaggAvatar.tsx index e57a56a3..ea0bdb65 100644 --- a/src/components/profile/Avatar.tsx +++ b/src/components/profile/TaggAvatar.tsx @@ -1,28 +1,27 @@ import React from 'react'; -import {Image, StyleSheet} from 'react-native'; +import {StyleSheet} from 'react-native'; import {useSelector} from 'react-redux'; import {RootState} from '../../store/rootreducer'; import {ScreenType} from '../../types'; +import {Avatar} from '../common'; const PROFILE_DIM = 100; -interface AvatarProps { +interface TaggAvatarProps { style?: object; userXId: string | undefined; screenType: ScreenType; } -const Avatar: React.FC<AvatarProps> = ({style, screenType, userXId}) => { +const TaggAvatar: React.FC<TaggAvatarProps> = ({ + style, + screenType, + userXId, +}) => { const {avatar} = useSelector((state: RootState) => userXId ? state.userX[screenType][userXId] : state.user, ); - return ( - <Image - style={[styles.image, style]} - defaultSource={require('../../assets/images/avatar-placeholder.png')} - source={{uri: avatar, cache: 'reload'}} - /> - ); + return <Avatar style={[styles.image, style]} uri={avatar} />; }; const styles = StyleSheet.create({ @@ -33,4 +32,4 @@ const styles = StyleSheet.create({ }, }); -export default Avatar; +export default TaggAvatar; diff --git a/src/components/profile/index.ts b/src/components/profile/index.ts index 260f4217..c544c3f2 100644 --- a/src/components/profile/index.ts +++ b/src/components/profile/index.ts @@ -8,3 +8,4 @@ export {default as Friends} from './Friends'; export {default as ProfileMoreInfoDrawer} from './ProfileMoreInfoDrawer'; export {default as MomentMoreInfoDrawer} from './MomentMoreInfoDrawer'; export {default as UniversityIcon} from './UniversityIcon'; +export {default as TaggAvatar} from './TaggAvatar'; diff --git a/src/components/search/ExploreSectionUser.tsx b/src/components/search/ExploreSectionUser.tsx index d8c92be2..c949acd4 100644 --- a/src/components/search/ExploreSectionUser.tsx +++ b/src/components/search/ExploreSectionUser.tsx @@ -1,13 +1,13 @@ import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; -import {TextStyle, ViewStyle} from 'react-native'; import { - Image, StyleProp, StyleSheet, Text, + TextStyle, TouchableOpacity, ViewProps, + ViewStyle, } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {useDispatch, useSelector, useStore} from 'react-redux'; @@ -15,6 +15,7 @@ import {loadImageFromURL} from '../../services'; import {RootState} from '../../store/rootReducer'; import {ProfilePreviewType, ScreenType} from '../../types'; import {fetchUserX, normalize, userXInStore} from '../../utils'; +import {Avatar} from '../common'; /** * Search Screen for user recommendations and a search @@ -30,7 +31,7 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({ externalStyles, }) => { const {id, username, first_name, last_name} = user; - const [avatar, setAvatar] = useState<string | null>(null); + const [avatar, setAvatar] = useState<string>(); const navigation = useNavigation(); const {user: loggedInUser} = useSelector((state: RootState) => state.user); const state: RootState = useStore().getState(); @@ -71,14 +72,7 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({ angle={90} angleCenter={{x: 0.5, y: 0.5}} style={styles.gradient}> - <Image - source={ - avatar - ? {uri: avatar} - : require('../../assets/images/avatar-placeholder.png') - } - style={styles.profile} - /> + <Avatar style={styles.profile} uri={avatar} /> </LinearGradient> <Text style={[styles.name, externalStyles?.name]} numberOfLines={2}> {first_name} {last_name} diff --git a/src/components/search/SearchResultCell.tsx b/src/components/search/SearchResultCell.tsx index 5a6ea110..16e62a53 100644 --- a/src/components/search/SearchResultCell.tsx +++ b/src/components/search/SearchResultCell.tsx @@ -22,10 +22,10 @@ import { } from '../../utils'; import { checkIfUserIsBlocked, - defaultUserProfile, fetchUserX, userXInStore, } from '../../utils/users'; +import {Avatar} from '../common'; interface SearchResults { profileData: ProfilePreviewType; @@ -129,11 +129,7 @@ const SearchResultsCell: React.FC<SearchResults> = ({ <TouchableOpacity onPress={addToRecentlyStoredAndNavigateToProfile} style={styles.cellContainer}> - <Image - defaultSource={defaultUserProfile()} - source={{uri: avatar}} - style={styles.imageContainer} - /> + <Avatar style={styles.imageContainer} uri={avatar} /> <View style={[styles.initialTextContainer, styles.multiText]}> <Text style={styles.initialTextStyle}>{`@${username}`}</Text> <Text style={styles.secondaryTextStyle}> diff --git a/src/components/taggs/TwitterTaggPost.tsx b/src/components/taggs/TwitterTaggPost.tsx index 0a6f53d8..db07ba66 100644 --- a/src/components/taggs/TwitterTaggPost.tsx +++ b/src/components/taggs/TwitterTaggPost.tsx @@ -11,7 +11,7 @@ import { } from '../../constants'; import {TwitterPostType} from '../../types'; import {handleOpenSocialUrlOnBrowser, SCREEN_WIDTH} from '../../utils'; -import {DateLabel, PostCarousel} from '../common'; +import {Avatar, DateLabel, PostCarousel} from '../common'; interface TwitterTaggPostProps { ownerHandle: string; @@ -31,14 +31,7 @@ const TwitterTaggPost: React.FC<TwitterTaggPostProps> = ({ )} {/* Post header (avatar and handle) */} <View style={styles.header}> - <Image - style={styles.avatar} - source={ - post.profile_pic - ? {uri: post.profile_pic} - : require('../../assets/images/avatar-placeholder.png') - } - /> + <Avatar style={styles.avatar} uri={post.profile_pic} /> <Text style={styles.headerText} onPress={() => handleOpenSocialUrlOnBrowser(post.handle, 'Twitter')}> @@ -84,13 +77,9 @@ const TwitterTaggPost: React.FC<TwitterTaggPostProps> = ({ <View style={styles.replyHeader}> {post.in_reply_to.text !== 'This tweet is unavailable' && ( <> - <Image - style={styles.replyAvatar} - source={ - post.in_reply_to.profile_pic - ? {uri: post.in_reply_to.profile_pic} - : require('../../assets/images/avatar-placeholder.png') - } + <Avatar + style={styles.avatar} + uri={post.in_reply_to.profile_pic} /> <Text style={styles.replyHandleText} diff --git a/src/constants/strings.ts b/src/constants/strings.ts index 50e4518b..e8f2725d 100644 --- a/src/constants/strings.ts +++ b/src/constants/strings.ts @@ -56,6 +56,8 @@ export const ERROR_UPLOAD_MOMENT = 'Unable to upload moment. Please retry'; export const ERROR_UPLOAD_SMALL_PROFILE_PIC = "Can't have a profile without a pic to represent you, please upload a small profile picture"; export const ERROR_UPLOAD_SP_PHOTO = 'Unable to update suggested people photo. Please retry!'; export const ERROR_VERIFICATION_FAILED_SHORT = 'Verification failed 😓'; +export const FIRST_MESSAGE = 'How about sending your first message to your friend'; +export const START_CHATTING = 'Let’s Start Chatting!'; export const MARKED_AS_MSG = (str: string) => `Marked as ${str}`; export const MOMENT_DELETED_MSG = 'Moment deleted....Some moments have to go, to create space for greater ones'; export const NO_NEW_NOTIFICATIONS = 'You have no new notifications'; diff --git a/src/screens/badge/BadgeScreenHeader.tsx b/src/screens/badge/BadgeScreenHeader.tsx index aef49c85..81bbf42b 100644 --- a/src/screens/badge/BadgeScreenHeader.tsx +++ b/src/screens/badge/BadgeScreenHeader.tsx @@ -15,9 +15,7 @@ const BadgeScreenHeader: React.FC<BadgeScreenHeaderProps> = ({university}) => { style={styles.icon} /> <View style={styles.universityTextContainer}> - <Text style={styles.universityText}> - {university} University Badges - </Text> + <Text style={styles.universityText}>{university} Badges</Text> </View> <View style={styles.searchTextContainer}> <Text style={styles.searchText}> diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx index d2cfcb5d..1df5c2da 100644 --- a/src/screens/chat/ChatListScreen.tsx +++ b/src/screens/chat/ChatListScreen.tsx @@ -9,6 +9,7 @@ import {TabsGradient} from '../../components'; import {ChannelPreview, MessagesHeader} from '../../components/messages'; import {MainStackParams} from '../../routes'; import {RootState} from '../../store/rootReducer'; +import EmptyContentView from '../../components/common/EmptyContentView'; import { LocalAttachmentType, LocalChannelType, @@ -99,6 +100,9 @@ const ChatListScreen: React.FC<ChatListScreenProps> = () => { sort={{last_message_at: -1}} maxUnreadCount={99} Preview={ChannelPreview} + EmptyStateIndicator={() => { + return <EmptyContentView viewType={'ChatList'} />; + }} /> </View> </Chat> diff --git a/src/screens/chat/ChatResultsCell.tsx b/src/screens/chat/ChatResultsCell.tsx index e1eb97dd..14062810 100644 --- a/src/screens/chat/ChatResultsCell.tsx +++ b/src/screens/chat/ChatResultsCell.tsx @@ -1,13 +1,13 @@ import {useNavigation} from '@react-navigation/native'; import React, {useContext, useEffect, useState} from 'react'; -import {Alert, Image, StyleSheet, Text, View} from 'react-native'; +import {Alert, StyleSheet, Text, View} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {ChatContext} from '../../App'; +import {Avatar} from '../../components'; import {ERROR_FAILED_TO_CREATE_CHANNEL} from '../../constants/strings'; import {loadImageFromURL} from '../../services'; import {ProfilePreviewType, UserType} from '../../types'; import {createChannel, normalize, SCREEN_WIDTH} from '../../utils'; -import {defaultUserProfile} from '../../utils/users'; interface ChatResults { profileData: ProfilePreviewType; @@ -58,11 +58,7 @@ const ChatResultsCell: React.FC<ChatResults> = ({ <TouchableOpacity onPress={createChannelIfNotPresentAndNavigate} style={styles.cellContainer}> - <Image - defaultSource={defaultUserProfile()} - source={{uri: avatar}} - style={styles.imageContainer} - /> + <Avatar style={styles.imageContainer} uri={avatar} /> <View style={[styles.initialTextContainer, styles.multiText]}> <Text style={styles.initialTextStyle}>{`@${username}`}</Text> <Text style={styles.secondaryTextStyle}> diff --git a/src/screens/chat/ChatScreen.tsx b/src/screens/chat/ChatScreen.tsx index 5874b8b6..a8e975eb 100644 --- a/src/screens/chat/ChatScreen.tsx +++ b/src/screens/chat/ChatScreen.tsx @@ -1,6 +1,6 @@ -import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; +import {useFocusEffect} from '@react-navigation/core'; import {StackNavigationProp} from '@react-navigation/stack'; -import React, {useContext, useEffect} from 'react'; +import React, {useCallback, useContext, useEffect} from 'react'; import {StyleSheet} from 'react-native'; import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'; import { @@ -9,8 +9,8 @@ import { DeepPartial, MessageInput, MessageList, - useAttachmentPickerContext, Theme, + useAttachmentPickerContext, } from 'stream-chat-react-native'; import {ChatContext} from '../../App'; import { @@ -19,12 +19,11 @@ import { DateHeader, MessageAvatar, MessageFooter, - TabsGradient, TypingIndicator, } from '../../components'; import {MainStackParams} from '../../routes'; import {ScreenType} from '../../types'; -import {HeaderHeight, isIPhoneX, normalize, SCREEN_WIDTH} from '../../utils'; +import {HeaderHeight, normalize, SCREEN_WIDTH} from '../../utils'; type ChatScreenNavigationProp = StackNavigationProp<MainStackParams, 'Chat'>; interface ChatScreenProps { @@ -33,9 +32,8 @@ interface ChatScreenProps { /* * Screen that displays all of the user's active conversations. */ -const ChatScreen: React.FC<ChatScreenProps> = () => { +const ChatScreen: React.FC<ChatScreenProps> = ({navigation}) => { const {channel, chatClient} = useContext(ChatContext); - const tabbarHeight = useBottomTabBarHeight(); const {setTopInset} = useAttachmentPickerContext(); const insets = useSafeAreaInsets(); const chatTheme: DeepPartial<Theme> = { @@ -129,12 +127,25 @@ const ChatScreen: React.FC<ChatScreenProps> = () => { setTopInset(insets.top + HeaderHeight); }); + //Function to get the parent TabBar navigator and setting the option for this screen. + useFocusEffect( + useCallback(() => { + navigation.dangerouslyGetParent()?.setOptions({ + tabBarVisible: false, + }); + return () => { + navigation.dangerouslyGetParent()?.setOptions({ + tabBarVisible: true, + }); + }; + }, [navigation]), + ); + return ( <SafeAreaView style={[ styles.container, - // unable to figure out the padding issue, a hacky solution - {paddingBottom: isIPhoneX() ? tabbarHeight + 20 : tabbarHeight + 50}, + styles.textBoxStyles, // Update : removed hacky soln for a common height. Original : unable to figure out the padding issue, a hacky solution {paddingBottom: isIPhoneX() ? tabbarHeight + 20 : tabbarHeight + 50}, ]}> <ChatHeader screenType={ScreenType.Chat} /> <Chat client={chatClient} style={chatTheme}> @@ -157,7 +168,6 @@ const ChatScreen: React.FC<ChatScreenProps> = () => { <MessageInput Input={ChatInput} /> </Channel> </Chat> - <TabsGradient /> </SafeAreaView> ); }; @@ -167,6 +177,7 @@ const styles = StyleSheet.create({ backgroundColor: 'white', flex: 1, }, + textBoxStyles: {paddingBottom: 60}, }); export default ChatScreen; diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx index 3efd9af8..9fbc4cfe 100644 --- a/src/screens/main/NotificationsScreen.tsx +++ b/src/screens/main/NotificationsScreen.tsx @@ -28,7 +28,7 @@ import { import {RootState} from '../../store/rootReducer'; import {NotificationType, ScreenType} from '../../types'; import {getDateAge, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; -import EmptyNotificationView from './notification/EmptyNotificationView'; +import EmptyContentView from '../../components/common/EmptyContentView'; const NotificationsScreen: React.FC = () => { const {newNotificationReceived} = useSelector( @@ -304,7 +304,7 @@ const NotificationsScreen: React.FC = () => { extraData={requestLimit} ListEmptyComponent={ <View style={styles.emptyViewContainer}> - <EmptyNotificationView /> + <EmptyContentView viewType={'Notification'} /> </View> } /> diff --git a/src/screens/main/notification/EmptyNotificationView.tsx b/src/screens/main/notification/EmptyNotificationView.tsx deleted file mode 100644 index f43cfb2a..00000000 --- a/src/screens/main/notification/EmptyNotificationView.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import {Image, Text, StyleSheet, View} from 'react-native'; -import LinearGradient from 'react-native-linear-gradient'; -import {UP_TO_DATE, NO_NEW_NOTIFICATIONS} from '../../../constants/strings'; -import {NOTIFICATION_GRADIENT} from '../../../constants/constants'; -import {SCREEN_HEIGHT, normalize} from '../../../utils'; -const EmptyNotificationView: React.FC = () => { - return ( - <View style={styles.container}> - <LinearGradient - style={styles.backgroundLinearView} - useAngle={true} - angle={180} - colors={NOTIFICATION_GRADIENT}> - <Image - source={require('../../../assets/images/empty_notifications.png')} - /> - </LinearGradient> - <View style={styles.topMargin}> - <Text style={styles.upperTextStyle}>{UP_TO_DATE}</Text> - </View> - <View> - <Text style={styles.bottomTextStyle}>{NO_NEW_NOTIFICATIONS}</Text> - </View> - </View> - ); -}; - -const styles = StyleSheet.create({ - container: {alignItems: 'center'}, - topMargin: {marginTop: SCREEN_HEIGHT * 0.025}, - upperTextStyle: { - fontWeight: '700', - fontSize: normalize(23), - lineHeight: normalize(40), - }, - bottomTextStyle: { - color: '#2D3B45', - fontWeight: '600', - fontSize: normalize(20), - lineHeight: normalize(40), - }, - backgroundLinearView: { - borderRadius: 135.5, - }, -}); - -export default EmptyNotificationView; diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index 041fba05..3b970864 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -168,7 +168,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { //Stores token received in the response into client's AsynStorage try { userLogin(dispatch, {userId: data.UserID, username}); - fcmService.sendFcmTokenToServer(); + fcmService.sendFcmTokenToServer(chatClient); connectChatAccount(data.UserID, chatClient); } catch (err) { Alert.alert(ERROR_INVALID_LOGIN); diff --git a/src/screens/onboarding/OnboardingStepThree.tsx b/src/screens/onboarding/OnboardingStepThree.tsx index 638f0889..34173b39 100644 --- a/src/screens/onboarding/OnboardingStepThree.tsx +++ b/src/screens/onboarding/OnboardingStepThree.tsx @@ -27,7 +27,6 @@ import { ERROR_SELECT_CLASS_YEAR, ERROR_SELECT_GENDER, ERROR_SELECT_UNIVERSITY, - ERROR_UPLOAD_SMALL_PROFILE_PIC, } from '../../constants/strings'; import {OnboardingStackParams} from '../../routes/onboarding'; import {patchEditProfile} from '../../services'; @@ -160,10 +159,6 @@ const OnboardingStepThree: React.FC<OnboardingStepThreeProps> = ({ }; const handleSubmit = async () => { - if (!form.smallPic) { - Alert.alert(ERROR_UPLOAD_SMALL_PROFILE_PIC); - return; - } if (form.classYear === -1) { Alert.alert(ERROR_SELECT_CLASS_YEAR); return; diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 156ee41c..041f0da2 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -11,9 +11,10 @@ import { TouchableWithoutFeedback, View, } from 'react-native'; +import {MentionInput} from 'react-native-controlled-mentions'; import {Button} from 'react-native-elements'; import {useDispatch, useSelector} from 'react-redux'; -import {SearchBackground, TaggBigInput} from '../../components'; +import {SearchBackground} from '../../components'; import {CaptionScreenHeader} from '../../components/'; import TaggLoadingIndicator from '../../components/common/TaggLoadingIndicator'; import {TAGG_LIGHT_BLUE_2} from '../../constants'; @@ -26,6 +27,7 @@ import { } from '../../store/actions'; import {RootState} from '../../store/rootReducer'; import {SCREEN_WIDTH, StatusBarHeight} from '../../utils'; +import {mentionPartTypes} from '../../utils/comments'; /** * Upload Screen to allow users to upload posts to Tagg @@ -49,10 +51,6 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { const [caption, setCaption] = useState(''); const [loading, setLoading] = useState(false); - const handleCaptionUpdate = (newCaption: string) => { - setCaption(newCaption); - }; - const navigateToProfile = () => { //Since the logged In User is navigating to own profile, useXId is not required navigation.navigate('Profile', { @@ -112,12 +110,13 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { source={{uri: image.path}} resizeMode={'cover'} /> - <TaggBigInput - style={styles.text} - multiline + <MentionInput + containerStyle={styles.text} placeholder="Write something....." placeholderTextColor="gray" - onChangeText={handleCaptionUpdate} + value={caption} + onChange={setCaption} + partTypes={mentionPartTypes} /> </View> </KeyboardAvoidingView> diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx index b0208f6f..1a913e58 100644 --- a/src/screens/profile/MomentCommentsScreen.tsx +++ b/src/screens/profile/MomentCommentsScreen.tsx @@ -7,13 +7,8 @@ import {AddComment} from '../../components/'; import CommentsContainer from '../../components/comments/CommentsContainer'; import {ADD_COMMENT_TEXT} from '../../constants/strings'; import {headerBarOptions, MainStackParams} from '../../routes/main'; -import {CommentType} from '../../types'; -import { - HeaderHeight, - normalize, - SCREEN_HEIGHT, - SCREEN_WIDTH, -} from '../../utils'; +import {CommentThreadType, CommentType} from '../../types'; +import {HeaderHeight, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; /** * Comments Screen for an image uploaded @@ -30,18 +25,35 @@ interface MomentCommentsScreenProps { route: MomentCommentsScreenRouteProps; } +type MomentCommentContextType = { + commentTapped: CommentType | CommentThreadType | undefined; + setCommentTapped: ( + comment: CommentType | CommentThreadType | undefined, + ) => void; + shouldUpdateAllComments: boolean; + setShouldUpdateAllComments: (available: boolean) => void; + commentsLength: number; + setCommentsLength: (length: number) => void; +}; + +export const CommentContext = React.createContext( + {} as MomentCommentContextType, +); + const MomentCommentsScreen: React.FC<MomentCommentsScreenProps> = ({route}) => { const navigation = useNavigation(); const {moment_id, screenType, comment_id} = route.params; //Receives comment length from child CommentsContainer const [commentsLength, setCommentsLength] = useState<number>(0); - const [newCommentsAvailable, setNewCommentsAvailable] = React.useState(true); + const [shouldUpdateAllComments, setShouldUpdateAllComments] = React.useState( + true, + ); //Keeps track of the current comments object in focus so that the application knows which comment to post a reply to - const [commentObjectInFocus, setCommentObjectInFocus] = useState< - CommentType | undefined - >(undefined); + const [commentTapped, setCommentTapped] = useState< + CommentType | CommentThreadType | undefined + >(); useEffect(() => { navigation.setOptions({ @@ -50,36 +62,39 @@ const MomentCommentsScreen: React.FC<MomentCommentsScreenProps> = ({route}) => { }, [commentsLength, navigation]); return ( - <View style={styles.background}> - <SafeAreaView> - <View style={styles.body}> - <CommentsContainer - objectId={moment_id} - commentId={comment_id} - screenType={screenType} - setCommentsLength={setCommentsLength} - newCommentsAvailable={newCommentsAvailable} - setNewCommentsAvailable={setNewCommentsAvailable} - setCommentObjectInFocus={setCommentObjectInFocus} - commentObjectInFocus={commentObjectInFocus} - typeOfComment={'Comment'} - /> - <AddComment - placeholderText={ - commentObjectInFocus - ? ADD_COMMENT_TEXT(commentObjectInFocus.commenter.username) - : ADD_COMMENT_TEXT() - } - setNewCommentsAvailable={setNewCommentsAvailable} - objectId={ - commentObjectInFocus ? commentObjectInFocus.comment_id : moment_id - } - isCommentInFocus={commentObjectInFocus ? true : false} - /> - </View> - </SafeAreaView> - <TabsGradient /> - </View> + <CommentContext.Provider + value={{ + commentTapped, + setCommentTapped, + shouldUpdateAllComments, + setShouldUpdateAllComments, + commentsLength, + setCommentsLength, + }}> + <View style={styles.background}> + <SafeAreaView> + <View style={styles.body}> + <CommentsContainer + objectId={moment_id} + commentId={comment_id} + screenType={screenType} + shouldUpdate={shouldUpdateAllComments} + setShouldUpdate={setShouldUpdateAllComments} + isThread={false} + /> + <AddComment + placeholderText={ + !commentTapped + ? ADD_COMMENT_TEXT() + : ADD_COMMENT_TEXT(commentTapped.commenter.username) + } + momentId={moment_id} + /> + </View> + </SafeAreaView> + <TabsGradient /> + </View> + </CommentContext.Provider> ); }; @@ -88,39 +103,12 @@ const styles = StyleSheet.create({ backgroundColor: 'white', height: '100%', }, - header: {justifyContent: 'center', padding: '3%'}, - headerText: { - position: 'absolute', - alignSelf: 'center', - fontSize: normalize(18), - fontWeight: '700', - lineHeight: normalize(21.48), - letterSpacing: normalize(1.3), - }, - headerButton: { - width: '5%', - aspectRatio: 1, - padding: 0, - marginLeft: '5%', - alignSelf: 'flex-start', - }, - headerButtonText: { - color: 'black', - fontSize: 18, - fontWeight: '400', - }, body: { marginTop: HeaderHeight, width: SCREEN_WIDTH * 0.9, height: SCREEN_HEIGHT * 0.8, paddingTop: '3%', }, - scrollView: { - paddingHorizontal: 20, - }, - scrollViewContent: { - justifyContent: 'center', - }, }); export default MomentCommentsScreen; diff --git a/src/screens/profile/SocialMediaTaggs.tsx b/src/screens/profile/SocialMediaTaggs.tsx index 9186f187..52d20683 100644 --- a/src/screens/profile/SocialMediaTaggs.tsx +++ b/src/screens/profile/SocialMediaTaggs.tsx @@ -59,7 +59,7 @@ const SocialMediaTaggs: React.FC<SocialMediaTaggsProps> = ({ useEffect(() => { navigation.setOptions({ headerTitle: () => { - return <AvatarTitle avatar={avatar ?? null} />; + return <AvatarTitle avatar={avatar} />; }, }); }, [avatar, navigation]); diff --git a/src/types/types.ts b/src/types/types.ts index 18ddbfba..00501d49 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -23,6 +23,9 @@ export interface CategoryPreviewType { } export type FriendshipStatusType = 'friends' | 'requested' | 'no_record'; +export interface EmptyViewProps { + viewType: 'Notification' | 'ChatList'; +} export enum UniversityType { Brown = 'Brown University', @@ -218,8 +221,6 @@ export type NotificationType = { unread: boolean; }; -export type TypeOfComment = 'Comment' | 'Thread'; - export type TypeOfNotification = // notification_object is undefined | 'DFT' diff --git a/src/utils/comments.tsx b/src/utils/comments.tsx new file mode 100644 index 00000000..a71e3857 --- /dev/null +++ b/src/utils/comments.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import {StyleProp, Text, TextStyle} from 'react-native'; +import { + isMentionPartType, + parseValue, + Part, + PartType, +} from 'react-native-controlled-mentions'; +import TaggTypeahead from '../components/common/TaggTypeahead'; +import {TAGG_LIGHT_BLUE} from '../constants'; +import {UserType} from '../types'; + +/** + * Part renderer + * + * https://github.com/dabakovich/react-native-controlled-mentions#rendering-mentioninputs-value + */ +const renderPart = ( + part: Part, + index: number, + handlePress: (user: UserType) => void, +) => { + // Just plain text + if (!part.partType) { + return <Text key={index}>{part.text}</Text>; + } + + // Mention type part + if (isMentionPartType(part.partType)) { + return ( + <Text + key={`${index}-${part.data?.trigger}`} + style={part.partType.textStyle} + onPress={() => { + if (part.data) { + handlePress({ + userId: part.data.id, + username: part.data.name, + }); + } + }}> + {part.text} + </Text> + ); + } + + // Other styled part types + return ( + <Text key={`${index}-pattern`} style={part.partType.textStyle}> + {part.text} + </Text> + ); +}; + +interface RenderProps { + value: string; + styles: StyleProp<TextStyle>; + onPress: (user: UserType) => void; +} + +/** + * Value renderer. Parsing value to parts array and then mapping the array using 'renderPart' + * + * https://github.com/dabakovich/react-native-controlled-mentions#rendering-mentioninputs-value + */ +export const renderTextWithMentions: React.FC<RenderProps> = ({ + value, + styles, + onPress, +}) => { + const {parts} = parseValue(value, mentionPartTypes); + return ( + <Text style={styles}> + {parts.map((part, index) => renderPart(part, index, onPress))} + </Text> + ); +}; + +export const mentionPartTypes: PartType[] = [ + { + trigger: '@', + renderSuggestions: (props) => <TaggTypeahead {...props} />, + allowedSpacesCount: 0, + isInsertSpaceAfterMention: true, + textStyle: {color: TAGG_LIGHT_BLUE}, + }, +]; diff --git a/src/utils/users.ts b/src/utils/users.ts index 87bcbdfc..334cb3c0 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -18,8 +18,8 @@ import {loadUserX} from './../store/actions/userX'; import {AppDispatch} from './../store/configureStore'; import {RootState} from './../store/rootReducer'; import { - ProfilePreviewType, ProfileInfoType, + ProfilePreviewType, ScreenType, UserType, UniversityBadge, @@ -170,11 +170,6 @@ export const checkIfUserIsBlocked = async ( return await isUserBlocked(userId, loggedInUser.userId, token); }; -export const defaultUserProfile = () => { - const defaultImage = require('../assets/images/avatar-placeholder.png'); - return defaultImage; -}; - /** * Used to determine whether the logged-in user is able to view userX's private * information or not. @@ -223,3 +218,25 @@ export const removeUserBadge = async ( dispatch(updateUserBadges(badges)); } }; + +export const navigateToProfile = async ( + state: RootState, + dispatch: any, + navigation: any, + screenType: ScreenType, + user: UserType, +) => { + const loggedInUserId = state.user.user.userId; + const {userId, username} = user; + if (!userXInStore(state, screenType, userId)) { + await fetchUserX( + dispatch, + {userId: userId, username: username}, + screenType, + ); + } + navigation.push('Profile', { + userXId: userId === loggedInUserId ? undefined : userId, + screenType, + }); +}; @@ -2692,6 +2692,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -3943,7 +3948,7 @@ inquirer@^7.0.0: strip-ansi "^6.0.0" through "^2.3.6" -internal-slot@^1.0.3: +internal-slot@^1.0.2, internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== @@ -6260,6 +6265,14 @@ react-native-contacts@^6.0.4: resolved "https://registry.yarnpkg.com/react-native-contacts/-/react-native-contacts-6.0.5.tgz#26503c862a197eef790aee243053c0ceb23d7a0f" integrity sha512-2BPW1bODJPnKbi/Wr2+m43hlrxcYrzMkWVAre3nMwqekSrsapMu/iSpQbxcB3Ia8cvBNH6Cu2tSqGpqY4Lc/lg== +react-native-controlled-mentions@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/react-native-controlled-mentions/-/react-native-controlled-mentions-2.2.5.tgz#2eddcdf6b6a0ead0c448be4bb0239e10eca84a29" + integrity sha512-fNIQWZMPy9OvPKO3MeLX+J76Ld7OuvrrLn4xwXMOjsYjuEWtVBNc4t/ADxNc3woQCfuSEkST+SbafFpUwwLMIA== + dependencies: + diff "5.0.0" + string.prototype.matchall "4.0.3" + react-native-date-picker@^3.2.5: version "3.2.10" resolved "https://registry.yarnpkg.com/react-native-date-picker/-/react-native-date-picker-3.2.10.tgz#5e690db8e628255d8390e33b1e6f8fa9477f6fb9" @@ -6631,7 +6644,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.3.1: +regexp.prototype.flags@^1.3.0, regexp.prototype.flags@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== @@ -7064,7 +7077,7 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -side-channel@^1.0.4: +side-channel@^1.0.3, side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== @@ -7385,6 +7398,19 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string.prototype.matchall@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz#24243399bc31b0a49d19e2b74171a15653ec996a" + integrity sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has-symbols "^1.0.1" + internal-slot "^1.0.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.3" + string.prototype.matchall@^4.0.2, string.prototype.matchall@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz#608f255e93e072107f5de066f81a2dfb78cf6b29" |