Merge remote-tracking branch 'upstream/develop' into inji-415-release

This commit is contained in:
Swati Goel
2023-09-29 00:12:10 +05:30
366 changed files with 44285 additions and 30354 deletions

2
.bundle/config Normal file
View File

@@ -0,0 +1,2 @@
BUNDLE_PATH: "vendor/bundle"
BUNDLE_FORCE_RUBY_PLATFORM: 1

11
.env
View File

@@ -1,9 +1,15 @@
# after making changes to the env file, ensure to start the bundler (or the project) with a --reset-cache # after making changes to the env file, ensure to start the bundler (or the project) with a --reset-cache
# eg . npm build android:newlogic --reset-cache # eg . npm build android:newlogic --reset-cache
MIMOTO_HOST=https://api.qa-inji.mosip.net
#MIMOTO_HOST=http://mock.mimoto.newlogic.dev #MIMOTO_HOST=http://mock.mimoto.newlogic.dev
MIMOTO_HOST=https://api.qa-inji.mosip.net
ESIGNET_HOST=https://api.qa-inji.mosip.net
GOOGLE_NEARBY_MESSAGES_API_KEY= GOOGLE_NEARBY_MESSAGES_API_KEY=
OBSRV_HOST = https://dataset-api.obsrv.mosip.net
#Application Theme can be ( orange | purple ) #Application Theme can be ( orange | purple )
APPLICATION_THEME=orange APPLICATION_THEME=orange
@@ -12,3 +18,6 @@ CREDENTIAL_REGISTRY_EDIT=true
#supported languages( en, fil, ar, hi, kn, ta) #supported languages( en, fil, ar, hi, kn, ta)
APPLICATION_LANGUAGE=en APPLICATION_LANGUAGE=en
#Toggle for openID for VC
ENABLE_OPENID_FOR_VC=false

4
.eslintrc.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: '@react-native',
};

View File

@@ -1,7 +1,8 @@
name: Android Beta Build name: Android Beta Build
env: env:
backendServiceDefaultUrl: https://api.sandbox.mosip.net mimotoBackendServiceDefaultUrl: https://api.sandbox.mosip.net
esignetBackendServiceDefaultUrl: https://api.sandbox.mosip.net
on: on:
workflow_dispatch: workflow_dispatch:
@@ -21,8 +22,13 @@ on:
required: true required: true
default: False default: False
type: string type: string
backendServiceUrl: mimotoBackendServiceUrl:
description: 'Backend service URL' description: 'Mimoto backend service URL'
required: true
default: 'https://api.sandbox.mosip.net'
type: string
esignetBackendServiceUrl:
description: 'Esignet backend service URL'
required: true required: true
default: 'https://api.sandbox.mosip.net' default: 'https://api.sandbox.mosip.net'
type: string type: string
@@ -133,7 +139,8 @@ jobs:
cd android/scripts cd android/scripts
./beta-build.sh ./beta-build.sh
env: env:
MIMOTO_HOST: ${{ github.event.inputs.backendServiceUrl }} MIMOTO_HOST: ${{ github.event.inputs.mimotoBackendServiceUrl }}
ESIGNET_HOST: ${{ github.event.inputs.esignetBackendServiceUrl }}
APPLICATION_THEME: ${{ github.event.inputs.theme }} APPLICATION_THEME: ${{ github.event.inputs.theme }}
RELEASE_KEYSTORE_ALIAS: androidreleasekey RELEASE_KEYSTORE_ALIAS: androidreleasekey
RELEASE_KEYSTORE_PASSWORD: '${{secrets.INJI_ANDROID_RELEASE_STOREPASS}}' RELEASE_KEYSTORE_PASSWORD: '${{secrets.INJI_ANDROID_RELEASE_STOREPASS}}'

View File

@@ -1,13 +1,25 @@
name: Android Internal Build name: Android Internal Build
run-name: ${{ github.event.inputs.buildname }}
env: env:
backendServiceDefaultUrl: https://api.sandbox.mosip.net mimotoBackendServiceDefaultUrl: https://api.sandbox.mosip.net
esignetBackendServiceDefaultUrl: https://api.sandbox.mosip.net
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
backendServiceUrl: buildname:
description: 'Backend service URL' description: 'Build App For'
required: true
default: 'Sprint-x/Collab/release-x.x.x'
type: string
mimotoBackendServiceUrl:
description: 'Mimoto backend service URL'
required: true
default: 'https://api.sandbox.mosip.net'
type: string
esignetBackendServiceUrl:
description: 'Esignet backend service URL'
required: true required: true
default: 'https://api.sandbox.mosip.net' default: 'https://api.sandbox.mosip.net'
type: string type: string
@@ -41,7 +53,7 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '16.x' node-version: '16.x'
- name: Cache local npm repository - name: Cache local npm repository
uses: actions/cache@v3.0.11 uses: actions/cache@v3.0.11
with: with:
@@ -68,7 +80,7 @@ jobs:
run: | run: |
# Strip git ref prefix from version # Strip git ref prefix from version
echo "BRANCH_NAME=$(echo ${{ github.ref }} | sed -e 's,.*/\(.*\),\1,')" >> $GITHUB_ENV echo "BRANCH_NAME=$(echo ${{ github.ref }} | sed -e 's,.*/\(.*\),\1,')" >> $GITHUB_ENV
echo "GPG_TTY=$(tty)" >> $GITHUB_ENV echo "GPG_TTY=$(tty)" >> $GITHUB_ENV
- name: Setup branch and GPG public key - name: Setup branch and GPG public key
run: | run: |
@@ -99,11 +111,11 @@ jobs:
cd android/app cd android/app
export RELEASE_KEYSTORE_ALIAS=androidreleasekey export RELEASE_KEYSTORE_ALIAS=androidreleasekey
export RELEASE_KEYSTORE_PASSWORD=$RELEASE_KEYSTORE_PASSWORD export RELEASE_KEYSTORE_PASSWORD=$RELEASE_KEYSTORE_PASSWORD
env: env:
RELEASE_KEYSTORE_PASSWORD: '${{secrets.INJI_ANDROID_RELEASE_STOREPASS}}' RELEASE_KEYSTORE_PASSWORD: '${{secrets.INJI_ANDROID_RELEASE_STOREPASS}}'
- name: Create Google Play Config file - name: Create Google Play Config file
run : | run: |
cd android cd android
echo "$INJI_ANDROID_PLAY_STORE_CONFIG_JSON" > play_config.json.b64 echo "$INJI_ANDROID_PLAY_STORE_CONFIG_JSON" > play_config.json.b64
base64 -d -i play_config.json.b64 > play_config.json base64 -d -i play_config.json.b64 > play_config.json
@@ -111,14 +123,103 @@ jobs:
INJI_ANDROID_PLAY_STORE_CONFIG_JSON: ${{ secrets.INJI_ANDROID_PLAY_STORE_CONFIG_JSON }} INJI_ANDROID_PLAY_STORE_CONFIG_JSON: ${{ secrets.INJI_ANDROID_PLAY_STORE_CONFIG_JSON }}
- name: Run Build - name: Run Build
run: | run: |
cd android/scripts cd android/scripts
./internal-build.sh ./internal-build.sh
env: env:
MIMOTO_HOST: ${{ github.event.inputs.backendServiceUrl }} MIMOTO_HOST: ${{ github.event.inputs.mimotoBackendServiceUrl }}
APPLICATION_THEME: ${{ github.event.inputs.theme }} ESIGNET_HOST: ${{ github.event.inputs.esignetBackendServiceUrl }}
APPLICATION_THEME: ${{ github.event.inputs.theme }}
RELEASE_KEYSTORE_ALIAS: androidreleasekey RELEASE_KEYSTORE_ALIAS: androidreleasekey
RELEASE_KEYSTORE_PASSWORD: '${{secrets.INJI_ANDROID_RELEASE_STOREPASS}}' RELEASE_KEYSTORE_PASSWORD: '${{secrets.INJI_ANDROID_RELEASE_STOREPASS}}'
SLACK_URL: '${{ secrets.SLACK_WEBHOOK_DEVOPS }}' SLACK_URL: '${{ secrets.SLACK_WEBHOOK_DEVOPS }}'
PLAY_CONSOLE_RELEASE_DESCRIPTION: ${{ github.event.inputs.buildDescription }} PLAY_CONSOLE_RELEASE_DESCRIPTION: ${{ github.event.inputs.buildDescription }}
CREDENTIAL_REGISTRY_EDIT: ${{ github.event.inputs.registry_edit }} CREDENTIAL_REGISTRY_EDIT: ${{ github.event.inputs.registry_edit }}
upload-to-actions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/setup-node@v3
with:
node-version: '16.x'
- name: Cache local npm repository
uses: actions/cache@v3.0.11
with:
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
path: ~/.npm
- name: Cache local gradle repository
uses: actions/cache@v3.0.11
with:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
path: |
~/.gradle/caches
~/.gradle/wrapper
- name: Install npm dependencies
run: |
npm i
- name: Create .env.local file
run: |
echo "${{ secrets.ENV_FILE }}" > .env.local > android/local.properties
- name: Setup branch and env
run: |
# Strip git ref prefix from version
echo "BRANCH_NAME=$(echo ${{ github.ref }} | sed -e 's,.*/\(.*\),\1,')" >> $GITHUB_ENV
echo "GPG_TTY=$(tty)" >> $GITHUB_ENV
- name: Setup branch and GPG public key
run: |
# Strip git ref prefix from version
echo ${{ env.BRANCH_NAME }}
echo ${{ env.GPG_TTY }}
sudo apt-get --yes install gnupg2
gpg2 --import ./.github/keys/mosipgpgkey_pub.gpg
gpg2 --quiet --batch --passphrase=${{secrets.gpg_secret}} --allow-secret-key-import --import ./.github/keys/mosipgpgkey_sec.gpg
- name: Setup Firebase
run: |
cd android
if [ ! -z "$FIREBASE_SECRET" ] && [ -f app/google-services.json.gpg ];then rm -f app/google-services.json;gpg2 --quiet --batch --passphrase=$FIREBASE_SECRET --pinentry-mode loopback --decrypt --output app/google-services.json app/mosip-google-services.json.gpg;fi
env:
FIREBASE_SECRET: ${{ secrets.GPG_SECRET }}
- name: Generate keystore
run: |
echo "$ANDROID_KEYSTORE_FILE" > release.keystore.b64
base64 -d -i release.keystore.b64 > android/app/release.keystore
env:
ANDROID_KEYSTORE_FILE: ${{ secrets.INJI_ANDROID_RELEASE_KEYSTORE }}
- name: Create Google Play Config file
run: |
cd android
echo "$INJI_ANDROID_PLAY_STORE_CONFIG_JSON" > play_config.json.b64
base64 -d -i play_config.json.b64 > play_config.json
env:
INJI_ANDROID_PLAY_STORE_CONFIG_JSON: ${{ secrets.INJI_ANDROID_PLAY_STORE_CONFIG_JSON }}
- name: Run Build for upload
run: |
npx jetify
cd android
./gradlew :app:assembleMosipRelease
env:
MIMOTO_HOST: ${{ github.event.inputs.mimotoBackendServiceUrl }}
ESIGNET_HOST: ${{ github.event.inputs.esignetBackendServiceUrl }}
APPLICATION_THEME: ${{ github.event.inputs.theme }}
RELEASE_KEYSTORE_ALIAS: androidreleasekey
RELEASE_KEYSTORE_PASSWORD: '${{secrets.INJI_ANDROID_RELEASE_STOREPASS}}'
CREDENTIAL_REGISTRY_EDIT: ${{ github.event.inputs.registry_edit }}
- name: Upload Artifact
uses: actions/upload-artifact@v3.1.1
with:
name: ${{ github.event.inputs.buildname }}
path: android/app/build/outputs/apk/mosip/release/
retention-days: 10

View File

@@ -0,0 +1,72 @@
name: Inji iOS Automation Build
on:
workflow_dispatch:
inputs:
mimotoBackendServiceUrl:
description: 'Mimoto backend service URL'
required: true
default: 'https://api.sandbox.mosip.net'
type: string
esignetBackendServiceUrl:
description: 'Esignet backend service URL'
required: true
default: 'https://api.sandbox.mosip.net'
type: string
theme:
description: 'Application Theme'
required: true
default: 'orange'
type: choice
options:
- orange
- purple
registry_edit:
description: 'Edit Registry'
required: true
default: 'true'
type: choice
options:
- false
- true
jobs:
build_ios:
name: Uploading app to Actions
runs-on: macos-13
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/setup-node@v3
with:
node-version: '18.x'
- name: Install npm dependencies
run: |
npm install
- name: Install Pod
run: |
cd ios
pod install
pod install
- name: Build iOS for automation on simulator
run: |
cd ios
fastlane build_app_for_automation_on_simulator
- name: Upload Inji.app artifact
uses: actions/upload-artifact@v3.1.1
with:
name: Inji
path: ios/fastlane/Inji_app_file/Products/Applications/
retention-days: 1
- name: Upload Runner Log on Failure
if: failure() # Only run this step if the previous steps failed
uses: actions/upload-artifact@v3.1.1
with:
name: runner-log
path: /Users/runner/Library/Logs/gym/
retention-days: 1

View File

@@ -1,10 +1,15 @@
name: Inji iOS build name: Inji iOS build
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
backendServiceUrl: mimotoBackendServiceUrl:
description: 'Backend service URL' description: 'Mimoto backend service URL'
required: true
default: 'https://api.sandbox.mosip.net'
type: string
esignetBackendServiceUrl:
description: 'Esignet backend service URL'
required: true required: true
default: 'https://api.sandbox.mosip.net' default: 'https://api.sandbox.mosip.net'
type: string type: string
@@ -45,22 +50,23 @@ jobs:
build_ios: build_ios:
name: Deploying to Testflight name: Deploying to Testflight
runs-on: macos-13 runs-on: macos-13
steps: steps:
- uses: actions/checkout@v3.1.0 - uses: actions/checkout@v3.1.0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '16.x' node-version: '18.x'
- name: Install npm dependencies - name: Install npm dependencies
run: | run: |
npm install npm install
- name: Install Pod - name: Install Pod
run: | run: |
cd ios cd ios
pod install pod install
pod install
- name: Deploy iOS Beta to TestFlight - name: Deploy iOS Beta to TestFlight
run: | run: |
cd ios cd ios
@@ -82,7 +88,13 @@ jobs:
SLACK_URL: '${{ secrets.SLACK_WEBHOOK_DEVOPS }}' SLACK_URL: '${{ secrets.SLACK_WEBHOOK_DEVOPS }}'
MATCH_PASSWORD: '${{ secrets.INJI_IOS_MATCH_PASSWORD }}' MATCH_PASSWORD: '${{ secrets.INJI_IOS_MATCH_PASSWORD }}'
APPLICATION_THEME: ${{ github.event.inputs.theme }} APPLICATION_THEME: ${{ github.event.inputs.theme }}
CREDENTIAL_REGISTRY_EDIT: ${{ github.event.inputs.registry_edit }}
MIMOTO_HOST: ${{ github.event.inputs.backendServiceUrl }}
TESTFLIGHT_INTERNAL_TESTERS_GROUP: ${{ github.event.inputs.internal-testers }} TESTFLIGHT_INTERNAL_TESTERS_GROUP: ${{ github.event.inputs.internal-testers }}
TESTFLIGHT_BETA_APP_DESCRIPTION: ${{ github.event.inputs.buildDescription }} TESTFLIGHT_BETA_APP_DESCRIPTION: ${{ github.event.inputs.buildDescription }}
- name: Upload Runner Log on Failure
if: failure() # Only run this step if the previous steps failed
uses: actions/upload-artifact@v3.1.1
with:
name: runner-log
path: /Users/runner/Library/Logs/gym/
retention-days: 1

32
.gitignore vendored
View File

@@ -41,6 +41,7 @@ DerivedData
*.ipa *.ipa
*.xcuserstate *.xcuserstate
project.xcworkspace project.xcworkspace
ios/.xcode.env.local
# Android/IntelliJ # Android/IntelliJ
# #
@@ -50,6 +51,10 @@ build/
local.properties local.properties
*.iml *.iml
*.hprof *.hprof
*.hprof
.cxx/
*.keystore
!debug.keystore
# node.js # node.js
# #
@@ -57,6 +62,18 @@ node_modules/
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output
# BUCK # BUCK
buck-out/ buck-out/
\.buckd/ \.buckd/
@@ -64,9 +81,15 @@ buck-out/
# Bundle artifacts # Bundle artifacts
*.jsbundle *.jsbundle
# CocoaPods
ios/Pods/
# Ruby / CocoaPods
/ios/Pods/
/vendor/bundle/
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
# testing
/coverage
c
# OS X temporary files that should never be committed # OS X temporary files that should never be committed
.DS_Store .DS_Store
src/components/.DS_Store src/components/.DS_Store
@@ -94,3 +117,8 @@ temp/
yalc.lock yalc.lock
android/app/debug.keystore android/app/debug.keystore
# Expo
.expo
dist/
web-build/

7
.prettierrc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
arrowParens: 'avoid',
bracketSameLine: true,
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
};

View File

@@ -1,13 +0,0 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"bracketSameLine": true,
"jsxSingleQuote": false,
"quoteProps": "consistent",
"printWidth": 80,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}

5
.watchmanconfig Normal file
View File

@@ -0,0 +1,5 @@
{
"ignore_dirs": [],
"fsevents_latency": 0.5,
"fsevents_try_resync": true
}

63
App.tsx
View File

