diff --git a/android/app/src/main/java/com/uniteapprn/MainActivity.java b/android/app/src/main/java/com/uniteapprn/MainActivity.java index dc42a0d..687f566 100644 --- a/android/app/src/main/java/com/uniteapprn/MainActivity.java +++ b/android/app/src/main/java/com/uniteapprn/MainActivity.java @@ -4,6 +4,9 @@ import android.content.SharedPreferences; import android.os.Bundle; import com.facebook.react.ReactActivity; +import com.facebook.react.ReactActivityDelegate; +import com.facebook.react.ReactRootView; +import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; import org.devio.rn.splashscreen.SplashScreen; import ie.gov.tracing.storage.SharedPrefs; @@ -32,4 +35,14 @@ private void saveLaunchTime() { editor.putLong("lastLaunchTime", System.currentTimeMillis()); editor.apply(); } + + @Override + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityDelegate(this, getMainComponentName()) { + @Override + protected ReactRootView createRootView() { + return new RNGestureHandlerEnabledRootView(MainActivity.this); + } + }; + } } diff --git a/babel.config.js b/babel.config.js index 304fa75..387e12e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -31,6 +31,7 @@ module.exports = { "@utils": "./src/utils", "@logger": "./src/logger", "@navigation": "./src/navigation", + "@linking": "./src/linking", }, }, ], diff --git a/docs/CODE_QUALITY.md b/docs/CODE_QUALITY.md new file mode 100644 index 0000000..593f4e6 --- /dev/null +++ b/docs/CODE_QUALITY.md @@ -0,0 +1,21 @@ +### Code Quality + +This code-base is available open-source to the general public, it is important that all developers working on this project do their best to write high-quality code that is testable, scalable, and follows software development best practices. + +#### Code style +- We will be using TypeScript for all our React components +- Install prettier on your code editor +- Run yarn lint often to check for linting rules + +#### Structure +- Write tests for all code relating to reducers/state manipulation +- Unless absolutely necessary do not use any (TypeScript) +- Follow the project folder structure, place files where they belong +- We will be using functional components +- For consistency, use hooks whenever possible. for e.g. Use useDispatch instead of redux HOC + +#### Styles +- No inline styles. Use styled-components whenever possible +- Limit custom styles (including styled-components) in views, try to extract component whenever possible +- Use font sizes, colors, etc from constants +- Use our @components/atoms/Text component only, do not use the default Text from react-native (we have custom text styles applied by default) diff --git a/docs/GIT_HYGIENE.md b/docs/GIT_HYGIENE.md new file mode 100644 index 0000000..a625653 --- /dev/null +++ b/docs/GIT_HYGIENE.md @@ -0,0 +1,6 @@ +### Git Hygiene + +- When merging PRs to develop, please use Squash and merge +- Make sure to review the commit message before finalizing the merge, don't just leave defaults in there +- Use Merge from release branches to master +- Use Merge from master to develop \ No newline at end of file diff --git a/docs/SERVER_DRIVEN_UI.md b/docs/SERVER_DRIVEN_UI.md new file mode 100644 index 0000000..a26dd03 --- /dev/null +++ b/docs/SERVER_DRIVEN_UI.md @@ -0,0 +1,136 @@ +### Server-driven UI + +#### Cards + +| key | value | +| --- | ----- | +| component | `Card` | +| title | title of the card | +| body | optional, body of the card | +| backgroundColor | optional, background color of the color, defaults to `#FFFFFF` | +| icon | optional, icon of the card | +| externalLink | optional, if specified, display the card as an external link and open the link in a browser when tapped | +| deepLink | optional, if specified, deep link to a specific screen. | +| action | `share-app` optional, if specified, shares the app when tapped | + +> Please specify one of `action`, `deepLink` or `externalLink` + +Example: +```json +{ + "component": "Card", + "title": "foo", + "body": "bar", + "backgroundColor": "#FF0000", + "icon": "http://foo/image.png", + "externalLink": "http://govt.nz", + "deepLink": "nzcovidtracer://SomeScreen" +} +``` +#### Panels + +| key | value | +| --- | ----- | +| component | `Panel` | +| title | title of the card | +| body | body of the card | +| backgroundColor | optional, background color of the color, defaults to `#FFFFFF` | +| buttons | optional, primary and secondary button, empty or empty array to hide buttons | +| buttons.text | button text | +| buttons.externalLink | optional, if specified, open the link on a browser when tapped | +| buttons.deepLink | optional, if specified, navigate the deep link when tapped | +| buttons.accessibilityHint | optional, if specified, override the default accessibility hint | + +> Please specify either `deepLink` or `externalLink` + +Example: +```json +{ + "component": "Panel", + "title": "foo", + "body": "bar", + "backgroundColor": "#FF0000", + "buttons": [{ + "text": "primary", + "deepLink": "nzcovidtracer://SomeScreen" + }, { + "text": "secondary", + "externalLink": "http://govt.nz", + "accessibilityHint": "Custom hint" + }] +} +``` + +#### Info blocks + +| key | value | +| --- | ----- | +| component | `InfoBlock` | +| icon | optional, icon of the card | +| backgroundColor | optional, background of the card, defaults to `#FFF1D0` | +| title | required, title of the card | +| body | required, body of the card | + +Example: +```json +{ + "component": "InfoBlock", + "icon": "http://foo/image.png", + "backgroundColor": "#FF0000", + "title": "foo", + "body": "bar" +} +``` + +#### Sections + +Section group cards and panels together: + +| key | value | +| --- | ----- | +| title | optional, section title | +| data | an array of `Card` or `Panel` components | + +There might be multiple sections in a scroll view. + +Example (2 sections): +```json +[{ + "data": [{ + "component": "Panel", + "title": "foo", + "body": "bar", + "buttons": [{ + "text": "primary", + "deepLink": "nzcovidtracer://SomeScreen" + }] + }, { + "component": "Card", + "title": "foo", + "body": "bar" + }] +}, +{ + "title": "Section 2", + "data": [{ + "component": "Panel", + "title": "foo", + "body": "bar" + }] +}] +``` + + +#### Deep links + +For security and maintainability reasons, only a specified set of deeplinks will be supported + +| link | behaviour | +| --- | ----- | +| nzcovidtracer://dashboard/today | Navigate to `dashboard` on the `today tab` | +| nzcovidtracer://dashboard/resources | Navigate to `dashboard` on the `resources tab` | +| nzcovidtracer://nhi | Navigate to `privacy` -> `add nhi`, if user has no NHI stored. Otherwise navigate to `edit nhi` | +| nzcovidtracer://diary | Navigate to `diary` | +| nzcovidtracer://manualEntry | Navigate to `manual entry` | + +> Please note that these links are only supported from server driven components for now \ No newline at end of file diff --git a/global.d.ts b/global.d.ts index ffdc2ea..094ec1b 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,3 +1,5 @@ declare module "@bugfender/rn-bugfender"; +declare module "react-native-bluetooth-state-manager"; declare module "react-native-google-safetynet"; declare module "react-native-ios11-devicecheck"; +declare module "*.png"; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 141cba4..4595630 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -359,6 +359,8 @@ PODS: - RealmJS (6.1.2): - GCDWebServer - React + - RNBluetoothStateManager (1.3.1): + - React - RNCAsyncStorage (1.12.0): - React - RNCClipboard (1.4.0): @@ -382,8 +384,8 @@ PODS: - Firebase/Messaging (~> 7.0.0) - React-Core - RNFBApp - - RNGestureHandler (1.8.0): - - React + - RNGestureHandler (1.10.3): + - React-Core - RNIOS11DeviceCheck (0.0.3): - React - RNKeychain (6.2.0): @@ -449,6 +451,7 @@ DEPENDENCIES: - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - RealmJS (from `../node_modules/realm`) + - RNBluetoothStateManager (from `../node_modules/react-native-bluetooth-state-manager`) - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)" - "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)" @@ -567,6 +570,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RealmJS: :path: "../node_modules/realm" + RNBluetoothStateManager: + :path: "../node_modules/react-native-bluetooth-state-manager" RNCAsyncStorage: :path: "../node_modules/@react-native-community/async-storage" RNCClipboard: @@ -661,6 +666,7 @@ SPEC CHECKSUMS: React-RCTVibration: 4d2e726957f4087449739b595f107c0d4b6c2d2d ReactCommon: a0a1edbebcac5e91338371b72ffc66aa822792ce RealmJS: 44cd1b81352bbd37f582b5061d5abc1f6c783393 + RNBluetoothStateManager: afd3d90feffa2e02bb4f6abad588648840d0f727 RNCAsyncStorage: 2a692bcb9b69b76a2f1a95f33db057129700af64 RNCClipboard: a1dd5a278566666ac8f9c9b517b7922c801bcd98 RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f @@ -670,7 +676,7 @@ SPEC CHECKSUMS: RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff RNFBApp: 2810471bdc092151f20f16a9f8915b1ad70325b2 RNFBMessaging: 37ea050803005191f1c42ff02234353225429c07 - RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39 + RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 RNIOS11DeviceCheck: a4a545fdd08230a17a8ce7608e95038ee23a32aa RNKeychain: b8e0711b959a19c5b057d1e970d3c83d159b6da5 RNPermissions: 477d88d0be7ecb0a7b344e4d5fbb0352de104626 diff --git a/ios/UniteAppRN.xcodeproj/project.pbxproj b/ios/UniteAppRN.xcodeproj/project.pbxproj index 04cf935..50c651b 100644 --- a/ios/UniteAppRN.xcodeproj/project.pbxproj +++ b/ios/UniteAppRN.xcodeproj/project.pbxproj @@ -817,6 +817,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCTTypeSafety/RCTTypeSafety.framework", + "${BUILT_PRODUCTS_DIR}/RNBluetoothStateManager/RNBluetoothStateManager.framework", "${BUILT_PRODUCTS_DIR}/RNCAsyncStorage/RNCAsyncStorage.framework", "${BUILT_PRODUCTS_DIR}/RNCClipboard/RNCClipboard.framework", "${BUILT_PRODUCTS_DIR}/RNCMaskedView/RNCMaskedView.framework", @@ -880,6 +881,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCTTypeSafety.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNBluetoothStateManager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCAsyncStorage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCClipboard.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCMaskedView.framework", @@ -953,6 +955,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCTTypeSafety/RCTTypeSafety.framework", + "${BUILT_PRODUCTS_DIR}/RNBluetoothStateManager/RNBluetoothStateManager.framework", "${BUILT_PRODUCTS_DIR}/RNCAsyncStorage/RNCAsyncStorage.framework", "${BUILT_PRODUCTS_DIR}/RNCClipboard/RNCClipboard.framework", "${BUILT_PRODUCTS_DIR}/RNCMaskedView/RNCMaskedView.framework", @@ -1016,6 +1019,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCTTypeSafety.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNBluetoothStateManager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCAsyncStorage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCClipboard.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCMaskedView.framework", @@ -1111,6 +1115,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCTTypeSafety/RCTTypeSafety.framework", + "${BUILT_PRODUCTS_DIR}/RNBluetoothStateManager/RNBluetoothStateManager.framework", "${BUILT_PRODUCTS_DIR}/RNCAsyncStorage/RNCAsyncStorage.framework", "${BUILT_PRODUCTS_DIR}/RNCClipboard/RNCClipboard.framework", "${BUILT_PRODUCTS_DIR}/RNCMaskedView/RNCMaskedView.framework", @@ -1174,6 +1179,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCTTypeSafety.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNBluetoothStateManager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCAsyncStorage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCClipboard.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCMaskedView.framework", @@ -1324,6 +1330,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCTTypeSafety/RCTTypeSafety.framework", + "${BUILT_PRODUCTS_DIR}/RNBluetoothStateManager/RNBluetoothStateManager.framework", "${BUILT_PRODUCTS_DIR}/RNCAsyncStorage/RNCAsyncStorage.framework", "${BUILT_PRODUCTS_DIR}/RNCClipboard/RNCClipboard.framework", "${BUILT_PRODUCTS_DIR}/RNCMaskedView/RNCMaskedView.framework", @@ -1387,6 +1394,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCTTypeSafety.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNBluetoothStateManager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCAsyncStorage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCClipboard.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCMaskedView.framework", @@ -1680,6 +1688,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCTTypeSafety/RCTTypeSafety.framework", + "${BUILT_PRODUCTS_DIR}/RNBluetoothStateManager/RNBluetoothStateManager.framework", "${BUILT_PRODUCTS_DIR}/RNCAsyncStorage/RNCAsyncStorage.framework", "${BUILT_PRODUCTS_DIR}/RNCClipboard/RNCClipboard.framework", "${BUILT_PRODUCTS_DIR}/RNCMaskedView/RNCMaskedView.framework", @@ -1743,6 +1752,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCTTypeSafety.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNBluetoothStateManager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCAsyncStorage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCClipboard.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCMaskedView.framework", @@ -1816,6 +1826,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCTTypeSafety/RCTTypeSafety.framework", + "${BUILT_PRODUCTS_DIR}/RNBluetoothStateManager/RNBluetoothStateManager.framework", "${BUILT_PRODUCTS_DIR}/RNCAsyncStorage/RNCAsyncStorage.framework", "${BUILT_PRODUCTS_DIR}/RNCClipboard/RNCClipboard.framework", "${BUILT_PRODUCTS_DIR}/RNCMaskedView/RNCMaskedView.framework", @@ -1879,6 +1890,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCTTypeSafety.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNBluetoothStateManager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCAsyncStorage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCClipboard.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCMaskedView.framework", @@ -1962,6 +1974,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCTTypeSafety/RCTTypeSafety.framework", + "${BUILT_PRODUCTS_DIR}/RNBluetoothStateManager/RNBluetoothStateManager.framework", "${BUILT_PRODUCTS_DIR}/RNCAsyncStorage/RNCAsyncStorage.framework", "${BUILT_PRODUCTS_DIR}/RNCClipboard/RNCClipboard.framework", "${BUILT_PRODUCTS_DIR}/RNCMaskedView/RNCMaskedView.framework", @@ -2025,6 +2038,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCTTypeSafety.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNBluetoothStateManager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCAsyncStorage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCClipboard.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCMaskedView.framework", diff --git a/ios/UniteAppRN/AppDelegate.m b/ios/UniteAppRN/AppDelegate.m index 19b6182..0447362 100644 --- a/ios/UniteAppRN/AppDelegate.m +++ b/ios/UniteAppRN/AppDelegate.m @@ -8,6 +8,7 @@ #import #import "RNFBMessagingModule.h" #import +#import #import #import @@ -101,4 +102,11 @@ -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNoti completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge); } +// Required to enable deep linking for ios 9.x or newer +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options +{ + return [RCTLinkingManager application:application openURL:url options:options]; +} @end diff --git a/ios/UniteAppRN/Info-DEV.plist b/ios/UniteAppRN/Info-DEV.plist index 30f2a5f..cb8f754 100644 --- a/ios/UniteAppRN/Info-DEV.plist +++ b/ios/UniteAppRN/Info-DEV.plist @@ -60,6 +60,10 @@ NSCameraUsageDescription We need your camera to scan QR codes + NSBluetoothAlwaysUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. + NSBluetoothPeripheralUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. UIAppFonts Baloo-Black.ttf diff --git a/ios/UniteAppRN/Info-DEVOPS.plist b/ios/UniteAppRN/Info-DEVOPS.plist index c58008c..c658107 100644 --- a/ios/UniteAppRN/Info-DEVOPS.plist +++ b/ios/UniteAppRN/Info-DEVOPS.plist @@ -60,6 +60,10 @@ NSCameraUsageDescription We need your camera to scan QR codes + NSBluetoothAlwaysUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. + NSBluetoothPeripheralUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. UIAppFonts Baloo-Black.ttf diff --git a/ios/UniteAppRN/Info-ENF.plist b/ios/UniteAppRN/Info-ENF.plist index 3334e94..a09a5d7 100644 --- a/ios/UniteAppRN/Info-ENF.plist +++ b/ios/UniteAppRN/Info-ENF.plist @@ -60,6 +60,10 @@ NSCameraUsageDescription We need your camera to scan QR codes + NSBluetoothAlwaysUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. + NSBluetoothPeripheralUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. UIAppFonts Baloo-Black.ttf diff --git a/ios/UniteAppRN/Info-TEST.plist b/ios/UniteAppRN/Info-TEST.plist index 5e50571..a6da772 100644 --- a/ios/UniteAppRN/Info-TEST.plist +++ b/ios/UniteAppRN/Info-TEST.plist @@ -60,6 +60,10 @@ NSCameraUsageDescription We need your camera to scan QR codes + NSBluetoothAlwaysUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. + NSBluetoothPeripheralUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. UIAppFonts Baloo-Black.ttf diff --git a/ios/UniteAppRN/Info-UAT.plist b/ios/UniteAppRN/Info-UAT.plist index 8b5b19c..934c31c 100644 --- a/ios/UniteAppRN/Info-UAT.plist +++ b/ios/UniteAppRN/Info-UAT.plist @@ -60,6 +60,10 @@ NSCameraUsageDescription We need your camera to scan QR codes + NSBluetoothAlwaysUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. + NSBluetoothPeripheralUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. UIAppFonts Baloo-Black.ttf diff --git a/ios/UniteAppRN/Info.plist b/ios/UniteAppRN/Info.plist index debe7b3..57af920 100644 --- a/ios/UniteAppRN/Info.plist +++ b/ios/UniteAppRN/Info.plist @@ -52,6 +52,10 @@ NSCameraUsageDescription We need your camera to scan QR codes + NSBluetoothAlwaysUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. + NSBluetoothPeripheralUsageDescription + NZ COVID Tracer uses Bluetooth Low Energy to exchange anonymous, random keys with other nearby phones that also have Bluetooth tracing enabled. UIAppFonts Baloo-Black.ttf diff --git a/jestSetup.js b/jestSetup.js index f1f25c1..0f36177 100644 --- a/jestSetup.js +++ b/jestSetup.js @@ -24,6 +24,10 @@ jest.mock("./src/logger/createTransports"); jest.mock("react-native-config"); +jest.mock("react-native-exposure-notification-service", () => {}); + +jest.mock("react-native-bluetooth-state-manager", () => {}); + jest.mock('react-native-push-notification', () => ({ configure: jest.fn(), onRegister: jest.fn(), diff --git a/package.json b/package.json index 8c687cd..ae5211b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "appcenter": "3.1.2", "appcenter-crashes": "3.1.2", "aws-amplify": "^3.3.4", - "axios": "^0.20.0", + "axios": "^0.21.1", "base64-js": "^1.3.1", "covidtracer-migration": "./lib/covidtracer_migration/", "crypto-js": "3.3.0", @@ -53,13 +53,14 @@ "react": "16.13.1", "react-i18next": "^11.7.2", "react-native": "0.63.2", + "react-native-bluetooth-state-manager": "^1.3.1", "react-native-camera": "^3.40.0", "react-native-config": "^1.3.3", "react-native-country-picker-modal": "^2.0.0", "react-native-device-info": "^6.0.2", "react-native-exposure-notification-service": "rushdigital/react-native-exposure-notification-service#fix/use-v1-android-23", "react-native-fast-image": "^8.3.4", - "react-native-gesture-handler": "^1.8.0", + "react-native-gesture-handler": "^1.10.3", "react-native-google-safetynet": "^1.0.0", "react-native-haptic-feedback": "^1.11.0", "react-native-ios11-devicecheck": "^0.0.3", @@ -81,6 +82,7 @@ "redux-saga": "^1.1.3", "reselect": "^4.0.0", "styled-components": "^5.1.1", + "url": "^0.11.0", "use-debounce": "^6.0.0", "xcode": "^3.0.1", "yup": "^0.29.3" diff --git a/scripts/setenv.sh b/scripts/setenv.sh index 9728592..5f2f4b8 100755 --- a/scripts/setenv.sh +++ b/scripts/setenv.sh @@ -27,7 +27,9 @@ echo "ENFServerUrl=${ENFServerUrl}" >>.env echo "SafetynetKey=${SafetynetKey}" >>.env echo "ENFCheckInterval=${ENFCheckInterval}" >>.env echo "CovidStatsUrl=${CovidStatsUrl}" >>.env - +echo "ResourcesUrl=${ResourcesUrl}" >>.env +echo "AssetWhitelist=${AssetWhitelist}" >>.env +echo "ANDROID_VERSION_CODE_OFFSET=${ANDROID_VERSION_CODE_OFFSET}" >>.env printf "\n.env created with contents:\n" cat .env diff --git a/setup.ts b/setup.ts index b9f6cbb..5077d06 100644 --- a/setup.ts +++ b/setup.ts @@ -24,10 +24,14 @@ import { enableScreens } from "react-native-screens"; import { setupAnalytics } from "./src/analytics/setupAnalytics"; import { configure as configurePush } from "./src/notifications"; -enableScreens(); - const { logError, logInfo } = createLogger("setup"); +if (config.HideLogs) { + LogBox.ignoreAllLogs(); +} + +enableScreens(); + logInfo("app launched with env variables:" + "\n" + formatEnv(true)); async function applyPublicDbFileProtection() { diff --git a/src/App.tsx b/src/App.tsx index 9f767fa..e638dbd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,10 @@ import { colors } from "@constants"; -import { setCurrentRouteName } from "@features/device/reducer"; +import { SwitchProvider } from "@features/dashboard/components/SwitchProvider"; import { ExposureProvider } from "@features/enf/components/ExposureProvider"; import { createPersistor, createStore } from "@lib/reduxStore"; -import { navigationRef } from "@navigation/navigation"; -import { NavigationContainer } from "@react-navigation/native"; +import { NavigationContainer } from "@navigation/NavigationContainer"; import { ModalStack } from "@views/ModalStack"; -import { AnyScreen } from "@views/screens"; -import React, { useCallback, useLayoutEffect, useRef, useState } from "react"; +import React, { useLayoutEffect, useRef, useState } from "react"; import { StatusBar } from "react-native"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { Provider } from "react-redux"; @@ -28,16 +26,6 @@ export function App() { const storeRef = useRef(); const persistorRef = useRef(); - const handleNavigationStateChange = useCallback(() => { - const currentRouteName = navigationRef.current?.getCurrentRoute()?.name; - - if (storeRef.current && currentRouteName) { - storeRef.current.dispatch( - setCurrentRouteName(currentRouteName as AnyScreen), - ); - } - }, []); - if (!hasStore || storeRef.current == null || persistorRef.current == null) { return null; } @@ -49,13 +37,11 @@ export function App() { - - - + + + + + diff --git a/src/__snapshots__/config.spec.ts.snap b/src/__snapshots__/config.spec.ts.snap index 861e27a..9902552 100644 --- a/src/__snapshots__/config.spec.ts.snap +++ b/src/__snapshots__/config.spec.ts.snap @@ -11,6 +11,7 @@ Object { "ApiClientIdIOS": "", "ApiDomain": "", "AppAddress": "", + "AssetWhitelist": "*", "CognitoIdentityPoolId": "", "CognitoUserPoolAppClientId": "", "CognitoUserPoolPoolId": "", @@ -27,11 +28,13 @@ Object { "FeedbackEmailLink": "mailto:help@covidtracer.min.health.nz?subject=NZ COVID Tracer App Feedback", "GeneratePrivateDbEncryptionKey": false, "HelpPageUrl": "https://www.health.govt.nz/our-work/diseases-and-conditions/covid-19-novel-coronavirus/covid-19-novel-coronavirus-resources-and-tools/nz-covid-tracer-app/questions-and-answers-nz-covid-tracer", + "HideLogs": false, "IsDev": true, "MaxCheckInDays": 60, "PinpointApplicationId": "", "PinpointRegion": "", "PrivateDbEncryptionKeyService": "unite-app-react-native-private-db-key", + "ResourcesUrl": "", "SafetynetKey": "", "SupportPhoneLink": "tel:0800800606", "WebAppBaseUrl": "", diff --git a/src/analytics/events.ts b/src/analytics/events.ts index a631df6..7491979 100644 --- a/src/analytics/events.ts +++ b/src/analytics/events.ts @@ -5,6 +5,7 @@ import { ExposureEventPayloads, ExposureEvents, } from "@features/exposure/events"; +import { LinkingEventPayloads, LinkingEvents } from "@linking/events"; /** * TODO Move to smaller files, divided by feature. @@ -40,10 +41,12 @@ const AnalyticsEvent = { ...ExposureEvents, ...AnnouncementEvents, ...DeviceEvents, + ...LinkingEvents, } as const; export type EventPayloads = ENFEventPayloads & ExposureEventPayloads & - DeviceEventPayloads; + DeviceEventPayloads & + LinkingEventPayloads; export { AnalyticsEvent }; diff --git a/src/assets/icons/manual-entry.png b/src/assets/icons/manual-entry.png new file mode 100644 index 0000000..577e6b5 Binary files /dev/null and b/src/assets/icons/manual-entry.png differ diff --git a/src/assets/icons/manual-entry@2x.png b/src/assets/icons/manual-entry@2x.png new file mode 100644 index 0000000..2b69a6f Binary files /dev/null and b/src/assets/icons/manual-entry@2x.png differ diff --git a/src/assets/icons/manual-entry@3x.png b/src/assets/icons/manual-entry@3x.png new file mode 100644 index 0000000..52013f3 Binary files /dev/null and b/src/assets/icons/manual-entry@3x.png differ diff --git a/src/assets/icons/no-connection.png b/src/assets/icons/no-connection.png new file mode 100644 index 0000000..4d8cf80 Binary files /dev/null and b/src/assets/icons/no-connection.png differ diff --git a/src/assets/icons/no-connection@2x.png b/src/assets/icons/no-connection@2x.png new file mode 100644 index 0000000..c3e0315 Binary files /dev/null and b/src/assets/icons/no-connection@2x.png differ diff --git a/src/assets/icons/no-connection@3x.png b/src/assets/icons/no-connection@3x.png new file mode 100644 index 0000000..4cd0a78 Binary files /dev/null and b/src/assets/icons/no-connection@3x.png differ diff --git a/src/components/atoms/Button.tsx b/src/components/atoms/Button.tsx index 23f4af8..b0cb767 100644 --- a/src/components/atoms/Button.tsx +++ b/src/components/atoms/Button.tsx @@ -1,6 +1,6 @@ import { colors, fontSizes } from "@constants"; import React, { useMemo } from "react"; -import { TextStyle, ViewStyle } from "react-native"; +import { StyleSheet, TextStyle, ViewStyle } from "react-native"; import styled from "styled-components/native"; import { testable } from "../../testable"; @@ -105,3 +105,22 @@ function Button({ } export default testable(Button); + +const styles = { + small: StyleSheet.create({ + container: { + paddingHorizontal: 16, + paddingVertical: 20, + }, + text: { + fontSize: fontSizes.small, + }, + }), +}; + +export const presets: { [key: string]: Partial } = { + small: { + style: styles.small.container, + textStyle: styles.small.text, + }, +}; diff --git a/src/components/atoms/CloseButton.tsx b/src/components/atoms/CloseButton.tsx new file mode 100644 index 0000000..b44d65e --- /dev/null +++ b/src/components/atoms/CloseButton.tsx @@ -0,0 +1,50 @@ +import { grid3x } from "@constants"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { StyleSheet } from "react-native"; +import styled from "styled-components/native"; + +import ImageButton from "./ImageButton"; + +const Container = styled(ImageButton)` + padding: 13px 13px ${grid3x}px ${grid3x}px; + position: absolute; + top: 0px; + right: 0px; + z-index: 1; +`; + +interface Props { + onDismiss(): void; + accessibilityLabel?: string; + accessibilityHint?: string; +} + +export function CloseButton({ + onDismiss, + accessibilityHint, + accessibilityLabel, +}: Props) { + const { t } = useTranslation(); + + accessibilityLabel = accessibilityLabel + ? accessibilityLabel + : t("accessibility:button:close"); + + return ( + + ); +} + +export const styles = StyleSheet.create({ + closeButtonImage: { + width: 14.4, + height: 14.4, + }, +}); diff --git a/src/components/atoms/Divider.tsx b/src/components/atoms/Divider.tsx index 241a870..f8d21bf 100644 --- a/src/components/atoms/Divider.tsx +++ b/src/components/atoms/Divider.tsx @@ -4,7 +4,7 @@ import { ViewStyle } from "react-native"; import styled from "styled-components/native"; const Line = styled.View` - background-color: ${colors.divider}; + background-color: ${colors.platinum}; width: 100%; height: 1px; `; diff --git a/src/components/atoms/ErrorBanner.tsx b/src/components/atoms/ErrorBanner.tsx new file mode 100644 index 0000000..d5b95a3 --- /dev/null +++ b/src/components/atoms/ErrorBanner.tsx @@ -0,0 +1,52 @@ +import { colors, fontFamilies, fontSizes } from "@constants"; +import React from "react"; +import { ImageSourcePropType } from "react-native"; +import styled from "styled-components/native"; + +import { Text } from "./Text"; + +interface ErrorBannerProps { + icon: ImageSourcePropType; + title: string; + body: string; +} + +const Container = styled.View` + flex-direction: row; + background: ${colors.lightPink}; + padding: 18px 20px 14px 20px; +`; + +const Icon = styled.Image` + width: 40px; + height: 40px; +`; + +const Title = styled(Text)` + font-size: ${fontSizes.normal}px; + font-family: ${fontFamilies["baloo-semi-bold"]}; + line-height: 20px; +`; + +const Body = styled(Text)` + font-size: ${fontSizes.small}px; + font-family: ${fontFamilies["open-sans"]}; + line-height: 20px; +`; + +const TextContainer = styled.View` + flex: 1; + margin-left: 14px; +`; + +export function ErrorBanner({ title, body, icon }: ErrorBannerProps) { + return ( + + + + {title} + {body} + + + ); +} diff --git a/src/components/atoms/HeaderButton.tsx b/src/components/atoms/HeaderButton.tsx new file mode 100644 index 0000000..10e2af8 --- /dev/null +++ b/src/components/atoms/HeaderButton.tsx @@ -0,0 +1,47 @@ +import { colors, fontFamilies } from "@constants"; +import React from "react"; +import { ViewStyle } from "react-native"; +import styled from "styled-components/native"; + +export interface HeaderButtonProps { + text: string; + onPress?(): void; + accessibilityLabel?: string; + accessibilityHint?: string; + style?: ViewStyle; +} + +const Container = styled.View` + align-items: center; + justify-content: center; + padding-right: 20px; +`; + +const Title = styled.Text` + text-decoration: underline; + color: ${colors.primaryBlack}; + font-size: 14px; + font-family: ${fontFamilies["open-sans-bold"]}; +`; + +export function HeaderButton({ + onPress, + text, + accessibilityLabel, + accessibilityHint, + style, +}: HeaderButtonProps) { + return ( + + + {text} + + + ); +} diff --git a/src/components/atoms/Image.tsx b/src/components/atoms/Image.tsx new file mode 100644 index 0000000..b6fea02 --- /dev/null +++ b/src/components/atoms/Image.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { ImageSourcePropType } from "react-native"; +import styled from "styled-components/native"; + +import { RemoteImage } from "./RemoteImage"; + +const BaseImage = styled.Image<{ width: number; height: number }>` + width: ${(props) => props.width}px; + height: ${(props) => props.height}px; +`; + +export interface ImageProps { + /** + * source of the image. Pass string to fetch from a url + */ + source: ImageSourcePropType | string; + width: number; + height: number; +} + +export function Image({ source, width, height }: ImageProps) { + return typeof source === "string" ? ( + + ) : ( + + ); +} diff --git a/src/components/atoms/ListPlaceHolder.tsx b/src/components/atoms/ListPlaceHolder.tsx new file mode 100644 index 0000000..715858e --- /dev/null +++ b/src/components/atoms/ListPlaceHolder.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import styled from "styled-components/native"; + +import { PlaceHolder } from "./PlaceHolder"; +import { VerticalSpacing } from "./VerticalSpacing"; + +export const Container = styled.View` + padding: 20px 16px; +`; + +export function ListPlaceHolder() { + return ( + + + + + + + + + + + + + ); +} diff --git a/src/components/atoms/NotAccessible.tsx b/src/components/atoms/NotAccessible.tsx new file mode 100644 index 0000000..8a5b553 --- /dev/null +++ b/src/components/atoms/NotAccessible.tsx @@ -0,0 +1,17 @@ +import React, { ReactNode } from "react"; +import { View } from "react-native"; + +interface Props { + children: ReactNode; +} + +export function NotAccessible({ children }: Props) { + return ( + + {children} + + ); +} diff --git a/src/components/atoms/PanelButton.tsx b/src/components/atoms/PanelButton.tsx new file mode 100644 index 0000000..c826f50 --- /dev/null +++ b/src/components/atoms/PanelButton.tsx @@ -0,0 +1,66 @@ +import Button, { presets as buttonPresets } from "@components/atoms/Button"; +import { + presets as secondaryButtonPresets, + SecondaryButton, +} from "@components/atoms/SecondaryButton"; +import { useLinking } from "@linking/useLinking"; +import React, { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; + +interface PanelButtonProps { + type: "primary" | "secondary"; + text: string; + accessibilityHint?: string; + externalLink?: string; + deepLink?: string; +} + +export function PanelButton({ + type, + text, + accessibilityHint: hintOverride, + externalLink, + deepLink, +}: PanelButtonProps) { + const { openExternalLink, openDeepLink } = useLinking(); + + const handlePress = useCallback(() => { + if (externalLink != null) { + openExternalLink(externalLink); + } else if (deepLink != null) { + openDeepLink(deepLink); + } + }, [externalLink, deepLink, openExternalLink, openDeepLink]); + + const isLink = externalLink != null; + + const { t } = useTranslation(); + + const accessibilityHint = useMemo(() => { + if (hintOverride) { + return hintOverride; + } + return isLink ? t("accessibility:linkAccessibilityHint") : undefined; + }, [hintOverride, t, isLink]); + + switch (type) { + case "primary": + return ( +