├── .watchmanconfig ├── .gitattributes ├── ios ├── Stub.swift ├── ProtectScotland │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── test-protect-logo │ │ │ ├── Contents.json │ │ │ └── test-protect-logo.imageset │ │ │ │ ├── launch.png │ │ │ │ ├── launch@2x.png │ │ │ │ ├── launch@3x.png │ │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Protect Scotland Icon 1024.png │ │ │ ├── Protect Scotland Icon 20@2x.png │ │ │ ├── Protect Scotland Icon 20@3x.png │ │ │ ├── Protect Scotland Icon 29@2x.png │ │ │ ├── Protect Scotland Icon 29@3x.png │ │ │ ├── Protect Scotland Icon 40@2x.png │ │ │ ├── Protect Scotland Icon 40@3x.png │ │ │ ├── Protect Scotland Icon 60@2x.png │ │ │ ├── Protect Scotland Icon 60@3x.png │ │ │ └── Contents.json │ ├── main.m │ ├── ProtectScotland.entitlements │ ├── AppDelegate.h │ ├── Base.lproj │ │ └── LaunchScreen.xib │ └── Info.plist ├── ProtectScotland-Bridging-Header.h ├── .env.default.sample ├── ProtectScotland.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── ProtectScotlandTests.xcscheme │ │ └── ProtectScotland.xcscheme ├── ProtectScotland.xcworkspace │ ├── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist │ └── contents.xcworkspacedata └── fastlane │ ├── Appfile │ ├── Fastfile │ └── README.md ├── app.json ├── app_context.png ├── android ├── fastlane │ ├── Appfile │ ├── Pluginfile │ ├── README.md │ └── Fastfile ├── app │ ├── debug.keystore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── launch.png │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── launch.png │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── launch.png │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── launch.png │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── launch.png │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_notification.png │ │ │ │ └── drawable │ │ │ │ │ └── background_splash.xml │ │ │ └── java │ │ │ │ └── gov │ │ │ │ └── scot │ │ │ │ └── covidtracker │ │ │ │ ├── SplashActivity.java │ │ │ │ ├── generated │ │ │ │ └── BasePackageList.java │ │ │ │ ├── MainApplication.java │ │ │ │ └── MainActivity.java │ │ └── debug │ │ │ └── AndroidManifest.xml │ ├── build_defs.bzl │ ├── proguard-rules.pro │ └── _BUCK ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .env.default.sample ├── settings.gradle ├── build.gradle └── gradle.properties ├── app_server_side.png ├── components ├── views │ ├── onboarding │ │ ├── common.tsx │ │ ├── privacy.tsx │ │ ├── your-data.tsx │ │ ├── test-result.tsx │ │ ├── why-use.tsx │ │ ├── upgrade-notice.tsx │ │ └── permissions-info.tsx │ ├── terms.tsx │ ├── data-policy.tsx │ ├── loading.tsx │ ├── calculator.tsx │ └── tests.tsx ├── molecules │ ├── modal │ │ ├── index.ts │ │ └── clear-contacts.tsx │ ├── new-version-card.tsx │ ├── header.tsx │ ├── onboarding-navbar.tsx │ ├── close-contact-step.tsx │ ├── action-card.tsx │ ├── go-to-settings.tsx │ └── arrow-link.tsx ├── atoms │ ├── spacing.tsx │ ├── heading.tsx │ ├── divider.tsx │ ├── media.tsx │ ├── title.tsx │ ├── rounded-box.tsx │ ├── logo.tsx │ ├── container.tsx │ ├── illustration.tsx │ ├── modal-close.tsx │ ├── progress-bar.tsx │ ├── back.tsx │ ├── text.tsx │ ├── symptom-checker.tsx │ ├── note-link.tsx │ └── message.tsx ├── organisms │ └── modals │ │ ├── index.ts │ │ ├── push-notifications.tsx │ │ ├── bluetooth-notification.tsx │ │ └── exposure-notifications.tsx └── templates │ └── base.tsx ├── constants └── urls.ts ├── .env.sample ├── assets ├── fonts │ ├── Lato-Bold.ttf │ ├── Roboto-Bold.ttf │ ├── Lato-Regular.ttf │ └── Roboto-Regular.ttf └── images │ ├── back │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── logo │ ├── logo.png │ ├── logo@2x.png │ └── logo@3x.png │ ├── map │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── wave │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-back │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-bt │ ├── icon-bt.png │ ├── icon-bt@2x.png │ └── icon-bt@3x.png │ ├── icon-jar │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-logo │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-menu │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-note │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-plus │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-tel │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── symptoms │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── tracing │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── grid-paused │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-arrow │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-close │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-eye │ ├── icon-eye.png │ ├── icon-eye@2x.png │ └── icon-eye@3x.png │ ├── icon-key │ ├── icon-key.png │ ├── icon-key@2x.png │ └── icon-key@3x.png │ ├── icon-paused │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── message │ ├── ios │ │ ├── image.png │ │ ├── image@2x.png │ │ └── image@3x.png │ └── android │ │ ├── image.png │ │ ├── image@2x.png │ │ └── image@3x.png │ ├── icon-bell │ ├── icon-bell.png │ ├── icon-bell@2x.png │ └── icon-bell@3x.png │ ├── icon-comment │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-opt-out │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── restrictions │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── test-view-logo │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── tracing-active │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── warning-icon │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── wave-inverted │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-arrow-white │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-back-light │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-close-green │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-note-yellow │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-union │ ├── icon-union.png │ ├── icon-union@2x.png │ └── icon-union@3x.png │ ├── onboarding-group │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── onboarding-logo │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── tracing-contact │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── tracing-inactive │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── about-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-arrow-purple │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-external-link │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-settings-white │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── test-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-community-white │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-eye-pink │ ├── icon-eye-pink.png │ ├── icon-eye-pink@2x.png │ └── icon-eye-pink@3x.png │ ├── icon-jar-pink │ ├── icon-jar-pink.png │ ├── icon-jar-pink@2x.png │ └── icon-jar-pink@3x.png │ ├── icon-key-pink │ ├── icon-key-pink.png │ ├── icon-key-pink@2x.png │ └── icon-key-pink@3x.png │ ├── privacy-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── tracing-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── why-use-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── calculator-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── community-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── downloads-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-bell-pink │ ├── icon-bell-pink.png │ ├── icon-bell-pink@2x.png │ └── icon-bell-pink@3x.png │ ├── icon-external-link-light │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-tracing-active-big │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── permissions-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── send-notice-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── test-result-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── your-data-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-tracing-inactive-big │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── icon-comment-pink │ ├── icon-comment-pink.png │ ├── icon-comment-pink@2x.png │ └── icon-comment-pink@3x.png │ ├── notification │ ├── ios │ │ ├── age-group-1 │ │ │ ├── image.png │ │ │ ├── image@2x.png │ │ │ └── image@3x.png │ │ └── age-group-2-3 │ │ │ ├── image.png │ │ │ ├── image@2x.png │ │ │ └── image@3x.png │ └── android │ │ ├── age-group-1 │ │ ├── image.png │ │ ├── image@2x.png │ │ └── image@3x.png │ │ └── age-group-2-3 │ │ ├── image.png │ │ ├── image@2x.png │ │ └── image@3x.png │ ├── your-data-modal-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── test-result-modal-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ ├── age-sorting-age-group-2-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png │ └── age-sorting-age-group-3-illustration │ ├── image.png │ ├── image@2x.png │ └── image@3x.png ├── theme ├── layouts │ ├── shared.ts │ ├── index.ts │ ├── onboarding-with-navbar.tsx │ └── scrollable.tsx ├── index.ts ├── colors.ts └── text.ts ├── .buckconfig ├── .prettierrc.js ├── index.js ├── hooks ├── index.ts ├── version.tsx ├── app-state.tsx ├── age-group-translation.tsx ├── a11y-element.tsx └── confirmation-space.tsx ├── Gemfile ├── utils ├── web-browser.ts └── exposure.ts ├── e2e ├── config.json ├── test.e2e.ts └── environment.js ├── services ├── i18n │ ├── common.tsx │ └── index.ts ├── notifications │ ├── hooks.tsx │ ├── reminder.tsx │ └── index.tsx └── api │ └── exposures.ts ├── .github ├── dependabot.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── integration.yml ├── __tests__ └── App-test.tsx ├── .eslintrc.js ├── metro.config.js ├── babel.config.js ├── types └── missingTypes.d.ts ├── .detoxrc.json ├── postinstall.sh ├── navigation.tsx ├── .gitignore └── providers └── reminder.tsx /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /ios/Stub.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | // DO NOT DELETE 3 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ProtectScotland", 3 | "displayName": "Protect Scotland" 4 | } 5 | -------------------------------------------------------------------------------- /app_context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/app_context.png -------------------------------------------------------------------------------- /android/fastlane/Appfile: -------------------------------------------------------------------------------- 1 | json_key_file(ENV["ANDROID_API_KEY_FILE"]) 2 | package_name(ENV["PACKAGE_NAME"]) 3 | -------------------------------------------------------------------------------- /app_server_side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/app_server_side.png -------------------------------------------------------------------------------- /components/views/onboarding/common.tsx: -------------------------------------------------------------------------------- 1 | export interface OnboardingPageProps { 2 | handleNext(): void; 3 | } 4 | -------------------------------------------------------------------------------- /constants/urls.ts: -------------------------------------------------------------------------------- 1 | import {API_HOST} from '@env'; 2 | 3 | export const urls = { 4 | api: API_HOST 5 | }; 6 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | ENV=development 2 | API_HOST= 3 | BUILD_VERSION=0.0.1 4 | SAFETYNET_KEY= 5 | HIDE_DEBUG=n 6 | TEST_TOKEN= 7 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/debug.keystore -------------------------------------------------------------------------------- /assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /assets/images/back/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/back/image.png -------------------------------------------------------------------------------- /assets/images/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/logo/logo.png -------------------------------------------------------------------------------- /assets/images/map/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/map/image.png -------------------------------------------------------------------------------- /assets/images/wave/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/wave/image.png -------------------------------------------------------------------------------- /assets/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /assets/images/logo/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/logo/logo@2x.png -------------------------------------------------------------------------------- /assets/images/logo/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/logo/logo@3x.png -------------------------------------------------------------------------------- /assets/images/map/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/map/image@2x.png -------------------------------------------------------------------------------- /assets/images/map/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/map/image@3x.png -------------------------------------------------------------------------------- /components/molecules/modal/index.ts: -------------------------------------------------------------------------------- 1 | import Modal from './modal'; 2 | 3 | export * from './modal'; 4 | 5 | export default Modal; 6 | -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "author": "xcode", 4 | "version": 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Protect Scotland 3 | 4 | -------------------------------------------------------------------------------- /assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /assets/images/back/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/back/image@2x.png -------------------------------------------------------------------------------- /assets/images/back/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/back/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-back/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-back/image.png -------------------------------------------------------------------------------- /assets/images/icon-bt/icon-bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bt/icon-bt.png -------------------------------------------------------------------------------- /assets/images/icon-jar/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-jar/image.png -------------------------------------------------------------------------------- /assets/images/icon-logo/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-logo/image.png -------------------------------------------------------------------------------- /assets/images/icon-menu/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-menu/image.png -------------------------------------------------------------------------------- /assets/images/icon-note/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-note/image.png -------------------------------------------------------------------------------- /assets/images/icon-plus/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-plus/image.png -------------------------------------------------------------------------------- /assets/images/icon-tel/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tel/image.png -------------------------------------------------------------------------------- /assets/images/symptoms/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/symptoms/image.png -------------------------------------------------------------------------------- /assets/images/tracing/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing/image.png -------------------------------------------------------------------------------- /assets/images/wave/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/wave/image@2x.png -------------------------------------------------------------------------------- /assets/images/wave/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/wave/image@3x.png -------------------------------------------------------------------------------- /theme/layouts/shared.ts: -------------------------------------------------------------------------------- 1 | export const SPACING_HORIZONTAL = 40; 2 | export const SPACING_TOP = 16; 3 | export const SPACING_BOTTOM = 8; 4 | -------------------------------------------------------------------------------- /assets/images/grid-paused/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/grid-paused/image.png -------------------------------------------------------------------------------- /assets/images/icon-arrow/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow/image.png -------------------------------------------------------------------------------- /assets/images/icon-close/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-close/image.png -------------------------------------------------------------------------------- /assets/images/icon-eye/icon-eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-eye/icon-eye.png -------------------------------------------------------------------------------- /assets/images/icon-jar/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-jar/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-jar/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-jar/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-key/icon-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-key/icon-key.png -------------------------------------------------------------------------------- /assets/images/icon-paused/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-paused/image.png -------------------------------------------------------------------------------- /assets/images/icon-tel/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tel/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-tel/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tel/image@3x.png -------------------------------------------------------------------------------- /assets/images/message/ios/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/message/ios/image.png -------------------------------------------------------------------------------- /assets/images/symptoms/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/symptoms/image@2x.png -------------------------------------------------------------------------------- /assets/images/symptoms/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/symptoms/image@3x.png -------------------------------------------------------------------------------- /assets/images/tracing/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing/image@2x.png -------------------------------------------------------------------------------- /assets/images/tracing/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing/image@3x.png -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | [android] 2 | target = Google Inc.:Google APIs:23 3 | 4 | [maven_repositories] 5 | central = https://repo1.maven.org/maven2 6 | -------------------------------------------------------------------------------- /assets/images/grid-paused/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/grid-paused/image@2x.png -------------------------------------------------------------------------------- /assets/images/grid-paused/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/grid-paused/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-arrow/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-arrow/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-back/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-back/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-back/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-back/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-bell/icon-bell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bell/icon-bell.png -------------------------------------------------------------------------------- /assets/images/icon-bt/icon-bt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bt/icon-bt@2x.png -------------------------------------------------------------------------------- /assets/images/icon-bt/icon-bt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bt/icon-bt@3x.png -------------------------------------------------------------------------------- /assets/images/icon-close/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-close/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-close/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-close/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-comment/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-comment/image.png -------------------------------------------------------------------------------- /assets/images/icon-eye/icon-eye@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-eye/icon-eye@2x.png -------------------------------------------------------------------------------- /assets/images/icon-eye/icon-eye@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-eye/icon-eye@3x.png -------------------------------------------------------------------------------- /assets/images/icon-key/icon-key@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-key/icon-key@2x.png -------------------------------------------------------------------------------- /assets/images/icon-key/icon-key@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-key/icon-key@3x.png -------------------------------------------------------------------------------- /assets/images/icon-logo/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-logo/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-logo/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-logo/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-menu/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-menu/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-menu/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-menu/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-note/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-note/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-note/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-note/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-opt-out/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-opt-out/image.png -------------------------------------------------------------------------------- /assets/images/icon-paused/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-paused/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-paused/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-paused/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-plus/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-plus/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-plus/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-plus/image@3x.png -------------------------------------------------------------------------------- /assets/images/message/ios/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/message/ios/image@2x.png -------------------------------------------------------------------------------- /assets/images/message/ios/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/message/ios/image@3x.png -------------------------------------------------------------------------------- /assets/images/restrictions/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/restrictions/image.png -------------------------------------------------------------------------------- /assets/images/test-view-logo/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-view-logo/image.png -------------------------------------------------------------------------------- /assets/images/tracing-active/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-active/image.png -------------------------------------------------------------------------------- /assets/images/warning-icon/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/warning-icon/image.png -------------------------------------------------------------------------------- /assets/images/wave-inverted/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/wave-inverted/image.png -------------------------------------------------------------------------------- /assets/images/icon-arrow-white/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow-white/image.png -------------------------------------------------------------------------------- /assets/images/icon-back-light/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-back-light/image.png -------------------------------------------------------------------------------- /assets/images/icon-bell/icon-bell@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bell/icon-bell@2x.png -------------------------------------------------------------------------------- /assets/images/icon-bell/icon-bell@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bell/icon-bell@3x.png -------------------------------------------------------------------------------- /assets/images/icon-close-green/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-close-green/image.png -------------------------------------------------------------------------------- /assets/images/icon-comment/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-comment/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-comment/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-comment/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-note-yellow/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-note-yellow/image.png -------------------------------------------------------------------------------- /assets/images/icon-opt-out/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-opt-out/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-opt-out/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-opt-out/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-union/icon-union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-union/icon-union.png -------------------------------------------------------------------------------- /assets/images/message/android/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/message/android/image.png -------------------------------------------------------------------------------- /assets/images/onboarding-group/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/onboarding-group/image.png -------------------------------------------------------------------------------- /assets/images/onboarding-logo/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/onboarding-logo/image.png -------------------------------------------------------------------------------- /assets/images/restrictions/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/restrictions/image@2x.png -------------------------------------------------------------------------------- /assets/images/restrictions/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/restrictions/image@3x.png -------------------------------------------------------------------------------- /assets/images/tracing-contact/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-contact/image.png -------------------------------------------------------------------------------- /assets/images/tracing-inactive/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-inactive/image.png -------------------------------------------------------------------------------- /assets/images/warning-icon/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/warning-icon/image@2x.png -------------------------------------------------------------------------------- /assets/images/warning-icon/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/warning-icon/image@3x.png -------------------------------------------------------------------------------- /assets/images/wave-inverted/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/wave-inverted/image@2x.png -------------------------------------------------------------------------------- /assets/images/wave-inverted/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/wave-inverted/image@3x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/test-protect-logo/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "author": "xcode", 4 | "version": 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'none' 6 | }; 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /assets/images/about-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/about-illustration/image.png -------------------------------------------------------------------------------- /assets/images/icon-arrow-purple/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow-purple/image.png -------------------------------------------------------------------------------- /assets/images/icon-arrow-white/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow-white/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-arrow-white/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow-white/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-back-light/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-back-light/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-back-light/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-back-light/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-close-green/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-close-green/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-close-green/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-close-green/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-external-link/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-external-link/image.png -------------------------------------------------------------------------------- /assets/images/icon-note-yellow/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-note-yellow/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-note-yellow/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-note-yellow/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-settings-white/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-settings-white/image.png -------------------------------------------------------------------------------- /assets/images/icon-union/icon-union@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-union/icon-union@2x.png -------------------------------------------------------------------------------- /assets/images/icon-union/icon-union@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-union/icon-union@3x.png -------------------------------------------------------------------------------- /assets/images/message/android/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/message/android/image@2x.png -------------------------------------------------------------------------------- /assets/images/message/android/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/message/android/image@3x.png -------------------------------------------------------------------------------- /assets/images/onboarding-group/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/onboarding-group/image@2x.png -------------------------------------------------------------------------------- /assets/images/onboarding-group/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/onboarding-group/image@3x.png -------------------------------------------------------------------------------- /assets/images/onboarding-logo/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/onboarding-logo/image@2x.png -------------------------------------------------------------------------------- /assets/images/onboarding-logo/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/onboarding-logo/image@3x.png -------------------------------------------------------------------------------- /assets/images/test-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-illustration/image.png -------------------------------------------------------------------------------- /assets/images/test-view-logo/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-view-logo/image@2x.png -------------------------------------------------------------------------------- /assets/images/test-view-logo/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-view-logo/image@3x.png -------------------------------------------------------------------------------- /assets/images/tracing-active/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-active/image@2x.png -------------------------------------------------------------------------------- /assets/images/tracing-active/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-active/image@3x.png -------------------------------------------------------------------------------- /assets/images/tracing-contact/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-contact/image@2x.png -------------------------------------------------------------------------------- /assets/images/tracing-contact/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-contact/image@3x.png -------------------------------------------------------------------------------- /assets/images/tracing-inactive/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-inactive/image@2x.png -------------------------------------------------------------------------------- /assets/images/tracing-inactive/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-inactive/image@3x.png -------------------------------------------------------------------------------- /ios/ProtectScotland-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /assets/images/about-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/about-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/about-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/about-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-arrow-purple/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow-purple/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-arrow-purple/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-arrow-purple/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-community-white/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-community-white/image.png -------------------------------------------------------------------------------- /assets/images/icon-external-link/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-external-link/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-external-link/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-external-link/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-eye-pink/icon-eye-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-eye-pink/icon-eye-pink.png -------------------------------------------------------------------------------- /assets/images/icon-jar-pink/icon-jar-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-jar-pink/icon-jar-pink.png -------------------------------------------------------------------------------- /assets/images/icon-key-pink/icon-key-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-key-pink/icon-key-pink.png -------------------------------------------------------------------------------- /assets/images/privacy-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/privacy-illustration/image.png -------------------------------------------------------------------------------- /assets/images/test-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/test-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/tracing-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-illustration/image.png -------------------------------------------------------------------------------- /assets/images/why-use-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/why-use-illustration/image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-hdpi/launch.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-mdpi/launch.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xhdpi/launch.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6B11A5 4 | -------------------------------------------------------------------------------- /assets/images/calculator-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/calculator-illustration/image.png -------------------------------------------------------------------------------- /assets/images/community-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/community-illustration/image.png -------------------------------------------------------------------------------- /assets/images/downloads-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/downloads-illustration/image.png -------------------------------------------------------------------------------- /assets/images/icon-bell-pink/icon-bell-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bell-pink/icon-bell-pink.png -------------------------------------------------------------------------------- /assets/images/icon-community-white/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-community-white/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-community-white/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-community-white/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-external-link-light/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-external-link-light/image.png -------------------------------------------------------------------------------- /assets/images/icon-eye-pink/icon-eye-pink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-eye-pink/icon-eye-pink@2x.png -------------------------------------------------------------------------------- /assets/images/icon-eye-pink/icon-eye-pink@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-eye-pink/icon-eye-pink@3x.png -------------------------------------------------------------------------------- /assets/images/icon-jar-pink/icon-jar-pink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-jar-pink/icon-jar-pink@2x.png -------------------------------------------------------------------------------- /assets/images/icon-jar-pink/icon-jar-pink@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-jar-pink/icon-jar-pink@3x.png -------------------------------------------------------------------------------- /assets/images/icon-key-pink/icon-key-pink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-key-pink/icon-key-pink@2x.png -------------------------------------------------------------------------------- /assets/images/icon-key-pink/icon-key-pink@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-key-pink/icon-key-pink@3x.png -------------------------------------------------------------------------------- /assets/images/icon-settings-white/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-settings-white/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-settings-white/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-settings-white/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-tracing-active-big/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tracing-active-big/image.png -------------------------------------------------------------------------------- /assets/images/permissions-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/permissions-illustration/image.png -------------------------------------------------------------------------------- /assets/images/privacy-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/privacy-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/privacy-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/privacy-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/send-notice-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/send-notice-illustration/image.png -------------------------------------------------------------------------------- /assets/images/test-result-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-result-illustration/image.png -------------------------------------------------------------------------------- /assets/images/tracing-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/tracing-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/tracing-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/why-use-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/why-use-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/why-use-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/why-use-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/your-data-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/your-data-illustration/image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xxhdpi/launch.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launch.png -------------------------------------------------------------------------------- /assets/images/calculator-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/calculator-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/calculator-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/calculator-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/community-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/community-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/community-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/community-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/downloads-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/downloads-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/downloads-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/downloads-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-bell-pink/icon-bell-pink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bell-pink/icon-bell-pink@2x.png -------------------------------------------------------------------------------- /assets/images/icon-bell-pink/icon-bell-pink@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-bell-pink/icon-bell-pink@3x.png -------------------------------------------------------------------------------- /assets/images/icon-tracing-active-big/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tracing-active-big/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-tracing-active-big/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tracing-active-big/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-tracing-inactive-big/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tracing-inactive-big/image.png -------------------------------------------------------------------------------- /assets/images/your-data-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/your-data-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/your-data-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/your-data-illustration/image@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-increment_version_code' 6 | -------------------------------------------------------------------------------- /assets/images/icon-comment-pink/icon-comment-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-comment-pink/icon-comment-pink.png -------------------------------------------------------------------------------- /assets/images/icon-external-link-light/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-external-link-light/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-external-link-light/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-external-link-light/image@3x.png -------------------------------------------------------------------------------- /assets/images/icon-tracing-inactive-big/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tracing-inactive-big/image@2x.png -------------------------------------------------------------------------------- /assets/images/icon-tracing-inactive-big/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-tracing-inactive-big/image@3x.png -------------------------------------------------------------------------------- /assets/images/notification/ios/age-group-1/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/ios/age-group-1/image.png -------------------------------------------------------------------------------- /assets/images/permissions-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/permissions-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/permissions-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/permissions-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/send-notice-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/send-notice-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/send-notice-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/send-notice-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/test-result-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-result-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/test-result-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-result-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/your-data-modal-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/your-data-modal-illustration/image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/images/notification/ios/age-group-1/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/ios/age-group-1/image@2x.png -------------------------------------------------------------------------------- /assets/images/notification/ios/age-group-1/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/ios/age-group-1/image@3x.png -------------------------------------------------------------------------------- /assets/images/notification/ios/age-group-2-3/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/ios/age-group-2-3/image.png -------------------------------------------------------------------------------- /assets/images/test-result-modal-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-result-modal-illustration/image.png -------------------------------------------------------------------------------- /assets/images/your-data-modal-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/your-data-modal-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/your-data-modal-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/your-data-modal-illustration/image@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-hdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-mdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /assets/images/icon-comment-pink/icon-comment-pink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-comment-pink/icon-comment-pink@2x.png -------------------------------------------------------------------------------- /assets/images/icon-comment-pink/icon-comment-pink@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/icon-comment-pink/icon-comment-pink@3x.png -------------------------------------------------------------------------------- /assets/images/notification/android/age-group-1/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/android/age-group-1/image.png -------------------------------------------------------------------------------- /assets/images/notification/android/age-group-2-3/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/android/age-group-2-3/image.png -------------------------------------------------------------------------------- /assets/images/notification/ios/age-group-2-3/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/ios/age-group-2-3/image@2x.png -------------------------------------------------------------------------------- /assets/images/notification/ios/age-group-2-3/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/ios/age-group-2-3/image@3x.png -------------------------------------------------------------------------------- /assets/images/test-result-modal-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-result-modal-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/test-result-modal-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/test-result-modal-illustration/image@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /assets/images/age-sorting-age-group-2-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/age-sorting-age-group-2-illustration/image.png -------------------------------------------------------------------------------- /assets/images/age-sorting-age-group-3-illustration/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/age-sorting-age-group-3-illustration/image.png -------------------------------------------------------------------------------- /assets/images/notification/android/age-group-1/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/android/age-group-1/image@2x.png -------------------------------------------------------------------------------- /assets/images/notification/android/age-group-1/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/android/age-group-1/image@3x.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry} from 'react-native'; 2 | import App from './App'; 3 | import {name as appName} from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /assets/images/age-sorting-age-group-2-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/age-sorting-age-group-2-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/age-sorting-age-group-2-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/age-sorting-age-group-2-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/age-sorting-age-group-3-illustration/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/age-sorting-age-group-3-illustration/image@2x.png -------------------------------------------------------------------------------- /assets/images/age-sorting-age-group-3-illustration/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/age-sorting-age-group-3-illustration/image@3x.png -------------------------------------------------------------------------------- /assets/images/notification/android/age-group-2-3/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/android/age-group-2-3/image@2x.png -------------------------------------------------------------------------------- /assets/images/notification/android/age-group-2-3/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/assets/images/notification/android/age-group-2-3/image@3x.png -------------------------------------------------------------------------------- /hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './a11y-element'; 2 | export * from './app-state'; 3 | export * from './confirmation-space'; 4 | export * from './version'; 5 | export * from './age-group-translation'; 6 | -------------------------------------------------------------------------------- /theme/layouts/index.ts: -------------------------------------------------------------------------------- 1 | import {Scrollable} from './scrollable'; 2 | import OnboardingWithNavbar from './onboarding-with-navbar'; 3 | 4 | export default { 5 | Scrollable, 6 | OnboardingWithNavbar 7 | }; 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | 5 | 6 | plugins_path = File.join(File.dirname(__FILE__), 'android', 'fastlane', 'Pluginfile') 7 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 8 | -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 1024.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 20@2x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 20@3x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 29@2x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 29@3x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 40@2x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 40@3x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 60@2x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Protect Scotland Icon 60@3x.png -------------------------------------------------------------------------------- /utils/web-browser.ts: -------------------------------------------------------------------------------- 1 | import * as WebBrowser from 'expo-web-browser'; 2 | 3 | export const openBrowserAsync = (link: string) => 4 | WebBrowser.openBrowserAsync(link, { 5 | enableBarCollapsing: true, 6 | showInRecents: true 7 | }); 8 | -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/test-protect-logo/test-protect-logo.imageset/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/test-protect-logo/test-protect-logo.imageset/launch.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/test-protect-logo/test-protect-logo.imageset/launch@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/test-protect-logo/test-protect-logo.imageset/launch@2x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/test-protect-logo/test-protect-logo.imageset/launch@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NES-Digital-Service/protect-scotland/HEAD/ios/ProtectScotland/Images.xcassets/test-protect-logo/test-protect-logo.imageset/launch@3x.png -------------------------------------------------------------------------------- /ios/ProtectScotland/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ios/.env.default.sample: -------------------------------------------------------------------------------- 1 | APPLE_ID=yourappleid@email.com 2 | APP_IDENTIFIER=gov.scot.covidtracker 3 | ITC_TEAM_ID=itunes_connect_team_id 4 | TEAM_ID=apple_dev_team_id 5 | XCODEPROJ=ProtectScotland.xcodeproj 6 | WORKSPACE=ProtectScotland.xcworkspace 7 | SCHEME=ProtectScotland 8 | -------------------------------------------------------------------------------- /components/atoms/spacing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View} from 'react-native'; 3 | import {scale} from '../../theme'; 4 | 5 | const Spacing: React.FC<{s: number}> = ({s}) => ( 6 | 7 | ); 8 | 9 | export default Spacing; 10 | -------------------------------------------------------------------------------- /e2e/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ts-jest", 3 | "testRunner": "jest-circus/runner", 4 | "testTimeout": 120000, 5 | "testEnvironment": "./environment", 6 | "testRegex": "\\.e2e\\.ts$", 7 | "reporters": ["detox/runners/jest/streamlineReporter"], 8 | "verbose": true 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 26 08:35:18 GMT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/ProtectScotland.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /services/i18n/common.tsx: -------------------------------------------------------------------------------- 1 | import en from '../../assets/lang/en.json'; 2 | 3 | export const fallback = 'en'; 4 | export const defaultNamespace = 'common'; 5 | export const namespaces = ['common']; 6 | 7 | export const supportedLocales = { 8 | en: { 9 | name: 'English', 10 | ...en 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: 'npm' 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: '/' 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: 'daily' -------------------------------------------------------------------------------- /ios/ProtectScotland.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/ProtectScotland/ProtectScotland.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.exposure-notification 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/ProtectScotland.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/ProtectScotland.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /__tests__/App-test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import App from '../App'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | renderer.create(); 10 | }); 11 | -------------------------------------------------------------------------------- /e2e/test.e2e.ts: -------------------------------------------------------------------------------- 1 | import {expect, device, element, by} from 'detox'; 2 | 3 | describe('E2E journey', () => { 4 | beforeAll(async () => { 5 | await device.launchApp({newInstance: true}); 6 | }); 7 | 8 | it('should have welcome screen', async () => { 9 | await expect(element(by.id('welcome'))).toBeVisible(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /ios/ProtectScotland.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@react-native-community', 'plugin:react-native-a11y/all'], 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['@typescript-eslint', 'jest', 'detox'], 6 | env: { 7 | 'jest/globals': true, 8 | 'detox/detox': true 9 | }, 10 | rules: { 11 | 'comma-dangle': 'off' 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /components/organisms/modals/index.ts: -------------------------------------------------------------------------------- 1 | import {ExposureNotificationsModal} from './exposure-notifications'; 2 | import {BluetoothNotificationsModal} from './bluetooth-notification'; 3 | import {PushNotificationsModal} from './push-notifications'; 4 | 5 | export { 6 | ExposureNotificationsModal, 7 | BluetoothNotificationsModal, 8 | PushNotificationsModal 9 | }; 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What does this PR do? 2 | 3 | 4 | 5 | ### Related issue 6 | 7 | 8 | 9 | ### Additional notes & screenshots 10 | 11 | 12 | -------------------------------------------------------------------------------- /ios/fastlane/Appfile: -------------------------------------------------------------------------------- 1 | app_identifier(ENV['APP_IDENTIFIER']) # The bundle identifier of your app 2 | apple_id(ENV['APPLE_ID']) # Your Apple email address 3 | 4 | itc_team_id(ENV['ITC_TEAM_ID']) # App Store Connect Team ID 5 | team_id(ENV['TEAM_ID']) # Developer Portal Team ID 6 | 7 | # For more information about the Appfile, see: 8 | # https://docs.fastlane.tools/advanced/#appfile 9 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false 14 | } 15 | }) 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /android/.env.default.sample: -------------------------------------------------------------------------------- 1 | # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one 2 | ANDROID_API_KEY_FILE="/path/to/your/play/api/android-api-key.json" 3 | ANDROID_KEYSTORE_PATH="/path/to/your/upload-keystore.jks" 4 | ANDROID_KEYSTORE_PASSWORD=yourkeystorepasswd 5 | ANDROID_KEY_ALIAS=upload 6 | ANDROID_KEY_PASSWORD=yourkeypasswd 7 | PACKAGE_NAME="gov.scot.covidtracker" 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | env: { 4 | production: { 5 | plugins: ['transform-remove-console'] 6 | } 7 | }, 8 | plugins: [ 9 | [ 10 | 'dotenv-import', 11 | { 12 | moduleName: '@env', 13 | path: '.env', 14 | safe: false, 15 | allowUndefined: false 16 | } 17 | ] 18 | ] 19 | }; 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /e2e/environment.js: -------------------------------------------------------------------------------- 1 | const { 2 | DetoxCircusEnvironment, 3 | SpecReporter, 4 | WorkerAssignReporter 5 | } = require('detox/runners/jest-circus'); 6 | 7 | class CustomDetoxEnvironment extends DetoxCircusEnvironment { 8 | constructor(config) { 9 | super(config); 10 | 11 | this.registerListeners({ 12 | SpecReporter, 13 | WorkerAssignReporter 14 | }); 15 | } 16 | } 17 | 18 | module.exports = CustomDetoxEnvironment; 19 | -------------------------------------------------------------------------------- /components/templates/base.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet} from 'react-native'; 3 | 4 | import Container from '../atoms/container'; 5 | import {colors} from '../../theme'; 6 | 7 | const Base: React.FC = ({children}) => ( 8 | {children} 9 | ); 10 | 11 | const styles = StyleSheet.create({ 12 | container: { 13 | backgroundColor: colors.primaryPurple 14 | } 15 | }); 16 | 17 | export {Base}; 18 | -------------------------------------------------------------------------------- /hooks/version.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react'; 2 | import {getVersion, Version} from 'react-native-exposure-notification-service'; 3 | 4 | export function useVersion(): Version | undefined { 5 | const [version, setVersion] = useState(); 6 | useEffect(() => { 7 | const getVer = async () => { 8 | const ver = await getVersion(); 9 | setVersion(ver); 10 | }; 11 | getVer(); 12 | }, []); 13 | 14 | return version; 15 | } 16 | -------------------------------------------------------------------------------- /types/missingTypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-native-easy-markdown'; 2 | declare module 'react-native-ssl-pinning'; 3 | declare module 'react-native-google-safetynet'; 4 | declare module 'react-native-ios11-devicecheck'; 5 | declare module '@env' { 6 | export const API_HOST: string; 7 | export const BUILD_VERSION: string; 8 | export const ENV: 'development' | 'production'; 9 | export const SAFETYNET_KEY: string; 10 | export const HIDE_DEBUG: 'y' | 'n'; 11 | export const TEST_TOKEN: string; 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/java/gov/scot/covidtracker/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package gov.scot.covidtracker; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | public class SplashActivity extends AppCompatActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | 12 | Intent intent = new Intent(this, MainActivity.class); 13 | startActivity(intent); 14 | finish(); 15 | } 16 | } -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/test-protect-logo/test-protect-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "launch.png", 5 | "idiom": "universal", 6 | "scale": "1x" 7 | }, 8 | { 9 | "filename": "launch@2x.png", 10 | "idiom": "universal", 11 | "scale": "2x" 12 | }, 13 | { 14 | "filename": "launch@3x.png", 15 | "idiom": "universal", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "author": "xcode", 21 | "version": 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /theme/index.ts: -------------------------------------------------------------------------------- 1 | import {Dimensions, Platform} from 'react-native'; 2 | import colors from './colors'; 3 | 4 | import getTextStyles from './text'; 5 | 6 | const SCREEN_HEIGHT = Dimensions.get('window').height; 7 | const PADDING_TOP = Platform.OS === 'ios' ? 65 : 30; 8 | const REF_HEIGHT = 667; 9 | 10 | const text = getTextStyles(scale); 11 | 12 | function scale(value: number): number { 13 | const ratio = value / REF_HEIGHT; 14 | return Math.min(Math.round(ratio * SCREEN_HEIGHT), value); 15 | } 16 | 17 | export {scale, text, colors, PADDING_TOP}; 18 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /hooks/app-state.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react'; 2 | import {AppStateStatus, AppState} from 'react-native'; 3 | 4 | export function useAppState(): [AppStateStatus] { 5 | const [state, setState] = useState(AppState.currentState); 6 | const handler = (nextState: AppStateStatus) => { 7 | setState(nextState); 8 | }; 9 | 10 | useEffect(() => { 11 | AppState.addEventListener('change', handler); 12 | return () => { 13 | AppState.removeEventListener('change', handler); 14 | }; 15 | }, []); 16 | 17 | return [state]; 18 | } 19 | -------------------------------------------------------------------------------- /ios/ProtectScotland/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | 7 | @interface AppDelegate : UIResponder 8 | 9 | @property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter; 10 | @property (nonatomic, strong) UIWindow *window; 11 | @property (strong, nonatomic) UIViewController *viewController; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /services/notifications/hooks.tsx: -------------------------------------------------------------------------------- 1 | import {PushNotification as PushNotificationType} from 'react-native-push-notification'; 2 | import {NavigationContainerRef} from '@react-navigation/native'; 3 | export const notificationHooks: NotificationHooks = {}; 4 | 5 | interface Token { 6 | os: string; 7 | token: string; 8 | } 9 | 10 | export interface NotificationHooks { 11 | handleNotification?: (notification: PushNotificationType) => void; 12 | handleRegister?: (token: Token) => void; 13 | handleAction?: (notification: PushNotificationType) => void; 14 | navigation?: NavigationContainerRef; 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | checks: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12.x 16 | 17 | - name: Cache dependencies 18 | uses: actions/cache@v2 19 | with: 20 | path: | 21 | **/node_modules 22 | key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} 23 | 24 | - run: yarn 25 | 26 | - run: yarn run lint 27 | - run: yarn run typecheck 28 | -------------------------------------------------------------------------------- /components/atoms/heading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Text, StyleSheet, View} from 'react-native'; 3 | 4 | import Spacing from './spacing'; 5 | 6 | interface HeadingProps { 7 | text: string; 8 | lineWidth?: number; 9 | } 10 | 11 | export const Heading: React.FC = ({text, lineWidth}) => ( 12 | <> 13 | {text} 14 | 15 | 16 | 17 | ); 18 | 19 | const styles = StyleSheet.create({ 20 | heading: { 21 | paddingBottom: 8 22 | }, 23 | line: { 24 | height: 6, 25 | flexDirection: 'row' 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /components/atoms/divider.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {View, StyleSheet} from 'react-native'; 3 | import {colors} from '../../theme'; 4 | 5 | interface DividerProps { 6 | color?: 'lilac' | 'white'; 7 | } 8 | 9 | export const Divider: FC = ({color = 'lilac'}) => ( 10 | 11 | 12 | 13 | ); 14 | 15 | const styles = StyleSheet.create({ 16 | divider: { 17 | height: 2, 18 | width: '100%' 19 | }, 20 | fill: { 21 | ...StyleSheet.absoluteFillObject, 22 | left: -10, 23 | right: -10 24 | } 25 | }); 26 | 27 | export default Divider; 28 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /components/atoms/media.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {View, StyleSheet, StyleProp, ViewStyle} from 'react-native'; 3 | 4 | import Container from './container'; 5 | 6 | interface MediaProps { 7 | left: React.ReactNode; 8 | leftStyle?: StyleProp; 9 | } 10 | 11 | const Media: FC = ({left, leftStyle, children}) => ( 12 | 13 | {left} 14 | {children} 15 | 16 | ); 17 | 18 | const styles = StyleSheet.create({ 19 | row: { 20 | flexDirection: 'row' 21 | }, 22 | left: { 23 | marginRight: 25 24 | } 25 | }); 26 | 27 | export default Media; 28 | -------------------------------------------------------------------------------- /components/atoms/title.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import Text, {TextProps} from '../atoms/text'; 6 | 7 | interface TitleProps extends TextProps { 8 | title: string; 9 | } 10 | 11 | export const Title: FC = ({title, ...props}) => { 12 | const {t} = useTranslation(); 13 | return ( 14 | 20 | {t(title)} 21 | 22 | ); 23 | }; 24 | 25 | const styles = StyleSheet.create({ 26 | title: { 27 | marginBottom: 30 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /.detoxrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "testRunner": "jest", 3 | "runnerConfig": "e2e/config.json", 4 | "configurations": { 5 | "ios.sim.debug": { 6 | "type": "ios.simulator", 7 | "binaryPath": "./ios/build/Build/Products/Debug-iphonesimulator/Protect-Scot.app", 8 | "build": "xcodebuild -workspace ios/ProtectScotland.xcworkspace -scheme ProtectScotland -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", 9 | "device": { 10 | "type": "iPhone 11 Pro Max" 11 | } 12 | }, 13 | "android.sim.debug": { 14 | "type": "android.emulator", 15 | "binaryPath": "SPECIFY_PATH_TO_YOUR_APP_BINARY", 16 | "device": { 17 | "avdName": "Pixel_2_API_29" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/atoms/rounded-box.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, View, ViewStyle} from 'react-native'; 3 | 4 | import {colors} from '../../theme'; 5 | 6 | interface RoundedBoxProps { 7 | children: React.ReactNode; 8 | style?: ViewStyle; 9 | } 10 | 11 | const RoundedBox: FC = ({children, style}) => ( 12 | {children} 13 | ); 14 | 15 | const styles = StyleSheet.create({ 16 | container: { 17 | borderWidth: 1, 18 | borderStyle: 'solid', 19 | borderRadius: 10, 20 | flex: 1, 21 | paddingVertical: 23, 22 | paddingHorizontal: 30, 23 | width: '100%', 24 | color: colors.darkGrey, 25 | borderColor: colors.darkGrey 26 | } 27 | }); 28 | 29 | export default RoundedBox; 30 | -------------------------------------------------------------------------------- /ios/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | desc "Push a new beta build to TestFlight" 20 | lane :beta do 21 | increment_build_number(xcodeproj: ENV['XCODEPROJ']) 22 | build_app(workspace: ENV['WORKSPACE'], scheme: ENV['SCHEME']) 23 | upload_to_testflight 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /services/notifications/reminder.tsx: -------------------------------------------------------------------------------- 1 | import {PushNotification as PushNotificationType} from 'react-native-push-notification'; 2 | import {NotificationHooks} from 'services/notifications/hooks'; 3 | import {ScreenNames} from '../../navigation'; 4 | 5 | const MAX_ATTEMPTS = 50; 6 | const ATTEMPTS_DELAY = 200; 7 | 8 | export const reminderNotification = { 9 | id: 12345, 10 | handler: (notification: PushNotificationType, hooks: NotificationHooks) => { 11 | console.log(hooks); 12 | let attempts = 0; 13 | const handle = () => { 14 | if (hooks.navigation) { 15 | hooks.navigation.navigate(ScreenNames.tracing, {notification: true}); 16 | } else if (attempts < MAX_ATTEMPTS) { 17 | attempts++; 18 | setTimeout(handle, ATTEMPTS_DELAY); 19 | } 20 | }; 21 | handle(); 22 | } 23 | } as const; 24 | -------------------------------------------------------------------------------- /ios/fastlane/README.md: -------------------------------------------------------------------------------- 1 | # fastlane documentation 2 | 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | 13 | ``` 14 | [sudo] gem install fastlane -NV 15 | ``` 16 | 17 | or alternatively using `brew install fastlane` 18 | 19 | # Available Actions 20 | 21 | ## iOS 22 | 23 | ### ios beta 24 | 25 | ``` 26 | fastlane ios beta 27 | ``` 28 | 29 | Push a new beta build to TestFlight 30 | 31 | --- 32 | 33 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 34 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 35 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 36 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ProtectScotland' 2 | apply from: '../node_modules/react-native-unimodules/gradle.groovy' 3 | includeUnimodulesProjects() 4 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 5 | include ':app' 6 | 7 | include ':react-native-permissions' 8 | project(':react-native-permissions').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-permissions/android') 9 | 10 | include ':react-native-push-notification' 11 | project(':react-native-push-notification').projectDir = file('../node_modules/react-native-push-notification/android') 12 | 13 | include ':react-native-google-safetynet' 14 | project(':react-native-google-safetynet').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-safetynet/android') 15 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | -keepattributes Signature 13 | -keep public class com.horcrux.svg.** {*;} 14 | -keep class com.google.crypto.tink.** { *; } 15 | -keep class net.sqlcipher.** { *; } 16 | -keep class net.sqlcipher.database.* { *; } 17 | -keep class * extends androidx.room.RoomDatabase 18 | -keep class * extends com.google.auto 19 | -keep class org.checkerframework.checker.nullness.qual.** { *; } 20 | 21 | 22 | -------------------------------------------------------------------------------- /components/atoms/logo.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Image, StyleSheet, View} from 'react-native'; 3 | import {useSafeAreaInsets} from 'react-native-safe-area-context'; 4 | 5 | const LogoImage = require('../../assets/images/logo/logo.png'); 6 | 7 | const Logo: FC = () => { 8 | const insets = useSafeAreaInsets(); 9 | 10 | return ( 11 | 12 | 17 | 18 | ); 19 | }; 20 | 21 | const styles = StyleSheet.create({ 22 | container: { 23 | width: '100%', 24 | alignItems: 'center', 25 | position: 'absolute', 26 | zIndex: 1 27 | }, 28 | logo: { 29 | width: 101, 30 | height: 29, 31 | marginBottom: 12 32 | } 33 | }); 34 | 35 | export default Logo; 36 | -------------------------------------------------------------------------------- /theme/colors.ts: -------------------------------------------------------------------------------- 1 | const colors = { 2 | amber: '#F18E00', 3 | backgroundYellow: '#F1EEC7', 4 | baseGrey: '#D5D5D6', 5 | blueGrey: '#828DA9', 6 | blueGreyLight: '#C4E2F0', 7 | darkerPurple: '#200B22', 8 | darkGrey: '#3B3B3B', 9 | darkPurple: '#3C1540', 10 | errorRed: '#B20000', 11 | errorRedTint: '#F4E2DF', 12 | greenBackground: '#F6F9F2', 13 | hotPink: '#FF267E', 14 | lighterGrey: '#F8F8F8', 15 | lighterPurple: '#F0E7F1', 16 | lightGrey: '#EBEBEB', 17 | lilac: '#9658A1', 18 | lilac50: 'rgba(150, 88, 161, 0.5)', 19 | notificationYellowTint: '#FFF8EC', 20 | paleGrey: '#D5D5D6', 21 | pink: '#D2267E', 22 | primaryPurple: '#6B11A5', 23 | resultYellow: '#F4F48B', 24 | shareBlue: '#6398C9', 25 | validationGreen: '#00A606', 26 | validationGreenTint: '#EDF5EE', 27 | white: '#FFFFFF', 28 | transparent: 'transparent', 29 | lightGray: '#808B8D' 30 | }; 31 | 32 | export default colors; 33 | -------------------------------------------------------------------------------- /postinstall.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | cp ./android/app/src/main/res/mipmap-hdpi/ic_notification.png ./node_modules/react-native-exposure-notification-service/android/src/main/res/mipmap-hdpi/ic_notification.png 4 | 5 | cp ./android/app/src/main/res/mipmap-mdpi/ic_notification.png ./node_modules/react-native-exposure-notification-service/android/src/main/res/mipmap-mdpi/ic_notification.png 6 | 7 | cp ./android/app/src/main/res/mipmap-xhdpi/ic_notification.png ./node_modules/react-native-exposure-notification-service/android/src/main/res/mipmap-xhdpi/ic_notification.png 8 | 9 | cp ./android/app/src/main/res/mipmap-xxhdpi/ic_notification.png ./node_modules/react-native-exposure-notification-service/android/src/main/res/mipmap-xxhdpi/ic_notification.png 10 | 11 | cp ./android/app/src/main/res/mipmap-xxxhdpi/ic_notification.png ./node_modules/react-native-exposure-notification-service/android/src/main/res/mipmap-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /services/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import {initReactI18next} from 'react-i18next'; 3 | import * as Localization from 'expo-localization'; 4 | 5 | import { 6 | fallback, 7 | defaultNamespace, 8 | namespaces, 9 | supportedLocales 10 | } from './common'; 11 | 12 | const languageDetector = { 13 | type: 'languageDetector', 14 | async: true, 15 | detect: async (callback: (lang: string) => void) => { 16 | callback(Localization.locale.split('-')[0]); 17 | }, 18 | init: () => {}, 19 | cacheUserLanguage: () => {} 20 | }; 21 | 22 | i18n 23 | // @ts-ignore 24 | .use(languageDetector) 25 | .use(initReactI18next) 26 | .init({ 27 | fallbackLng: fallback, 28 | resources: supportedLocales, 29 | ns: namespaces, 30 | defaultNS: defaultNamespace, 31 | debug: false, 32 | interpolation: { 33 | escapeValue: false 34 | } 35 | }); 36 | 37 | export default i18n; 38 | -------------------------------------------------------------------------------- /hooks/age-group-translation.tsx: -------------------------------------------------------------------------------- 1 | import {useTranslation} from 'react-i18next'; 2 | import {useApplication} from '../providers/context'; 3 | 4 | export type GetTranslation = (namespace: string, options?: object) => string; 5 | 6 | export function useAgeGroupTranslation() { 7 | const {t} = useTranslation(); 8 | const {user} = useApplication(); 9 | 10 | const getTranslation: GetTranslation = ( 11 | namespace: string, 12 | options: object = {} 13 | ) => { 14 | const defaultTranslation = t(`${namespace}:default`, options); 15 | if (!user?.ageGroup) { 16 | return defaultTranslation; 17 | } 18 | 19 | const notFound = namespace 20 | .split(':') 21 | .slice(1) 22 | .concat([user.ageGroup]) 23 | .join('.'); 24 | 25 | const translation = t(`${namespace}:${user.ageGroup}`, options); 26 | return translation === notFound ? defaultTranslation : translation; 27 | }; 28 | 29 | return {getTranslation}; 30 | } 31 | -------------------------------------------------------------------------------- /android/app/src/main/java/gov/scot/covidtracker/generated/BasePackageList.java: -------------------------------------------------------------------------------- 1 | package gov.scot.covidtracker.generated; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import org.unimodules.core.interfaces.Package; 6 | 7 | public class BasePackageList { 8 | public List getPackageList() { 9 | return Arrays.asList( 10 | new expo.modules.constants.ConstantsPackage(), 11 | new expo.modules.crypto.CryptoPackage(), 12 | new expo.modules.filesystem.FileSystemPackage(), 13 | new expo.modules.font.FontLoaderPackage(), 14 | new expo.modules.haptics.HapticsPackage(), 15 | new expo.modules.imageloader.ImageLoaderPackage(), 16 | new expo.modules.intentlauncher.IntentLauncherPackage(), 17 | new expo.modules.localization.LocalizationPackage(), 18 | new expo.modules.permissions.PermissionsPackage(), 19 | new expo.modules.securestore.SecureStorePackage(), 20 | new expo.modules.webbrowser.WebBrowserPackage() 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /navigation.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {NavigationContainerRef} from '@react-navigation/native'; 3 | 4 | export const isMountedRef = React.createRef(); 5 | 6 | export const navigationRef = React.createRef(); 7 | 8 | export enum ScreenNames { 9 | dashboard = 'dashboard', 10 | ageConfirmation = 'ageConfirmation', 11 | ageSorting = 'ageSorting', 12 | ageUnder = 'ageUnder', 13 | locationConfirmation = 'locationConfirmation', 14 | tracing = 'tracing', 15 | about = 'about', 16 | community = 'community', 17 | settings = 'settings', 18 | onboarding = 'onboarding', 19 | closeContact = 'closeContact', 20 | tests = 'tests', 21 | testsAdd = 'testsAdd', 22 | testsResult = 'testsResult', 23 | terms = 'terms', 24 | dataPolicy = 'dataPolicy', 25 | leave = 'leave', 26 | debug = 'debug', 27 | askPermissions = 'permissions-info', 28 | pause = 'pause', 29 | yourDataModal = 'yourDataModal', 30 | testResultModal = 'testResultModal', 31 | calculatorModal = 'calculatorModal', 32 | sendNotice = 'sendNotice' 33 | } 34 | -------------------------------------------------------------------------------- /components/atoms/container.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {View, StyleSheet, ViewProps} from 'react-native'; 3 | 4 | interface ContainerProps extends ViewProps { 5 | center?: 'horizontal' | 'vertical' | 'both'; 6 | stretch?: boolean; 7 | } 8 | 9 | const Container: FC = ({ 10 | center, 11 | stretch = true, 12 | children, 13 | style, 14 | ...props 15 | }) => ( 16 | 29 | {children} 30 | 31 | ); 32 | 33 | const styles = StyleSheet.create({ 34 | base: {width: '100%'}, 35 | stretch: {flex: 1}, 36 | centerHorizontal: {alignItems: 'center'}, 37 | centerVertical: {justifyContent: 'center'} 38 | }); 39 | 40 | export default Container; 41 | -------------------------------------------------------------------------------- /android/fastlane/README.md: -------------------------------------------------------------------------------- 1 | # fastlane documentation 2 | 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | 13 | ``` 14 | [sudo] gem install fastlane -NV 15 | ``` 16 | 17 | or alternatively using `brew install fastlane` 18 | 19 | # Available Actions 20 | 21 | ## Android 22 | 23 | ### android internal 24 | 25 | ``` 26 | fastlane android internal 27 | ``` 28 | 29 | Deploy a new Internal Track Build to Google Play 30 | 31 | ### android build_release_apk 32 | 33 | ``` 34 | fastlane android build_release_apk 35 | ``` 36 | 37 | ### android build_release_bundle 38 | 39 | ``` 40 | fastlane android build_release_bundle 41 | ``` 42 | 43 | --- 44 | 45 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 46 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 47 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 48 | -------------------------------------------------------------------------------- /components/atoms/illustration.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Image, ImageProps, StyleSheet, Dimensions} from 'react-native'; 3 | 4 | import Container from './container'; 5 | 6 | const WINDOW_WIDTH = Dimensions.get('window').width; 7 | 8 | interface IllustrationProps extends ImageProps { 9 | fullWidth?: boolean; 10 | } 11 | 12 | const Illustration: React.FC = ({ 13 | fullWidth, 14 | source, 15 | ...props 16 | }) => { 17 | const {width: imgWidth, height: imgHeight} = Image.resolveAssetSource(source); 18 | 19 | return ( 20 | 21 | 34 | 35 | ); 36 | }; 37 | 38 | const styles = StyleSheet.create({ 39 | fullWidth: {width: '100%'} 40 | }); 41 | 42 | export default Illustration; 43 | -------------------------------------------------------------------------------- /android/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:android) 17 | 18 | platform :android do 19 | desc "Deploy a new Internal Track Build to Google Play" 20 | lane :internal do 21 | increment_version_code( 22 | gradle_file_path: "app/build.gradle", 23 | ) 24 | gradle(task: "bundle", build_type: "Release") 25 | upload_to_play_store( 26 | track: "internal", 27 | skip_upload_apk: true, 28 | release_status: "draft" 29 | ) 30 | end 31 | 32 | lane :build_release_apk do 33 | gradle(task: "clean assembleRelease") 34 | end 35 | 36 | lane :build_release_bundle do 37 | gradle(task: "clean bundle", build_type: "Release") 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | minSdkVersion = 23 6 | compileSdkVersion = 29 7 | targetSdkVersion = 29 8 | } 9 | repositories { 10 | google() 11 | jcenter() 12 | maven { url 'https://dl.bintray.com/android/android-tools/' } 13 | } 14 | dependencies { 15 | classpath('com.android.tools.build:gradle:4.1.2') 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | mavenLocal() 25 | maven { 26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 27 | url("$rootDir/../node_modules/react-native/android") 28 | } 29 | maven { 30 | // Android JSC is installed from npm 31 | url("$rootDir/../node_modules/jsc-android/dist") 32 | } 33 | 34 | google() 35 | jcenter() 36 | maven { url 'https://www.jitpack.io' } 37 | } 38 | } 39 | 40 | task clean(type: Delete) { 41 | delete rootProject.buildDir 42 | } 43 | -------------------------------------------------------------------------------- /components/organisms/modals/push-notifications.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | import {StyleSheet, Linking} from 'react-native'; 4 | 5 | import Markdown from '../../atoms/markdown'; 6 | import Modal from '../../molecules/modal'; 7 | import {text, colors} from '../../../theme'; 8 | import {ModalProps} from '../../molecules/modal'; 9 | 10 | export const PushNotificationsModal: FC = (props) => { 11 | const {t} = useTranslation(); 12 | return ( 13 | 25 | 26 | {t('modals:sendNotifications:instructions', {name: t('common:name')})} 27 | 28 | 29 | ); 30 | }; 31 | 32 | const modalMarkdownStyles = StyleSheet.create({ 33 | text: { 34 | ...text.default, 35 | color: colors.white 36 | }, 37 | listItemNumber: { 38 | color: colors.white 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /components/atoms/modal-close.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Image, TouchableWithoutFeedback} from 'react-native'; 3 | import {useNavigation} from '@react-navigation/native'; 4 | import {StackNavigationProp} from '@react-navigation/stack'; 5 | import {useTranslation} from 'react-i18next'; 6 | 7 | const CloseIcon = require('../../assets/images/icon-close/image.png'); 8 | 9 | interface ModalCloseProps { 10 | onPress?: () => void; 11 | notification?: boolean; 12 | icon?: React.ReactNode; 13 | } 14 | 15 | export const ModalClose: FC = ({ 16 | onPress, 17 | notification = false, 18 | icon 19 | }) => { 20 | const navigation = useNavigation>(); 21 | const {t} = useTranslation(); 22 | 23 | const handlePress = () => (onPress ? onPress() : navigation.pop()); 24 | 25 | return ( 26 | 27 | 37 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /components/organisms/modals/bluetooth-notification.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | import {StyleSheet} from 'react-native'; 4 | 5 | import Markdown from '../../atoms/markdown'; 6 | import Modal from '../../molecules/modal'; 7 | import {text, colors} from '../../../theme'; 8 | import {ModalProps} from '../../molecules/modal'; 9 | import {goToSettingsAction} from '../../molecules/go-to-settings'; 10 | 11 | export const BluetoothNotificationsModal: FC = (props) => { 12 | const {t} = useTranslation(); 13 | return ( 14 | goToSettingsAction(true), 22 | hint: t('modals:bluetoothNotifications:btnLabel'), 23 | label: t('modals:bluetoothNotifications:btnLabel') 24 | } 25 | ]}> 26 | 27 | {t('modals:bluetoothNotifications:instructions')} 28 | 29 | 30 | ); 31 | }; 32 | 33 | const modalMarkdownStyles = StyleSheet.create({ 34 | text: { 35 | ...text.default, 36 | color: colors.white 37 | }, 38 | listItemNumber: { 39 | color: colors.white 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /components/views/terms.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, ScrollView, View, Platform, StatusBar} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import {useApplication} from '../../providers/context'; 6 | import {Title} from '../atoms/title'; 7 | import {Back} from '../atoms/back'; 8 | import {ModalClose} from '../atoms/modal-close'; 9 | import Markdown from '../atoms/markdown'; 10 | import Spacing from '../atoms/spacing'; 11 | import {SPACING_HORIZONTAL} from '../../theme/layouts/shared'; 12 | 13 | export const Terms: FC = () => { 14 | const {t} = useTranslation(); 15 | const {user} = useApplication(); 16 | 17 | return ( 18 | <> 19 | 20 | 21 | {user && } 22 | {!user && ( 23 | 24 | 25 | 26 | )} 27 | 28 | 29 | <Markdown>{t('terms:body', {link: t('links:n')})}</Markdown> 30 | </ScrollView> 31 | </> 32 | ); 33 | }; 34 | 35 | const styles = StyleSheet.create({ 36 | container: { 37 | paddingTop: Platform.OS === 'ios' ? 65 : 30, 38 | paddingHorizontal: SPACING_HORIZONTAL 39 | }, 40 | modalClose: { 41 | justifyContent: 'flex-end', 42 | alignItems: 'flex-end' 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /theme/layouts/onboarding-with-navbar.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC, MutableRefObject} from 'react'; 2 | import {ScrollView, ViewStyle} from 'react-native'; 3 | 4 | import Container from '../../components/atoms/container'; 5 | import NavBar from '../../components/molecules/onboarding-navbar'; 6 | import {Scrollable} from './scrollable'; 7 | 8 | interface OnboardingWithNavbarProps { 9 | goBack(): void; 10 | canGoBack: boolean; 11 | sections: number; 12 | activeSection: number; 13 | backgroundColor?: string; 14 | scrollableStyle?: ViewStyle; 15 | contentContainerStyle?: ViewStyle; 16 | scrollViewRef?: MutableRefObject<ScrollView | null>; 17 | } 18 | 19 | const OnboardingWithNavbar: FC<OnboardingWithNavbarProps> = ({ 20 | canGoBack, 21 | goBack, 22 | children, 23 | sections, 24 | activeSection, 25 | backgroundColor, 26 | contentContainerStyle, 27 | scrollableStyle, 28 | scrollViewRef 29 | }) => ( 30 | <Container style={[!!backgroundColor && {backgroundColor: backgroundColor}]}> 31 | <NavBar 32 | canGoBack={canGoBack} 33 | goBack={goBack} 34 | sections={sections} 35 | activeSection={activeSection} 36 | /> 37 | <Scrollable 38 | scrollableStyle={scrollableStyle} 39 | scrollViewRef={scrollViewRef} 40 | importantForAccessibility="no" 41 | contentContainerStyle={contentContainerStyle}> 42 | {children} 43 | </Scrollable> 44 | </Container> 45 | ); 46 | 47 | export default OnboardingWithNavbar; 48 | -------------------------------------------------------------------------------- /components/views/data-policy.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, ScrollView, View, Platform, StatusBar} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import {useApplication} from '../../providers/context'; 6 | import {Title} from '../atoms/title'; 7 | import {Back} from '../atoms/back'; 8 | import {ModalClose} from '../atoms/modal-close'; 9 | import Markdown from '../atoms/markdown'; 10 | import Spacing from '../atoms/spacing'; 11 | import {SPACING_HORIZONTAL} from '../../theme/layouts/shared'; 12 | 13 | export const DataPolicy: FC = () => { 14 | const {t} = useTranslation(); 15 | const {user} = useApplication(); 16 | 17 | return ( 18 | <> 19 | <StatusBar barStyle="default" /> 20 | <ScrollView style={styles.container}> 21 | {user && <Back variant="light" />} 22 | {!user && ( 23 | <View style={styles.modalClose}> 24 | <ModalClose /> 25 | </View> 26 | )} 27 | <Spacing s={44} /> 28 | <Title accessible title="dataPolicy:title" /> 29 | <Markdown>{t('dataPolicy:body', {link: t('links:m')})}</Markdown> 30 | </ScrollView> 31 | </> 32 | ); 33 | }; 34 | 35 | const styles = StyleSheet.create({ 36 | container: { 37 | paddingTop: Platform.OS === 'ios' ? 65 : 30, 38 | paddingHorizontal: SPACING_HORIZONTAL 39 | }, 40 | modalClose: { 41 | justifyContent: 'flex-end', 42 | alignItems: 'flex-end' 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.33.1 29 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 30 | -------------------------------------------------------------------------------- /components/atoms/progress-bar.tsx: -------------------------------------------------------------------------------- 1 | import React, {useRef, useEffect} from 'react'; 2 | import {StyleSheet, Animated, View} from 'react-native'; 3 | import {colors} from '../../theme'; 4 | 5 | interface ProgressBarProps { 6 | style?: any; 7 | sections: number; 8 | activeSection: number; 9 | } 10 | 11 | const ProgressBar: React.FC<ProgressBarProps> = ({ 12 | style, 13 | sections = 1, 14 | activeSection = 1 15 | }) => { 16 | const width = useRef(new Animated.Value((100 / sections) * activeSection)) 17 | .current; 18 | useEffect(() => { 19 | const newWidth = (100 / sections) * activeSection; 20 | Animated.timing(width, { 21 | toValue: newWidth, 22 | duration: 200, 23 | useNativeDriver: false 24 | }).start(); 25 | }, [activeSection, sections, width]); 26 | 27 | return ( 28 | <View style={[styles.container, style]}> 29 | <Animated.View 30 | style={[ 31 | styles.bar, 32 | { 33 | width: width.interpolate({ 34 | inputRange: [0, 100], 35 | outputRange: ['0%', '100%'] 36 | }) 37 | } 38 | ]} 39 | /> 40 | </View> 41 | ); 42 | }; 43 | 44 | const styles = StyleSheet.create({ 45 | container: { 46 | width: '100%', 47 | height: 4, 48 | borderRadius: 1, 49 | backgroundColor: colors.white 50 | }, 51 | bar: { 52 | flex: 1, 53 | borderRadius: 1, 54 | backgroundColor: colors.primaryPurple 55 | } 56 | }); 57 | 58 | export default ProgressBar; 59 | -------------------------------------------------------------------------------- /components/molecules/new-version-card.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | StyleSheet, 4 | TouchableWithoutFeedback, 5 | View, 6 | Platform, 7 | Linking 8 | } from 'react-native'; 9 | import {useTranslation} from 'react-i18next'; 10 | 11 | import {colors} from '../../theme'; 12 | import Spacing from '../atoms/spacing'; 13 | import Text from '../atoms/text'; 14 | import RoundedBox from '../atoms/rounded-box'; 15 | 16 | export const NewVersionCard = () => { 17 | const {t} = useTranslation(); 18 | 19 | const onUpdate = () => { 20 | Linking.openURL( 21 | Platform.OS === 'ios' 22 | ? t('newVersion:appstoreUrl') 23 | : t('newVersion:playstoreUrl') 24 | ); 25 | }; 26 | 27 | return ( 28 | <TouchableWithoutFeedback onPress={onUpdate}> 29 | <View> 30 | <RoundedBox style={styles.content}> 31 | <Text variant="h4"> 32 | {t('newVersion:title', { 33 | storeName: 34 | Platform.OS === 'ios' 35 | ? t('newVersion:appstore') 36 | : t('newVersion:playstore') 37 | })} 38 | </Text> 39 | <Spacing s={8} /> 40 | <Text bold color="errorRed"> 41 | {t('newVersion:link')} 42 | </Text> 43 | </RoundedBox> 44 | </View> 45 | </TouchableWithoutFeedback> 46 | ); 47 | }; 48 | 49 | const styles = StyleSheet.create({ 50 | content: { 51 | borderColor: colors.primaryPurple, 52 | width: 'auto' 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /theme/text.ts: -------------------------------------------------------------------------------- 1 | import colors from './colors'; 2 | 3 | export default (scale: (v: number) => number) => { 4 | const text = { 5 | h1Heading: { 6 | fontFamily: 'lato-bold', 7 | fontSize: scale(28), 8 | lineHeight: scale(36) 9 | }, 10 | h2Heading: { 11 | fontFamily: 'lato-bold', 12 | fontSize: scale(26), 13 | lineHeight: scale(32) 14 | }, 15 | h3Heading: { 16 | fontFamily: 'lato-bold', 17 | fontSize: scale(24), 18 | lineHeight: scale(32) 19 | }, 20 | h4Heading: { 21 | fontFamily: 'lato-bold', 22 | fontSize: scale(18), 23 | lineHeight: scale(23) 24 | }, 25 | leader: { 26 | fontFamily: 'lato', 27 | fontSize: scale(21), 28 | lineHeight: scale(28) 29 | }, 30 | paragraph: { 31 | fontFamily: 'roboto', 32 | fontSize: scale(18), 33 | lineHeight: scale(28) 34 | }, 35 | smallerParagraph: { 36 | fontFamily: 'roboto', 37 | fontSize: scale(16), 38 | lineHeight: scale(21) 39 | }, 40 | fontFamily: { 41 | roboto: 'roboto', 42 | robotoBold: 'roboto-bold', 43 | lato: 'lato', 44 | latoBold: 'lato-bold' 45 | } 46 | }; 47 | 48 | const defaultText = { 49 | ...text.paragraph, 50 | color: colors.darkGrey 51 | }; 52 | 53 | const defaultBold = { 54 | ...defaultText, 55 | fontFamily: text.fontFamily.robotoBold 56 | }; 57 | 58 | return { 59 | default: defaultText, 60 | defaultBold, 61 | ...text 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /android/app/_BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "gov.scot.covidtracker", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "gov.scot.covidtracker", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /hooks/a11y-element.tsx: -------------------------------------------------------------------------------- 1 | import React, {useRef, useCallback, useMemo} from 'react'; 2 | import { 3 | AccessibilityInfo, 4 | findNodeHandle, 5 | StyleSheet, 6 | View, 7 | ViewProps, 8 | Platform 9 | } from 'react-native'; 10 | import {useIsFocused} from '@react-navigation/native'; 11 | 12 | import {useApplication} from '../providers/context'; 13 | 14 | export const A11yView = React.forwardRef<View, ViewProps>((props, ref) => ( 15 | <View 16 | ref={ref} 17 | accessible={Platform.OS === 'android'} 18 | style={StyleSheet.absoluteFill} 19 | {...props} 20 | /> 21 | )); 22 | 23 | export function useA11yElement() { 24 | const { 25 | accessibility: {screenReaderEnabled} 26 | } = useApplication(); 27 | const focusRef = useRef(null); 28 | const isFocused = useIsFocused(); 29 | 30 | const focusA11yElement = useCallback( 31 | (timeout = 250) => { 32 | if (screenReaderEnabled && focusRef.current && isFocused) { 33 | const tag = findNodeHandle(focusRef.current); 34 | if (tag) { 35 | const id = setTimeout( 36 | () => AccessibilityInfo.setAccessibilityFocus(tag), 37 | timeout 38 | ); 39 | return () => clearTimeout(id); 40 | } 41 | } 42 | }, 43 | // eslint-disable-next-line react-hooks/exhaustive-deps 44 | [screenReaderEnabled, focusRef.current, isFocused] 45 | ); 46 | 47 | const a11yProps = useMemo( 48 | () => ({ 49 | ref: focusRef 50 | }), 51 | [] 52 | ); 53 | 54 | return {a11yProps, focusRef, focusA11yElement}; 55 | } 56 | -------------------------------------------------------------------------------- /components/views/loading.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, Image, View, StatusBar} from 'react-native'; 3 | import Spinner from 'react-native-loading-spinner-overlay'; 4 | import {useTranslation} from 'react-i18next'; 5 | import {useSafeAreaInsets} from 'react-native-safe-area-context'; 6 | 7 | import Spacing from '../atoms/spacing'; 8 | import {colors} from '../../theme'; 9 | 10 | export const Loading: FC = () => { 11 | const {t} = useTranslation(); 12 | const insets = useSafeAreaInsets(); 13 | return ( 14 | <View 15 | style={[ 16 | styles.container, 17 | {paddingBottom: insets.bottom, paddingTop: insets.top} 18 | ]}> 19 | <StatusBar barStyle="light-content" /> 20 | <View style={styles.bg} /> 21 | <Spacing s={50} /> 22 | <Image 23 | style={styles.center} 24 | resizeMode="contain" 25 | source={require('../../assets/images/logo/logo.png')} 26 | accessible 27 | accessibilityRole="text" 28 | accessibilityHint={t('common:name')} 29 | accessibilityIgnoresInvertColors={false} 30 | /> 31 | <Spacing s={64} /> 32 | <Spinner animation="fade" visible overlayColor={'transparent'} /> 33 | </View> 34 | ); 35 | }; 36 | 37 | const styles = StyleSheet.create({ 38 | container: { 39 | flex: 1, 40 | backgroundColor: colors.white, 41 | justifyContent: 'flex-start' 42 | }, 43 | bg: { 44 | ...StyleSheet.absoluteFillObject, 45 | flex: 1, 46 | backgroundColor: colors.primaryPurple 47 | }, 48 | center: { 49 | alignSelf: 'center' 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /ios/ProtectScotland/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "Protect Scotland Icon 20@2x.png", 5 | "idiom": "iphone", 6 | "scale": "2x", 7 | "size": "20x20" 8 | }, 9 | { 10 | "filename": "Protect Scotland Icon 20@3x.png", 11 | "idiom": "iphone", 12 | "scale": "3x", 13 | "size": "20x20" 14 | }, 15 | { 16 | "filename": "Protect Scotland Icon 29@2x.png", 17 | "idiom": "iphone", 18 | "scale": "2x", 19 | "size": "29x29" 20 | }, 21 | { 22 | "filename": "Protect Scotland Icon 29@3x.png", 23 | "idiom": "iphone", 24 | "scale": "3x", 25 | "size": "29x29" 26 | }, 27 | { 28 | "filename": "Protect Scotland Icon 40@2x.png", 29 | "idiom": "iphone", 30 | "scale": "2x", 31 | "size": "40x40" 32 | }, 33 | { 34 | "filename": "Protect Scotland Icon 40@3x.png", 35 | "idiom": "iphone", 36 | "scale": "3x", 37 | "size": "40x40" 38 | }, 39 | { 40 | "filename": "Protect Scotland Icon 60@2x.png", 41 | "idiom": "iphone", 42 | "scale": "2x", 43 | "size": "60x60" 44 | }, 45 | { 46 | "filename": "Protect Scotland Icon 60@3x.png", 47 | "idiom": "iphone", 48 | "scale": "3x", 49 | "size": "60x60" 50 | }, 51 | { 52 | "filename": "Protect Scotland Icon 1024.png", 53 | "idiom": "ios-marketing", 54 | "scale": "1x", 55 | "size": "1024x1024" 56 | } 57 | ], 58 | "info": { 59 | "author": "xcode", 60 | "version": 1 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /utils/exposure.ts: -------------------------------------------------------------------------------- 1 | import {differenceInCalendarDays, addDays, format} from 'date-fns'; 2 | import {CloseContact} from 'react-native-exposure-notification-service'; 3 | 4 | export const getExposureDate = (contacts?: CloseContact[]) => { 5 | if (contacts && contacts.length > 0) { 6 | const {exposureDate} = contacts[0]; 7 | return new Date(exposureDate); 8 | } 9 | }; 10 | 11 | export const getSelfIsolationRemainingDays = ( 12 | isolationDuration: number, 13 | contacts?: CloseContact[] 14 | ) => { 15 | const exposureDate = getExposureDate(contacts); 16 | const remainingDays = exposureDate 17 | ? isolationDuration - differenceInCalendarDays(Date.now(), exposureDate) 18 | : -1; 19 | return remainingDays > 0 ? remainingDays : 0; 20 | }; 21 | 22 | export const DATE_FORMAT = 'd MMMM yyyy'; 23 | 24 | export const getIsolationEndDate = ( 25 | isolationDuration: number, 26 | contacts?: CloseContact[], 27 | fmt: string = DATE_FORMAT 28 | ) => { 29 | const exposureDate = getExposureDate(contacts); 30 | 31 | if (exposureDate) { 32 | const isolationEndDate = addDays(exposureDate, isolationDuration); 33 | return { 34 | raw: isolationEndDate, 35 | formatted: format(isolationEndDate, fmt) 36 | }; 37 | } 38 | }; 39 | 40 | export const hasCurrentExposure = ( 41 | isolationDuration: number, 42 | contacts?: CloseContact[] 43 | ) => { 44 | if (!contacts || contacts.length === 0) { 45 | return false; 46 | } 47 | 48 | const exposureDate = getExposureDate(contacts); 49 | const daysDiff = differenceInCalendarDays(new Date(), exposureDate!); 50 | return daysDiff < isolationDuration; 51 | }; 52 | -------------------------------------------------------------------------------- /components/molecules/header.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, View, Image, TouchableWithoutFeedback} from 'react-native'; 3 | import {useNavigation} from '@react-navigation/native'; 4 | import {useTranslation} from 'react-i18next'; 5 | 6 | import {ScreenNames} from '../../navigation'; 7 | import {SPACING_HORIZONTAL} from '../../theme/layouts/shared'; 8 | 9 | const IconLogo = require('../../assets/images/icon-logo/image.png'); 10 | const IconMenu = require('../../assets/images/icon-menu/image.png'); 11 | 12 | export const Header: FC = () => { 13 | const navigation = useNavigation(); 14 | const {t} = useTranslation(); 15 | return ( 16 | <View style={styles.container}> 17 | <Image source={IconLogo} accessibilityIgnoresInvertColors={false} /> 18 | <TouchableWithoutFeedback 19 | accessibilityRole="button" 20 | accessibilityHint={t('settings:titleHint')} 21 | accessibilityLabel={t('settings:title')} 22 | onPress={() => navigation.navigate(ScreenNames.settings)}> 23 | <Image 24 | style={styles.settings} 25 | source={IconMenu} 26 | resizeMode="center" 27 | accessibilityIgnoresInvertColors={false} 28 | /> 29 | </TouchableWithoutFeedback> 30 | </View> 31 | ); 32 | }; 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | flexDirection: 'row', 37 | justifyContent: 'space-between', 38 | alignItems: 'center', 39 | paddingTop: 65, 40 | paddingHorizontal: SPACING_HORIZONTAL, 41 | paddingBottom: 25 42 | }, 43 | settings: { 44 | width: 44, 45 | height: 44 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /services/notifications/index.tsx: -------------------------------------------------------------------------------- 1 | import PushNotification, { 2 | PushNotification as PushNotificationType 3 | } from 'react-native-push-notification'; 4 | import {reminderNotification} from './reminder'; 5 | import {notificationHooks} from './hooks'; 6 | 7 | interface Token { 8 | os: string; 9 | token: string; 10 | } 11 | 12 | const notificationTypes = [reminderNotification]; 13 | 14 | const getId = (notification: PushNotificationType): string => { 15 | // @ts-ignore 16 | return notification.id || notification.data.id; 17 | }; 18 | 19 | PushNotification.configure({ 20 | onNotification: (notification: PushNotificationType) => { 21 | console.log(`Responding to notification ${getId(notification)}`); 22 | const notificationType = notificationTypes.find( 23 | ({id}) => String(id) === getId(notification) 24 | ); 25 | 26 | if (notificationType?.handler) { 27 | notificationType?.handler(notification, notificationHooks); 28 | } else if (notificationHooks.handleNotification) { 29 | notificationHooks.handleNotification(notification); 30 | } 31 | }, 32 | onRegister: (token: Token) => { 33 | if (notificationHooks.handleRegister) { 34 | notificationHooks.handleRegister(token); 35 | } 36 | }, 37 | // senderID: '1087125483031', 38 | permissions: { 39 | alert: true, 40 | badge: true, 41 | sound: true 42 | }, 43 | popInitialNotification: true, 44 | requestPermissions: false 45 | }); 46 | 47 | PushNotification.createChannel( 48 | { 49 | channelId: 'default', 50 | channelName: 'Default channel' 51 | }, 52 | () => {} 53 | ); 54 | 55 | export {notificationHooks}; 56 | -------------------------------------------------------------------------------- /components/molecules/onboarding-navbar.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, View} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import Container from '../atoms/container'; 6 | import ProgressBar from '../atoms/progress-bar'; 7 | import {Back} from '../atoms/back'; 8 | 9 | interface NavBarProps { 10 | goBack(): void; 11 | sections: number; 12 | activeSection: number; 13 | canGoBack: boolean; 14 | } 15 | 16 | const NavBar: FC<NavBarProps> = ({ 17 | goBack, 18 | sections, 19 | activeSection, 20 | canGoBack 21 | }) => { 22 | const {t} = useTranslation(); 23 | return ( 24 | <> 25 | <View style={styles.container}> 26 | <View style={styles.back}> 27 | {canGoBack && <Back onPress={goBack} />} 28 | </View> 29 | <Container 30 | center="vertical" 31 | accessible 32 | accessibilityHint={t('onboarding:navbar:accessibilityHint', { 33 | step: activeSection, 34 | total: sections 35 | })}> 36 | <ProgressBar sections={sections} activeSection={activeSection} /> 37 | </Container> 38 | </View> 39 | </> 40 | ); 41 | }; 42 | 43 | const styles = StyleSheet.create({ 44 | container: { 45 | flexDirection: 'row', 46 | justifyContent: 'space-between', 47 | paddingTop: 56, 48 | paddingHorizontal: 40, 49 | paddingBottom: 20, 50 | width: '100%' 51 | }, 52 | back: { 53 | width: 30, 54 | height: 30, 55 | marginRight: 24 56 | }, 57 | wave: { 58 | position: 'absolute', 59 | zIndex: 10 60 | } 61 | }); 62 | 63 | export default NavBar; 64 | -------------------------------------------------------------------------------- /components/atoms/back.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {View, Image, StyleSheet, TouchableWithoutFeedback} from 'react-native'; 3 | import {useNavigation} from '@react-navigation/native'; 4 | import {useTranslation} from 'react-i18next'; 5 | import {colors} from '../../theme'; 6 | 7 | const IconBack = require('../../assets/images/icon-back-light/image.png'); 8 | const IconBackDark = require('../../assets/images/icon-back/image.png'); 9 | const IconBackArrowDark = require('../../assets/images/back/image.png'); 10 | 11 | export interface BackProps { 12 | variant?: 'default' | 'light' | 'dark'; 13 | onPress?: () => void; 14 | } 15 | 16 | const icons = { 17 | default: IconBackArrowDark, 18 | light: IconBack, 19 | dark: IconBackDark 20 | }; 21 | 22 | export const Back: FC<BackProps> = ({ 23 | variant = 'default', 24 | onPress: onPressProp 25 | }) => { 26 | const navigation = useNavigation(); 27 | const {t} = useTranslation(); 28 | 29 | const onPress = () => navigation.goBack(); 30 | 31 | return ( 32 | <TouchableWithoutFeedback 33 | accessible 34 | accessibilityRole="button" 35 | accessibilityHint={t('common:back:hint')} 36 | accessibilityLabel={t('common:back:label')} 37 | onPress={onPressProp || onPress}> 38 | <View style={styles.container}> 39 | <Image 40 | source={icons[variant]} 41 | accessibilityIgnoresInvertColors={false} 42 | /> 43 | </View> 44 | </TouchableWithoutFeedback> 45 | ); 46 | }; 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | width: 30, 51 | height: 30, 52 | borderRadius: 15, 53 | backgroundColor: colors.white, 54 | alignItems: 'center', 55 | justifyContent: 'center' 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /components/views/onboarding/privacy.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Image} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import Button from '../../atoms/button'; 6 | import Spacing from '../../atoms/spacing'; 7 | import Markdown from '../../atoms/markdown'; 8 | import Container from '../../atoms/container'; 9 | import {OnboardingPageProps} from './common'; 10 | import Text from '../../atoms/text'; 11 | import {openBrowserAsync} from '../../../utils/web-browser'; 12 | 13 | const IllustrationSource = require('../../../assets/images/privacy-illustration/image.png'); 14 | 15 | const PrivacyInfo: FC<OnboardingPageProps> = ({handleNext}) => { 16 | const {t} = useTranslation(); 17 | 18 | return ( 19 | <> 20 | <Container> 21 | <Container center="horizontal" stretch={false}> 22 | <Image 23 | source={IllustrationSource} 24 | accessibilityIgnoresInvertColors={false} 25 | /> 26 | </Container> 27 | <Spacing s={48} /> 28 | <Text variant="h2" accessible> 29 | {t('onboarding:privacy:title')} 30 | </Text> 31 | <Spacing s={24} /> 32 | <Markdown>{t('onboarding:privacy:text')}</Markdown> 33 | <Spacing s={24} /> 34 | </Container> 35 | <Button 36 | type="secondary" 37 | onPress={() => openBrowserAsync(t('links:m'))} 38 | hint={t('onboarding:privacy:buttonHint')}> 39 | {t('onboarding:privacy:buttonHint')} 40 | </Button> 41 | <Spacing s={16} /> 42 | <Button onPress={handleNext} hint={t('common:next:hint')}> 43 | {t('common:next:label')} 44 | </Button> 45 | <Spacing s={24} /> 46 | </> 47 | ); 48 | }; 49 | 50 | export default PrivacyInfo; 51 | -------------------------------------------------------------------------------- /components/views/onboarding/your-data.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Image} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import Button from '../../atoms/button'; 6 | import Spacing from '../../atoms/spacing'; 7 | import Markdown from '../../atoms/markdown'; 8 | import Container from '../../atoms/container'; 9 | import {OnboardingPageProps} from './common'; 10 | import Text from '../../atoms/text'; 11 | import {ScreenNames} from '../../../navigation'; 12 | import {useNavigation} from '@react-navigation/native'; 13 | 14 | const IllustrationSource = require('../../../assets/images/your-data-illustration/image.png'); 15 | 16 | const YourData: FC<OnboardingPageProps> = ({handleNext}) => { 17 | const navigation = useNavigation(); 18 | const {t} = useTranslation(); 19 | return ( 20 | <> 21 | <Container> 22 | <Container center="horizontal" stretch={false}> 23 | <Image 24 | source={IllustrationSource} 25 | accessibilityIgnoresInvertColors={false} 26 | /> 27 | </Container> 28 | <Spacing s={48} /> 29 | <Text variant="h2" accessible> 30 | {t('onboarding:yourData:title')} 31 | </Text> 32 | <Spacing s={24} /> 33 | <Markdown>{t('onboarding:yourData:text')}</Markdown> 34 | <Spacing s={24} /> 35 | </Container> 36 | <Button 37 | type="secondary" 38 | onPress={() => navigation.navigate(ScreenNames.yourDataModal)}> 39 | {t('onboarding:yourData:buttonLabel')} 40 | </Button> 41 | <Spacing s={16} /> 42 | <Button onPress={handleNext} hint={t('common:next:hint')}> 43 | {t('common:next:label')} 44 | </Button> 45 | <Spacing s={24} /> 46 | </> 47 | ); 48 | }; 49 | 50 | export default YourData; 51 | -------------------------------------------------------------------------------- /components/views/onboarding/test-result.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Image} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import Button from '../../atoms/button'; 6 | import Spacing from '../../atoms/spacing'; 7 | import Markdown from '../../atoms/markdown'; 8 | import Container from '../../atoms/container'; 9 | import {OnboardingPageProps} from './common'; 10 | import Text from '../../atoms/text'; 11 | import {ScreenNames} from '../../../navigation'; 12 | import {useNavigation} from '@react-navigation/native'; 13 | 14 | const IllustrationSource = require('../../../assets/images/test-result-illustration/image.png'); 15 | 16 | const TestResult: FC<OnboardingPageProps> = ({handleNext}) => { 17 | const navigation = useNavigation(); 18 | const {t} = useTranslation(); 19 | return ( 20 | <> 21 | <Container> 22 | <Container center="horizontal" stretch={false}> 23 | <Image 24 | source={IllustrationSource} 25 | accessibilityIgnoresInvertColors={false} 26 | /> 27 | </Container> 28 | <Spacing s={48} /> 29 | <Text variant="h2" accessible> 30 | {t('onboarding:testResult:title')} 31 | </Text> 32 | <Spacing s={24} /> 33 | <Markdown>{t('onboarding:testResult:text')}</Markdown> 34 | <Spacing s={24} /> 35 | </Container> 36 | <Button 37 | type="secondary" 38 | onPress={() => navigation.navigate(ScreenNames.testResultModal)} 39 | hint={t('onboarding:testResult:buttonHint')}> 40 | {t('onboarding:testResult:buttonLabel')} 41 | </Button> 42 | <Spacing s={16} /> 43 | <Button onPress={handleNext} hint={t('common:next:hint')}> 44 | {t('common:next:label')} 45 | </Button> 46 | <Spacing s={24} /> 47 | </> 48 | ); 49 | }; 50 | 51 | export default TestResult; 52 | -------------------------------------------------------------------------------- /components/molecules/modal/clear-contacts.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | import {StyleSheet} from 'react-native'; 4 | import {useExposure} from 'react-native-exposure-notification-service'; 5 | import * as SecureStore from 'expo-secure-store'; 6 | 7 | import Markdown from '../../atoms/markdown'; 8 | import Modal, {ModalProps} from '.'; 9 | import {text, colors} from '../../../theme'; 10 | import Spacing from '../../atoms/spacing'; 11 | 12 | export const ClearContactsModal: FC<ModalProps> = (props) => { 13 | const {t} = useTranslation(); 14 | const {deleteExposureData, getCloseContacts} = useExposure(); 15 | return ( 16 | <Modal 17 | {...props} 18 | closeButton={false} 19 | buttons={[ 20 | { 21 | action: async () => { 22 | try { 23 | await deleteExposureData(); 24 | await getCloseContacts(); 25 | await SecureStore.deleteItemAsync('niexposuredate'); 26 | } catch (e) { 27 | console.log('Error deleting exposure data', e); 28 | } 29 | }, 30 | hint: t('modals:clearContacts:okBtnHint'), 31 | label: t('modals:clearContacts:okBtnLabel') 32 | }, 33 | { 34 | action: () => {}, 35 | hint: t('modals:clearContacts:cancelBtnHint'), 36 | label: t('modals:clearContacts:cancelBtnLabel'), 37 | type: 'secondary' 38 | } 39 | ]}> 40 | <Markdown markdownStyles={modalMarkdownStyles}> 41 | {t('modals:clearContacts:instructions')} 42 | </Markdown> 43 | <Spacing s={20} /> 44 | </Modal> 45 | ); 46 | }; 47 | 48 | const modalMarkdownStyles = StyleSheet.create({ 49 | text: { 50 | ...text.leader, 51 | color: colors.darkGrey, 52 | textAlign: 'center' 53 | }, 54 | block: { 55 | margin: 0 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /components/views/onboarding/why-use.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Image} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | import Button from '../../atoms/button'; 6 | import Spacing from '../../atoms/spacing'; 7 | import Markdown from '../../atoms/markdown'; 8 | import Container from '../../atoms/container'; 9 | import {OnboardingPageProps} from './common'; 10 | import Text from '../../atoms/text'; 11 | import {ArrowLink} from '../../molecules/arrow-link'; 12 | 13 | const IllustrationSource = require('../../../assets/images/why-use-illustration/image.png'); 14 | 15 | const WhyUse: FC<OnboardingPageProps> = ({handleNext}) => { 16 | const {t} = useTranslation(); 17 | 18 | return ( 19 | <> 20 | <Container> 21 | <Container center="horizontal" stretch={false}> 22 | <Image 23 | source={IllustrationSource} 24 | accessibilityIgnoresInvertColors={false} 25 | /> 26 | </Container> 27 | <Spacing s={48} /> 28 | <Text variant="h2" accessible> 29 | {t('onboarding:whyUse:title')} 30 | </Text> 31 | <Spacing s={24} /> 32 | <Markdown>{t('onboarding:whyUse:text')}</Markdown> 33 | <ArrowLink externalLink={t('links:f')}> 34 | <Text variant="h4" color="primaryPurple"> 35 | {t('onboarding:whyUse:link1')} 36 | </Text> 37 | </ArrowLink> 38 | <Spacing s={16} /> 39 | <ArrowLink externalLink={t('links:q')}> 40 | <Text variant="h4" color="primaryPurple"> 41 | {t('onboarding:whyUse:link2')} 42 | </Text> 43 | </ArrowLink> 44 | <Spacing s={46} /> 45 | </Container> 46 | <Button onPress={handleNext} hint={t('common:next:hint')}> 47 | {t('common:next:label')} 48 | </Button> 49 | <Spacing s={24} /> 50 | </> 51 | ); 52 | }; 53 | 54 | export default WhyUse; 55 | -------------------------------------------------------------------------------- /hooks/confirmation-space.tsx: -------------------------------------------------------------------------------- 1 | // Provides the heights for age and location confirmation images and content 2 | // sized so they best fit the screen ratio without cutting off the graphic. 3 | import {useMemo} from 'react'; 4 | import {Dimensions} from 'react-native'; 5 | 6 | const {width: WINDOW_WIDTH, height: WINDOW_HEIGHT} = Dimensions.get('window'); 7 | 8 | // The height / width ratio of the illustration and logo 9 | const IMAGE_RATIO = 993 / 1125; 10 | const LOGO_RATIO = 377 / 1125; 11 | // If the ratio of the window dimensions are less then this then don't trim the illustration 12 | const NO_TRIM_RATIO = 0.462; 13 | // If the ratio of the window dimensions are more then this then use the max trim 14 | const MAX_TRIM_RATIO = 0.53; 15 | // The maximum amount to trim from the top of the image 16 | const MAX_TRIM = -30; 17 | // The illustration is displayed full width. From this we can calculate the height 18 | const ILLUSTRATION_HEIGHT = IMAGE_RATIO * WINDOW_WIDTH; 19 | const LOGO_HEIGHT = LOGO_RATIO * WINDOW_WIDTH; 20 | 21 | export function useConfirmationSpace( 22 | minContentHeight: number 23 | ): { 24 | topTrim: number; 25 | contentHeight: number; 26 | ILLUSTRATION_HEIGHT: number; 27 | LOGO_HEIGHT: number; 28 | } { 29 | const contentHeight = useMemo(() => { 30 | const bottomHeight = WINDOW_HEIGHT - ILLUSTRATION_HEIGHT - LOGO_HEIGHT; 31 | return minContentHeight > bottomHeight ? minContentHeight : bottomHeight; 32 | }, [minContentHeight]); 33 | 34 | const topTrim = useMemo(() => { 35 | const screenRatio = WINDOW_WIDTH / WINDOW_HEIGHT; 36 | const trimRange = MAX_TRIM_RATIO - NO_TRIM_RATIO; 37 | const trimPercent = 38 | screenRatio < NO_TRIM_RATIO 39 | ? 0 40 | : screenRatio > MAX_TRIM_RATIO 41 | ? 1 42 | : 1 - (MAX_TRIM_RATIO - screenRatio) / trimRange; 43 | return trimPercent * MAX_TRIM; 44 | }, []); 45 | 46 | return {topTrim, contentHeight, ILLUSTRATION_HEIGHT, LOGO_HEIGHT}; 47 | } 48 | -------------------------------------------------------------------------------- /components/views/onboarding/upgrade-notice.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Linking, Platform} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | import {useExposure} from 'react-native-exposure-notification-service'; 5 | 6 | import Button from '../../atoms/button'; 7 | import Spacing from '../../atoms/spacing'; 8 | import Text from '../../atoms/text'; 9 | import Container from '../../atoms/container'; 10 | 11 | interface UpgradeNoticeProps {} 12 | 13 | const UpgradeNotice: FC<UpgradeNoticeProps> = () => { 14 | const {t} = useTranslation(); 15 | const exposure = useExposure(); 16 | 17 | const checkForUpgradeHandler = async () => { 18 | try { 19 | if (Platform.OS === 'ios') { 20 | Linking.openURL('App-Prefs:'); 21 | } else { 22 | await exposure.triggerUpdate(); 23 | await exposure.supportsExposureApi(); 24 | } 25 | } catch (err) { 26 | console.log('Error handling check for upgrade', err); 27 | } 28 | }; 29 | 30 | return ( 31 | <> 32 | <Container> 33 | <Spacing s={16} /> 34 | <Text variant="h2" light accessible> 35 | {t(`onboarding:upgradeNotice:${Platform.OS}:title`)} 36 | </Text> 37 | <Spacing s={46} /> 38 | <Text light>{t(`onboarding:upgradeNotice:${Platform.OS}:text1`)}</Text> 39 | <Spacing s={24} /> 40 | <Text variant="h2" light> 41 | {t('onboarding:upgradeNotice:text2')} 42 | </Text> 43 | <Spacing s={24} /> 44 | </Container> 45 | <Button 46 | onPress={checkForUpgradeHandler} 47 | hint={t( 48 | `onboarding:upgradeNotice:accessibility:${Platform.OS}:upgradeHint` 49 | )} 50 | label={t(`onboarding:upgradeNotice:${Platform.OS}:btnLabel`)}> 51 | {t(`onboarding:upgradeNotice:${Platform.OS}:btnLabel`)} 52 | </Button> 53 | {Platform.OS === 'android' && <Spacing s={30} />} 54 | </> 55 | ); 56 | }; 57 | 58 | export default UpgradeNotice; 59 | -------------------------------------------------------------------------------- /components/molecules/close-contact-step.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'; 3 | 4 | import Media from '../atoms/media'; 5 | import Text from '../atoms/text'; 6 | import Spacing from '../atoms/spacing'; 7 | import {ArrowLink} from './arrow-link'; 8 | import {colors, scale} from '../../theme'; 9 | 10 | const Number: FC = ({children}) => ( 11 | <View style={styles.number}> 12 | <Text bold color="white" align="center" style={styles.numberText}> 13 | {children} 14 | </Text> 15 | </View> 16 | ); 17 | 18 | interface CloseContactStepProps { 19 | number: number; 20 | title: string; 21 | text?: string | React.ReactNode; 22 | link?: string; 23 | linkText?: string; 24 | onPress?: () => void; 25 | } 26 | 27 | const CloseContactStep: FC<CloseContactStepProps> = ({ 28 | number, 29 | title, 30 | text, 31 | link, 32 | linkText, 33 | onPress 34 | }) => ( 35 | <TouchableWithoutFeedback onPress={onPress}> 36 | <View> 37 | <Media left={<Number>{number}</Number>} leftStyle={styles.left}> 38 | <Text variant="h4" color="errorRed"> 39 | {title} 40 | </Text> 41 | <Spacing s={16} /> 42 | {typeof text === 'string' ? <Text color="darkGrey">{text}</Text> : text} 43 | {link && ( 44 | <> 45 | <Spacing s={24} /> 46 | <ArrowLink externalLink={link}> 47 | <Text variant="h4" color="primaryPurple"> 48 | {linkText} 49 | </Text> 50 | </ArrowLink> 51 | </> 52 | )} 53 | </Media> 54 | </View> 55 | </TouchableWithoutFeedback> 56 | ); 57 | 58 | const styles = StyleSheet.create({ 59 | left: { 60 | marginRight: 16 61 | }, 62 | number: { 63 | width: 30, 64 | height: 30, 65 | borderRadius: 15, 66 | backgroundColor: colors.darkGrey 67 | }, 68 | numberText: { 69 | fontSize: scale(17) 70 | } 71 | }); 72 | 73 | export default CloseContactStep; 74 | -------------------------------------------------------------------------------- /components/atoms/text.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import { 3 | Text as T, 4 | StyleSheet, 5 | TextProps as TProps, 6 | TextStyle 7 | } from 'react-native'; 8 | 9 | import {useA11yElement} from '../../hooks'; 10 | import {text, colors} from '../../theme'; 11 | 12 | export interface TextProps extends TProps { 13 | variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'leader' | 'normal' | 'small'; 14 | inline?: boolean; 15 | light?: boolean; 16 | color?: keyof typeof colors; 17 | align?: TextStyle['textAlign']; 18 | bold?: boolean; 19 | } 20 | 21 | export const Text: React.FC<TextProps> = ({ 22 | variant = 'normal', 23 | inline, 24 | accessible, 25 | light, 26 | color, 27 | style, 28 | children, 29 | align, 30 | bold, 31 | ...props 32 | }) => { 33 | const {focusRef, focusA11yElement} = useA11yElement(); 34 | 35 | useEffect(() => { 36 | if (accessible) { 37 | focusA11yElement(); 38 | } 39 | }, [accessible, focusA11yElement]); 40 | 41 | return ( 42 | <T 43 | ref={focusRef} 44 | style={[ 45 | styles.base, 46 | styles[variant], 47 | styles.dark, 48 | inline && styles.inline, 49 | light && styles.light, 50 | !!color && {color: colors[color]}, 51 | align && {textAlign: align}, 52 | bold && { 53 | fontFamily: 54 | variant === 'leader' 55 | ? text.fontFamily.latoBold 56 | : text.fontFamily.robotoBold 57 | }, 58 | style 59 | ]} 60 | accessible={accessible} 61 | {...props}> 62 | {children} 63 | </T> 64 | ); 65 | }; 66 | 67 | const styles = StyleSheet.create({ 68 | base: {width: '100%'}, 69 | inline: {width: 'auto'}, 70 | h1: {...text.h1Heading}, 71 | h2: {...text.h2Heading}, 72 | h3: {...text.h3Heading}, 73 | h4: {...text.h4Heading}, 74 | leader: {...text.leader}, 75 | normal: {...text.paragraph}, 76 | small: {...text.smallerParagraph}, 77 | dark: {color: colors.darkerPurple}, 78 | light: {color: colors.white} 79 | }); 80 | 81 | export default Text; 82 | -------------------------------------------------------------------------------- /ios/ProtectScotland.xcodeproj/xcshareddata/xcschemes/ProtectScotlandTests.xcscheme: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Scheme 3 | LastUpgradeVersion = "1160" 4 | version = "1.3"> 5 | <BuildAction 6 | parallelizeBuildables = "YES" 7 | buildImplicitDependencies = "YES"> 8 | </BuildAction> 9 | <TestAction 10 | buildConfiguration = "Debug" 11 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 12 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 13 | shouldUseLaunchSchemeArgsEnv = "YES"> 14 | <Testables> 15 | <TestableReference 16 | skipped = "NO"> 17 | <BuildableReference 18 | BuildableIdentifier = "primary" 19 | BlueprintIdentifier = "00E356ED1AD99517003FC87E" 20 | BuildableName = "ProtectScotlandTests.xctest" 21 | BlueprintName = "ProtectScotlandTests" 22 | ReferencedContainer = "container:ProtectScotland.xcodeproj"> 23 | </BuildableReference> 24 | </TestableReference> 25 | </Testables> 26 | </TestAction> 27 | <LaunchAction 28 | buildConfiguration = "Debug" 29 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 30 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 31 | launchStyle = "0" 32 | useCustomWorkingDirectory = "NO" 33 | ignoresPersistentStateOnLaunch = "NO" 34 | debugDocumentVersioning = "YES" 35 | debugServiceExtension = "internal" 36 | allowLocationSimulation = "YES"> 37 | </LaunchAction> 38 | <ProfileAction 39 | buildConfiguration = "Release" 40 | shouldUseLaunchSchemeArgsEnv = "YES" 41 | savedToolIdentifier = "" 42 | useCustomWorkingDirectory = "NO" 43 | debugDocumentVersioning = "YES"> 44 | </ProfileAction> 45 | <AnalyzeAction 46 | buildConfiguration = "Debug"> 47 | </AnalyzeAction> 48 | <ArchiveAction 49 | buildConfiguration = "Release" 50 | revealArchiveInOrganizer = "YES"> 51 | </ArchiveAction> 52 | </Scheme> 53 | -------------------------------------------------------------------------------- /components/atoms/symptom-checker.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, View, Image, TouchableWithoutFeedback} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | import * as WebBrowser from 'expo-web-browser'; 5 | 6 | import RoundedBox from '../atoms/rounded-box'; 7 | import {text, colors} from '../../theme'; 8 | import Markdown from './markdown'; 9 | 10 | const SymptomCheckerImage = require('../../assets/images/symptoms/image.png'); 11 | 12 | export const SymptomCheckerMessage: FC = () => { 13 | const {t} = useTranslation(); 14 | const handle = () => { 15 | WebBrowser.openBrowserAsync(t('links:b'), { 16 | enableBarCollapsing: true, 17 | showInRecents: true 18 | }); 19 | }; 20 | 21 | return ( 22 | <TouchableWithoutFeedback onPress={handle}> 23 | <View> 24 | <RoundedBox style={styles.container}> 25 | <Image 26 | width={135} 27 | height={130} 28 | source={SymptomCheckerImage} 29 | accessibilityIgnoresInvertColors={false} 30 | accessibilityHint={t('symptomChecker:hint')} 31 | /> 32 | <View style={styles.symptomMessage}> 33 | <Markdown markdownStyles={markdownStyles}> 34 | {t('symptomChecker:message')} 35 | </Markdown> 36 | </View> 37 | </RoundedBox> 38 | </View> 39 | </TouchableWithoutFeedback> 40 | ); 41 | }; 42 | 43 | const markdownStyles = StyleSheet.create({ 44 | text: { 45 | ...text.smallerParagraph, 46 | color: colors.darkerPurple 47 | }, 48 | // @ts-ignore 49 | strong: { 50 | ...text.h4Heading, 51 | color: colors.primaryPurple 52 | } 53 | }); 54 | 55 | const styles = StyleSheet.create({ 56 | container: { 57 | backgroundColor: colors.lighterPurple, 58 | borderColor: colors.lilac, 59 | alignItems: 'center', 60 | flexDirection: 'row', 61 | marginLeft: 45, 62 | marginRight: 45, 63 | width: 'auto', 64 | paddingHorizontal: 20, 65 | paddingVertical: 0 66 | }, 67 | symptomMessage: { 68 | paddingVertical: 20, 69 | marginLeft: 15, 70 | flex: 1 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /components/atoms/note-link.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import { 3 | TouchableWithoutFeedback, 4 | View, 5 | Image, 6 | Text, 7 | StyleSheet 8 | } from 'react-native'; 9 | import * as WebBrowser from 'expo-web-browser'; 10 | import {useTranslation} from 'react-i18next'; 11 | 12 | import {text as T, colors} from '../../theme'; 13 | 14 | const IconNote = require('../../assets/images/icon-note/image.png'); 15 | 16 | interface NoteLink { 17 | text: string; 18 | link: string; 19 | background?: string; 20 | } 21 | 22 | export const NoteLink: FC<NoteLink> = ({ 23 | background = colors.darkGrey, 24 | link, 25 | text 26 | }) => { 27 | const {t} = useTranslation(); 28 | const handle = () => { 29 | WebBrowser.openBrowserAsync(t(link), { 30 | enableBarCollapsing: true, 31 | showInRecents: true 32 | }); 33 | }; 34 | 35 | return ( 36 | <TouchableWithoutFeedback onPress={handle}> 37 | <View style={[styles.container, {borderColor: background}]}> 38 | <View style={[styles.icon, {backgroundColor: background}]}> 39 | <Image 40 | source={IconNote} 41 | width={36} 42 | height={36} 43 | resizeMethod="resize" 44 | resizeMode="contain" 45 | accessibilityIgnoresInvertColors={false} 46 | /> 47 | </View> 48 | <View style={styles.textContainer}> 49 | <Text style={[styles.text, styles.heading, {color: background}]}> 50 | {t(text)} 51 | </Text> 52 | </View> 53 | </View> 54 | </TouchableWithoutFeedback> 55 | ); 56 | }; 57 | 58 | const styles = StyleSheet.create({ 59 | container: { 60 | flexDirection: 'row', 61 | borderWidth: 1, 62 | borderStyle: 'solid', 63 | borderRadius: 10, 64 | overflow: 'hidden' 65 | }, 66 | heading: { 67 | fontWeight: 'bold' 68 | }, 69 | icon: { 70 | alignItems: 'center', 71 | justifyContent: 'center', 72 | padding: 12, 73 | width: 100 74 | }, 75 | text: { 76 | ...T.smallerParagraph 77 | }, 78 | textContainer: { 79 | flex: 1, 80 | padding: 13, 81 | paddingHorizontal: 25 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /android/app/src/main/java/gov/scot/covidtracker/MainApplication.java: -------------------------------------------------------------------------------- 1 | package gov.scot.covidtracker; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.util.Log; 6 | 7 | import com.facebook.react.PackageList; 8 | import com.facebook.react.ReactApplication; 9 | import com.facebook.react.ReactInstanceManager; 10 | import com.facebook.react.ReactNativeHost; 11 | import com.facebook.react.ReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.util.List; 15 | import java.util.Arrays; 16 | 17 | import gov.scot.covidtracker.generated.BasePackageList; 18 | import org.unimodules.adapters.react.ModuleRegistryAdapter; 19 | import org.unimodules.adapters.react.ReactModuleRegistryProvider; 20 | import org.unimodules.core.interfaces.SingletonModule; 21 | 22 | public class MainApplication extends Application implements ReactApplication { 23 | private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(new BasePackageList().getPackageList(), Arrays.<SingletonModule>asList()); 24 | 25 | private final ReactNativeHost mReactNativeHost = 26 | new ReactNativeHost(this) { 27 | @Override 28 | public boolean getUseDeveloperSupport() { 29 | return BuildConfig.DEBUG; 30 | } 31 | 32 | @Override 33 | protected List<ReactPackage> getPackages() { 34 | @SuppressWarnings("UnnecessaryLocalVariable") 35 | List<ReactPackage> packages = new PackageList(this).getPackages(); 36 | List<ReactPackage> unimodules = Arrays.<ReactPackage>asList( 37 | new ModuleRegistryAdapter(mModuleRegistryProvider) 38 | ); 39 | packages.addAll(unimodules); 40 | return packages; 41 | } 42 | 43 | @Override 44 | protected String getJSMainModuleName() { 45 | return "index"; 46 | } 47 | }; 48 | 49 | @Override 50 | public ReactNativeHost getReactNativeHost() { 51 | return mReactNativeHost; 52 | } 53 | 54 | @Override 55 | public void onCreate() { 56 | super.onCreate(); 57 | SoLoader.init(this, /* native exopackage */ false); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ios/ProtectScotland/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES"> 3 | <device id="retina6_5" orientation="portrait" appearance="light"/> 4 | <dependencies> 5 | <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/> 6 | <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> 7 | </dependencies> 8 | <objects> 9 | <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> 10 | <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> 11 | <view contentMode="scaleToFill" id="iN0-l3-epB"> 12 | <rect key="frame" x="0.0" y="0.0" width="480" height="844"/> 13 | <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> 14 | <subviews> 15 | <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="test-protect-logo" translatesAutoresizingMaskIntoConstraints="NO" id="fcW-Do-ePc"> 16 | <rect key="frame" x="110" y="349" width="260" height="180"/> 17 | <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> 18 | </imageView> 19 | </subviews> 20 | <color key="backgroundColor" red="0.41960784313725491" green="0.066666666666666666" blue="0.6470588235294118" alpha="1" colorSpace="custom" customColorSpace="displayP3"/> 21 | <nil key="simulatedStatusBarMetrics"/> 22 | <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> 23 | <point key="canvasLocation" x="547.82608695652175" y="575.22321428571422"/> 24 | </view> 25 | </objects> 26 | <resources> 27 | <image name="test-protect-logo" width="260" height="180"/> 28 | </resources> 29 | </document> 30 | -------------------------------------------------------------------------------- /theme/layouts/scrollable.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC, MutableRefObject} from 'react'; 2 | import { 3 | StyleSheet, 4 | ScrollView, 5 | RefreshControl, 6 | ViewStyle, 7 | AccessibilityProps 8 | } from 'react-native'; 9 | import {useSafeAreaInsets} from 'react-native-safe-area-context'; 10 | 11 | import {SPACING_TOP, SPACING_BOTTOM, SPACING_HORIZONTAL} from './shared'; 12 | import Container from '../../components/atoms/container'; 13 | import Spacing from '../../components/atoms/spacing'; 14 | 15 | interface LayoutProps { 16 | toast?: React.ReactNode; 17 | backgroundColor?: string; 18 | refresh?: { 19 | refreshing: boolean; 20 | onRefresh: () => void; 21 | }; 22 | scrollViewRef?: MutableRefObject<ScrollView | null>; 23 | safeArea?: boolean; 24 | children: React.ReactNode; 25 | contentContainerStyle?: ViewStyle; 26 | scrollableStyle?: ViewStyle; 27 | importantForAccessibility?: AccessibilityProps['importantForAccessibility']; 28 | } 29 | 30 | export const Scrollable: FC<LayoutProps> = ({ 31 | toast, 32 | backgroundColor, 33 | refresh, 34 | scrollViewRef, 35 | safeArea = true, 36 | children, 37 | contentContainerStyle, 38 | scrollableStyle = {}, 39 | importantForAccessibility = 'auto' 40 | }) => { 41 | const insets = useSafeAreaInsets(); 42 | const refreshControl = refresh && <RefreshControl {...refresh} />; 43 | 44 | return ( 45 | <Container style={[!!backgroundColor && {backgroundColor}]}> 46 | <ScrollView 47 | ref={scrollViewRef} 48 | keyboardShouldPersistTaps="always" 49 | style={scrollableStyle} 50 | importantForAccessibility={importantForAccessibility} 51 | contentContainerStyle={[ 52 | styles.scrollView, 53 | {paddingBottom: (safeArea ? insets.bottom : 0) + SPACING_BOTTOM}, 54 | contentContainerStyle 55 | ]} 56 | refreshControl={refreshControl}> 57 | {toast && ( 58 | <> 59 | {toast} 60 | <Spacing s={8} /> 61 | </> 62 | )} 63 | {children} 64 | </ScrollView> 65 | </Container> 66 | ); 67 | }; 68 | 69 | const styles = StyleSheet.create({ 70 | scrollView: { 71 | paddingTop: SPACING_TOP, 72 | paddingHorizontal: SPACING_HORIZONTAL 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /android/app/src/main/java/gov/scot/covidtracker/MainActivity.java: -------------------------------------------------------------------------------- 1 | package gov.scot.covidtracker; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import androidx.annotation.Nullable; 7 | import com.facebook.react.ReactActivity; 8 | import com.facebook.react.ReactActivityDelegate; 9 | import com.rajivshah.safetynet.RNGoogleSafetyNetPackage; 10 | 11 | public class MainActivity extends ReactActivity { 12 | 13 | /** 14 | * Returns the name of the main component registered from JavaScript. This is used to schedule 15 | * rendering of the component. 16 | */ 17 | @Override 18 | protected String getMainComponentName() { 19 | return "ProtectScotland"; 20 | } 21 | 22 | public static class ActivityDelegate extends ReactActivityDelegate { 23 | private static final String NOTIFICATION = "exposureNotificationClicked"; 24 | private Bundle mInitialProps = null; 25 | private final 26 | @Nullable 27 | Activity mActivity; 28 | 29 | public ActivityDelegate(Activity activity, String mainComponentName) { 30 | super(activity, mainComponentName); 31 | this.mActivity = activity; 32 | } 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | Log.i("ActivityDelegate", "onCreate"); 37 | 38 | Bundle bundle = mActivity.getIntent().getExtras(); 39 | if (bundle != null && bundle.containsKey(NOTIFICATION)) { 40 | Log.i("ActivityDelegate", "notification: " + bundle.getBoolean(NOTIFICATION)); 41 | mInitialProps = new Bundle(); 42 | mInitialProps.putBoolean(NOTIFICATION, bundle.getBoolean(NOTIFICATION)); 43 | } 44 | super.onCreate(null); 45 | } 46 | 47 | @Override 48 | protected Bundle getLaunchOptions() { 49 | return mInitialProps; 50 | } 51 | } 52 | 53 | @Override 54 | protected ReactActivityDelegate createReactActivityDelegate() { 55 | return new ActivityDelegate(this, getMainComponentName()); 56 | } 57 | 58 | @Override 59 | protected void onCreate(Bundle savedInstanceState) { 60 | Log.i("MainActivity", "onCreate"); 61 | super.onCreate(null); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /ios/ProtectScotland/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>BGTaskSchedulerPermittedIdentifiers</key> 6 | <array> 7 | <string>$(PRODUCT_BUNDLE_IDENTIFIER).exposure-notification</string> 8 | </array> 9 | <key>CFBundleDevelopmentRegion</key> 10 | <string>en</string> 11 | <key>CFBundleDisplayName</key> 12 | <string>$(PRODUCT_NAME)</string> 13 | <key>CFBundleExecutable</key> 14 | <string>$(EXECUTABLE_NAME)</string> 15 | <key>CFBundleIdentifier</key> 16 | <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> 17 | <key>CFBundleInfoDictionaryVersion</key> 18 | <string>6.0</string> 19 | <key>CFBundleName</key> 20 | <string>$(PRODUCT_NAME)</string> 21 | <key>CFBundlePackageType</key> 22 | <string>APPL</string> 23 | <key>CFBundleShortVersionString</key> 24 | <string>$(MARKETING_VERSION)</string> 25 | <key>CFBundleSignature</key> 26 | <string>????</string> 27 | <key>CFBundleSpokenName</key> 28 | <string>Protect Scotland</string> 29 | <key>CFBundleVersion</key> 30 | <string>$(CURRENT_PROJECT_VERSION)</string> 31 | <key>ENAPIVersion</key> 32 | <integer>2</integer> 33 | <key>ENDeveloperRegion</key> 34 | <string>GB-SCT</string> 35 | <key>ITSAppUsesNonExemptEncryption</key> 36 | <false/> 37 | <key>LSApplicationCategoryType</key> 38 | <string></string> 39 | <key>LSRequiresIPhoneOS</key> 40 | <true/> 41 | <key>NSAppTransportSecurity</key> 42 | <dict> 43 | <key>NSAllowsArbitraryLoads</key> 44 | <false/> 45 | <key>NSExceptionDomains</key> 46 | <dict> 47 | <key>localhost</key> 48 | <dict> 49 | <key>NSExceptionAllowsInsecureHTTPLoads</key> 50 | <true/> 51 | </dict> 52 | </dict> 53 | </dict> 54 | <key>UIBackgroundModes</key> 55 | <array> 56 | <string>fetch</string> 57 | <string>processing</string> 58 | </array> 59 | <key>UILaunchStoryboardName</key> 60 | <string>LaunchScreen</string> 61 | <key>UIRequiredDeviceCapabilities</key> 62 | <array> 63 | <string>telephony</string> 64 | <string>bluetooth-le</string> 65 | <string>armv7</string> 66 | </array> 67 | <key>UISupportedInterfaceOrientations</key> 68 | <array> 69 | <string>UIInterfaceOrientationPortrait</string> 70 | </array> 71 | <key>UIViewControllerBasedStatusBarAppearance</key> 72 | <false/> 73 | </dict> 74 | </plist> 75 | -------------------------------------------------------------------------------- /components/atoms/message.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import { 3 | StyleSheet, 4 | View, 5 | Image, 6 | TouchableWithoutFeedback, 7 | TouchableWithoutFeedbackProps, 8 | ImageSourcePropType 9 | } from 'react-native'; 10 | import {withTranslation, WithTranslation} from 'react-i18next'; 11 | 12 | import RoundedBox from './rounded-box'; 13 | import Container from './container'; 14 | import {text, colors} from '../../theme'; 15 | import Markdown from './markdown'; 16 | import {openBrowserAsync} from '../../utils/web-browser'; 17 | 18 | const SymptomCheckerImage = require('../../assets/images/symptoms/image.png'); 19 | 20 | interface MessageProps extends TouchableWithoutFeedbackProps, WithTranslation { 21 | image?: ImageSourcePropType; 22 | markdown?: string; 23 | link?: string; 24 | } 25 | 26 | const MessageBase: FC<MessageProps> = ({ 27 | t, 28 | image = SymptomCheckerImage, 29 | markdown = t('symptomChecker:message'), 30 | link = t('links:b'), 31 | accessibilityLabel = t('symptomChecker:a11y:label'), 32 | accessibilityHint = t('symptomChecker:a11y:hint'), 33 | ...props 34 | }) => ( 35 | <TouchableWithoutFeedback 36 | onPress={() => openBrowserAsync(link!)} 37 | accessibilityRole="link" 38 | accessibilityLabel={accessibilityLabel} 39 | accessibilityHint={accessibilityHint} 40 | {...props}> 41 | <View> 42 | <RoundedBox style={styles.container}> 43 | <Image 44 | width={135} 45 | height={130} 46 | source={image} 47 | accessibilityIgnoresInvertColors={false} 48 | /> 49 | <Container style={styles.symptomMessage}> 50 | <Markdown markdownStyles={markdownStyles}>{markdown}</Markdown> 51 | </Container> 52 | </RoundedBox> 53 | </View> 54 | </TouchableWithoutFeedback> 55 | ); 56 | 57 | const markdownStyles = StyleSheet.create({ 58 | text: { 59 | ...text.smallerParagraph, 60 | color: colors.darkerPurple 61 | }, 62 | // @ts-ignore 63 | strong: { 64 | ...text.h4Heading, 65 | color: colors.primaryPurple 66 | }, 67 | block: { 68 | margin: 0 69 | } 70 | }); 71 | 72 | const styles = StyleSheet.create({ 73 | container: { 74 | borderColor: colors.lighterPurple, 75 | backgroundColor: colors.lighterPurple, 76 | alignItems: 'center', 77 | flexDirection: 'row', 78 | width: 'auto', 79 | paddingHorizontal: 20, 80 | paddingVertical: 0 81 | }, 82 | symptomMessage: { 83 | paddingVertical: 20, 84 | marginLeft: 15 85 | } 86 | }); 87 | 88 | export const Message = withTranslation()(MessageBase); 89 | -------------------------------------------------------------------------------- /components/molecules/action-card.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import { 3 | StyleSheet, 4 | View, 5 | ViewStyle, 6 | Image, 7 | TouchableWithoutFeedback, 8 | AccessibilityProps 9 | } from 'react-native'; 10 | 11 | import Text from '../atoms/text'; 12 | import Container from '../atoms/container'; 13 | import {text, colors} from '../../theme'; 14 | import {openBrowserAsync} from '../../utils/web-browser'; 15 | 16 | const IconNote = require('../../assets/images/icon-note/image.png'); 17 | const IconNoteYellow = require('../../assets/images/icon-note-yellow/image.png'); 18 | 19 | interface CardProps extends AccessibilityProps { 20 | style?: ViewStyle; 21 | content: string; 22 | link: string; 23 | inverted?: boolean; 24 | } 25 | 26 | const ActionCard: FC<CardProps> = ({ 27 | style, 28 | content, 29 | link, 30 | inverted = false, 31 | ...props 32 | }) => { 33 | return ( 34 | <TouchableWithoutFeedback 35 | onPress={() => openBrowserAsync(link)} 36 | accessibilityRole="link" 37 | {...props}> 38 | <View style={[styles.container, style, inverted && styles.inverted]}> 39 | <View style={[styles.left, inverted && styles.invertedBg]}> 40 | <Image 41 | accessibilityIgnoresInvertColors={false} 42 | source={inverted ? IconNoteYellow : IconNote} 43 | style={styles.note} 44 | resizeMethod="resize" 45 | resizeMode="contain" 46 | /> 47 | </View> 48 | <Container> 49 | <Text variant="small" color="darkGrey" style={styles.text}> 50 | {content} 51 | </Text> 52 | </Container> 53 | </View> 54 | </TouchableWithoutFeedback> 55 | ); 56 | }; 57 | 58 | const styles = StyleSheet.create({ 59 | inverted: { 60 | color: colors.darkGrey, 61 | borderColor: colors.darkGrey, 62 | fontFamily: text.fontFamily.latoBold 63 | }, 64 | invertedBg: {backgroundColor: colors.darkGrey}, 65 | container: { 66 | flexDirection: 'row', 67 | borderWidth: 1, 68 | borderStyle: 'solid', 69 | borderColor: colors.darkGrey, 70 | borderRadius: 10 71 | }, 72 | left: { 73 | backgroundColor: colors.darkGrey, 74 | justifyContent: 'center', 75 | paddingVertical: 31, 76 | paddingLeft: 30, 77 | paddingRight: 24, 78 | borderBottomLeftRadius: 10, 79 | borderTopLeftRadius: 10 80 | }, 81 | note: { 82 | alignSelf: 'center' 83 | }, 84 | text: { 85 | paddingLeft: 20, 86 | paddingVertical: 20, 87 | paddingRight: 30 88 | } 89 | }); 90 | 91 | export default ActionCard; 92 | -------------------------------------------------------------------------------- /components/organisms/modals/exposure-notifications.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | import {Platform, StyleSheet} from 'react-native'; 4 | import { 5 | useExposure, 6 | StatusState, 7 | AuthorisedStatus 8 | } from 'react-native-exposure-notification-service'; 9 | 10 | import Markdown from '../../atoms/markdown'; 11 | import Modal, {ModalProps} from '../../molecules/modal'; 12 | import {text, colors} from '../../../theme'; 13 | import {goToSettingsAction} from '../../molecules/go-to-settings'; 14 | import {ScrollView} from 'react-native-gesture-handler'; 15 | 16 | export const ExposureNotificationsModal: FC<ModalProps> = (props) => { 17 | const {t} = useTranslation(); 18 | const {status, askPermissions, isAuthorised} = useExposure(); 19 | const ensUnknown = status.state === StatusState.unknown; 20 | const ensDisabled = status.state === StatusState.disabled; 21 | const notAuthorised = isAuthorised === AuthorisedStatus.unknown; 22 | 23 | return ( 24 | <Modal 25 | {...props} 26 | type="dark" 27 | title={t('modals:exposureNotifications:title')} 28 | buttons={ 29 | ensUnknown || notAuthorised 30 | ? [ 31 | { 32 | variant: 'inverted', 33 | action: async () => await askPermissions(), 34 | hint: t('common:turnOnBtnHint'), 35 | label: t('common:turnOnBtnLabel') 36 | } 37 | ] 38 | : [ 39 | { 40 | variant: 'inverted', 41 | action: () => goToSettingsAction(false, askPermissions), 42 | hint: ensDisabled 43 | ? t('common:turnOnBtnHint') 44 | : Platform.OS === 'android' 45 | ? t('common:turnOnBtnHint') 46 | : t('common:goToSettingsHint'), 47 | label: ensDisabled 48 | ? t('common:turnOnBtnLabel') 49 | : Platform.OS === 'android' 50 | ? t('common:turnOnBtnLabel') 51 | : t('common:goToSettings') 52 | } 53 | ] 54 | }> 55 | <ScrollView> 56 | <Markdown markdownStyles={modalMarkdownStyles}> 57 | {ensUnknown || notAuthorised 58 | ? t('modals:exposureNotifications:turnOn') 59 | : t(`modals:exposureNotifications:instructions${Platform.OS}`)} 60 | </Markdown> 61 | </ScrollView> 62 | </Modal> 63 | ); 64 | }; 65 | 66 | const modalMarkdownStyles = StyleSheet.create({ 67 | text: { 68 | ...text.default, 69 | color: colors.white 70 | }, 71 | listItemNumber: { 72 | color: colors.white 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /services/api/exposures.ts: -------------------------------------------------------------------------------- 1 | import { 2 | digestStringAsync, 3 | CryptoDigestAlgorithm, 4 | CryptoEncoding 5 | } from 'expo-crypto'; 6 | 7 | import {verify} from '.'; 8 | import {request} from './utils'; 9 | 10 | import {urls} from '../../constants/urls'; 11 | 12 | export enum ValidationResult { 13 | NetworkError, 14 | Error, 15 | Invalid, 16 | Expired, 17 | Valid 18 | } 19 | 20 | interface ValidateCodeResponse { 21 | token?: string; 22 | result: ValidationResult; 23 | } 24 | 25 | export const validateCode = async ( 26 | code: string 27 | ): Promise<ValidateCodeResponse> => { 28 | const controlHash = await digestStringAsync( 29 | CryptoDigestAlgorithm.SHA512, 30 | code.substr(0, 3), 31 | {encoding: CryptoEncoding.HEX} 32 | ); 33 | const codeHash = await digestStringAsync(CryptoDigestAlgorithm.SHA512, code, { 34 | encoding: CryptoEncoding.HEX 35 | }); 36 | 37 | const hash = `${controlHash}${codeHash}`; 38 | 39 | try { 40 | const resp = await request(`${urls.api}/exposures/verify`, { 41 | authorizationHeaders: true, 42 | method: 'POST', 43 | mode: 'cors', 44 | headers: { 45 | 'Content-Type': 'application/json' 46 | }, 47 | body: JSON.stringify({hash}) 48 | }); 49 | 50 | if (!resp) { 51 | throw new Error('Invalid response'); 52 | } 53 | const responseData = await resp.json(); 54 | 55 | return { 56 | result: ValidationResult.Valid, 57 | token: responseData.token 58 | }; 59 | } catch (err) { 60 | console.log('Code validation error: ', err, err.message); 61 | 62 | if (err.message && err.message === 'Network Unavailable') { 63 | return {result: ValidationResult.NetworkError}; 64 | } 65 | 66 | if (err.status === 410) { 67 | return {result: ValidationResult.Expired}; 68 | } else if (err.status >= 400 && err.status <= 499) { 69 | return {result: ValidationResult.Invalid}; 70 | } 71 | 72 | return { 73 | result: ValidationResult.Error 74 | }; 75 | } 76 | }; 77 | 78 | export const uploadExposureKeys = async ( 79 | uploadToken: string, 80 | exposures: any[] 81 | ): Promise<void> => { 82 | const resp = await request(`${urls.api}/exposures`, { 83 | authorizationHeaders: true, 84 | method: 'POST', 85 | mode: 'cors', 86 | headers: { 87 | 'Content-Type': 'application/json' 88 | }, 89 | body: JSON.stringify({ 90 | token: uploadToken, 91 | exposures, 92 | ...(await verify(uploadToken)) 93 | }) 94 | }); 95 | 96 | if (!resp || resp.status !== 204) { 97 | throw new Error('Upload failed'); 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /components/molecules/go-to-settings.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {Linking, Platform} from 'react-native'; 3 | import * as IntentLauncher from 'expo-intent-launcher'; 4 | import { 5 | useExposure, 6 | StatusState, 7 | StatusType, 8 | AuthorisedStatus 9 | } from 'react-native-exposure-notification-service'; 10 | 11 | import Button from '../atoms/button'; 12 | import {useTranslation} from 'react-i18next'; 13 | 14 | export const goToSettingsAction = async ( 15 | bluetoothDisabled?: boolean, 16 | askPermissions?: () => Promise<void> 17 | ) => { 18 | try { 19 | if (Platform.OS === 'ios') { 20 | Linking.openSettings(); 21 | } else { 22 | bluetoothDisabled 23 | ? await IntentLauncher.startActivityAsync( 24 | IntentLauncher.ACTION_SETTINGS 25 | ) 26 | : await askPermissions!(); 27 | } 28 | } catch (err) { 29 | console.log('Error handling go to settings', err); 30 | } 31 | }; 32 | 33 | const GoToSettings: FC = () => { 34 | const {t} = useTranslation(); 35 | const {status, askPermissions, isAuthorised} = useExposure(); 36 | const platform = Platform.OS === 'ios' ? 'ios' : 'android'; 37 | 38 | const bluetoothDisabled = 39 | status.state === StatusState.disabled && 40 | status.type?.includes(StatusType.bluetooth); 41 | 42 | const ensUnknown = status.state === StatusState.unknown; 43 | const ensDisabled = status.state === StatusState.disabled; 44 | const notAuthorised = isAuthorised === AuthorisedStatus.unknown; 45 | 46 | return ( 47 | <Button 48 | variant="dark" 49 | rounded 50 | onPress={async () => 51 | ensUnknown || notAuthorised 52 | ? await askPermissions() 53 | : goToSettingsAction(bluetoothDisabled, askPermissions) 54 | } 55 | label={ 56 | ensUnknown || notAuthorised 57 | ? t('common:turnOnBtnLabel') 58 | : ensDisabled 59 | ? t('common:turnOnBtnLabel') 60 | : platform === 'android' 61 | ? t('common:turnOnBtnLabel') 62 | : t('common:goToSettings') 63 | } 64 | hint={ 65 | ensUnknown || notAuthorised 66 | ? t('common:turnOnBtnHint') 67 | : ensDisabled 68 | ? t('common:turnOnBtnHint') 69 | : platform === 'android' 70 | ? t('common:turnOnBtnHint') 71 | : t('common:goToSettingsHint') 72 | }> 73 | {ensUnknown || notAuthorised 74 | ? t('common:turnOnBtnLabel') 75 | : ensDisabled 76 | ? t('common:turnOnBtnLabel') 77 | : platform === 'android' 78 | ? t('common:turnOnBtnLabel') 79 | : t('common:goToSettings')} 80 | </Button> 81 | ); 82 | }; 83 | 84 | export default GoToSettings; 85 | -------------------------------------------------------------------------------- /components/views/calculator.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {ScrollView, StyleSheet, View, Text} from 'react-native'; 3 | import {useSafeArea} from 'react-native-safe-area-context'; 4 | import {useTranslation} from 'react-i18next'; 5 | import {StackNavigationProp} from '@react-navigation/stack'; 6 | 7 | import Spacing from '../atoms/spacing'; 8 | import {ModalHeader} from '../molecules/modal-header'; 9 | import {SPACING_BOTTOM} from '../../theme/layouts/shared'; 10 | import Illustration from '../atoms/illustration'; 11 | import Markdown from '../atoms/markdown'; 12 | import {PADDING_TOP, text, colors} from '../../theme'; 13 | import {useNavigation} from '@react-navigation/native'; 14 | 15 | const CalculatorIllustration = require('../../assets/images/calculator-illustration/image.png'); 16 | 17 | interface CalculatorModalProps { 18 | navigation: StackNavigationProp<any>; 19 | } 20 | 21 | export const CalculatorModal: FC<CalculatorModalProps> = () => { 22 | const {t} = useTranslation(); 23 | const insets = useSafeArea(); 24 | const navigation = useNavigation<StackNavigationProp<any>>(); 25 | 26 | return ( 27 | <ScrollView 28 | keyboardShouldPersistTaps="always" 29 | style={styles.container} 30 | contentContainerStyle={[ 31 | styles.contentContainer, 32 | {paddingBottom: insets.bottom + SPACING_BOTTOM} 33 | ]}> 34 | <ModalHeader 35 | heading="calculator:heading" 36 | color="amber" 37 | onClosePress={() => navigation.goBack()} 38 | /> 39 | <View style={styles.top}> 40 | <Illustration 41 | source={CalculatorIllustration} 42 | accessibilityIgnoresInvertColors={false} 43 | accessibilityHint={t('calculator:illustrationAlt')} 44 | accessibilityLabel={t('calculator:illustrationAlt')} 45 | /> 46 | <Markdown markdownStyles={markdownStyles}> 47 | {t('calculator:body')} 48 | </Markdown> 49 | <Spacing s={18} /> 50 | <Text style={styles.highlight}>{t('calculator:highlight')}</Text> 51 | <Spacing s={50} /> 52 | </View> 53 | </ScrollView> 54 | ); 55 | }; 56 | 57 | const markdownStyles = StyleSheet.create({ 58 | text: { 59 | ...text.paragraph, 60 | color: colors.darkerPurple 61 | }, 62 | // @ts-ignore 63 | strong: { 64 | ...text.h4Heading, 65 | color: colors.darkerPurple 66 | } 67 | }); 68 | 69 | const styles = StyleSheet.create({ 70 | container: { 71 | flex: 1 72 | }, 73 | contentContainer: { 74 | flexGrow: 1, 75 | paddingTop: PADDING_TOP, 76 | paddingLeft: 45, 77 | paddingRight: 45 78 | }, 79 | top: {flex: 1}, 80 | highlight: { 81 | ...text.h3Heading, 82 | color: colors.amber 83 | } 84 | }); 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | android/app/release/ 32 | 33 | # Visual Studio Code 34 | # 35 | .vscode/ 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # BUCK 44 | buck-out/ 45 | \.buckd/ 46 | *.keystore 47 | !debug.keystore 48 | 49 | # fastlane 50 | # 51 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 52 | # screenshots whenever they are needed. 53 | # For more information about the recommended setup visit: 54 | # https://docs.fastlane.tools/best-practices/source-control/ 55 | 56 | */fastlane/report.xml 57 | */fastlane/Preview.html 58 | */fastlane/screenshots 59 | 60 | # Bundle artifact 61 | *.jsbundle 62 | 63 | # CocoaPods 64 | /ios/Pods/ 65 | 66 | # dotenv environment variables file 67 | .env 68 | 69 | # Lighthouse 70 | lighthouse-report.html 71 | 72 | # Mac attributes 73 | .DS_Store 74 | 75 | # NPM configuration file with personal access tokens 76 | .npmrc 77 | 78 | # Frontend build 79 | build 80 | 81 | # Editors 82 | *.sublime* 83 | .vscode 84 | 85 | # Misc 86 | *.zip 87 | 88 | # Xcode 89 | ## User settings 90 | xcuserdata/ 91 | 92 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 93 | *.xcscmblueprint 94 | *.xccheckout 95 | 96 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 97 | build/ 98 | DerivedData/ 99 | *.moved-aside 100 | *.pbxuser 101 | !default.pbxuser 102 | *.mode1v3 103 | !default.mode1v3 104 | *.mode2v3 105 | !default.mode2v3 106 | *.perspectivev3 107 | !default.perspectivev3 108 | 109 | ## Xcode Patch 110 | *.xcodeproj/* 111 | !*.xcodeproj/project.pbxproj 112 | !*.xcodeproj/xcshareddata/ 113 | !*.xcworkspace/contents.xcworkspacedata 114 | /*.gcno 115 | 116 | ## Android 117 | .idea 118 | .gradle 119 | local.properties 120 | *.iml 121 | .settings 122 | .classpath 123 | .project 124 | .github/secrets/ 125 | 126 | # fastlane specific 127 | fastlane/report.xml 128 | 129 | # fastlane environment 130 | ios/.env.default 131 | android/.env.default 132 | 133 | # deliver temporary files 134 | fastlane/Preview.html 135 | 136 | # snapshot generated screenshots 137 | fastlane/screenshots 138 | 139 | # scan temporary files 140 | fastlane/test_output 141 | 142 | # misc 143 | /.eslintcache 144 | -------------------------------------------------------------------------------- /components/views/tests.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import {StyleSheet, ScrollView, Image, Platform} from 'react-native'; 3 | import {useSafeAreaInsets} from 'react-native-safe-area-context'; 4 | import {useTranslation} from 'react-i18next'; 5 | import {StackNavigationProp} from '@react-navigation/stack'; 6 | 7 | import Text from '../atoms/text'; 8 | import Button from '../atoms/button'; 9 | import Spacing from '../atoms/spacing'; 10 | import {ModalHeader} from '../molecules/modal-header'; 11 | import {SPACING_BOTTOM, SPACING_HORIZONTAL} from '../../theme/layouts/shared'; 12 | import {ScreenNames} from '../../navigation'; 13 | import {ArrowLink} from '../molecules/arrow-link'; 14 | import {openBrowserAsync} from '../../utils/web-browser'; 15 | 16 | const JarIcon = require('../../assets/images/icon-jar/image.png'); 17 | const Illustration = require('../../assets/images/test-illustration/image.png'); 18 | const IconPlus = require('../../assets/images/icon-plus/image.png'); 19 | 20 | interface TestsProps { 21 | navigation: StackNavigationProp<any>; 22 | } 23 | 24 | export const Tests: FC<TestsProps> = ({navigation}) => { 25 | const {t} = useTranslation(); 26 | const insets = useSafeAreaInsets(); 27 | 28 | const handleAddTestResult = () => navigation.navigate(ScreenNames.testsAdd); 29 | 30 | return ( 31 | <ScrollView 32 | contentContainerStyle={[ 33 | styles.contentContainer, 34 | {paddingBottom: insets.bottom + SPACING_BOTTOM} 35 | ]}> 36 | <ModalHeader heading="tests:heading" color="darkGrey" icon={JarIcon} /> 37 | <Spacing s={40} /> 38 | <Image 39 | source={Illustration} 40 | style={styles.logo} 41 | accessibilityIgnoresInvertColors={false} 42 | /> 43 | <Spacing s={40} /> 44 | <Text align="center" variant="leader" color="darkGrey"> 45 | {t('tests:content')} 46 | </Text> 47 | <Spacing s={25} /> 48 | <Button 49 | onPress={handleAddTestResult} 50 | icon={IconPlus} 51 | label={t('tests:addTestResult')} 52 | variant="dark"> 53 | {t('tests:addTestResult')} 54 | </Button> 55 | <Spacing s={25} /> 56 | <Button 57 | onPress={() => openBrowserAsync(t('links:t'))} 58 | label={t('tests:bookATest')} 59 | variant="inverted"> 60 | {t('tests:bookATest')} 61 | </Button> 62 | <Spacing s={54} /> 63 | <ArrowLink 64 | externalLink={t('links:j')} 65 | accessibilityHint={t('tests:view:a11y:hint')} 66 | accessibilityLabel={t('tests:view:a11y:label')}> 67 | <Text variant="h4" color="primaryPurple"> 68 | {t('tests:view:tellMore')} 69 | </Text> 70 | </ArrowLink> 71 | <Spacing s={54} /> 72 | </ScrollView> 73 | ); 74 | }; 75 | 76 | const styles = StyleSheet.create({ 77 | logo: {alignSelf: 'center'}, 78 | contentContainer: { 79 | flexGrow: 1, 80 | paddingTop: Platform.OS === 'ios' ? 65 : 30, 81 | paddingHorizontal: SPACING_HORIZONTAL 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /components/views/onboarding/permissions-info.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC, useState} from 'react'; 2 | import {Image, StyleSheet} from 'react-native'; 3 | import {useTranslation} from 'react-i18next'; 4 | import {StackNavigationProp} from '@react-navigation/stack'; 5 | import {useExposure} from 'react-native-exposure-notification-service'; 6 | import * as SecureStore from 'expo-secure-store'; 7 | 8 | import Button from '../../atoms/button'; 9 | import Spacing from '../../atoms/spacing'; 10 | import Markdown from '../../atoms/markdown'; 11 | import {ScreenNames} from '../../../navigation'; 12 | import {useSettings} from '../../../providers/settings'; 13 | import {useApplication} from '../../../providers/context'; 14 | import Container from '../../atoms/container'; 15 | import Text from '../../atoms/text'; 16 | import {text, colors} from '../../../theme'; 17 | 18 | const IllustrationSource = require('../../../assets/images/permissions-illustration/image.png'); 19 | 20 | interface PermissionInfoProps { 21 | navigation: StackNavigationProp<any>; 22 | } 23 | 24 | const PermissionsInfo: FC<PermissionInfoProps> = ({navigation}) => { 25 | const {t} = useTranslation(); 26 | const {reload} = useSettings(); 27 | const [disabled, setDisabled] = useState(false); 28 | const {askPermissions} = useExposure(); 29 | const application = useApplication(); 30 | 31 | const handlePermissions = async () => { 32 | setDisabled(true); 33 | SecureStore.setItemAsync('analyticsConsent', String(true), {}); 34 | try { 35 | await askPermissions(); 36 | reload(); 37 | await application.setContext({completedExposureOnboarding: true}); 38 | 39 | setTimeout( 40 | () => 41 | navigation.reset({ 42 | index: 0, 43 | routes: [{name: ScreenNames.dashboard}] 44 | }), 45 | 1000 46 | ); 47 | } catch (e) { 48 | setDisabled(false); 49 | console.log("Error opening app's settings", e); 50 | } 51 | }; 52 | 53 | return ( 54 | <> 55 | <Container> 56 | <Container center="horizontal" stretch={false}> 57 | <Image 58 | source={IllustrationSource} 59 | accessibilityIgnoresInvertColors={false} 60 | /> 61 | </Container> 62 | <Spacing s={48} /> 63 | <Text variant="h2" light accessible> 64 | {t('onboarding:permissionsInfo:view:title')} 65 | </Text> 66 | <Spacing s={24} /> 67 | <Markdown markdownStyles={markdownStyles}> 68 | {t('onboarding:permissionsInfo:view:text')} 69 | </Markdown> 70 | </Container> 71 | <Spacing s={46} /> 72 | <Button 73 | disabled={disabled} 74 | onPress={handlePermissions} 75 | hint={t('common:next:hint')}> 76 | {t('common:next:label')} 77 | </Button> 78 | <Spacing s={24} /> 79 | </> 80 | ); 81 | }; 82 | 83 | const markdownStyles = StyleSheet.create({ 84 | text: { 85 | ...text.default, 86 | color: colors.white 87 | } 88 | }); 89 | 90 | export default PermissionsInfo; 91 | -------------------------------------------------------------------------------- /providers/reminder.tsx: -------------------------------------------------------------------------------- 1 | import React, {createContext, useContext, useEffect, useState} from 'react'; 2 | import AsyncStorage from '@react-native-community/async-storage'; 3 | import {useTranslation} from 'react-i18next'; 4 | import PushNotification from 'react-native-push-notification'; 5 | import {addDays} from 'date-fns'; 6 | import { 7 | StatusState, useExposure 8 | } from 'react-native-exposure-notification-service'; 9 | 10 | interface State { 11 | paused: string | null; 12 | checked: boolean; 13 | } 14 | 15 | interface ReminderContextValue extends State { 16 | setReminder: (date: Date) => void; 17 | deleteReminder: () => void; 18 | cancelReminder: () => void; 19 | } 20 | 21 | const initialState = { 22 | paused: null, 23 | checked: false 24 | }; 25 | 26 | export const ReminderContext = createContext( 27 | initialState as ReminderContextValue 28 | ); 29 | 30 | export interface API { 31 | children: any; 32 | } 33 | 34 | const REMINDER_KEY = 'ni.reminder'; 35 | const REMINDER_ID = 12345; 36 | 37 | export const Provider = ({children}: API) => { 38 | const [state, setState] = useState<State>(initialState); 39 | const {t} = useTranslation(); 40 | const exposure = useExposure(); 41 | 42 | useEffect(() => { 43 | const checkRunning = async () => { 44 | AsyncStorage.getItem(REMINDER_KEY).then((paused) => { 45 | setState({ 46 | paused: paused && exposure.status.state !== StatusState.active ? paused : null, 47 | checked: true 48 | }) 49 | }); 50 | }; 51 | 52 | checkRunning(); 53 | }, [exposure.status.state]); 54 | 55 | const cancelReminder = () => 56 | PushNotification.cancelLocalNotifications({id: String(REMINDER_ID)}); 57 | 58 | const deleteReminder = () => { 59 | PushNotification.cancelLocalNotifications({id: String(REMINDER_ID)}); 60 | AsyncStorage.removeItem(REMINDER_KEY); 61 | setState({ 62 | ...state, 63 | paused: null 64 | }); 65 | }; 66 | 67 | const setReminder = (date: Date) => { 68 | const currentDate = new Date(); 69 | const notificationDate = date < currentDate ? addDays(date, 1) : date; 70 | const timestamp = String(notificationDate.getTime()); 71 | AsyncStorage.setItem(REMINDER_KEY, timestamp); 72 | 73 | PushNotification.localNotificationSchedule({ 74 | channelId: 'default', 75 | id: REMINDER_ID, 76 | title: t('reminder:title'), 77 | message: t('reminder:message'), 78 | date: notificationDate, 79 | repeatType: 'hour', 80 | // @ts-ignore 81 | allowWhileIdle: true 82 | }); 83 | 84 | setState({ 85 | ...state, 86 | paused: timestamp 87 | }); 88 | }; 89 | 90 | const value: ReminderContextValue = { 91 | ...state, 92 | setReminder, 93 | deleteReminder, 94 | cancelReminder 95 | }; 96 | 97 | return ( 98 | <ReminderContext.Provider value={value}> 99 | {children} 100 | </ReminderContext.Provider> 101 | ); 102 | }; 103 | 104 | export const ReminderProvider = Provider; 105 | 106 | export const useReminder = () => useContext(ReminderContext); 107 | -------------------------------------------------------------------------------- /components/molecules/arrow-link.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react'; 2 | import { 3 | AccessibilityProps, 4 | Image, 5 | StyleSheet, 6 | Text, 7 | View, 8 | ViewStyle, 9 | TextStyle, 10 | TouchableWithoutFeedback, 11 | ImageSourcePropType 12 | } from 'react-native'; 13 | 14 | import {openBrowserAsync} from '../../utils/web-browser'; 15 | import {colors} from '../../theme'; 16 | import {ScreenNames} from '../../navigation'; 17 | const ArrowIcon = require('../../assets/images/icon-arrow/image.png'); 18 | const ArrowIconPurple = require('../../assets/images/icon-arrow-purple/image.png'); 19 | const ExternalLinkIcon = require('../../assets/images/icon-external-link/image.png'); 20 | const ExternalLinkIconLight = require('../../assets/images/icon-external-link-light/image.png'); 21 | 22 | interface ArrowLinkProps extends AccessibilityProps { 23 | navigation?: any; 24 | screen?: ScreenNames; 25 | externalLink?: string; 26 | containerStyle?: ViewStyle; 27 | textStyle?: TextStyle; 28 | invert?: boolean; 29 | fullWidth?: boolean; 30 | onPress?: () => void; 31 | icon?: ImageSourcePropType; 32 | } 33 | 34 | export const ArrowLink: FC<ArrowLinkProps> = ({ 35 | navigation, 36 | screen, 37 | onPress, 38 | externalLink, 39 | accessibilityLabel, 40 | accessibilityHint, 41 | containerStyle = {}, 42 | textStyle = {}, 43 | invert, 44 | fullWidth = !!externalLink, 45 | children, 46 | icon, 47 | ...props 48 | }) => { 49 | const handlePress = () => { 50 | if (screen && navigation) { 51 | return navigation.navigate(screen); 52 | } 53 | if (externalLink) { 54 | return openBrowserAsync(externalLink); 55 | } 56 | }; 57 | return ( 58 | <TouchableWithoutFeedback 59 | onPress={onPress ? onPress : handlePress} 60 | accessibilityRole="link" 61 | accessibilityHint={accessibilityHint} 62 | accessibilityLabel={accessibilityLabel} 63 | {...props}> 64 | <View style={[styles.linkContainer, containerStyle]}> 65 | <View 66 | style={[!fullWidth && styles.inline, fullWidth && styles.fullWidth]}> 67 | {children || ( 68 | <Text style={[styles.text, textStyle]}>{accessibilityLabel}</Text> 69 | )} 70 | </View> 71 | <Image 72 | source={ 73 | icon || 74 | (externalLink 75 | ? invert 76 | ? ExternalLinkIconLight 77 | : ExternalLinkIcon 78 | : invert 79 | ? ArrowIconPurple 80 | : ArrowIcon) 81 | } 82 | accessibilityIgnoresInvertColors={false} 83 | style={styles.icon} 84 | /> 85 | </View> 86 | </TouchableWithoutFeedback> 87 | ); 88 | }; 89 | 90 | const styles = StyleSheet.create({ 91 | linkContainer: { 92 | flexDirection: 'row', 93 | alignItems: 'center' 94 | }, 95 | inline: {maxWidth: '80%'}, 96 | fullWidth: {flex: 1}, 97 | text: { 98 | textAlign: 'left', 99 | justifyContent: 'flex-start', 100 | color: colors.darkGrey 101 | }, 102 | icon: {marginLeft: 10} 103 | }); 104 | -------------------------------------------------------------------------------- /ios/ProtectScotland.xcodeproj/xcshareddata/xcschemes/ProtectScotland.xcscheme: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Scheme 3 | LastUpgradeVersion = "1160" 4 | version = "1.3"> 5 | <BuildAction 6 | parallelizeBuildables = "YES" 7 | buildImplicitDependencies = "YES"> 8 | <BuildActionEntries> 9 | <BuildActionEntry 10 | buildForTesting = "YES" 11 | buildForRunning = "YES" 12 | buildForProfiling = "YES" 13 | buildForArchiving = "YES" 14 | buildForAnalyzing = "YES"> 15 | <BuildableReference 16 | BuildableIdentifier = "primary" 17 | BlueprintIdentifier = "13B07F861A680F5B00A75B9A" 18 | BuildableName = "Protect-Scot.app" 19 | BlueprintName = "ProtectScotland" 20 | ReferencedContainer = "container:ProtectScotland.xcodeproj"> 21 | </BuildableReference> 22 | </BuildActionEntry> 23 | </BuildActionEntries> 24 | </BuildAction> 25 | <TestAction 26 | buildConfiguration = "Debug" 27 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 28 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 29 | shouldUseLaunchSchemeArgsEnv = "YES"> 30 | <Testables> 31 | </Testables> 32 | </TestAction> 33 | <LaunchAction 34 | buildConfiguration = "Debug" 35 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 36 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 37 | launchStyle = "0" 38 | useCustomWorkingDirectory = "NO" 39 | ignoresPersistentStateOnLaunch = "NO" 40 | debugDocumentVersioning = "YES" 41 | debugServiceExtension = "internal" 42 | allowLocationSimulation = "YES"> 43 | <BuildableProductRunnable 44 | runnableDebuggingMode = "0"> 45 | <BuildableReference 46 | BuildableIdentifier = "primary" 47 | BlueprintIdentifier = "13B07F861A680F5B00A75B9A" 48 | BuildableName = "Protect-Scot.app" 49 | BlueprintName = "ProtectScotland" 50 | ReferencedContainer = "container:ProtectScotland.xcodeproj"> 51 | </BuildableReference> 52 | </BuildableProductRunnable> 53 | </LaunchAction> 54 | <ProfileAction 55 | buildConfiguration = "Release" 56 | shouldUseLaunchSchemeArgsEnv = "YES" 57 | savedToolIdentifier = "" 58 | useCustomWorkingDirectory = "NO" 59 | debugDocumentVersioning = "YES"> 60 | <BuildableProductRunnable 61 | runnableDebuggingMode = "0"> 62 | <BuildableReference 63 | BuildableIdentifier = "primary" 64 | BlueprintIdentifier = "13B07F861A680F5B00A75B9A" 65 | BuildableName = "Protect-Scot.app" 66 | BlueprintName = "ProtectScotland" 67 | ReferencedContainer = "container:ProtectScotland.xcodeproj"> 68 | </BuildableReference> 69 | </BuildableProductRunnable> 70 | </ProfileAction> 71 | <AnalyzeAction 72 | buildConfiguration = "Debug"> 73 | </AnalyzeAction> 74 | <ArchiveAction 75 | buildConfiguration = "Release" 76 | revealArchiveInOrganizer = "YES"> 77 | </ArchiveAction> 78 | </Scheme> 79 | --------------------------------------------------------------------------------