@@ -1,23 +1,29 @@
import React, { useContext, useEffect } from 'react'; import React, {useContext, useEffect} from 'react';
import AppLoading from 'expo-app-loading'; import AppLoading from 'expo-app-loading';
import { AppLayout } from './screens/AppLayout'; import {AppLayout} from './screens/AppLayout';
import { useFont } from './shared/hooks/useFont'; import {useFont} from './shared/hooks/useFont';
import { GlobalContextProvider } from './components/GlobalContextProvider'; import {GlobalContextProvider} from './components/GlobalContextProvider';
import { GlobalContext } from './shared/GlobalContext'; import {GlobalContext} from './shared/GlobalContext';
import { useSelector } from '@xstate/react'; import {useSelector} from '@xstate/react';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { import {
selectIsDecryptError, selectIsDecryptError,
selectIsKeyInvalidateError, selectIsKeyInvalidateError,
selectIsReadError, selectIsReadError,
selectIsReady, selectIsReady,
} from './machines/app'; } from './machines/app';
import { DualMessageOverlay } from './components/DualMessageOverlay'; import {DualMessageOverlay} from './components/DualMessageOverlay';
import { useApp } from './screens/AppController'; import {useApp} from './screens/AppController';
import { Alert } from 'react-native'; import {Alert} from 'react-native';
import { ErrorMessageOverlay } from './components/MessageOverlay'; import {
getAppInfoData,
getTelemetryConfigData,
initializeTelemetry,
sendAppInfoEvent,
} from './shared/telemetry/TelemetryUtils';
import {MessageOverlay} from './components/MessageOverlay';
import SecureKeystore from 'react-native-secure-keystore'; import SecureKeystore from 'react-native-secure-keystore';
import { isCustomSecureKeystore } from './shared/cryptoutil/cryptoUtil'; import {isCustomSecureKeystore} from './shared/cryptoutil/cryptoUtil';
import i18n from './i18n'; import i18n from './i18n';
// kludge: this is a bad practice but has been done temporarily to surface // kludge: this is a bad practice but has been done temporarily to surface
@@ -35,36 +41,43 @@ const DecryptErrorAlert = (controller, t) => {
}, },
]); ]);
}; };
function configureTelemetry() {
const config = getTelemetryConfigData();
initializeTelemetry(config);
sendAppInfoEvent(getAppInfoData());
}
const AppLayoutWrapper: React.FC = () => { const AppLayoutWrapper: React.FC = () => {
const { appService } = useContext(GlobalContext); const {appService} = useContext(GlobalContext);
const isDecryptError = useSelector(appService, selectIsDecryptError); const isDecryptError = useSelector(appService, selectIsDecryptError);
const controller = useApp(); const controller = useApp();
const { t } = useTranslation('WelcomeScreen'); const {t} = useTranslation('WelcomeScreen');
if (isDecryptError) { if (isDecryptError) {
DecryptErrorAlert(controller, t); DecryptErrorAlert(controller, t);
} }
configureTelemetry();
return <AppLayout />; return <AppLayout />;
}; };
const AppLoadingWrapper: React.FC = () => { const AppLoadingWrapper: React.FC = () => {
const { appService } = useContext(GlobalContext); const {appService} = useContext(GlobalContext);
const isReadError = useSelector(appService, selectIsReadError); const isReadError = useSelector(appService, selectIsReadError);
const isKeyInvalidateError = useSelector( const isKeyInvalidateError = useSelector(
appService, appService,
selectIsKeyInvalidateError selectIsKeyInvalidateError,
); );
const controller = useApp(); const controller = useApp();
const { t } = useTranslation('WelcomeScreen'); const {t} = useTranslation('WelcomeScreen');
return ( return (
<> <>
<AppLoading /> <AppLoading />
<MessageOverlay
<ErrorMessageOverlay
translationPath={'WelcomeScreen'}
isVisible={isKeyInvalidateError} isVisible={isKeyInvalidateError}
error={'errors.invalidateKeyError'} title={t('errors.invalidateKeyError.title')}
onDismiss={controller.RESET} message={t('errors.invalidateKeyError.message')}
onButtonPress={controller.RESET}
buttonText={t('common:ok')}
customHeight={'auto'}
/> />
{isReadError ? ( {isReadError ? (
@@ -81,16 +94,16 @@ const AppLoadingWrapper: React.FC = () => {
}; };
const AppInitialization: React.FC = () => { const AppInitialization: React.FC = () => {
const { appService } = useContext(GlobalContext); const {appService} = useContext(GlobalContext);
const isReady = useSelector(appService, selectIsReady); const isReady = useSelector(appService, selectIsReady);
const hasFontsLoaded = useFont(); const hasFontsLoaded = useFont();
const { t } = useTranslation('common'); const {t} = useTranslation('common');
useEffect(() => { useEffect(() => {
if (isCustomSecureKeystore()) { if (isCustomSecureKeystore()) {
SecureKeystore.updatePopup( SecureKeystore.updatePopup(
t('biometricPopup.title'), t('biometricPopup.title'),
t('biometricPopup.description') t('biometricPopup.description'),
); );
} }
}, [i18n.language]); }, [i18n.language]);

View File

@@ -174,3 +174,26 @@ More info here:
## Credits ## Credits
Credits listed [here](/Credits.md) Credits listed [here](/Credits.md)
## Congratulations! :tada:
You've successfully run and modified your React Native App. :partying_face:
### Now what?
- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started).
# Troubleshooting
If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
# Learn More
To learn more about React Native, take a look at the following resources:
- [React Native Website](https://reactnative.dev) - learn more about React Native.
- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment.
- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**.
- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts.
- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.

View File

@@ -1,13 +0,0 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=C\:/Program Files/OpenJDK/openjdk-11.0.12_7
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true

View File

@@ -1,55 +0,0 @@
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
lib_deps = []
create_aar_targets(glob(["libs/*.aar"]))
create_jar_targets(glob(["libs/*.jar"]))
android_library(
name = "all-libs",
exported_deps = lib_deps,
)
android_library(
name = "app-code",
srcs = glob([
"src/main/java/**/*.java",
]),
deps = [
":all-libs",
":build_config",
":res",
],
)
android_build_config(
name = "build_config",
package = "io.mosip.residentapp",
)
android_resource(
name = "res",
package = "io.mosip.residentapp",
res = "src/main/res",
)
android_binary(
name = "app",
keystore = "//android/keystores:debug",
manifest = "src/main/AndroidManifest.xml",
package_type = "debug",
deps = [
":app-code",
],
)

View File

@@ -5,152 +5,109 @@ plugins {
apply plugin: "com.android.application" apply plugin: "com.android.application"
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.google.firebase.crashlytics'
apply plugin: "com.facebook.react"
import com.android.build.OutputFile import com.android.build.OutputFile
/** react {
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets /* Folders */
* and bundleReleaseJsAndAssets). // The root of your project, i.e. where "package.json" lives. Default is '..'
* These basically call `react-native bundle` with the correct arguments during the Android build // root = file("../")
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the // The folder where the react-native NPM package is. Default is ../node_modules/react-native
* bundle directly from the development server. Below you can see all the possible configurations // reactNativeDir = file("../node_modules/react-native")
* and their defaults. If you decide to add a configuration block, make sure to add it before the // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
* `apply from: "../../node_modules/react-native/react.gradle"` line. // codegenDir = file("../node_modules/@react-native/codegen")
* // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
* project.ext.react = [ // cliFile = file("../node_modules/react-native/cli.js")
* // the name of the generated asset file containing your JS bundle /* Variants */
* bundleAssetName: "index.android.bundle", // The list of variants to that are debuggable. For those we're going to
* // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
* // the entry file for bundle generation. If none specified and // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
* // "index.android.js" exists, it will be used. Otherwise "index.js" is // debuggableVariants = ["liteDebug", "prodDebug"]
* // default. Can be overridden with ENTRY_FILE environment variable. /* Bundling */
* entryFile: "index.android.js", // A list containing the node command and its flags. Default is just 'node'.
* // nodeExecutableAndArgs = ["node"]
* // https://reactnative.dev/docs/performance#enable-the-ram-format //
* bundleCommand: "ram-bundle", // The command to run when bundling. By default is 'bundle'
* // bundleCommand = "ram-bundle"
* // whether to bundle JS and assets in debug mode //
* bundleInDebug: false, // The path to the CLI configuration file. Default is empty.
* // bundleConfig = file(../rn-cli.config.js)
* // whether to bundle JS and assets in release mode //
* bundleInRelease: true, // The name of the generated asset file containing your JS bundle
* // bundleAssetName = "MyApplication.android.bundle"
* // whether to bundle JS and assets in another build variant (if configured). //
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
* // The configuration property can be in the following formats // entryFile = file("../js/MyApplication.android.js")
* // 'bundleIn${productFlavor}${buildType}' //
* // 'bundleIn${buildType}' // A list of extra flags to pass to the 'bundle' commands.
* // bundleInFreeDebug: true, // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
* // bundleInPaidRelease: true, // extraPackagerArgs = []
* // bundleInBeta: true, /* Hermes Commands */
* // The hermes compiler command to run. By default it is 'hermesc'
* // whether to disable dev mode in custom build variants (by default only disabled in release) // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
* // for example: to disable dev mode in the staging build type (if configured) //
* devDisabledInStaging: true, // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
* // The configuration property can be in the following formats // hermesFlags = ["-O", "-output-source-map"]
* // 'devDisabledIn${productFlavor}${buildType}' }
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
enableHermes: (findProperty('expo.jsEngine') ?: "jsc") == "hermes",
bundleInDebug: true,
bundleInRelease: true,
devDisabledInRelease: true,
cliPath: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/cli.js",
hermesCommand: new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/%OS-BIN%/hermesc",
composeSourceMapsPath: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/scripts/compose-source-maps.js",
]
apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle")
/** /**
* Set this to true to create two separate APKs instead of one: * Set this to true to create four separate APKs instead of one,
* - An APK that only works on ARM devices * one for each native architecture. This is useful if you don't
* - An APK that only works on x86 devices * use App Bundles (https://developer.android.com/guide/app-bundle/)
* The advantage is the size of the APK is reduced by about 4MB. * and want to have separate APKs to upload to the Play Store.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/ */
def enableSeparateBuildPerCPUArchitecture = true def enableSeparateBuildPerCPUArchitecture = true
/** /**
* Run Proguard to shrink the Java bytecode in release builds. * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/ */
def enableProguardInReleaseBuilds = false def enableProguardInReleaseBuilds = false
/** /**
* The preferred build flavor of JavaScriptCore. * The preferred build flavor of JavaScriptCore (JSC)
* *
* For example, to use the international variant, you can use: * For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
* *
* The international variant includes ICU i18n library and necessary data * The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that * give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default. * this variant is about 6MiB larger per architecture than default.
*/ */
def jscFlavor = 'org.webkit:android-jsc:+' def jscFlavor = 'org.webkit:android-jsc:+'
/** /**
* Whether to enable the Hermes VM. * Private function to get the list of Native Architectures you want to build.
* * This reads the value from reactNativeArchitectures in your gradle.properties
* This should be set on project.ext.react and mirrored here. If it is not set * file and works together with the --active-arch-only flag of react-native run-android.
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/ */
def enableHermes = project.ext.react.get("enableHermes", false); def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
androidGitVersion { androidGitVersion {
baseCode 0 baseCode 0
codeFormat 'MXXNXXPXX' codeFormat 'MXXNXXPXX'
format '% tag %%commit_%%branch%' format '%tag_%%commit_%%branch%'
} }
android { android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
ext { ext {
APP_NAME_RELEASE = "@string/app_name" APP_NAME_RELEASE = "@string/app_name"
APP_NAME_PH = "@string/app_name_ph"
APP_NAME_MOSIP = "@string/app_name_mosip" APP_NAME_MOSIP = "@string/app_name_mosip"
APP_NAME_BETA = "@string/app_name_beta" APP_NAME_BETA = "@string/app_name_beta"
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 namespace 'io.mosip.residentapp'
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'io.mosip.residentapp'
defaultConfig { defaultConfig {
applicationId 'io.mosip.residentapp' applicationId 'io.mosip.residentapp'
@@ -164,7 +121,8 @@ android {
manifestPlaceholders = [ manifestPlaceholders = [
APP_NAME: APP_NAME_RELEASE, APP_NAME: APP_NAME_RELEASE,
GOOGLE_NEARBY_MESSAGES_API_KEY: "${properties.getProperty('GOOGLE_NEARBY_MESSAGES_API_KEY')}" GOOGLE_NEARBY_MESSAGES_API_KEY: "${properties.getProperty('GOOGLE_NEARBY_MESSAGES_API_KEY')}",
appAuthRedirectScheme: 'io.mosip.residentapp.inji'
] ]
} }
splits { splits {
@@ -175,6 +133,7 @@ android {
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
} }
} }
signingConfigs { signingConfigs {
release { release {
def keystore = file('release.keystore') def keystore = file('release.keystore')
@@ -233,28 +192,22 @@ android {
] ]
dimension "inji" dimension "inji"
} }
ph {
versionName defaultConfig.versionName + "-ph"
manifestPlaceholders = [
APP_NAME: APP_NAME_PH
]
dimension "inji"
}
} }
android.applicationVariants.all { variant -> android.applicationVariants.all { variant ->
variant.outputs.all { output -> variant.outputs.all { output ->
def datetime = new Date().format('yyyyMMdd_HHmm') def datetime = new Date().format('yyyyMMdd_HHmm')
def architecture = output.getFilter(com.android.build.OutputFile.ABI) ?: "universal" def architecture = output.getFilter(com.android.build.OutputFile.ABI) ?: "universal"
outputFileName = "${defaultConfig.applicationId}-${variant.versionName}_${datetime}_${architecture}.apk" outputFileName = "Inji_${variant.versionName}_${datetime}_${architecture}.apk"
} }
} }
} }
dependencies { dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion // The version of react-native is set by the React Native Gradle Plugin
implementation "com.facebook.react:react-native:+"// From node_modules implementation("com.facebook.react:react-android")
implementation 'com.facebook.soloader:soloader:0.10.1+' implementation 'com.facebook.soloader:soloader:0.10.1+'
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
@@ -283,9 +236,7 @@ dependencies {
} }
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper' exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp' exclude group:'com.squareup.okhttp3', module:'okhttp'
@@ -294,9 +245,8 @@ dependencies {
exclude group:'com.facebook.flipper' exclude group:'com.facebook.flipper'
} }
if (enableHermes) { if (hermesEnabled.toBoolean()) {
debugImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim(), "../android/hermes-debug.aar")) implementation("com.facebook.react:hermes-android")
releaseImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim(), "../android/hermes-release.aar"))
} else { } else {
implementation jscFlavor implementation jscFlavor
} }
@@ -306,15 +256,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
} }
// Run this once to be able to run the application with BUCK apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesAppBuildGradle(project)
apply from: "./eas-build.gradle" apply from: "./eas-build.gradle"
try { try {

View File

@@ -1,19 +0,0 @@
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)

View File

@@ -12,5 +12,8 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/>
<!-- from Android 12, Location permission isn't required as we aren't using it to determine location --> <!-- from Android 12, Location permission isn't required as we aren't using it to determine location -->
<application tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" android:usesCleartextTraffic="true" /> <application tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" android:usesCleartextTraffic="true" >
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
</application>
</manifest> </manifest>

View File

@@ -1,5 +1,5 @@
/** /**
* Copyright (c) Facebook, Inc. and its affiliates. * Copyright (c) Meta Platforms, Inc. and affiliates.
* *
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root * <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree. * directory of this source tree.
@@ -17,22 +17,27 @@ import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule; import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
/**
* Class responsible of loading Flipper inside your React Native application. This is the debug
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
*/
public class ReactNativeFlipper { public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) { if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context); final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new ReactFlipperPlugin());
client.addPlugin(new DatabasesFlipperPlugin(context)); client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context)); client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance()); client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder( NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() { new NetworkingModule.CustomClientBuilder() {
@@ -43,12 +48,13 @@ public class ReactNativeFlipper {
}); });
client.addPlugin(networkFlipperPlugin); client.addPlugin(networkFlipperPlugin);
client.start(); client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized // Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) { if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener( reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() { new ReactInstanceEventListener() {
@Override @Override
public void onReactContextInitialized(ReactContext reactContext) { public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this); reactInstanceManager.removeReactInstanceEventListener(this);

View File

@@ -1,54 +1,51 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.mosip.residentapp"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.mosip.residentapp">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/>
<!-- from Android 12, Location permission isn't required as we aren't using it to determine location --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https" /> <data android:scheme="https"/>
</intent> </intent>
</queries> </queries>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme"> <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme">
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true" /> <meta-data android:name="com.google.android.nearby.messages.API_KEY" android:value="${GOOGLE_NEARBY_MESSAGES_API_KEY}"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="43.0.0" /> <meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS" /> <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="48.0.0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0" /> <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@nlpaolo/mosip-resident-app" /> <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="com.google.android.nearby.messages.API_KEY" android:value="${GOOGLE_NEARBY_MESSAGES_API_KEY}" /> <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@anonymous/inji"/>
<activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustPan" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait"> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="io.mosip.residentapp" /> <data android:scheme="io.mosip.residentapp"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
</application> </application>
</manifest> </manifest>

View File

@@ -1,4 +1,5 @@
package io.mosip.residentapp; package io.mosip.residentapp;
import expo.modules.ReactActivityDelegateWrapper;
import android.Manifest; import android.Manifest;
import android.content.Context; import android.content.Context;
@@ -13,6 +14,9 @@ import androidx.core.content.ContextCompat;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate; import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView; import com.facebook.react.ReactRootView;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
import expo.modules.ReactActivityDelegateWrapper; import expo.modules.ReactActivityDelegateWrapper;
/** /**
@@ -108,4 +112,20 @@ public class MainActivity extends ReactActivity {
} }
recreate(); recreate();
} }
/**
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
* (aka React 18) with two boolean flags.
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate( this,
getMainComponentName(),
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
// If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
));
}
} }

View File

@@ -1,42 +0,0 @@
package io.mosip.residentapp;
import android.os.Bundle;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import expo.modules.ReactActivityDelegateWrapper;
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Set the theme to AppTheme BEFORE onCreate to support
// coloring the background, status bar, and navigation bar.
// This is required for expo-splash-screen.
setTheme(R.style.AppTheme);
super.onCreate(null);
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "main";
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegateWrapper(
this,
new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
});
}
}

View File

@@ -1,4 +1,6 @@
package io.mosip.residentapp; package io.mosip.residentapp;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
@@ -12,10 +14,10 @@ import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;
import timber.log.Timber; import timber.log.Timber;
import com.facebook.react.bridge.JSIModulePackage; import com.facebook.react.bridge.JSIModulePackage;
@@ -26,9 +28,8 @@ import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
public class MainApplication extends Application implements ReactApplication { public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper( private final ReactNativeHost mReactNativeHost =
this, new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) {
new ReactNativeHost(this) {
@Override @Override
public boolean getUseDeveloperSupport() { public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG; return BuildConfig.DEBUG;
@@ -47,6 +48,14 @@ public class MainApplication extends Application implements ReactApplication {
protected String getJSMainModuleName() { protected String getJSMainModuleName() {
return "index"; return "index";
} }
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
}); });
@Override @Override
@@ -59,8 +68,11 @@ public class MainApplication extends Application implements ReactApplication {
super.onCreate(); super.onCreate();
SoLoader.init(this, /* native exopackage */ false); SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
ApplicationLifecycleDispatcher.onApplicationCreate(this); // If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree()); Timber.plant(new Timber.DebugTree());
@@ -68,44 +80,10 @@ public class MainApplication extends Application implements ReactApplication {
// Setup Firebase // Setup Firebase
FirebaseAnalytics.getInstance(this); FirebaseAnalytics.getInstance(this);
Timber.plant(new CrashReportingTree()); Timber.plant(new CrashReportingTree());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
} }
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("io.mosip.residentapp.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/** /**
* A tree which logs important information for crash reporting. * A tree which logs important information for crash reporting.
*/ */
@@ -128,4 +106,10 @@ public class MainApplication extends Application implements ReactApplication {
} }
} }
} }
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:drawable="@color/splashscreen_background"/> android:drawable="@color/splashscreen_background"/>
</layer-list>
<!-- Splash screen image -->
<item>
<bitmap
android:src="@drawable/splash_image"
android:gravity="center" />
</item>
</layer-list>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_mosip"/> <foreground android:drawable="@mipmap/ic_launcher_mosip"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_mosip"/> <foreground android:drawable="@mipmap/ic_launcher_mosip"/>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,2 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources/> <resources/>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources> <resources>
<color name="splashscreen_background">#FFFFFF</color> <color name="splashscreen_background">#FFFFFF</color>
<color name="iconBackground">#FFFFFF</color> <color name="iconBackground">#FFFFFF</color>
<color name="colorPrimary">#023c69</color> <color name="colorPrimary">#023c69</color>
<color name="colorPrimaryDark">#ffffff</color> <color name="colorPrimaryDark">#ffffff</color>
<color name="ic_launcher_background">#ffffff</color>
</resources> </resources>

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources> <resources>
<string name="app_name">Inji</string> <string name="app_name">Inji</string>
<string name="app_name_mosip">MOSIP Resident App - Mosip/Inji</string> <string name="app_name_mosip">MOSIP Resident App - Mosip/Inji</string>
@@ -6,4 +5,5 @@
<string name="app_name_ph">MOSIP Resident App - PH</string> <string name="app_name_ph">MOSIP Resident App - PH</string>
<string name="expo_splash_screen_resize_mode" translatable="false">cover</string> <string name="expo_splash_screen_resize_mode" translatable="false">cover</string>
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string> <string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
<string name="ExpoLocalization_supportsRTL" translatable="false">undefined</string>
</resources> </resources>

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColor">@android:color/black</item> <item name="android:textColor">@android:color/black</item>

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package io.mosip.residentapp;
import android.content.Context;
import com.facebook.react.ReactInstanceManager;
/**
* Class responsible of loading Flipper inside your React Native application. This is the release
* flavor of it so it's empty as we don't want to load Flipper.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
// Do nothing as we don't want to initialize Flipper on Release.
}
}

View File

@@ -2,53 +2,29 @@
buildscript { buildscript {
ext { ext {
buildToolsVersion = "29.0.3" buildToolsVersion = "33.0.0"
minSdkVersion = 23 minSdkVersion = 23
compileSdkVersion = 33 compileSdkVersion = 33
targetSdkVersion = 33 targetSdkVersion = 33
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "21.4.7075529"
kotlinVersion = "1.9.0"
} }
repositories { repositories {
google() google()
gradlePluginPortal()
mavenCentral() mavenCentral()
jcenter()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:4.2.2") classpath("com.android.tools.build:gradle:7.3.1")
classpath 'com.google.gms:google-services:4.3.5' classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
classpath("com.facebook.react:react-native-gradle-plugin")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../android"))
}
maven {
// Android JSC is installed from npm
url(new File(["node", "--print", "require.resolve('jsc-android/package.json')"].execute(null, rootDir).text.trim(), "../dist"))
}
configurations.all {
resolutionStrategy {
force "com.facebook.soloader:soloader:0.10.1"
}
}
google()
gradlePluginPortal()
mavenCentral()
maven { url 'https://www.jitpack.io' }
jcenter()
} }
} }
allprojects { repositories { maven { url "$rootDir/../node_modules/expo-camera/android/maven" } } } // @generated begin expo-camera-import - expo prebuild (DO NOT MODIFY) sync-f244f4f3d8bf7229102e8f992b525b8602c74770
def expoCameraMavenPath = new File(["node", "--print", "require.resolve('expo-camera/package.json')"].execute(null, rootDir).text.trim(), "../android/maven")
allprojects { repositories { maven { url(expoCameraMavenPath) } } }
// @generated end expo-camera-import

View File

@@ -1,6 +1,7 @@
default_platform(:android) default_platform(:android)
MIMOTO_HOST = ENV["MIMOTO_HOST"] MIMOTO_HOST = ENV["MIMOTO_HOST"]
ESIGNET_HOST = ENV["ESIGNET_HOST"]
APPLICATION_THEME = ENV["APPLICATION_THEME"] APPLICATION_THEME = ENV["APPLICATION_THEME"]
RELEASE_KEYSTORE_ALIAS = ENV["RELEASE_KEYSTORE_ALIAS"] RELEASE_KEYSTORE_ALIAS = ENV["RELEASE_KEYSTORE_ALIAS"]
RELEASE_KEYSTORE_PASSWORD = ENV["RELEASE_KEYSTORE_PASSWORD"] RELEASE_KEYSTORE_PASSWORD = ENV["RELEASE_KEYSTORE_PASSWORD"]

View File

@@ -9,7 +9,7 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
@@ -26,19 +26,35 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Version of flipper SDK to use with React Native # Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.99.0 FLIPPER_VERSION=0.125.0
# The hosted JavaScript engine # # The hosted JavaScript engine
# Supported values: expo.jsEngine = "hermes" | "jsc" # # Supported values: expo.jsEngine = "hermes" | "jsc"
expo.jsEngine=jsc # expo.jsEngine=jsc
# Enable GIF support in React Native images (~200 B increase) # # Enable GIF support in React Native images (~200 B increase)
expo.gif.enabled=true # expo.gif.enabled=true
# Enable webp support in React Native images (~85 KB increase) # # Enable webp support in React Native images (~85 KB increase)
expo.webp.enabled=true # expo.webp.enabled=true
# Enable animated webp support (~3.4 MB increase) # # Enable animated webp support (~3.4 MB increase)
# Disabled by default because iOS doesn't support animated webp # # Disabled by default because iOS doesn't support animated webp
expo.webp.animated=false # expo.webp.animated=false
# If the AsyncStorage_db_size_in_MB is not configured, Default DB_MAX_SIZE is 6MB in Android # # If the AsyncStorage_db_size_in_MB is not configured, Default DB_MAX_SIZE is 6MB in Android
AsyncStorage_db_size_in_MB=30 # AsyncStorage_db_size_in_MB=30
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true
expo.jsEngine=hermes

Binary file not shown.

View File

@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

269
android/gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -17,78 +17,113 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@@ -105,79 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

37
android/gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -54,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -64,40 +65,28 @@ echo location of your Java installation.
goto fail goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega

View File

@@ -1,9 +1,8 @@
rootProject.name = 'Inji' rootProject.name = 'Inji'
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
useExpoModules()
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
include ':app' include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
useExpoModules()

View File

@@ -14,7 +14,7 @@ export default {
}, },
assetBundlePatterns: ['**/*'], assetBundlePatterns: ['**/*'],
ios: { ios: {
bundleIdentifier: 'io.mosip.residentapp', bundleIdentifier: 'io.mosip.inji.mobileid',
buildNumber: '1.0.0', buildNumber: '1.0.0',
supportsTablet: true, supportsTablet: true,
}, },
@@ -28,4 +28,5 @@ export default {
}, },
platforms: ['android', 'ios'], platforms: ['android', 'ios'],
privacy: 'hidden', privacy: 'hidden',
plugins: ['expo-localization'],
}; };

4
app.json Normal file
View File

@@ -0,0 +1,4 @@
{
"name": "inji",
"displayName": "Inji"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
assets/capture-button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
assets/card_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
assets/digit-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

BIN
assets/pin_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -0,0 +1,31 @@
import React from 'react';
import {View} from 'react-native';
import {Row, Text} from './ui';
import {Theme} from './ui/styleUtils';
import {Icon} from 'react-native-elements';
import testIDProps from '../shared/commonUtil';
export const BannerNotification: React.FC<BannerNotificationProps> = props => {
return (
<View {...testIDProps(props.testId)}>
<Row style={Theme.Styles.downloadingVcPopUp}>
<Text color={Theme.Colors.whiteText} weight="semibold" size="smaller">
{props.message}
</Text>
<Icon
testID="close"
name="close"
onPress={props.onClosePress}
color={Theme.Colors.whiteText}
size={19}
/>
</Row>
</View>
);
};
export interface BannerNotificationProps {
message: string;
onClosePress: () => void;
testId: string;
}

View File

@@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { Dimensions, View } from 'react-native'; import {Dimensions, StyleSheet} from 'react-native';
import { Overlay } from 'react-native-elements'; import {Overlay} from 'react-native-elements';
import { Column, Row, Text } from './ui'; import {Column, Row, Text} from './ui';
import { Button } from './ui/Button'; import {Button} from './ui/Button';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
/** /**
* DualMessageOverlay is like MessageOverlay but with two buttons * DualMessageOverlay is like MessageOverlay but with two buttons
@@ -12,10 +12,18 @@ import { Theme } from './ui/styleUtils';
* NOTE: This has been added for surfacing bugs and needs to be refactored * NOTE: This has been added for surfacing bugs and needs to be refactored
* before use. * before use.
*/ */
export const DualMessageOverlay: React.FC<DualMessageOverlayProps> = ( export const DualMessageOverlay: React.FC<DualMessageOverlayProps> = props => {
props const {t} = useTranslation('common');
) => { const style = StyleSheet.create({
const { t } = useTranslation('common'); customHeight: {
height: props.customHeight
? props.customHeight
: props.progress
? 100
: 150,
},
});
return ( return (
<Overlay <Overlay
isVisible={props.isVisible} isVisible={props.isVisible}
@@ -24,11 +32,7 @@ export const DualMessageOverlay: React.FC<DualMessageOverlayProps> = (
onBackdropPress={props.onBackdropPress}> onBackdropPress={props.onBackdropPress}>
<Column <Column
width={Dimensions.get('screen').width * 0.8} width={Dimensions.get('screen').width * 0.8}
style={ style={[Theme.MessageOverlayStyles.popupOverLay, style.customHeight]}>
!props.progress
? Theme.MessageOverlayStyles.popupOverLay
: { height: 230 }
}>
<Column padding="21" crossAlign="center"> <Column padding="21" crossAlign="center">
{props.title && ( {props.title && (
<Text <Text
@@ -59,7 +63,7 @@ export const DualMessageOverlay: React.FC<DualMessageOverlayProps> = (
)} )}
{props.children} {props.children}
</Column> </Column>
<Column style={{ marginBottom: 10 }}> <Column style={{marginBottom: 10}}>
<Row style={Theme.MessageOverlayStyles.buttonContainer}> <Row style={Theme.MessageOverlayStyles.buttonContainer}>
{!props.children && props.onTryAgain ? ( {!props.children && props.onTryAgain ? (
<Button <Button
@@ -103,4 +107,5 @@ export interface DualMessageOverlayProps {
onBackdropPress?: () => void; onBackdropPress?: () => void;
onShow?: () => void; onShow?: () => void;
onTryAgain?: () => void; onTryAgain?: () => void;
customHeight?: number | string | undefined;
} }

View File

@@ -1,21 +1,31 @@
import React, { useEffect, useState } from 'react'; import React, {useEffect, useState} from 'react';
import { Dimensions, I18nManager } from 'react-native'; import {Dimensions, I18nManager} from 'react-native';
import { Icon, ListItem, Overlay, Input } from 'react-native-elements'; import {Icon, ListItem, Overlay, Input} from 'react-native-elements';
import { Text, Column, Row, Button } from './ui'; import {Text, Column, Row, Button} from './ui';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
export const EditableListItem: React.FC<EditableListItemProps> = (props) => { export const EditableListItem: React.FC<EditableListItemProps> = props => {
const { t } = useTranslation('common'); const {t} = useTranslation('common');
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [newValue, setNewValue] = useState(props.value); const [items, setItems] = useState(props.items);
const [overlayOpened, setOverlayOpened] = useState(true); const [overlayOpened, setOverlayOpened] = useState(true);
useEffect(() => { useEffect(() => {
if (props.credentialRegistryResponse === 'success') { if (props.response === 'success') {
closePopup(); closePopup();
} }
}, [props.credentialRegistryResponse]); }, [props.response]);
function updateItems(label: string, value: string) {
const updatedItems = items.map(item => {
if (item.label === label) {
return {...item, value: value};
}
return item;
});
setItems(updatedItems);
}
return ( return (
<ListItem bottomDivider topDivider onPress={() => setIsEditing(true)}> <ListItem bottomDivider topDivider onPress={() => setIsEditing(true)}>
@@ -29,10 +39,10 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
<ListItem.Content> <ListItem.Content>
<ListItem.Title> <ListItem.Title>
<Text weight="semibold" color={Theme.Colors.profileLabel}> <Text weight="semibold" color={Theme.Colors.profileLabel}>
{props.label} {props.title}
</Text> </Text>
</ListItem.Title> </ListItem.Title>
<Text color={Theme.Colors.profileValue}>{props.value}</Text> <Text color={Theme.Colors.profileValue}>{props.content}</Text>
</ListItem.Content> </ListItem.Content>
<Icon <Icon
name="chevron-right" name="chevron-right"
@@ -40,26 +50,32 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
color={Theme.Colors.profileLanguageValue} color={Theme.Colors.profileLanguageValue}
/> />
<Overlay <Overlay
overlayStyle={{ padding: 24, elevation: 6 }} overlayStyle={{padding: 24, elevation: 6}}
isVisible={isEditing} isVisible={isEditing}
onBackdropPress={dismiss}> onBackdropPress={dismiss}>
<Column width={Dimensions.get('screen').width * 0.8}> <Column width={Dimensions.get('screen').width * 0.8}>
<Text>{t('editLabel', { label: props.label })}</Text> {props.items.map((item: ListItemProps, index) => {
<Input return (
autoFocus <React.Fragment key={index}>
value={newValue} <Text>{t('editLabel', {label: item.label})}</Text>
onChangeText={setNewValue} <Input
selectionColor={Theme.Colors.Cursor} autoFocus
inputStyle={{ value={items[index].value}
textAlign: I18nManager.isRTL ? 'right' : 'left', onChangeText={value => updateItems(item.label, value)}
}} selectionColor={Theme.Colors.Cursor}
/> inputStyle={{
{props.credentialRegistryResponse === 'error' && ( textAlign: I18nManager.isRTL ? 'right' : 'left',
<Text style={Theme.TextStyles.error}>{props.errorMessage}</Text> }}
)} />
{props.credentialRegistryResponse === 'success' && {index === 0 && props.response === 'error' && (
overlayOpened && <Text style={Theme.TextStyles.error}>
closePopup()} {props.errorMessage}
</Text>
)}
</React.Fragment>
);
})}
{props.response === 'success' && overlayOpened && closePopup()}
<Row> <Row>
<Button fill type="clear" title={t('cancel')} onPress={dismiss} /> <Button fill type="clear" title={t('cancel')} onPress={dismiss} />
<Button <Button
@@ -75,14 +91,13 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
); );
function edit() { function edit() {
props.onEdit(newValue); props.onEdit(items);
if (props.credentialRegistryResponse === undefined) { if (props.response === undefined) {
setIsEditing(false); setIsEditing(false);
} }
} }
function dismiss() { function dismiss() {
setNewValue(props.value);
setIsEditing(false); setIsEditing(false);
props.onCancel(); props.onCancel();
} }
@@ -94,14 +109,20 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
}; };
interface EditableListItemProps { interface EditableListItemProps {
label: string; title: string;
value: string; content: string;
items: ListItemProps[];
Icon: string; Icon: string;
IconType?: string; IconType?: string;
onEdit: (newValue: string) => void; onEdit: (values: ListItemProps[]) => void;
display?: 'none' | 'flex'; display?: 'none' | 'flex';
credentialRegistryResponse: string; response?: string;
onCancel: () => void; onCancel: () => void;
progress?: boolean; progress?: boolean;
errorMessage?: string; errorMessage?: string;
} }
interface ListItemProps {
label: string;
value: string;
}

View File

@@ -1,10 +1,15 @@
import React, { useCallback, useContext, useEffect, useRef } from 'react'; import React, {useCallback, useContext, useEffect, useRef} from 'react';
import Icon from 'react-native-vector-icons/MaterialIcons'; import {Camera} from 'expo-camera';
import { Camera } from 'expo-camera'; import {
import { Platform, StyleSheet } from 'react-native'; Platform,
import { Button, Centered, Column, Row, Text } from './ui'; StyleSheet,
import { useInterpret, useSelector } from '@xstate/react'; TouchableOpacity,
import { useTranslation } from 'react-i18next'; View,
Image,
} from 'react-native';
import {Button, Centered, Column, Row, Text} from './ui';
import {useInterpret, useSelector} from '@xstate/react';
import {useTranslation} from 'react-i18next';
import { import {
FaceScannerEvents, FaceScannerEvents,
selectIsCheckingPermission, selectIsCheckingPermission,
@@ -17,14 +22,14 @@ import {
selectIsCapturing, selectIsCapturing,
selectIsVerifying, selectIsVerifying,
} from '../machines/faceScanner'; } from '../machines/faceScanner';
import { GlobalContext } from '../shared/GlobalContext'; import {GlobalContext} from '../shared/GlobalContext';
import { selectIsActive } from '../machines/app'; import {selectIsActive} from '../machines/app';
import { RotatingIcon } from './RotatingIcon'; import {RotatingIcon} from './RotatingIcon';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
export const FaceScanner: React.FC<FaceScannerProps> = (props) => { export const FaceScanner: React.FC<FaceScannerProps> = props => {
const { t } = useTranslation('FaceScanner'); const {t} = useTranslation('FaceScanner');
const { appService } = useContext(GlobalContext); const {appService} = useContext(GlobalContext);
const isActive = useSelector(appService, selectIsActive); const isActive = useSelector(appService, selectIsActive);
const machine = useRef(createFaceScannerMachine(props.vcImage)); const machine = useRef(createFaceScannerMachine(props.vcImage));
@@ -46,7 +51,7 @@ export const FaceScanner: React.FC<FaceScannerProps> = (props) => {
service.send(FaceScannerEvents.READY(node)); service.send(FaceScannerEvents.READY(node));
} }
}, },
[isScanning] [isScanning],
); );
useEffect(() => { useEffect(() => {
@@ -80,34 +85,48 @@ export const FaceScanner: React.FC<FaceScannerProps> = (props) => {
} }
return ( return (
<Column crossAlign="center"> <View>
<Column style={[styles.scannerContainer]}> <View style={Theme.Styles.scannerContainer}>
<Camera <Camera
ratio="4:3" style={Theme.Styles.scanner}
style={styles.scanner}
type={whichCamera} type={whichCamera}
ref={setCameraRef} ref={setCameraRef}
/> />
</Column> </View>
<Text
align="center"
weight="semibold"
style={Theme.TextStyles.base}
margin="50 57 0 57">
{t('imageCaptureGuide')}
</Text>
<Centered margin="24 0"> <Centered margin="24 0">
{isCapturing || isVerifying ? ( {isCapturing || isVerifying ? (
<RotatingIcon name="sync" size={64} /> <RotatingIcon name="sync" size={64} />
) : ( ) : (
<Row crossAlign="center"> <Row align="center" style={{marginTop: 42}}>
<Icon <Centered style={Theme.Styles.imageCaptureButton}>
name="flip-camera-ios" <TouchableOpacity
color={Theme.Colors.flipCameraIcon} onPress={() => service.send(FaceScannerEvents.CAPTURE())}>
size={64} <Image source={Theme.ImageCaptureButton} />
onPress={() => service.send(FaceScannerEvents.FLIP_CAMERA())} </TouchableOpacity>
style={{ margin: 8, marginEnd: 32 }} <Text size="small" weight="semibold" margin="8">
/> {t('capture')}
<Icon </Text>
name="photo-camera" </Centered>
color={Theme.Colors.flipCameraIcon}
size={64} <Centered>
onPress={() => service.send(FaceScannerEvents.CAPTURE())} <TouchableOpacity
style={{ margin: 8, marginTop: 12, marginStart: 32 }} onPress={() => service.send(FaceScannerEvents.FLIP_CAMERA())}>
/> <Image
source={Theme.CameraFlipIcon}
style={Theme.Styles.cameraFlipIcon}
/>
</TouchableOpacity>
<Text size="smaller" weight="semibold" margin="8">
{t('flipCamera')}
</Text>
</Centered>
</Row> </Row>
)} )}
{/* TODO: remove warning when iOS SDK is ready */} {/* TODO: remove warning when iOS SDK is ready */}
@@ -117,7 +136,7 @@ export const FaceScanner: React.FC<FaceScannerProps> = (props) => {
</Text> </Text>
)} )}
</Centered> </Centered>
</Column> </View>
); );
}; };
@@ -126,21 +145,3 @@ interface FaceScannerProps {
onValid: () => void; onValid: () => void;
onInvalid: () => void; onInvalid: () => void;
} }
const styles = StyleSheet.create({
scannerContainer: {
borderWidth: 4,
borderColor: Theme.Colors.textValue,
borderRadius: 32,
justifyContent: 'center',
height: 400,
width: 300,
overflow: 'hidden',
},
scanner: {
height: '100%',
width: '100%',
margin: 'auto',
},
});

View File

@@ -1,16 +1,14 @@
import React, { useState } from 'react'; import React, {useState} from 'react';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { Pressable } from 'react-native'; import {Pressable} from 'react-native';
import { Modal } from './ui/Modal'; import {Modal} from './ui/Modal';
import { ScrollView } from 'react-native-gesture-handler'; import {ScrollView} from 'react-native-gesture-handler';
import { MainRouteProps } from '../routes/main'; import {MainRouteProps} from '../routes/main';
import { Column, Text } from './ui'; import {Column, Text} from './ui';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
export const HelpScreen: React.FC<HelpScreenProps & MainRouteProps> = ( export const HelpScreen: React.FC<HelpScreenProps & MainRouteProps> = props => {
props const {t} = useTranslation('HelpScreen');
) => {
const { t } = useTranslation('HelpScreen');
const [showHelpPage, setShowHelpPage] = useState(false); const [showHelpPage, setShowHelpPage] = useState(false);
@@ -23,6 +21,7 @@ export const HelpScreen: React.FC<HelpScreenProps & MainRouteProps> = (
{props.triggerComponent} {props.triggerComponent}
</Pressable> </Pressable>
<Modal <Modal
testID="helpScreen"
isVisible={showHelpPage} isVisible={showHelpPage}
headerTitle={t('header')} headerTitle={t('header')}
headerElevation={2} headerElevation={2}
@@ -75,5 +74,6 @@ export const HelpScreen: React.FC<HelpScreenProps & MainRouteProps> = (
}; };
interface HelpScreenProps { interface HelpScreenProps {
testID?: string;
triggerComponent: React.ReactElement; triggerComponent: React.ReactElement;
} }

View File

@@ -1,32 +1,40 @@
import React from 'react'; import {Icon, ListItem, Overlay} from 'react-native-elements';
import { Icon, ListItem, Overlay } from 'react-native-elements'; import {Theme} from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils'; import {Column, Row, Text} from '../components/ui';
import { Column, Row, Text } from '../components/ui'; import {WalletBinding} from '../screens/Home/MyVcs/WalletBinding';
import { WalletBinding } from '../screens/Home/MyVcs/WalletBinding'; import {Pressable, View} from 'react-native';
import { Pressable } from 'react-native'; import {useKebabPopUp} from './KebabPopUpController';
import { useKebabPopUp } from './KebabPopUpController'; import {ActorRefFrom} from 'xstate';
import { ActorRefFrom } from 'xstate'; import {ExistingMosipVCItemMachine} from '../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import { vcItemMachine } from '../machines/vcItem'; import {useTranslation} from 'react-i18next';
import { useTranslation } from 'react-i18next'; import {HistoryTab} from '../screens/Home/MyVcs/HistoryTab';
import { HistoryTab } from '../screens/Home/MyVcs/HistoryTab'; import {RemoveVcWarningOverlay} from '../screens/Home/MyVcs/RemoveVcWarningOverlay';
import { RemoveVcWarningOverlay } from '../screens/Home/MyVcs/RemoveVcWarningOverlay'; import {ScrollView} from 'react-native-gesture-handler';
import {VCMetadata} from '../shared/VCMetadata';
import testIDProps from '../shared/commonUtil';
export const KebabPopUp: React.FC<KebabPopUpProps> = (props) => { export const KebabPopUp: React.FC<KebabPopUpProps> = props => {
const controller = useKebabPopUp(props); const controller = useKebabPopUp(props);
const { t } = useTranslation('HomeScreenKebabPopUp'); const {t} = useTranslation('HomeScreenKebabPopUp');
return ( return (
<Column> <Column>
<Icon <Icon
{...testIDProps('ellipsis')}
accessible={true}
name={props.iconName} name={props.iconName}
type={props.iconType} type={props.iconType}
color={Theme.Colors.GrayIcon} color={Theme.Colors.GrayIcon}
size={Theme.ICON_SMALL_SIZE}
/> />
<Overlay <Overlay
isVisible={props.isVisible} isVisible={props.isVisible}
onBackdropPress={props.onDismiss} onBackdropPress={props.onDismiss}
overlayStyle={Theme.KebabPopUpStyles.kebabPopUp}> overlayStyle={Theme.KebabPopUpStyles.kebabPopUp}>
<Row style={Theme.KebabPopUpStyles.kebabHeaderStyle}> <Row style={Theme.KebabPopUpStyles.kebabHeaderStyle}>
<Text weight="bold">{t('title')}</Text> <View></View>
<Text testID="kebabTitle" weight="bold">
{t('title')}
</Text>
<Icon <Icon
name="close" name="close"
onPress={props.onDismiss} onPress={props.onDismiss}
@@ -34,17 +42,15 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = (props) => {
size={25} size={25}
/> />
</Row> </Row>
<Column> <ScrollView>
<ListItem bottomDivider> <ListItem bottomDivider>
<ListItem.Content> <ListItem.Content>
<ListItem.Title> <ListItem.Title
<Pressable onPress={controller.PIN_CARD}> onPress={controller.PIN_CARD}
<Text size="small" weight="bold"> {...testIDProps('pinOrUnPinCard')}>
{props.vcKey.split(':')[4] == 'true' <Text size="small" weight="bold">
? t('unPinCard') {props.vcMetadata.isPinned ? t('unPinCard') : t('pinCard')}
: t('pinCard')} </Text>
</Text>
</Pressable>
</ListItem.Title> </ListItem.Title>
</ListItem.Content> </ListItem.Content>
</ListItem> </ListItem>
@@ -56,19 +62,20 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = (props) => {
/> />
<HistoryTab <HistoryTab
testID="viewActivityLog"
service={props.service} service={props.service}
label={t('viewActivityLog')} label={t('viewActivityLog')}
vcKey={props.vcKey} vcMetadata={props.vcMetadata}
/> />
<ListItem bottomDivider> <ListItem bottomDivider>
<ListItem.Content> <ListItem.Content>
<ListItem.Title> <ListItem.Title
<Pressable onPress={() => controller.REMOVE(props.vcKey)}> onPress={() => controller.REMOVE(props.vcMetadata)}
<Text size="small" weight="bold"> {...testIDProps('removeFromWallet')}>
{t('removeFromWallet')} <Text size="small" weight="bold">
</Text> {t('removeFromWallet')}
</Pressable> </Text>
</ListItem.Title> </ListItem.Title>
</ListItem.Content> </ListItem.Content>
</ListItem> </ListItem>
@@ -78,7 +85,7 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = (props) => {
onConfirm={controller.CONFIRM} onConfirm={controller.CONFIRM}
onCancel={controller.CANCEL} onCancel={controller.CANCEL}
/> />
</Column> </ScrollView>
</Overlay> </Overlay>
</Column> </Column>
); );
@@ -87,8 +94,8 @@ export const KebabPopUp: React.FC<KebabPopUpProps> = (props) => {
export interface KebabPopUpProps { export interface KebabPopUpProps {
iconName: string; iconName: string;
iconType?: string; iconType?: string;
vcKey: string; vcMetadata: VCMetadata;
isVisible: boolean; isVisible: boolean;
onDismiss: () => void; onDismiss: () => void;
service: ActorRefFrom<typeof vcItemMachine>; service: ActorRefFrom<typeof ExistingMosipVCItemMachine>;
} }

View File

@@ -1,57 +1,70 @@
import { useSelector } from '@xstate/react'; import {useSelector} from '@xstate/react';
import { ActorRefFrom } from 'xstate'; import {ActorRefFrom} from 'xstate';
import { import {
selectKebabPopUpWalletBindingInProgress, ExistingMosipVCItemEvents,
ExistingMosipVCItemMachine,
selectEmptyWalletBindingId,
selectIsPinned,
selectKebabPopUp, selectKebabPopUp,
selectKebabPopUpAcceptingBindingOtp, selectKebabPopUpAcceptingBindingOtp,
selectKebabPopUpBindingWarning, selectKebabPopUpBindingWarning,
selectRemoveWalletWarning, selectKebabPopUpWalletBindingInProgress,
selectEmptyWalletBindingId,
selectIsPinned,
selectOtpError, selectOtpError,
selectRemoveWalletWarning,
selectShowActivities,
selectShowWalletBindingError, selectShowWalletBindingError,
selectWalletBindingError, selectWalletBindingError,
VcItemEvents, } from '../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
vcItemMachine, import {selectActivities} from '../machines/activityLog';
selectShowActivities, import {GlobalContext} from '../shared/GlobalContext';
} from '../machines/vcItem'; import {useContext} from 'react';
import { selectActivities } from '../machines/activityLog'; import {VCMetadata} from '../shared/VCMetadata';
import { GlobalContext } from '../shared/GlobalContext'; import {
import { useContext } from 'react'; EsignetMosipVCItemEvents,
EsignetMosipVCItemMachine,
} from '../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
export function useKebabPopUp(props) { export function useKebabPopUp(props) {
const service = props.service as ActorRefFrom<typeof vcItemMachine>; const service = props.service as
const PIN_CARD = () => service.send(VcItemEvents.PIN_CARD()); | ActorRefFrom<typeof ExistingMosipVCItemMachine>
const KEBAB_POPUP = () => service.send(VcItemEvents.KEBAB_POPUP()); | ActorRefFrom<typeof EsignetMosipVCItemMachine>;
const vcEvents =
props.vcKey !== undefined && props.vcMetadata.isFromOpenId4VCI()
? EsignetMosipVCItemEvents
: ExistingMosipVCItemEvents;
const PIN_CARD = () => service.send(vcEvents.PIN_CARD());
const KEBAB_POPUP = () => service.send(vcEvents.KEBAB_POPUP());
const ADD_WALLET_BINDING_ID = () => const ADD_WALLET_BINDING_ID = () =>
service.send(VcItemEvents.ADD_WALLET_BINDING_ID()); service.send(vcEvents.ADD_WALLET_BINDING_ID());
const CONFIRM = () => service.send(VcItemEvents.CONFIRM()); const CONFIRM = () => service.send(vcEvents.CONFIRM());
const REMOVE = (vcKey: string) => service.send(VcItemEvents.REMOVE(vcKey)); const REMOVE = (vcMetadata: VCMetadata) =>
const DISMISS = () => service.send(VcItemEvents.DISMISS()); service.send(vcEvents.REMOVE(vcMetadata));
const CANCEL = () => service.send(VcItemEvents.CANCEL()); const DISMISS = () => service.send(vcEvents.DISMISS());
const SHOW_ACTIVITY = () => service.send(VcItemEvents.SHOW_ACTIVITY()); const CANCEL = () => service.send(vcEvents.CANCEL());
const INPUT_OTP = (otp: string) => service.send(VcItemEvents.INPUT_OTP(otp)); const SHOW_ACTIVITY = () => service.send(vcEvents.SHOW_ACTIVITY());
const INPUT_OTP = (otp: string) => service.send(vcEvents.INPUT_OTP(otp));
const RESEND_OTP = () => service.send(vcEvents.RESEND_OTP());
const isPinned = useSelector(service, selectIsPinned); const isPinned = useSelector(service, selectIsPinned);
const isBindingWarning = useSelector(service, selectKebabPopUpBindingWarning); const isBindingWarning = useSelector(service, selectKebabPopUpBindingWarning);
const isRemoveWalletWarning = useSelector(service, selectRemoveWalletWarning); const isRemoveWalletWarning = useSelector(service, selectRemoveWalletWarning);
const isAcceptingOtpInput = useSelector( const isAcceptingOtpInput = useSelector(
service, service,
selectKebabPopUpAcceptingBindingOtp selectKebabPopUpAcceptingBindingOtp,
); );
const isWalletBindingError = useSelector( const isWalletBindingError = useSelector(
service, service,
selectShowWalletBindingError selectShowWalletBindingError,
); );
const otpError = useSelector(service, selectOtpError); const otpError = useSelector(service, selectOtpError);
const walletBindingError = useSelector(service, selectWalletBindingError); const walletBindingError = useSelector(service, selectWalletBindingError);
const WalletBindingInProgress = useSelector( const WalletBindingInProgress = useSelector(
service, service,
selectKebabPopUpWalletBindingInProgress selectKebabPopUpWalletBindingInProgress,
); );
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId); const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const isKebabPopUp = useSelector(service, selectKebabPopUp); const isKebabPopUp = useSelector(service, selectKebabPopUp);
const isShowActivities = useSelector(service, selectShowActivities); const isShowActivities = useSelector(service, selectShowActivities);
const { appService } = useContext(GlobalContext); const {appService} = useContext(GlobalContext);
const activityLogService = appService.children.get('activityLog'); const activityLogService = appService.children.get('activityLog');
return { return {
@@ -64,6 +77,7 @@ export function useKebabPopUp(props) {
REMOVE, REMOVE,
CANCEL, CANCEL,
INPUT_OTP, INPUT_OTP,
RESEND_OTP,
SHOW_ACTIVITY, SHOW_ACTIVITY,
isBindingWarning, isBindingWarning,
isAcceptingOtpInput, isAcceptingOtpInput,

View File

@@ -1,21 +1,23 @@
import React from 'react'; import React from 'react';
import { SUPPORTED_LANGUAGES } from '../i18n'; import {SUPPORTED_LANGUAGES} from '../i18n';
import { I18nManager, View } from 'react-native'; import {I18nManager, View} from 'react-native';
import { Picker } from './ui/Picker'; import {Picker} from './ui/Picker';
import Storage from '../shared/storage'; import Storage from '../shared/storage';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import i18next from 'i18next'; import i18next from 'i18next';
import RNRestart from 'react-native-restart'; import RNRestart from 'react-native-restart';
import {__SelectedLanguage} from '../shared/GlobalVariables';
export const LanguageSelector: React.FC<LanguageSelectorProps> = (props) => { export const LanguageSelector: React.FC<LanguageSelectorProps> = props => {
const { i18n } = useTranslation(); const {i18n} = useTranslation();
const languages = Object.entries(SUPPORTED_LANGUAGES).map( const languages = Object.entries(SUPPORTED_LANGUAGES).map(
([value, label]) => ({ label, value }) ([value, label]) => ({label, value}),
); );
const changeLanguage = async (language: string) => { const changeLanguage = async (language: string) => {
if (language !== i18n.language) { if (language !== i18n.language) {
await i18n.changeLanguage(language).then(async () => { await i18n.changeLanguage(language).then(async () => {
__SelectedLanguage.setValue(language);
await Storage.setItem('language', i18n.language); await Storage.setItem('language', i18n.language);
const isRTL = i18next.dir(language) === 'rtl' ? true : false; const isRTL = i18next.dir(language) === 'rtl' ? true : false;
if (isRTL !== I18nManager.isRTL) { if (isRTL !== I18nManager.isRTL) {
@@ -35,6 +37,7 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = (props) => {
return ( return (
<View> <View>
<Picker <Picker
testID="language"
items={languages} items={languages}
selectedValue={i18n.language} selectedValue={i18n.language}
onValueChange={changeLanguage} onValueChange={changeLanguage}

View File

@@ -1,13 +1,22 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { Dimensions } from 'react-native'; import {Dimensions, StyleSheet} from 'react-native';
import { Overlay, LinearProgress } from 'react-native-elements'; import {Overlay, LinearProgress} from 'react-native-elements';
import { Button, Column, Text } from './ui'; import {Button, Column, Text} from './ui';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => { export const MessageOverlay: React.FC<MessageOverlayProps> = props => {
const { t } = useTranslation('common'); const {t} = useTranslation('common');
const style = StyleSheet.create({
customHeight: {
height: props.customHeight
? props.customHeight
: props.progress
? 100
: 150,
},
});
return ( return (
<Overlay <Overlay
isVisible={props.isVisible} isVisible={props.isVisible}
@@ -16,11 +25,7 @@ export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => {
onBackdropPress={props.onBackdropPress}> onBackdropPress={props.onBackdropPress}>
<Column <Column
width={Dimensions.get('screen').width * 0.8} width={Dimensions.get('screen').width * 0.8}
style={ style={[Theme.MessageOverlayStyles.popupOverLay, style.customHeight]}>
!props.progress
? Theme.MessageOverlayStyles.popupOverLay
: { height: 100 }
}>
<Column padding="21" crossAlign="center"> <Column padding="21" crossAlign="center">
{props.title && ( {props.title && (
<Text <Text
@@ -52,11 +57,11 @@ export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => {
)} )}
{props.children} {props.children}
</Column> </Column>
{!props.children && props.onCancel ? ( {!props.children && props.onButtonPress ? (
<Button <Button
type="gradient" type="gradient"
title={t('cancel')} title={props.buttonText ? t(props.buttonText) : t('cancel')}
onPress={props.onCancel} onPress={props.onButtonPress}
styles={Theme.MessageOverlayStyles.button} styles={Theme.MessageOverlayStyles.button}
/> />
) : null} ) : null}
@@ -71,7 +76,7 @@ export const ErrorMessageOverlay: React.FC<ErrorMessageOverlayProps> = ({
onDismiss, onDismiss,
translationPath, translationPath,
}) => { }) => {
const { t } = useTranslation(translationPath); const {t} = useTranslation(translationPath);
return ( return (
<MessageOverlay <MessageOverlay
@@ -90,7 +95,7 @@ export interface ErrorMessageOverlayProps {
translationPath: string; translationPath: string;
} }
const Progress: React.FC<Pick<MessageOverlayProps, 'progress'>> = (props) => { const Progress: React.FC<Pick<MessageOverlayProps, 'progress'>> = props => {
return typeof props.progress === 'boolean' ? ( return typeof props.progress === 'boolean' ? (
props.progress && ( props.progress && (
<LinearProgress variant="indeterminate" color={Theme.Colors.Loading} /> <LinearProgress variant="indeterminate" color={Theme.Colors.Loading} />
@@ -103,11 +108,15 @@ const Progress: React.FC<Pick<MessageOverlayProps, 'progress'>> = (props) => {
export interface MessageOverlayProps { export interface MessageOverlayProps {
isVisible: boolean; isVisible: boolean;
title?: string; title?: string;
buttonText?: string;
message?: string; message?: string;
progress?: boolean | number; progress?: boolean | number;
requester?: boolean; requester?: boolean;
hint?: string; hint?: string;
onCancel?: () => void; onButtonPress?: () => void;
onStayInProgress?: () => void;
onRetry?: () => void;
onBackdropPress?: () => void; onBackdropPress?: () => void;
onShow?: () => void; onShow?: () => void;
customHeight?: number | string | undefined;
} }

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react'; import React, {useEffect, useState} from 'react';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { PinInput } from './PinInput'; import {PinInput} from './PinInput';
import { hashData } from '../shared/commonUtil'; import {hashData} from '../shared/commonUtil';
import { argon2iConfig } from '../shared/constants'; import {argon2iConfig} from '../shared/constants';
export const MAX_PIN = 6; export const MAX_PIN = 6;
export const PasscodeVerify: React.FC<PasscodeVerifyProps> = (props) => { export const PasscodeVerify: React.FC<PasscodeVerifyProps> = props => {
const { t } = useTranslation('PasscodeVerify'); const {t} = useTranslation('PasscodeVerify');
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
useEffect(() => { useEffect(() => {
@@ -16,7 +16,9 @@ export const PasscodeVerify: React.FC<PasscodeVerifyProps> = (props) => {
} }
}, [isVerified]); }, [isVerified]);
return <PinInput length={MAX_PIN} onDone={verify} />; return (
<PinInput testID="confirmPasscodePin" length={MAX_PIN} onDone={verify} />
);
async function verify(value: string) { async function verify(value: string) {
const hashedPasscode = await hashData(value, props.salt, argon2iConfig); const hashedPasscode = await hashData(value, props.salt, argon2iConfig);

View File

@@ -1,13 +1,13 @@
import React, { useEffect } from 'react'; import React, {useEffect} from 'react';
import { TextInput } from 'react-native'; import {TextInput} from 'react-native';
import { usePinInput } from '../machines/pinInput'; import {usePinInput} from '../machines/pinInput';
import { Row } from './ui'; import {Row} from './ui';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
export const PinInput: React.FC<PinInputProps> = (props) => { export const PinInput: React.FC<PinInputProps> = props => {
const { state, send, events } = usePinInput(props.length); const {state, send, events} = usePinInput(props.length);
const { inputRefs, values } = state.context; const {inputRefs, values} = state.context;
const { UPDATE_INPUT, FOCUS_INPUT, KEY_PRESS } = events; const {UPDATE_INPUT, FOCUS_INPUT, KEY_PRESS} = events;
useEffect(() => { useEffect(() => {
if (props.onDone && values.filter(Boolean).length === inputRefs.length) { if (props.onDone && values.filter(Boolean).length === inputRefs.length) {
@@ -16,7 +16,7 @@ export const PinInput: React.FC<PinInputProps> = (props) => {
}, [state]); }, [state]);
return ( return (
<Row width="100%"> <Row width="100%" testID={props.testID}>
{inputRefs.map((input, index) => ( {inputRefs.map((input, index) => (
<TextInput <TextInput
selectTextOnFocus selectTextOnFocus
@@ -29,7 +29,7 @@ export const PinInput: React.FC<PinInputProps> = (props) => {
ref={input} ref={input}
value={values[index]} value={values[index]}
// KNOWN ISSUE: https://github.com/facebook/react-native/issues/19507 // KNOWN ISSUE: https://github.com/facebook/react-native/issues/19507
onKeyPress={({ nativeEvent }) => send(KEY_PRESS(nativeEvent.key))} onKeyPress={({nativeEvent}) => send(KEY_PRESS(nativeEvent.key))}
onChangeText={(value: string) => onChangeText={(value: string) =>
send(UPDATE_INPUT(value.replace(/\D/g, ''), index)) send(UPDATE_INPUT(value.replace(/\D/g, ''), index))
} }
@@ -41,6 +41,7 @@ export const PinInput: React.FC<PinInputProps> = (props) => {
}; };
interface PinInputProps { interface PinInputProps {
testID?: string;
length: number; length: number;
onDone?: (value: string) => void; onDone?: (value: string) => void;
} }

View File

@@ -1,27 +1,22 @@
import React, { useState } from 'react'; import React, {useState} from 'react';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { Button, Centered, Column, Text } from './ui'; import {Button, Centered, Column, Text} from './ui';
import { Modal } from './ui/Modal'; import {Modal} from './ui/Modal';
import { Image } from 'react-native'; import {Image} from 'react-native';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
import PaginationDot from 'react-native-animated-pagination-dot'; import Spinner from 'react-native-spinkit';
export const ProgressingModal: React.FC<ProgressingModalProps> = (props) => { export const ProgressingModal: React.FC<ProgressingModalProps> = props => {
const { t } = useTranslation('ScanScreen'); const {t} = useTranslation('ScanScreen');
let n = 0;
const [curPage, setCurPage] = useState(n);
const highLightDot = () => setCurPage(n + 1);
return ( return (
<React.Fragment> <React.Fragment>
<Modal <Modal
isVisible={props.isVisible} isVisible={props.isVisible}
headerLeft={t(props.title)} headerTitle={t(props.title)}
onDismiss={props.onCancel} onDismiss={props.onCancel}
headerLabel={props.label}
headerElevation={3} headerElevation={3}
modalStyle={Theme.ModalStyles.progressingModal}
requester={props.requester}> requester={props.requester}>
<Centered crossAlign="center" fill> <Centered crossAlign="center" fill>
<Column margin="24 0" align="space-around"> <Column margin="24 0" align="space-around">
@@ -29,34 +24,43 @@ export const ProgressingModal: React.FC<ProgressingModalProps> = (props) => {
source={Theme.InjiProgressingLogo} source={Theme.InjiProgressingLogo}
height={2} height={2}
width={2} width={2}
style={{ marginBottom: 15, marginLeft: -6 }} style={{marginBottom: 15, marginLeft: -6}}
/> />
{props.progress && ( {props.progress && (
<PaginationDot <Spinner
activeDotColor={'black'} type="ThreeBounce"
curPage={curPage} color={Theme.Colors.Loading}
maxPage={3} style={{marginLeft: 6}}
/> />
)} )}
</Column> </Column>
{(props.isHintVisible || props.isBleErrorVisible) && (
<Column style={{ display: props.hint ? 'flex' : 'none' }}>
<Column style={Theme.SelectVcOverlayStyles.timeoutHintContainer}> <Column style={Theme.SelectVcOverlayStyles.timeoutHintContainer}>
<Text <Text
align="center" align="center"
color={Theme.Colors.TimoutText} margin="10"
color={Theme.Colors.TimoutHintText}
size="small"
style={Theme.TextStyles.bold}> style={Theme.TextStyles.bold}>
{props.hint} {props.hint}
</Text> </Text>
{props.onCancel && ( {props.onStayInProgress && (
<Button <Button
type="clear" type="clear"
title={t('common:cancel')} title={t('status.stayOnTheScreen')}
onPress={props.onCancel} onPress={props.onStayInProgress}
/>
)}
{props.onRetry && (
<Button
type="clear"
title={t('status.retry')}
onPress={props.onRetry}
/> />
)} )}
</Column> </Column>
</Column> )}
</Centered> </Centered>
</Modal> </Modal>
</React.Fragment> </React.Fragment>
@@ -65,11 +69,13 @@ export const ProgressingModal: React.FC<ProgressingModalProps> = (props) => {
export interface ProgressingModalProps { export interface ProgressingModalProps {
isVisible: boolean; isVisible: boolean;
isHintVisible: boolean;
isBleErrorVisible?: boolean;
title?: string; title?: string;
label?: string;
hint?: string; hint?: string;
onCancel?: () => void; onCancel?: () => void;
onStayInProgress?: () => void;
onRetry?: () => void;
requester?: boolean; requester?: boolean;
progress?: boolean | number; progress?: boolean | number;
onBackdropPress?: () => void;
} }

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useState } from 'react'; import React, {useEffect, useState} from 'react';
import { Dimensions, Pressable } from 'react-native'; import {Dimensions, Pressable, View} from 'react-native';
import { Icon, Overlay } from 'react-native-elements'; import {Icon, Overlay} from 'react-native-elements';
import { Centered, Column, Row, Text } from './ui'; import {Centered, Column, Row, Text} from './ui';
import QRCode from 'react-native-qrcode-svg'; import QRCode from 'react-native-qrcode-svg';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
import { Image } from 'react-native'; import {Image} from 'react-native';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = (props) => { export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = props => {
const { t } = useTranslation('VcDetails'); const {t} = useTranslation('VcDetails');
const [isQrOverlayVisible, setIsQrOverlayVisible] = useState(false); const [isQrOverlayVisible, setIsQrOverlayVisible] = useState(false);
@@ -16,16 +16,18 @@ export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = (props) => {
return ( return (
<React.Fragment> <React.Fragment>
<Pressable onPress={toggleQrOverlay}> <Pressable onPress={toggleQrOverlay}>
<Row margin="20 0 0 0"> <View style={Theme.QrCodeStyles.QrView}>
<QRCode <Row>
size={90} <QRCode
value={props.qrCodeDetailes} size={90}
backgroundColor={Theme.Colors.QRCodeBackgroundColor} value={props.qrCodeDetailes}
/> backgroundColor={Theme.Colors.QRCodeBackgroundColor}
</Row> />
</Row>
</View>
<Row <Row
align="flex-end" align="flex-end"
margin="-30 0 0 60" margin="-35 0 0 67"
style={Theme.QrCodeStyles.magnifierZoom}> style={Theme.QrCodeStyles.magnifierZoom}>
<Image source={Theme.MagnifierZoom} /> <Image source={Theme.MagnifierZoom} />
</Row> </Row>
@@ -33,10 +35,14 @@ export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = (props) => {
<Overlay <Overlay
isVisible={isQrOverlayVisible} isVisible={isQrOverlayVisible}
onBackdropPress={toggleQrOverlay} onBackdropPress={toggleQrOverlay}
overlayStyle={{ padding: 1, borderRadius: 21 }}> overlayStyle={{padding: 1, borderRadius: 21}}>
<Column style={Theme.QrCodeStyles.expandedQrCode}> <Column style={Theme.QrCodeStyles.expandedQrCode}>
<Row pY={20} style={Theme.QrCodeStyles.QrCodeHeader}> <Row pY={20} style={Theme.QrCodeStyles.QrCodeHeader}>
<Text align="center" style={Theme.TextStyles.header} weight="bold"> <Text
testID="qrCodeHeader"
align="center"
style={Theme.TextStyles.header}
weight="bold">
{t('qrCodeHeader')} {t('qrCodeHeader')}
</Text> </Text>
<Icon <Icon
@@ -46,7 +52,7 @@ export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = (props) => {
size={32} size={32}
/> />
</Row> </Row>
<Centered pY={30}> <Centered testID="qrCodeDetailes" pY={30}>
<QRCode <QRCode
size={300} size={300}
value={props.qrCodeDetailes} value={props.qrCodeDetailes}

View File

@@ -1,25 +1,19 @@
import React, { useContext, useEffect, useState } from 'react'; import React, {useContext, useEffect, useState} from 'react';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import { Camera } from 'expo-camera'; import {Camera} from 'expo-camera';
import { BarCodeEvent, BarCodeScanner } from 'expo-barcode-scanner'; import {BarCodeEvent, BarCodeScanner} from 'expo-barcode-scanner';
import { import {Linking, TouchableOpacity, View, Image, Pressable} from 'react-native';
Linking, import {Theme} from './ui/styleUtils';
TouchableOpacity, import {Column, Button, Text, Centered, Row} from './ui';
View, import {GlobalContext} from '../shared/GlobalContext';
Image, import {useSelector} from '@xstate/react';
Pressable, import {selectIsActive} from '../machines/app';
} from 'react-native'; import {useTranslation} from 'react-i18next';
import { Theme } from './ui/styleUtils'; import {useScanLayout} from '../screens/Scan/ScanLayoutController';
import { Column, Button, Text, Centered, Row } from './ui';
import { GlobalContext } from '../shared/GlobalContext';
import { useSelector } from '@xstate/react';
import { selectIsActive } from '../machines/app';
import { useTranslation } from 'react-i18next';
import { useScanLayout } from '../screens/Scan/ScanLayoutController';
export const QrScanner: React.FC<QrScannerProps> = (props) => { export const QrScanner: React.FC<QrScannerProps> = props => {
const { t } = useTranslation('QrScanner'); const {t} = useTranslation('QrScanner');
const { appService } = useContext(GlobalContext); const {appService} = useContext(GlobalContext);
const [hasPermission, setHasPermission] = useState(null); const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false); const [scanned, setScanned] = useState(false);
const [cameraType, setCameraType] = useState(Camera.Constants.Type.back); const [cameraType, setCameraType] = useState(Camera.Constants.Type.back);
@@ -106,12 +100,15 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
setCameraType( setCameraType(
cameraType === Camera.Constants.Type.back cameraType === Camera.Constants.Type.back
? Camera.Constants.Type.front ? Camera.Constants.Type.front
: Camera.Constants.Type.back : Camera.Constants.Type.back,
); );
}}> }}>
<Image source={Theme.CameraFlipIcon} /> <Image
source={Theme.CameraFlipIcon}
style={Theme.Styles.cameraFlipIcon}
/>
</TouchableOpacity> </TouchableOpacity>
<Text align="center" weight="semibold" margin="9 0"> <Text size="small" weight="semibold" margin="8">
{t('flipCamera')} {t('flipCamera')}
</Text> </Text>
</Column> </Column>

View File

@@ -0,0 +1,113 @@
import React, {useContext, useEffect, useRef} from 'react';
import {useInterpret, useSelector} from '@xstate/react';
import {Pressable, View} from 'react-native';
import {ActorRefFrom} from 'xstate';
import {GlobalContext} from '../../../shared/GlobalContext';
import {Theme} from '../../ui/styleUtils';
import {Row} from '../../ui';
import {KebabPopUp} from '../../KebabPopUp';
import {VCMetadata} from '../../../shared/VCMetadata';
import {EsignetMosipVCItemContent} from './EsignetMosipVCItemContent';
import {EsignetMosipVCActivationStatus} from './EsignetMosipVCItemActivationStatus';
import {
createEsignetMosipVCItemMachine,
EsignetMosipVCItemEvents,
EsignetMosipVCItemMachine,
selectContext,
selectGeneratedOn,
selectKebabPopUp,
selectVerifiableCredentials,
} from '../../../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine';
export const EsignetMosipVCItem: React.FC<EsignetMosipVCItemProps> = props => {
const {appService} = useContext(GlobalContext);
const machine = useRef(
createEsignetMosipVCItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcMetadata,
),
);
const service = useInterpret(machine.current, {devTools: __DEV__});
useEffect(() => {
service.send(EsignetMosipVCItemEvents.UPDATE_VC_METADATA(props.vcMetadata));
}, [props.vcMetadata]);
const context = useSelector(service, selectContext);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const DISMISS = () => service.send(EsignetMosipVCItemEvents.DISMISS());
const KEBAB_POPUP = () =>
service.send(EsignetMosipVCItemEvents.KEBAB_POPUP());
const credentials = useSelector(service, selectVerifiableCredentials);
const generatedOn = useSelector(service, selectGeneratedOn);
return (
<React.Fragment>
<Pressable
accessible={false}
onPress={() => props.onPress(service)}
disabled={!credentials}
style={
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<EsignetMosipVCItemContent
context={context}
credential={credentials}
generatedOn={generatedOn}
selectable={props.selectable}
selected={props.selected}
service={service}
iconName={props.iconName}
iconType={props.iconType}
onPress={() => props.onPress(service)}
/>
<View style={Theme.Styles.horizontalLine} />
{props.isSharingVc ? null : (
<Row style={Theme.Styles.activationTab}>
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<EsignetMosipVCActivationStatus
verifiableCredential={credentials}
showOnlyBindedVc={props.showOnlyBindedVc}
emptyWalletBindingId
/>
)}
<View style={Theme.Styles.verticalLine} />
<Row style={Theme.Styles.kebabIcon}>
<Pressable onPress={KEBAB_POPUP} accessible={false}>
<KebabPopUp
testID="ellipsis"
vcMetadata={props.vcMetadata}
iconName="dots-three-horizontal"
iconType="entypo"
isVisible={isKebabPopUp}
onDismiss={DISMISS}
service={service}
/>
</Pressable>
</Row>
</Row>
)}
</Pressable>
</React.Fragment>
);
};
export interface EsignetMosipVCItemProps {
vcMetadata: VCMetadata;
margin?: string;
selectable?: boolean;
selected?: boolean;
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof EsignetMosipVCItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof EsignetMosipVCItemMachine>) => void;
activeTab?: string;
iconName?: string;
iconType?: string;
isSharingVc?: boolean;
}

View File

@@ -0,0 +1,127 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Dimensions} from 'react-native';
import {Icon} from 'react-native-elements';
import {Theme} from '../../ui/styleUtils';
import {Row, Text} from '../../ui';
import {VerifiableCredential} from '../../../types/VC/EsignetMosipVC/vc';
const WalletUnverifiedIcon: React.FC = () => {
return (
<Icon
name="shield-alert"
color={Theme.Colors.Icon}
size={Theme.ICON_MID_SIZE}
type="material-community"
containerStyle={{marginStart: 10, bottom: 1, marginLeft: 10}}
/>
);
};
const WalletVerifiedIcon: React.FC = () => {
return (
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={Theme.ICON_MID_SIZE}
containerStyle={{marginStart: 10, bottom: 1, marginLeft: 10}}
/>
);
};
const WalletUnverifiedActivationDetails: React.FC<
WalletUnVerifiedDetailsProps
> = props => {
const {t} = useTranslation('VcDetails');
return (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row
crossAlign="center"
style={{
flex: 1,
}}>
{props.verifiableCredential && <WalletUnverifiedIcon />}
<Text
color={Theme.Colors.Details}
testID="activationPending"
weight="regular"
margin="8 10 10 5"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('offlineAuthDisabledHeader')}></Text>
</Row>
</Row>
);
};
const WalletVerifiedActivationDetails: React.FC<
WalletVerifiedDetailsProps
> = props => {
const {t} = useTranslation('WalletBinding');
return (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row
crossAlign="center"
style={{
flex: 1,
}}>
<WalletVerifiedIcon />
<Text
color={Theme.Colors.statusLabel}
testID="activated"
weight="regular"
size="smaller"
margin="8 10 10 5"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('profileAuthenticated')}></Text>
</Row>
</Row>
);
};
export const EsignetMosipVCActivationStatus: React.FC<
EsignetMosipVCActivationStatusProps
> = props => {
return (
<Row margin="0 0 0 -6">
{props.emptyWalletBindingId ? (
<WalletUnverifiedActivationDetails
verifiableCredential={props.verifiableCredential}
/>
) : (
<WalletVerifiedActivationDetails
verifiableCredential={props.verifiableCredential}
showOnlyBindedVc={props.showOnlyBindedVc}
/>
)}
</Row>
);
};
export interface EsignetMosipVCActivationStatusProps {
showOnlyBindedVc: boolean;
verifiableCredential: VerifiableCredential;
emptyWalletBindingId: boolean;
}
interface WalletVerifiedDetailsProps {
showOnlyBindedVc: boolean;
verifiableCredential: VerifiableCredential;
}
interface WalletUnVerifiedDetailsProps {
verifiableCredential: VerifiableCredential;
}

View File

@@ -0,0 +1,309 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Image, ImageBackground} from 'react-native';
import {CheckBox, Icon} from 'react-native-elements';
import {Column, Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import VerifiedIcon from '../../VerifiedIcon';
import {getLocalizedField} from '../../../i18n';
import {
Credential,
VerifiableCredential,
} from '../../../types/VC/EsignetMosipVC/vc';
import testIDProps from '../../../shared/commonUtil';
const getDetails = (arg1: string, arg2: string, credential: Credential) => {
if (arg1 === 'Status') {
return (
<Column>
<Text
testID="status"
weight="bold"
size="smaller"
color={
!credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}>
{arg1}
</Text>
<Row
style={{
justifyContent: 'space-between',
alignItems: 'center',
marginLeft: 9,
}}>
{!credential ? null : <VerifiedIcon />}
<Text
testID="valid"
numLines={1}
color={Theme.Colors.Details}
weight="semibold"
size="smaller"
style={
!credential
? Theme.Styles.loadingTitle
: Theme.Styles.detailsValue
}>
{!credential ? '' : arg2}
</Text>
</Row>
</Column>
);
} else {
return (
<Column>
<Text
color={
!credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
size="smaller"
weight="regular"
style={Theme.Styles.vcItemLabelHeader}>
{arg1}
</Text>
<Text
numLines={4}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
style={
!credential ? Theme.Styles.loadingTitle : Theme.Styles.subtitle
}>
{!credential ? '' : arg2}
</Text>
</Column>
);
}
};
function getIdNumber(id: string) {
if (id) {
return '*'.repeat(id.length - 4) + id.slice(-4);
}
}
export const EsignetMosipVCItemContent: React.FC<
EsignetMosipVCItemContentProps
> = props => {
//Assigning the UIN and VID from the VC details to display the idtype label
const uin = props.credential?.credential?.credentialSubject.UIN;
const vid = props.credential?.credential?.credentialSubject.VID;
const fullName = !props.credential?.credential
? ''
: getLocalizedField(
props.credential?.credential.credentialSubject.fullName,
);
const {t} = useTranslation('VcDetails');
const isvalid = !props.credential?.credential ? '' : t('valid');
const selectableOrCheck = props.selectable ? (
<CheckBox
checked={props.selected}
checkedIcon={<Icon name="radio-button-checked" />}
uncheckedIcon={<Icon name="radio-button-unchecked" />}
onPress={() => props.onPress()}
/>
) : null;
return (
<ImageBackground
source={!props.credential?.credential ? null : Theme.CloseCard}
resizeMode="stretch"
style={
!props.credential?.credential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
}>
<Column>
<Row align="space-between">
<Row margin="5 0 0 5">
<ImageBackground
imageStyle={Theme.Styles.faceImage}
source={
!props.credential?.credential
? Theme.ProfileIcon
: {uri: props.credential.credential.credentialSubject.face}
}
style={Theme.Styles.closeCardImage}>
{props.iconName && (
<Image
source={Theme.PinIcon}
style={Theme.Styles.pinIcon}
{...testIDProps('pinIcon')}
/>
)}
</ImageBackground>
<Column margin="0 0 10 20" height={96} align="space-between">
<Column>
<Text
testID="fullNameTitle"
weight="regular"
size="smaller"
color={
!props.credential?.credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}>
{t('fullName')}
</Text>
<Text
testID="fullNameValue"
weight="semibold"
style={
!props.credential?.credential
? Theme.Styles.loadingTitle
: Theme.Styles.detailsValue
}>
{fullName}
</Text>
</Column>
<Column>
<Text
testID="idType"
color={
!props.credential?.credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
weight="regular"
size="smaller"
align="left">
{t('idType')}
</Text>
<Text
testID="nationalCard"
weight="semibold"
color={Theme.Colors.Details}
style={
!props.credential?.credential
? Theme.Styles.loadingTitle
: Theme.Styles.detailsValue
}>
{t('nationalCard')}
</Text>
</Column>
</Column>
</Row>
<Column>
{props.credential?.credential ? selectableOrCheck : null}
</Column>
</Row>
<Row
align="space-between"
margin="9 10 0 7"
style={
!props.credential?.credential ? Theme.Styles.loadingContainer : null
}>
<Column>
{uin ? (
<Column margin="0 0 9 0">
<Text
testID="uin"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('uin')}
</Text>
<Text
testID="uinNumber"
weight="semibold"
size="extraSmall"
color={Theme.Colors.statusLabel}>
{getIdNumber(uin)}
</Text>
</Column>
) : null}
{vid ? (
<Column margin="0 0 9 0">
<Text
testID="vid"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('vid')}
</Text>
<Text
testID="vidNumber"
weight="semibold"
size="extraSmall"
color={Theme.Colors.Details}>
{getIdNumber(vid)}
</Text>
</Column>
) : null}
{!props.credential?.credential
? getDetails(t('id'), uin | vid, props.credential?.credential)
: null}
</Column>
</Row>
<Row
style={{flexDirection: 'row', flex: 1}}
align="space-between"
margin="0 8 5 8">
<Column margin="9 0 0 0">
<Text
testID="generatedOnTitle"
weight="regular"
size="smaller"
color={
!props.credential?.credential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}>
{t('generatedOn')}
</Text>
<Text
testID="generatedOnValue"
weight="semibold"
style={
!props.credential?.credential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{props.generatedOn}
</Text>
</Column>
<Column margin="0 35 0 0">
{props.credential?.credential
? getDetails(t('status'), isvalid, props.credential?.credential)
: null}
</Column>
<Column
testID="logo"
style={{
display: props.credential?.credential ? 'flex' : 'none',
justifyContent: 'center',
alignItems: 'center',
}}>
<Image
src={props.credential?.issuerLogo}
style={Theme.Styles.issuerLogo}
resizeMethod="scale"
resizeMode="contain"
/>
</Column>
</Row>
</Column>
</ImageBackground>
);
};
interface EsignetMosipVCItemContentProps {
context: any;
credential: VerifiableCredential;
generatedOn: string;
selectable: boolean;
selected: boolean;
iconName?: string;
iconType?: string;
service: any;
onPress?: () => void;
}

View File

@@ -0,0 +1,452 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Button, Column, Row, Text} from '../../ui';
import {Image, ImageBackground, View} from 'react-native';
import {Theme} from '../../ui/styleUtils';
import {QrCodeOverlay} from '../../QrCodeOverlay';
import {getLocalizedField} from '../../../i18n';
import VerifiedIcon from '../../VerifiedIcon';
import {CREDENTIAL_REGISTRY_EDIT} from 'react-native-dotenv';
import {TextItem} from '../../ui/TextItem';
import {format, formatDistanceToNow, parse} from 'date-fns';
import DateFnsLocale from 'date-fns/locale';
import {Icon} from 'react-native-elements';
import {WalletBindingResponse} from '../../../shared/cryptoutil/cryptoUtil';
import {
CredentialSubject,
VcIdType,
VCSharingReason,
VerifiableCredential,
VerifiablePresentation,
} from '../../../types/VC/EsignetMosipVC/vc';
export const EsignetMosipVCItemDetails: React.FC<
EsignetMosipVCItemDetailsProps
> = props => {
const {t, i18n} = useTranslation('VcDetails');
const uin = props.vc?.verifiableCredential.credential.credentialSubject.UIN;
const vid = props.vc?.verifiableCredential.credential.credentialSubject.VID;
if (props.vc?.verifiableCredential == null) {
return <Text align="center">Loading details...</Text>;
}
return (
<Column margin="10">
<ImageBackground
imageStyle={{width: '100%'}}
resizeMethod="scale"
resizeMode="stretch"
style={Theme.Styles.openCardBgContainer}
source={Theme.OpenCard}>
<Row align="space-between" padding="10" margin="0 10 0 10">
<Column align="space-evenly" crossAlign="center">
<Image
source={
props.vc?.verifiableCredential.credential.credentialSubject
?.face
? {
uri: props.vc?.verifiableCredential.credential
.credentialSubject.face,
}
: Theme.ProfileIcon
}
style={Theme.Styles.openCardImage}
/>
<QrCodeOverlay
qrCodeDetailes={String(props.vc.verifiableCredential)}
/>
<Column margin="20 0 0 0">
<Image
src={props.vc.verifiableCredential.issuerLogo}
style={Theme.Styles.issuerLogo}
/>
</Column>
</Column>
<Column align="space-evenly" padding="10">
<Column>
<Text
testID="fullNameTitle"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('fullName')}
</Text>
<Text
testID="fullNameValue"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential.credentialSubject
.fullName,
)}
</Text>
</Column>
<Row>
<Column>
<Column margin="20 0 0 0">
<Text
testID="gender"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('gender')}
</Text>
<Text
testID="genderValue"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential
.credentialSubject.gender,
)}
</Text>
</Column>
<Column margin="25 0 0 0">
<Text
testID="idType"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('idType')}
</Text>
<Text
testID="nationalCard"
weight="bold"
size="smaller"
color={Theme.Colors.Details}>
{t('nationalCard')}
</Text>
</Column>
{uin ? (
<Column margin="25 0 0 0">
<Text
testID="uin"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('uin')}
</Text>
<Text
testID="uinNumber"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{uin}
</Text>
</Column>
) : null}
{vid ? (
<Column margin="25 0 0 0">
<Text
testID="vid"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('vid')}
</Text>
<Text
testID="vidNumber"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{vid}
</Text>
</Column>
) : null}
<Column margin="30 0 0 0">
<Text
testID="generatedOnTitle"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('generatedOn')}
</Text>
<Text
testID="generatedOnValue"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{new Date(props.vc?.generatedOn).toLocaleDateString()}
</Text>
</Column>
</Column>
<Column margin="0 0 0 40">
<Column margin="20 0 0 0">
<Text
testID="dateOfBirth"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('dateOfBirth')}
</Text>
<Text
testID="dateOfBirthValue"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getFormattedDateOfBirth()}
</Text>
</Column>
<Column margin="25 0 0 0">
<Text
testID="status"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('status')}
</Text>
<Row
style={{
justifyContent: 'flex-start',
alignItems: 'center',
}}>
<VerifiedIcon />
<Text
testID="valid"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{t('valid')}
</Text>
</Row>
</Column>
<Column margin="92 0 0 0">
<Text
testID="phoneNumber"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('phoneNumber')}
</Text>
<Text
testID="phoneNumberValue"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential
.credentialSubject.phone,
)}
</Text>
</Column>
</Column>
</Row>
</Column>
</Row>
<View style={Theme.Styles.hrLine}></View>
<Column padding="10">
<Column fill style={Theme.Styles.labelPart}>
<Text
testID="emailId"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('email')}
</Text>
<Row>
<Text
testID="emailIdValue"
style={
props.vc?.verifiableCredential.credential.credentialSubject
.email.length > 25
? {flex: 1}
: {flex: 0}
}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credential.credentialSubject
.email,
)}
</Text>
</Row>
</Column>
<Column style={Theme.Styles.labelPart}>
<Text
testID="address"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('address')}
</Text>
<Row>
<Text
testID="addressValue"
style={{flex: 1}}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getFullAddress(
props.vc?.verifiableCredential.credential.credentialSubject,
)}
</Text>
</Row>
</Column>
{CREDENTIAL_REGISTRY_EDIT === 'true' && (
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('credentialRegistry')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{props.vc?.credentialRegistry}
</Text>
</Column>
)}
</Column>
</ImageBackground>
{props.vc?.reason?.length > 0 && (
<Text
testID="reasonForSharingTitle"
margin="24 24 16 24"
weight="semibold">
{t('reasonForSharing')}
</Text>
)}
{props.vc?.reason?.map((reason, index) => (
<TextItem
testID="reason"
key={index}
divider
label={formatDistanceToNow(reason.timestamp, {
addSuffix: true,
locale: DateFnsLocale[i18n.language],
})}
text={reason.message}
/>
))}
{props.activeTab !== 1 ? (
props.isBindingPending ? (
<Column style={Theme.Styles.openCardBgContainer} padding="10">
<Column margin={'0 0 4 0'} crossAlign={'flex-start'}>
<Image source={Theme.activationPending}></Image>
<Text
testID="offlineAuthDisabledHeader"
style={{flex: 1}}
weight="semibold"
size="small"
margin={'5 0 0 0'}
color={Theme.Colors.statusLabel}>
{t('offlineAuthDisabledHeader')}
</Text>
</Column>
<Text
testID="offlineAuthDisabledMessage"
style={{flex: 1, lineHeight: 17}}
weight="regular"
size="small"
margin={'3 0 0 0'}
color={Theme.Colors.statusMessage}>
{t('offlineAuthDisabledMessage')}
</Text>
<Button
testID="enableVerification"
title={t('enableVerification')}
onPress={props.onBinding}
type="gradient"
styles={{width: '100%'}}
/>
</Column>
) : (
<Column style={Theme.Styles.openCardBgContainer} padding="10">
<Row crossAlign="center">
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={28}
containerStyle={{marginStart: 4, bottom: 1}}
/>
<Text
testID="profileAuthenticated"
numLines={1}
color={Theme.Colors.statusLabel}
weight="bold"
size="smaller"
margin="10 10 10 10"
children={t('profileAuthenticated')}></Text>
</Row>
</Column>
)
) : (
<></>
)}
</Column>
);
function getFormattedDateOfBirth(): React.ReactNode {
return format(
parse(
getLocalizedField(
props.vc?.verifiableCredential.credential.credentialSubject
.dateOfBirth,
),
'yyyy/MM/dd',
new Date(),
),
'MM/dd/yyyy',
);
}
};
export interface EsignetMosipVCItemDetailsProps {
vc: VC;
isBindingPending: boolean;
onBinding?: () => void;
activeTab?: number;
}
export interface VC {
id: string;
idType: VcIdType;
tag: string;
verifiableCredential: VerifiableCredential;
verifiablePresentation?: VerifiablePresentation;
generatedOn: Date;
requestId: string;
isVerified: boolean;
lastVerifiedOn: number;
locked: boolean;
reason?: VCSharingReason[];
shouldVerifyPresence?: boolean;
walletBindingResponse?: WalletBindingResponse;
credentialRegistry: string;
isPinned?: boolean;
hashedId: string;
}
function getFullAddress(credential: CredentialSubject) {
if (!credential) {
return '';
}
const fields = [
'addressLine1',
'addressLine2',
'addressLine3',
'city',
'province',
'region',
];
return fields
.map(field => getLocalizedField(credential[field]))
.concat(credential.postalCode)
.filter(Boolean)
.join(', ');
}

View File

@@ -0,0 +1,134 @@
import React, {useContext, useEffect, useMemo, useRef} from 'react';
import {useInterpret, useSelector} from '@xstate/react';
import {View, Pressable} from 'react-native';
import {ActorRefFrom} from 'xstate';
import {
createExistingMosipVCItemMachine,
selectVerifiableCredential,
selectGeneratedOn,
ExistingMosipVCItemMachine,
selectContext,
selectTag,
selectEmptyWalletBindingId,
selectIsSavingFailedInIdle,
selectKebabPopUp,
} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {ExistingMosipVCItemEvents} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {ErrorMessageOverlay} from '../../MessageOverlay';
import {Theme} from '../../ui/styleUtils';
import {GlobalContext} from '../../../shared/GlobalContext';
import {ExistingMosipVCItemContent} from './ExistingMosipVCItemContent';
import {ExistingMosipVCItemActivationStatus} from './ExistingMosipVCItemActivationStatus';
import {Row} from '../../ui';
import {KebabPopUp} from '../../KebabPopUp';
import {VCMetadata} from '../../../shared/VCMetadata';
import {format} from 'date-fns';
export const ExistingMosipVCItem: React.FC<
ExistingMosipVCItemProps
> = props => {
const {appService} = useContext(GlobalContext);
const machine = useRef(
createExistingMosipVCItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcMetadata,
),
);
const service = useInterpret(machine.current, {devTools: __DEV__});
useEffect(() => {
service.send(
ExistingMosipVCItemEvents.UPDATE_VC_METADATA(props.vcMetadata),
);
}, [props.vcMetadata]);
const context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const DISMISS = () => service.send(ExistingMosipVCItemEvents.DISMISS());
const KEBAB_POPUP = () =>
service.send(ExistingMosipVCItemEvents.KEBAB_POPUP());
const isSavingFailedInIdle = useSelector(service, selectIsSavingFailedInIdle);
const storeErrorTranslationPath = 'errors.savingFailed';
const generatedOn = useSelector(service, selectGeneratedOn);
const formattedDate = format(new Date(generatedOn), 'MM/dd/yyyy');
const tag = useSelector(service, selectTag);
return (
<React.Fragment>
<Pressable
accessible={false}
onPress={() => props.onPress(service)}
disabled={!verifiableCredential}
style={
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<ExistingMosipVCItemContent
context={context}
verifiableCredential={verifiableCredential}
generatedOn={formattedDate}
tag={tag}
selectable={props.selectable}
selected={props.selected}
service={service}
iconName={props.iconName}
iconType={props.iconType}
onPress={() => props.onPress(service)}
/>
<View style={Theme.Styles.horizontalLine} />
{props.isSharingVc ? null : (
<Row style={Theme.Styles.activationTab}>
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<ExistingMosipVCItemActivationStatus
verifiableCredential={verifiableCredential}
emptyWalletBindingId={emptyWalletBindingId}
onPress={() => props.onPress(service)}
showOnlyBindedVc={props.showOnlyBindedVc}
/>
)}
<View style={Theme.Styles.verticalLine} />
<Row style={Theme.Styles.kebabIcon}>
<Pressable onPress={KEBAB_POPUP} accessible={false}>
<KebabPopUp
vcMetadata={props.vcMetadata}
iconName="dots-three-horizontal"
iconType="entypo"
isVisible={isKebabPopUp}
onDismiss={DISMISS}
service={service}
/>
</Pressable>
</Row>
</Row>
)}
</Pressable>
<ErrorMessageOverlay
isVisible={isSavingFailedInIdle}
error={storeErrorTranslationPath}
onDismiss={DISMISS}
translationPath={'VcDetails'}
/>
</React.Fragment>
);
};
export interface ExistingMosipVCItemProps {
vcMetadata: VCMetadata;
margin?: string;
selectable?: boolean;
selected?: boolean;
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
activeTab?: string;
iconName?: string;
iconType?: string;
isSharingVc?: boolean;
}

View File

@@ -0,0 +1,134 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Dimensions} from 'react-native';
import {Icon} from 'react-native-elements';
import {ActorRefFrom} from 'xstate';
import {ExistingMosipVCItemMachine} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine';
import {VerifiableCredential} from '../../../types/VC/ExistingMosipVC/vc';
import {Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
const WalletUnverifiedIcon: React.FC = () => {
return (
<Icon
name="shield-alert"
color={Theme.Colors.Icon}
size={Theme.ICON_MID_SIZE}
type="material-community"
containerStyle={{marginStart: 10, bottom: 1, marginLeft: 10}}
/>
);
};
const WalletVerifiedIcon: React.FC = () => {
return (
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={Theme.ICON_MID_SIZE}
containerStyle={{marginStart: 10, bottom: 1, marginLeft: 10}}
/>
);
};
const WalletUnverifiedActivationDetails: React.FC<
WalletUnVerifiedDetailsProps
> = props => {
const {t} = useTranslation('VcDetails');
return (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row
crossAlign="center"
style={{
flex: 1,
}}>
{props.verifiableCredential && <WalletUnverifiedIcon />}
<Text
color={Theme.Colors.Details}
testID="activationPending"
weight="regular"
margin="8 10 10 5"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('offlineAuthDisabledHeader')}></Text>
</Row>
</Row>
);
};
const WalletVerifiedActivationDetails: React.FC<
WalletVerifiedDetailsProps
> = props => {
const {t} = useTranslation('WalletBinding');
return (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row
crossAlign="center"
style={{
flex: 1,
}}>
<WalletVerifiedIcon />
<Text
color={Theme.Colors.statusLabel}
testID="activated"
weight="regular"
size="smaller"
margin="8 10 10 5"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('profileAuthenticated')}></Text>
</Row>
</Row>
);
};
export const ExistingMosipVCItemActivationStatus: React.FC<
ExistingMosipVCItemActivationStatusProps
> = props => {
return (
<Row margin="0 0 0 -6">
{props.emptyWalletBindingId ? (
<WalletUnverifiedActivationDetails
verifiableCredential={props.verifiableCredential}
onPress={props.onPress}
/>
) : (
<WalletVerifiedActivationDetails
verifiableCredential={props.verifiableCredential}
showOnlyBindedVc={props.showOnlyBindedVc}
onPress={props.onPress}
/>
)}
</Row>
);
};
interface ExistingMosipVCItemActivationStatusProps {
showOnlyBindedVc: boolean;
onPress: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
verifiableCredential: VerifiableCredential;
emptyWalletBindingId: boolean;
}
interface WalletVerifiedDetailsProps {
showOnlyBindedVc: boolean;
onPress: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
verifiableCredential: VerifiableCredential;
}
interface WalletUnVerifiedDetailsProps {
onPress: (vcRef?: ActorRefFrom<typeof ExistingMosipVCItemMachine>) => void;
verifiableCredential: VerifiableCredential;
}

View File

@@ -0,0 +1,313 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Image, ImageBackground, View} from 'react-native';
import {getLocalizedField} from '../../../i18n';
import {VerifiableCredential} from '../../../types/VC/ExistingMosipVC/vc';
import {VcItemTags} from '../common/VcItemTags';
import VerifiedIcon from '../../VerifiedIcon';
import {Column, Row, Text} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {CheckBox, Icon} from 'react-native-elements';
import testIDProps from '../../../shared/commonUtil';
const getDetails = (arg1, arg2, verifiableCredential) => {
if (arg1 === 'Status') {
return (
<Column
style={{
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 7,
}}>
<Text
testID="status"
weight="regular"
size="smaller"
color={
!verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}>
{arg1}
</Text>
<Row
style={{
justifyContent: 'space-between',
alignItems: 'center',
marginLeft: 9,
}}>
{!verifiableCredential ? null : <VerifiedIcon />}
<Text
testID="valid"
numLines={1}
color={Theme.Colors.Details}
weight="semibold"
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.detailsValue
}>
{!verifiableCredential ? '' : arg2}
</Text>
</Row>
</Column>
);
} else {
return (
<Column>
<Text
color={
!verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
size="smaller"
weight="regular"
style={Theme.Styles.vcItemLabelHeader}>
{arg1}
</Text>
<Text
numLines={4}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{!verifiableCredential ? '' : arg2}
</Text>
</Column>
);
}
};
function getIdNumber(id: string) {
return '*'.repeat(id.length - 4) + id.slice(-4);
}
export const ExistingMosipVCItemContent: React.FC<
ExistingMosipVCItemContentProps
> = props => {
//Assigning the UIN and VID from the VC details to display the idtype label
const uin = props.verifiableCredential?.credentialSubject.UIN;
const vid = props.verifiableCredential?.credentialSubject.VID;
const fullName = !props.verifiableCredential
? ''
: getLocalizedField(props.verifiableCredential.credentialSubject.fullName);
const {t} = useTranslation('VcDetails');
const isvalid = !props.verifiableCredential ? '' : t('valid');
const selectableOrCheck = props.selectable ? (
<CheckBox
checked={props.selected}
checkedIcon={<Icon name="radio-button-checked" />}
uncheckedIcon={<Icon name="radio-button-unchecked" />}
onPress={() => props.onPress()}
/>
) : null;
return (
<ImageBackground
source={!props.verifiableCredential ? null : Theme.CloseCard}
resizeMode="stretch"
style={
!props.verifiableCredential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
}>
<Column>
<Row align="space-between">
<Row margin="5 0 0 5">
<ImageBackground
imageStyle={Theme.Styles.faceImage}
source={
!props.verifiableCredential
? Theme.ProfileIcon
: {uri: props.context.credential.biometrics.face}
}
style={Theme.Styles.closeCardImage}>
{props.iconName && (
<Image
source={Theme.PinIcon}
style={Theme.Styles.pinIcon}
{...testIDProps('pinIcon')}
/>
)}
</ImageBackground>
<Column margin="0 0 10 20" height={96} align="space-between">
<Column>
<Text
testID="fullNameTitle"
weight="regular"
size="smaller"
color={
!props.verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}>
{t('fullName')}
</Text>
<Text
testID="fullNameValue"
weight="semibold"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.detailsValue
}>
{fullName}
</Text>
</Column>
<Column>
<Text
testID="idType"
color={
!props.verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
weight="regular"
size="smaller"
align="left">
{t('idType')}
</Text>
<Text
testID="nationalCard"
weight="semibold"
color={Theme.Colors.Details}
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.detailsValue
}>
{t('nationalCard')}
</Text>
</Column>
</Column>
</Row>
<Column>
{props.verifiableCredential ? selectableOrCheck : null}
</Column>
</Row>
<Row
align="space-between"
margin="9 10 0 7"
style={
!props.verifiableCredential ? Theme.Styles.loadingContainer : null
}>
<Column>
{uin ? (
<Column margin="0 0 9 0">
<Text
testID="uin"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('uin')}
</Text>
<Text
testID="uinNumber"
weight="semibold"
size="extraSmall"
color={Theme.Colors.statusLabel}>
{getIdNumber(uin)}
</Text>
</Column>
) : null}
{vid ? (
<Column margin="0 0 9 0">
<Text
testID="vid"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('vid')}
</Text>
<Text
testID="vidNumber"
weight="semibold"
size="extraSmall"
color={Theme.Colors.Details}>
{getIdNumber(vid)}
</Text>
</Column>
) : null}
{!props.verifiableCredential
? getDetails(t('id'), uin || vid, props.verifiableCredential)
: null}
</Column>
</Row>
<Row
style={{flexDirection: 'row', flex: 1}}
align="space-between"
margin="0 8 5 8">
<Column margin="9 0 0 0">
<Text
testID="generatedOnTitle"
weight="regular"
size="smaller"
color={
!props.verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}>
{t('generatedOn')}
</Text>
<Text
testID="generatedOnValue"
weight="semibold"
style={
!props.verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{props.generatedOn}
</Text>
</Column>
<Column margin="0 35 0 0">
{props.verifiableCredential
? getDetails(t('status'), isvalid, props.verifiableCredential)
: null}
</Column>
<Column
testID="logo"
style={{
display: props.verifiableCredential ? 'flex' : 'none',
justifyContent: 'center',
alignItems: 'center',
}}>
<Image
source={Theme.MosipSplashLogo}
style={Theme.Styles.logo}
resizeMethod="scale"
resizeMode="contain"
/>
</Column>
</Row>
</Column>
<VcItemTags tag={props.tag} />
</ImageBackground>
);
};
interface ExistingMosipVCItemContentProps {
context: any;
verifiableCredential: VerifiableCredential;
generatedOn: string;
tag: string;
selectable: boolean;
selected: boolean;
iconName?: string;
iconType?: string;
service: any;
onPress?: () => void;
}

View File

@@ -1,21 +1,23 @@
import { formatDistanceToNow } from 'date-fns'; import {formatDistanceToNow} from 'date-fns';
import React from 'react'; import React from 'react';
import * as DateFnsLocale from 'date-fns/locale'; import * as DateFnsLocale from 'date-fns/locale';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { Image, ImageBackground, View } from 'react-native'; import {Image, ImageBackground, View} from 'react-native';
import { Icon } from 'react-native-elements'; import {Icon} from 'react-native-elements';
import { VC, CredentialSubject } from '../types/vc'; import {VC, CredentialSubject} from '../../../types/VC/ExistingMosipVC/vc';
import { Button, Column, Row, Text } from './ui'; import {Button, Column, Row, Text} from '../../ui';
import { Theme } from './ui/styleUtils'; import {Theme} from '../../ui/styleUtils';
import { TextItem } from './ui/TextItem'; import {TextItem} from '../../ui/TextItem';
import { VcItemTags } from './VcItemTags'; import {VcItemTags} from '../common/VcItemTags';
import VerifiedIcon from './VerifiedIcon'; import VerifiedIcon from '../../VerifiedIcon';
import { getLocalizedField } from '../i18n'; import {getLocalizedField} from '../../../i18n';
import { CREDENTIAL_REGISTRY_EDIT } from 'react-native-dotenv'; import {CREDENTIAL_REGISTRY_EDIT} from 'react-native-dotenv';
import { QrCodeOverlay } from './QrCodeOverlay'; import {QrCodeOverlay} from '../../QrCodeOverlay';
export const VcDetails: React.FC<VcDetailsProps> = (props) => { export const ExistingMosipVCItemDetails: React.FC<
const { t, i18n } = useTranslation('VcDetails'); ExistingMosipVCItemDetailsProps
> = props => {
const {t, i18n} = useTranslation('VcDetails');
//Assigning the UIN and VID from the VC details to display the idtype label //Assigning the UIN and VID from the VC details to display the idtype label
const uin = props.vc?.verifiableCredential.credentialSubject.UIN; const uin = props.vc?.verifiableCredential.credentialSubject.UIN;
@@ -28,15 +30,17 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
return ( return (
<Column margin="10"> <Column margin="10">
<ImageBackground <ImageBackground
borderRadius={10} imageStyle={{width: '100%'}}
resizeMethod="scale"
resizeMode="stretch"
style={Theme.Styles.openCardBgContainer} style={Theme.Styles.openCardBgContainer}
source={Theme.OpenCard}> source={Theme.OpenCard}>
<Row align="space-between"> <Row align="space-between" padding="10" margin="0 10 0 10">
<Column align="space-evenly" crossAlign="center"> <Column align="space-evenly" crossAlign="center">
<Image <Image
source={ source={
props.vc?.credential.biometrics?.face props.vc?.credential.biometrics?.face
? { uri: props.vc?.credential.biometrics.face } ? {uri: props.vc?.credential.biometrics.face}
: Theme.ProfileIcon : Theme.ProfileIcon
} }
style={Theme.Styles.openCardImage} style={Theme.Styles.openCardImage}
@@ -44,36 +48,61 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
<QrCodeOverlay qrCodeDetailes={String(props.vc.credential)} /> <QrCodeOverlay qrCodeDetailes={String(props.vc.credential)} />
<Column margin="20 0 0 0"> <Column margin="20 0 0 0">
<Image source={Theme.MosipLogo} style={Theme.Styles.logo} /> <Image
source={Theme.MosipLogo}
style={Theme.Styles.vcDetailsLogo}
/>
</Column> </Column>
</Column> </Column>
<Column align="space-evenly"> <Column align="space-evenly" padding="10">
<Column> <Column>
<Text <Text
weight="bold" testID="fullNameTitle"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('fullName')} {t('fullName')}
</Text> </Text>
<Text <Text
testID="fullNameValue"
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
{getLocalizedField( {getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.fullName props.vc?.verifiableCredential.credentialSubject.fullName,
)} )}
</Text> </Text>
</Column> </Column>
<Row> <Row>
<Column> <Column>
<Column> <Column margin="20 0 0 0">
<Text <Text
weight="bold" testID="gender"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('gender')}
</Text>
<Text
testID="genderValue"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.gender,
)}
</Text>
</Column>
<Column margin="25 0 0 0">
<Text
testID="idType"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('idType')} {t('idType')}
</Text> </Text>
<Text <Text
testID="nationalCard"
weight="bold" weight="bold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
@@ -81,14 +110,16 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
</Text> </Text>
</Column> </Column>
{uin ? ( {uin ? (
<Column margin="20 0 0 0"> <Column margin="25 0 0 0">
<Text <Text
weight="bold" testID="uin"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('uin')} {t('uin')}
</Text> </Text>
<Text <Text
testID="uinNumber"
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
@@ -98,14 +129,16 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
) : null} ) : null}
{vid ? ( {vid ? (
<Column margin="20 0 0 0"> <Column margin="25 0 0 0">
<Text <Text
weight="bold" testID="vid"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('vid')} {t('vid')}
</Text> </Text>
<Text <Text
testID="vidNumber"
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
@@ -113,87 +146,83 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
</Text> </Text>
</Column> </Column>
) : null} ) : null}
<Column margin="20 0 0 0"> <Column margin="30 0 0 0">
<Text <Text
weight="bold" testID="generatedOnTitle"
size="smaller" weight="regular"
color={Theme.Colors.DetailsLabel}>
{t('dateOfBirth')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{new Date(
getLocalizedField(
props.vc?.verifiableCredential.credentialSubject
.dateOfBirth
)
).toLocaleDateString()}
</Text>
</Column>
</Column>
<Column margin="0 0 0 40">
<Column>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('gender')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.gender
)}
</Text>
</Column>
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('generatedOn')} {t('generatedOn')}
</Text> </Text>
<Text <Text
testID="generatedOnValue"
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
{new Date(props.vc?.generatedOn).toLocaleDateString()} {new Date(props.vc?.generatedOn).toLocaleDateString()}
</Text> </Text>
</Column> </Column>
</Column>
<Column margin="0 0 0 40">
<Column margin="20 0 0 0"> <Column margin="20 0 0 0">
<Text <Text
weight="bold" testID="dateOfBirth"
weight="regular"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('dateOfBirth')}
</Text>
<Text
testID="dateOfBirthValue"
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{new Date(
getLocalizedField(
props.vc?.verifiableCredential.credentialSubject
.dateOfBirth,
),
).toLocaleDateString()}
</Text>
</Column>
<Column margin="25 0 0 0">
<Text
testID="status"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('status')} {t('status')}
</Text> </Text>
<Row> <Row
style={{
justifyContent: 'flex-start',
alignItems: 'center',
}}>
{props.vc?.isVerified && <VerifiedIcon />}
<Text <Text
testID="valid"
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
{t('valid')} {t('valid')}
</Text> </Text>
{props.vc?.isVerified && <VerifiedIcon />}
</Row> </Row>
</Column> </Column>
<Column margin="20 0 0 0"> <Column margin="92 0 0 0">
<Text <Text
weight="bold" testID="phoneNumber"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('phoneNumber')} {t('phoneNumber')}
</Text> </Text>
<Text <Text
testID="phoneNumberValue"
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
{getLocalizedField( {getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.phone props.vc?.verifiableCredential.credentialSubject.phone,
)} )}
</Text> </Text>
</Column> </Column>
@@ -202,27 +231,29 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
</Column> </Column>
</Row> </Row>
<View style={Theme.Styles.hrLine}></View> <View style={Theme.Styles.hrLine}></View>
<Column> <Column padding="10">
<Column fill style={Theme.Styles.labelPart}> <Column fill style={Theme.Styles.labelPart}>
<Text <Text
weight="bold" testID="emailId"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('email')} {t('email')}
</Text> </Text>
<Row> <Row>
<Text <Text
testID="emailIdValue"
style={ style={
props.vc?.verifiableCredential.credentialSubject.email props.vc?.verifiableCredential.credentialSubject.email
.length > 25 .length > 25
? { flex: 1 } ? {flex: 1}
: { flex: 0 } : {flex: 0}
} }
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
{getLocalizedField( {getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.email props.vc?.verifiableCredential.credentialSubject.email,
)} )}
</Text> </Text>
</Row> </Row>
@@ -230,19 +261,21 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
<Column style={Theme.Styles.labelPart}> <Column style={Theme.Styles.labelPart}>
<Text <Text
weight="bold" testID="address"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('address')} {t('address')}
</Text> </Text>
<Row> <Row>
<Text <Text
style={{ flex: 1 }} testID="addressValue"
style={{flex: 1}}
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
{getFullAddress( {getFullAddress(
props.vc?.verifiableCredential.credentialSubject props.vc?.verifiableCredential.credentialSubject,
)} )}
</Text> </Text>
</Row> </Row>
@@ -250,12 +283,14 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
{CREDENTIAL_REGISTRY_EDIT === 'true' && ( {CREDENTIAL_REGISTRY_EDIT === 'true' && (
<Column fill style={Theme.Styles.labelPart}> <Column fill style={Theme.Styles.labelPart}>
<Text <Text
weight="bold" testID="credentialRegistry"
weight="regular"
size="smaller" size="smaller"
color={Theme.Colors.DetailsLabel}> color={Theme.Colors.DetailsLabel}>
{t('credentialRegistry')} {t('credentialRegistry')}
</Text> </Text>
<Text <Text
testID="credentialRegistryValue"
weight="semibold" weight="semibold"
size="smaller" size="smaller"
color={Theme.Colors.Details}> color={Theme.Colors.Details}>
@@ -268,13 +303,17 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
</ImageBackground> </ImageBackground>
{props.vc?.reason?.length > 0 && ( {props.vc?.reason?.length > 0 && (
<Text margin="24 24 16 24" weight="semibold"> <Text
testID="reasonForSharingTitle"
margin="24 24 16 24"
weight="semibold">
{t('reasonForSharing')} {t('reasonForSharing')}
</Text> </Text>
)} )}
{props.vc?.reason?.map((reason, index) => ( {props.vc?.reason?.map((reason, index) => (
<TextItem <TextItem
testID="reason"
key={index} key={index}
divider divider
label={formatDistanceToNow(reason.timestamp, { label={formatDistanceToNow(reason.timestamp, {
@@ -287,48 +326,48 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
{props.activeTab !== 1 ? ( {props.activeTab !== 1 ? (
props.isBindingPending ? ( props.isBindingPending ? (
<Column style={Theme.Styles.openCardBgContainer}> <Column style={Theme.Styles.openCardBgContainer} padding="10">
<Row margin={'0 0 5 0'} crossAlign={'center'}> <Column margin={'0 0 4 0'} crossAlign={'flex-start'}>
<Icon <Image source={Theme.activationPending}></Image>
name="shield-alert"
color={Theme.Colors.Icon}
size={30}
type="material-community"
/>
<Text <Text
style={{ flex: 1 }} testID="offlineAuthDisabledHeader"
style={{flex: 1}}
weight="semibold" weight="semibold"
size="small" size="small"
margin={'0 0 5 0'} margin={'5 0 0 0'}
color={Theme.Colors.statusLabel}> color={Theme.Colors.statusLabel}>
{t('offlineAuthDisabledHeader')} {t('offlineAuthDisabledHeader')}
</Text> </Text>
</Row> </Column>
<Text <Text
style={{ flex: 1 }} testID="offlineAuthDisabledMessage"
style={{flex: 1, lineHeight: 17}}
weight="regular" weight="regular"
size="small" size="small"
margin={'0 0 5 0'} margin={'3 0 0 0'}
color={Theme.Colors.statusLabel}> color={Theme.Colors.statusMessage}>
{t('offlineAuthDisabledMessage')} {t('offlineAuthDisabledMessage')}
</Text> </Text>
<Button <Button
testID="enableVerification"
title={t('enableVerification')} title={t('enableVerification')}
onPress={props.onBinding} onPress={props.onBinding}
type="radius" type="gradient"
styles={{width: '100%'}}
/> />
</Column> </Column>
) : ( ) : (
<Column style={Theme.Styles.openCardBgContainer}> <Column style={Theme.Styles.openCardBgContainer} padding="10">
<Row crossAlign="center"> <Row crossAlign="center">
<Icon <Icon
name="verified-user" name="verified-user"
color={Theme.Colors.VerifiedIcon} color={Theme.Colors.VerifiedIcon}
size={28} size={28}
containerStyle={{ marginStart: 4, bottom: 1 }} containerStyle={{marginStart: 4, bottom: 1}}
/> />
<Text <Text
testID="profileAuthenticated"
numLines={1} numLines={1}
color={Theme.Colors.statusLabel} color={Theme.Colors.statusLabel}
weight="bold" weight="bold"
@@ -345,7 +384,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
); );
}; };
interface VcDetailsProps { export interface ExistingMosipVCItemDetailsProps {
vc: VC; vc: VC;
isBindingPending: boolean; isBindingPending: boolean;
onBinding?: () => void; onBinding?: () => void;
@@ -367,7 +406,7 @@ function getFullAddress(credential: CredentialSubject) {
]; ];
return fields return fields
.map((field) => getLocalizedField(credential[field])) .map(field => getLocalizedField(credential[field]))
.concat(credential.postalCode) .concat(credential.postalCode)
.filter(Boolean) .filter(Boolean)
.join(', '); .join(', ');

View File

@@ -0,0 +1,19 @@
import React from 'react';
import {
EsignetMosipVCItemDetails,
EsignetMosipVCItemDetailsProps,
} from './EsignetMosipVCItem/EsignetMosipVCItemDetails';
import {VCMetadata} from '../../shared/VCMetadata';
import {
ExistingMosipVCItemDetails,
ExistingMosipVCItemDetailsProps,
} from './ExistingMosipVCItem/ExistingMosipVCItemDetails';
export const VcDetailsContainer: React.FC<
EsignetMosipVCItemDetailsProps | ExistingMosipVCItemDetailsProps
> = props => {
if (VCMetadata.fromVC(props.vc.vcMetadata).isFromOpenId4VCI()) {
return <EsignetMosipVCItemDetails {...props} />;
}
return <ExistingMosipVCItemDetails {...props} />;
};

View File

@@ -0,0 +1,18 @@
import {
EsignetMosipVCItem,
EsignetMosipVCItemProps,
} from './EsignetMosipVCItem/EsignetMosipVCItem';
import React from 'react';
import {
ExistingMosipVCItem,
ExistingMosipVCItemProps,
} from './ExistingMosipVCItem/ExistingMosipVCItem';
export const VcItemContainer: React.FC<
ExistingMosipVCItemProps | EsignetMosipVCItemProps
> = props => {
if (props.vcMetadata.isFromOpenId4VCI()) {
return <EsignetMosipVCItem {...props} />;
}
return <ExistingMosipVCItem {...props} />;
};

View File

@@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { Chip } from 'react-native-elements'; import {Chip} from 'react-native-elements';
import { Row, Text } from './ui'; import {Row, Text} from '../../ui';
import { Theme } from './ui/styleUtils'; import {Theme} from '../../ui/styleUtils';
export const VcItemTags: React.FC<{ tag: string }> = (props) => { export const VcItemTags: React.FC<{tag: string}> = props => {
return ( return (
props.tag !== '' && ( props.tag !== '' && (
<Row align="flex-end" margin={[8, 0, 0, 0]}> <Row align="flex-end" margin={[8, 0, 0, 0]}>

View File

@@ -1,16 +1,16 @@
{ {
"generatedOn": "Generated on", "generatedOn": "Generated On",
"status": "Status", "status": "Status",
"valid": "Valid", "valid": "Valid",
"photo": "Photo", "photo": "Photo",
"fullName": "Full name", "fullName": "Full Name",
"gender": "Gender", "gender": "Gender",
"dateOfBirth": "Date of birth", "dateOfBirth": "Date of Birth",
"phoneNumber": "Phone number", "phoneNumber": "Phone Number",
"email": "Email", "email": "Email",
"address": "Address", "address": "Address",
"reasonForSharing": "Reason for sharing", "reasonForSharing": "Reason for sharing",
"idType": "ID type", "idType": "ID Type",
"id": "Id", "id": "Id",
"nationalCard": "National Card", "nationalCard": "National Card",
"uin": "UIN", "uin": "UIN",

View File

@@ -1,118 +0,0 @@
import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { Pressable } from 'react-native';
import { ActorRefFrom } from 'xstate';
import {
createVcItemMachine,
selectVerifiableCredential,
selectGeneratedOn,
vcItemMachine,
selectContext,
selectTag,
selectEmptyWalletBindingId,
selectIsSavingFailedInIdle,
selectKebabPopUp,
} from '../machines/vcItem';
import { VcItemEvents } from '../machines/vcItem';
import { ErrorMessageOverlay } from './MessageOverlay';
import { Theme } from './ui/styleUtils';
import { GlobalContext } from '../shared/GlobalContext';
import { VcItemContent } from './VcItemContent';
import { VcItemActivationStatus } from './VcItemActivationStatus';
import { Row } from './ui';
import { KebabPopUp } from './KebabPopUp';
import { logState } from '../machines/app';
export const VcItem: React.FC<VcItemProps> = (props) => {
const { appService } = useContext(GlobalContext);
const machine = useRef(
createVcItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcKey
)
);
const service = useInterpret(machine.current, { devTools: __DEV__ });
service.subscribe(logState);
const context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const DISMISS = () => service.send(VcItemEvents.DISMISS());
const KEBAB_POPUP = () => service.send(VcItemEvents.KEBAB_POPUP());
const isSavingFailedInIdle = useSelector(service, selectIsSavingFailedInIdle);
const storeErrorTranslationPath = 'errors.savingFailed';
const generatedOn = useSelector(service, selectGeneratedOn);
const tag = useSelector(service, selectTag);
return (
<React.Fragment>
<Pressable
onPress={() => props.onPress(service)}
disabled={!verifiableCredential}
style={
props.selected
? Theme.Styles.selectedBindedVc
: Theme.Styles.closeCardBgContainer
}>
<VcItemContent
context={context}
verifiableCredential={verifiableCredential}
generatedOn={generatedOn}
tag={tag}
selectable={props.selectable}
selected={props.selected}
service={service}
iconName={props.iconName}
iconType={props.iconType}
onPress={() => props.onPress(service)}
/>
{props.isSharingVc ? null : (
<Row crossAlign="center">
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<VcItemActivationStatus
verifiableCredential={verifiableCredential}
emptyWalletBindingId={emptyWalletBindingId}
onPress={() => props.onPress(service)}
showOnlyBindedVc={props.showOnlyBindedVc}
/>
)}
<Pressable onPress={KEBAB_POPUP}>
<KebabPopUp
vcKey={props.vcKey}
iconName="dots-three-horizontal"
iconType="entypo"
isVisible={isKebabPopUp}
onDismiss={DISMISS}
service={service}
/>
</Pressable>
</Row>
)}
</Pressable>
<ErrorMessageOverlay
isVisible={isSavingFailedInIdle}
error={storeErrorTranslationPath}
onDismiss={DISMISS}
translationPath={'VcDetails'}
/>
</React.Fragment>
);
};
interface VcItemProps {
vcKey: string;
margin?: string;
selectable?: boolean;
selected?: boolean;
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
activeTab?: string;
iconName?: string;
iconType?: string;
isSharingVc?: boolean;
}

View File

@@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import {View} from 'react-native';
import { Icon } from 'react-native-elements'; import {Icon} from 'react-native-elements';
import { Theme } from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
const VerifiedIcon: React.FC = () => { const VerifiedIcon: React.FC = () => {
return ( return (
<View style={Theme.Styles.verifiedIconContainer}> <View style={Theme.Styles.verifiedIconContainer}>
<View style={Theme.Styles.verifiedIconInner}> <View style={Theme.Styles.verifiedIconInner}>
<Icon name="check-circle" color={Theme.Colors.VerifiedIcon} size={14} /> <Icon name="check-circle" color={Theme.Colors.VerifiedIcon} size={12} />
</View> </View>
</View> </View>
); );

Some files were not shown because too many files have changed in this diff Show More