Merge pull request #493 from MonobikashDas/qa-develop

merged latest develop code
This commit is contained in:
Monobikash Das
2023-01-25 20:06:08 +05:30
committed by GitHub
44 changed files with 1076 additions and 609 deletions

View File

@@ -102,7 +102,7 @@ apply from: new File(["node", "--print", "require.resolve('react-native/package.
* 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 = false
def enableSeparateBuildPerCPUArchitecture = true
/**
* Run Proguard to shrink the Java bytecode in release builds.
@@ -172,7 +172,7 @@ android {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
universalApk true // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
@@ -240,9 +240,10 @@ android {
}
android.applicationVariants.all { variant ->
variant.outputs.all {
variant.outputs.all { output ->
def datetime = new Date().format('yyyyMMdd_HHmm')
outputFileName = "${defaultConfig.applicationId}-${variant.versionName}_${datetime}.apk"
def architecture = output.getFilter(com.android.build.OutputFile.ABI) ?: "universal"
outputFileName = "${defaultConfig.applicationId}-${variant.versionName}_${datetime}_${architecture}.apk"
}
}
}

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Dimensions } from 'react-native';
import { Dimensions, I18nManager } from 'react-native';
import { Icon, ListItem, Overlay, Input } from 'react-native-elements';
import { Text, Column, Row, Button } from './ui';
import { Theme } from './ui/styleUtils';
@@ -31,7 +31,14 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
onBackdropPress={dismiss}>
<Column width={Dimensions.get('screen').width * 0.8}>
<Text>{t('editLabel', { label: props.label })}</Text>
<Input autoFocus value={newValue} onChangeText={setNewValue} />
<Input
autoFocus
value={newValue}
onChangeText={setNewValue}
inputStyle={{
textAlign: I18nManager.isRTL ? 'right' : 'left',
}}
/>
<Row>
<Button fill type="clear" title={t('cancel')} onPress={dismiss} />
<Button fill title={t('save')} onPress={edit} />

View File

@@ -75,7 +75,7 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
}
return (
<Column>
<Text color={Theme.Colors.DetailsLabel} size="smaller" align="left">
<Text color={Theme.Colors.DetailsLabel} size="smaller">
{arg1}
</Text>
<Text

View File

@@ -43,8 +43,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}
align="left">
color={Theme.Colors.DetailsLabel}>
{t('idType')}
</Text>
<Text weight="bold" size="smaller" color={Theme.Colors.Details}>
@@ -69,14 +68,12 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
<Text
weight="bold"
size="smaller"
align="left"
color={Theme.Colors.DetailsLabel}>
{t('fullName')}
</Text>
<Text
weight="semibold"
size="smaller"
align="left"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.fullName
@@ -144,7 +141,6 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
<Text
weight="semibold"
size="smaller"
align="left"
color={Theme.Colors.Details}>
{t('valid')}
</Text>
@@ -222,8 +218,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}
align="left">
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.email
)}
@@ -243,8 +238,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
style={{ flex: 1 }}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}
align="left">
color={Theme.Colors.Details}>
{getFullAddress(
props.vc?.verifiableCredential.credentialSubject
)}

View File

@@ -38,7 +38,6 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
<Text
weight="bold"
size="smaller"
align="left"
color={
!verifiableCredential
? Theme.Colors.LoadingDetailsLabel
@@ -73,8 +72,7 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
: Theme.Colors.DetailsLabel
}
weight="bold"
size="smaller"
align="left">
size="smaller">
{arg1}
</Text>
<Text
@@ -82,7 +80,6 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
color={Theme.Colors.Details}
weight="bold"
size="smaller"
align="left"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
@@ -180,8 +177,7 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
: Theme.Colors.DetailsLabel
}
weight="bold"
size="smaller"
align="left">
size="smaller">
{t('idType')}
</Text>
<Text

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Modal as RNModal, View } from 'react-native';
import { I18nManager, Modal as RNModal, View } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row, Text } from '.';
import { ElevationLevel, Theme } from './styleUtils';
@@ -24,7 +24,7 @@ export const Modal: React.FC<ModalProps> = (props) => {
}}>
{props.headerRight ? (
<Icon
name="chevron-left"
name={I18nManager.isRTL ? 'chevron-right' : 'chevron-left'}
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>

View File

@@ -9,7 +9,7 @@ export const Text: React.FC<TextProps> = (props: TextProps) => {
Theme.TextStyles.base,
Theme.TextStyles[weight],
props.color ? { color: props.color } : null,
props.align ? { textAlign: props.align } : null,
props.align ? { textAlign: props.align } : { textAlign: 'left' },
props.margin ? Theme.spacing('margin', props.margin) : null,
props.size ? Theme.TextStyles[props.size] : null,
props.style ? props.style : null,

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Modal as RNModal } from 'react-native';
import { I18nManager, Modal as RNModal } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row } from '.';
import { Theme, ElevationLevel } from './styleUtils';
@@ -15,7 +15,7 @@ export const Modal: React.FC<ModalProps> = (props) => {
<Row padding="16 32" elevation={props.headerElevation}>
{props.headerRight ? (
<Icon
name="chevron-left"
name={I18nManager.isRTL ? 'chevron-right' : 'chevron-left'}
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>

View File

@@ -652,6 +652,11 @@ export const DefaultTheme = {
zIndex: 1,
},
}),
claimsContainer: StyleSheet.create({
container: {
backgroundColor: Colors.Transparent,
},
}),
OpenCard: require('../../../assets/ID-open.png'),
CloseCard: require('../../../assets/ID-closed.png'),
ProfileIcon: require('../../../assets/placeholder-photo.png'),

View File

@@ -596,6 +596,11 @@ export const PurpleTheme = {
borderTopRightRadius: 0,
},
}),
claimsContainer: StyleSheet.create({
container: {
backgroundColor: Colors.Transparent,
},
}),
OpenCard: require('../../../purpleAassets/bg_cart_one.png'),
CloseCard: require('../../../purpleAassets/cart_unsel.png'),
ProfileIcon: require('../../../purpleAassets/profile_icon_unsel.png'),

View File

@@ -0,0 +1,356 @@
import React, { Fragment } from 'react';
import {
TouchableOpacity,
Modal,
View,
StatusBar,
I18nManager,
ViewStyle,
FlexStyle,
StyleProp,
StyleSheet,
ColorValue,
Platform,
Keyboard,
} from 'react-native';
import { withTheme } from 'react-native-elements/dist/config';
import { ThemeProps } from 'react-native-elements/dist/config';
import {
ScreenWidth,
ScreenHeight,
isIOS,
} from 'react-native-elements/dist/helpers';
import Triangle from './Triangle';
import getTooltipCoordinate, {
getElementVisibleWidth,
} from './getTooltipCoordinate';
export type TooltipProps = {
withPointer?: boolean;
popover?: React.ReactElement<{}>;
toggleOnPress?: boolean;
toggleAction?: string | 'onPress' | 'onLongPress';
height?: FlexStyle['height'];
width?: FlexStyle['width'];
containerStyle?: StyleProp<ViewStyle>;
pointerColor?: ColorValue;
onClose?(): void;
onOpen?(): void;
overlayColor?: ColorValue;
withOverlay?: boolean;
backgroundColor?: ColorValue;
highlightColor?: ColorValue;
skipAndroidStatusBar?: boolean;
ModalComponent?: typeof React.Component;
closeOnlyOnBackdropPress?: boolean;
} & typeof defaultProps;
const defaultProps = {
withOverlay: true,
overlayColor: 'rgba(250, 250, 250, 0.70)',
highlightColor: 'transparent',
withPointer: true,
toggleOnPress: true,
toggleAction: 'onPress',
height: 40,
width: 150,
containerStyle: {},
backgroundColor: '#617080',
onClose: () => {},
onOpen: () => {},
skipAndroidStatusBar: false,
ModalComponent: Modal,
closeOnlyOnBackdropPress: false,
};
type TooltipState = {
isVisible: boolean;
yOffset: number;
xOffset: number;
elementWidth: number;
elementHeight: number;
};
class Tooltip extends React.Component<
TooltipProps & Partial<ThemeProps<TooltipProps>>,
TooltipState
> {
static defaultProps = defaultProps;
_isMounted: boolean = false;
state = {
isVisible: false,
yOffset: 0,
xOffset: 0,
elementWidth: 0,
elementHeight: 0,
};
renderedElement?: View | null;
toggleTooltip = () => {
const { onClose } = this.props;
Keyboard.dismiss();
setTimeout(() => {
this.getElementPosition();
this._isMounted &&
this.setState((prevState) => {
if (prevState.isVisible) {
onClose && onClose();
}
return { isVisible: !prevState.isVisible };
});
}, 100);
};
wrapWithPress = (
toggleOnPress: TooltipProps['toggleOnPress'],
toggleAction: TooltipProps['toggleAction'],
children: React.ReactNode
) => {
if (toggleOnPress) {
return (
<TouchableOpacity
{...{ [toggleAction]: this.toggleTooltip }}
delayLongPress={250}
activeOpacity={1}>
{children}
</TouchableOpacity>
);
}
return children;
};
containerStyle = (withOverlay: boolean, overlayColor: string): ViewStyle => {
return {
backgroundColor: withOverlay ? overlayColor : 'transparent',
flex: 1,
};
};
getTooltipStyle = () => {
const { yOffset, xOffset, elementHeight, elementWidth } = this.state;
const { height, backgroundColor, width, withPointer, containerStyle } =
this.props;
const { x, y } = getTooltipCoordinate(
xOffset,
yOffset,
elementWidth,
elementHeight,
ScreenWidth,
ScreenHeight,
width,
height,
withPointer
);
return StyleSheet.flatten([
{
position: 'absolute',
[I18nManager.isRTL ? 'right' : 'left']: x,
top: y,
width,
height,
backgroundColor,
// default styles
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
borderRadius: 10,
padding: 10,
},
containerStyle,
]);
};
renderPointer = (tooltipY: FlexStyle['top']) => {
const { yOffset, xOffset, elementHeight, elementWidth } = this.state;
const { backgroundColor, pointerColor } = this.props;
const pastMiddleLine = yOffset > (tooltipY || 0);
return (
<View
style={{
position: 'absolute',
top: pastMiddleLine ? yOffset - 13 : yOffset + elementHeight - 2,
[I18nManager.isRTL ? 'right' : 'left']:
xOffset +
getElementVisibleWidth(elementWidth, xOffset, ScreenWidth) / 2 -
7.5,
}}>
<Triangle
style={{ borderBottomColor: pointerColor || backgroundColor }}
isDown={pastMiddleLine}
/>
</View>
);
};
getTooltipHighlightedButtonStyle = (): ViewStyle => {
const { highlightColor } = this.props;
const { yOffset, xOffset, elementWidth, elementHeight } = this.state;
return {
position: 'absolute',
top: yOffset,
[I18nManager.isRTL ? 'right' : 'left']: xOffset,
backgroundColor: highlightColor,
overflow: 'visible',
width: elementWidth,
height: elementHeight,
};
};
renderTouchableHighlightedButton = () => {
const TooltipHighlightedButtonStyle =
this.getTooltipHighlightedButtonStyle();
return (
<TouchableOpacity
testID="tooltipTouchableHighlightedButton"
onPress={() => this.toggleTooltip()}
style={TooltipHighlightedButtonStyle}>
{this.props.children}
</TouchableOpacity>
);
};
renderStaticHighlightedButton = () => {
const TooltipHighlightedButtonStyle =
this.getTooltipHighlightedButtonStyle();
return (
<View style={TooltipHighlightedButtonStyle}>{this.props.children}</View>
);
};
renderHighlightedButton = () => {
const { closeOnlyOnBackdropPress } = this.props;
if (closeOnlyOnBackdropPress) {
return this.renderTouchableHighlightedButton();
} else {
return this.renderStaticHighlightedButton();
}
};
renderContent = (withTooltip: boolean) => {
const { popover, withPointer, toggleOnPress, toggleAction } = this.props;
if (!withTooltip) {
return this.wrapWithPress(
toggleOnPress,
toggleAction,
this.props.children
);
}
const tooltipStyle = this.getTooltipStyle() as ViewStyle;
return (
<View>
{this.renderHighlightedButton()}
{withPointer && this.renderPointer(tooltipStyle.top)}
<View style={tooltipStyle} testID="tooltipPopoverContainer">
{popover}
</View>
</View>
);
};
componentDidMount() {
this._isMounted = true;
// wait to compute onLayout values.
requestAnimationFrame(this.getElementPosition);
}
componentWillUnmount() {
this._isMounted = false;
}
getElementPosition = () => {
const { skipAndroidStatusBar } = this.props;
this.renderedElement &&
this.renderedElement.measure(
(
_frameOffsetX,
_frameOffsetY,
width,
height,
pageOffsetX,
pageOffsetY
) => {
this._isMounted &&
this.setState({
xOffset: pageOffsetX,
yOffset:
isIOS || skipAndroidStatusBar
? pageOffsetY
: pageOffsetY -
Platform.select({
android: StatusBar.currentHeight,
ios: 20,
default: 0,
}),
elementWidth: width,
elementHeight: height,
});
}
);
};
renderStaticModalContent = () => {
const { withOverlay, overlayColor } = this.props;
return (
<Fragment>
<TouchableOpacity
style={this.containerStyle(withOverlay, overlayColor)}
onPress={this.toggleTooltip}
activeOpacity={1}
/>
<View>{this.renderContent(true)}</View>
</Fragment>
);
};
renderTogglingModalContent = () => {
const { withOverlay, overlayColor } = this.props;
return (
<TouchableOpacity
style={this.containerStyle(withOverlay, overlayColor)}
onPress={this.toggleTooltip}
activeOpacity={1}>
{this.renderContent(true)}
</TouchableOpacity>
);
};
renderModalContent = () => {
const { closeOnlyOnBackdropPress } = this.props;
if (closeOnlyOnBackdropPress) {
return this.renderStaticModalContent();
} else {
return this.renderTogglingModalContent();
}
};
render() {
const { isVisible } = this.state;
const { onOpen, ModalComponent } = this.props;
return (
<View
collapsable={false}
ref={(e) => {
this.renderedElement = e;
}}>
{this.renderContent(false)}
<ModalComponent
animationType="fade"
visible={isVisible}
transparent
onShow={onOpen}>
{this.renderModalContent()}
</ModalComponent>
</View>
);
}
}
export { Tooltip };
export default withTheme(Tooltip, 'Tooltip');

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
type TriangleProps = {
style?: StyleProp<ViewStyle>;
isDown?: boolean;
};
const Triangle: React.FunctionComponent<TriangleProps> = ({
style,
isDown,
}) => (
<View
style={StyleSheet.flatten([
styles.triangle,
style,
isDown ? styles.down : {},
])}
/>
);
const styles = StyleSheet.create({
down: {
transform: [{ rotate: '180deg' }],
},
triangle: {
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 8,
borderRightWidth: 8,
borderBottomWidth: 15,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: 'white',
},
});
export default Triangle;

View File

@@ -0,0 +1,142 @@
const getArea = (a: number, b: number) => a * b;
const getPointDistance = (a: number[], b: number[]) =>
Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
export const getElementVisibleWidth = (
elementWidth: number,
xOffset: number,
ScreenWidth: number
) => {
// Element is fully visible OR scrolled right
if (xOffset >= 0) {
return xOffset + elementWidth <= ScreenWidth // is element fully visible?
? elementWidth // element is fully visible;
: ScreenWidth - xOffset; // calculate visible width of scrolled element
}
// Element is scrolled LEFT
return elementWidth - xOffset; // calculate visible width of scrolled element
};
/*
type Coord = {
x: number,
y: number,
};
~Tooltip coordinate system:~
The tooltip coordinates are based on the element which it is wrapping.
We take the x and y coordinates of the element and find the best position
to place the tooltip. To find the best position we look for the side with the
most space. In order to find the side with the most space we divide the the
surroundings in four quadrants and check for the one with biggest area.
Once we know the quandrant with the biggest area it place the tooltip in that
direction.
To find the areas we first get 5 coordinate points. The center and the other 4 extreme points
which together make a perfect cross shape.
Once we know the coordinates we can get the length of the vertices which form each quadrant.
Since they are squares we only need two.
*/
const getTooltipCoordinate = (
x: number,
y: number,
width: number,
height: number,
ScreenWidth: number,
ScreenHeight: number,
tooltipWidth: number,
tooltipHeight: number,
withPointer: boolean
) => {
// The following are point coordinates: [x, y]
const center = [
x + getElementVisibleWidth(width, x, ScreenWidth) / 2,
y + height / 2,
];
const pOne = [center[0], 0];
const pTwo = [ScreenWidth, center[1]];
const pThree = [center[0], ScreenHeight];
const pFour = [0, center[1]];
// vertices
const vOne = getPointDistance(center, pOne);
const vTwo = getPointDistance(center, pTwo);
const vThree = getPointDistance(center, pThree);
const vFour = getPointDistance(center, pFour);
// Quadrant areas.
// type Areas = {
// area: number,
// id: number,
// };
const areas = [
getArea(vOne, vFour),
getArea(vOne, vTwo),
getArea(vTwo, vThree),
getArea(vThree, vFour),
].map((each, index) => ({ area: each, id: index }));
const sortedArea = areas.sort((a, b) => b.area - a.area);
// deslocated points
const dX = 0.001;
const dY = height / 2;
// Deslocate the coordinates in the direction of the quadrant.
const directionCorrection = [
[-1, -1],
[1, -1],
[1, 1],
[-1, 1],
];
const deslocateReferencePoint = [
[-tooltipWidth, -tooltipHeight],
[0, -tooltipHeight],
[0, 0],
[-tooltipWidth, 0],
];
// current quadrant index
const qIndex = sortedArea[0].id;
const getWithPointerOffsetY = () =>
withPointer ? 10 * directionCorrection[qIndex][1] : 0;
const getWithPointerOffsetX = () =>
withPointer ? center[0] - 18 * directionCorrection[qIndex][0] : center[0];
const newX =
getWithPointerOffsetX() +
(dX * directionCorrection[qIndex][0] + deslocateReferencePoint[qIndex][0]);
return {
x: constraintX(newX, qIndex, center[0], ScreenWidth, tooltipWidth),
y:
center[1] +
(dY * directionCorrection[qIndex][1] +
deslocateReferencePoint[qIndex][1]) +
getWithPointerOffsetY(),
};
};
const constraintX = (
newX: number,
qIndex: number,
x: number,
ScreenWidth: number,
tooltipWidth: number
) => {
switch (qIndex) {
// 0 and 3 are the left side quadrants.
case 0:
case 3: {
const maxWidth = newX > ScreenWidth ? ScreenWidth - 10 : newX;
return newX < 1 ? 10 : maxWidth;
}
// 1 and 2 are the right side quadrants
case 1:
case 2: {
const leftOverSpace = ScreenWidth - newX;
return leftOverSpace >= tooltipWidth
? newX
: newX - (tooltipWidth - leftOverSpace + 10);
}
default: {
return 0;
}
}
};
export default getTooltipCoordinate;

View File

@@ -282,7 +282,7 @@
},
"rejected": {
"title": "تنويه",
"message": "تم رفض {{vcLabel}} من قِبل {{Receiver}}"
"message": "تم رفض {{vcLabel}} من قِبل {{receiver}}"
}
},
"consentToPhotoVerification": "أوافق على التقاط صورتي للمصادقة"

View File

@@ -19,15 +19,15 @@
"deviceRefNumber": "Device reference number",
"name": "Name"
},
"PasscodeVerify": {
"passcodeMismatchError": "Passcode did not match."
},
"FaceScanner": {},
"OIDcAuth": {
"title": "OIDC Authentication",
"text": "To be replaced with the OIDC provider UI",
"verify": "Verify"
},
"PasscodeVerify": {
"passcodeMismatchError": "Passcode did not match."
},
"QrScanner": {
"missingPermissionText": "This app uses the camera to scan the QR code of another device.",
"allowCameraButton": "Allow access to camera"
@@ -48,18 +48,11 @@
"id": "Id",
"nationalCard": "National Card",
"uin": "UIN",
"enableVerification": "Activate",
"profileAuthenticated": "Activated for online login",
"offlineAuthDisabledHeader": "Activation pending for online login",
"offlineAuthDisabledMessage": "Please click the button below to activate this credential to be used for online login.",
"vid": "VID",
"verificationEnabledSuccess": "Activated for online login",
"goback": "GO BACK",
"BindingWarning": "You have already activated online login for this credential on another device. You will no longer be able to use that device for login if you activate it again on this device.",
"yes_confirm": "Yes, I Confirm",
"no": "No",
"Alert": "Alert",
"ok": "Okay"
"enableVerification": "Enable Verification",
"profileAuthenticated": "Profile is authenticated!",
"offlineAuthDisabledHeader": "Offline Authentication disabled!",
"offlineAuthDisabledMessage": "Click 'Enable Authentication' to enable this credentials to be used for offline authentication."
},
"AuthScreen": {
"header": "Would you like to use biometrics to unlock the application?",
@@ -123,8 +116,7 @@
"requestingOTP": "Requesting OTP..."
},
"OtpVerificationModal": {
"enterOtp": "Enter the 6-digit verification code we sent you",
"header": "OTP Verification"
"enterOtp": "Enter the 6-digit verification code we sent you"
},
"MyVcsTab": {
"addVcButton": "Add {{vcLabel}}",
@@ -155,7 +147,6 @@
"requestingOtp": "Requesting OTP...",
"editTag": "Rename",
"redirecting": "Redirecting...",
"inProgress": "Loading...",
"success": {
"unlocked": "{{vcLabel}} successfully unlocked",
"locked": "{{vcLabel}} successfully locked",
@@ -193,26 +184,6 @@
"revokeSuccessful": "VID successfully revoked",
"version": "Version"
},
"QrScreen": {
"title": "QR Login",
"alignQr": "Align the QR code within the frame to scan",
"confirmation": "Confirmation",
"checkDomain": "Also, check that there is a lock icon on the address bar.",
"domainHead": "https://",
"selectId": "Select ID",
"noBindedVc": "No Binded {{vcLabel}} Available to Verify",
"back": "Go Back",
"confirm": "Confirm",
"verify": "Verify",
"faceAuth": "Face Authentication",
"consent": "Consent",
"loading": "Loading...",
"domainWarning": "Please confirm the domain of the website you are scanning the QR code from is as below",
"access": " is requesting access to",
"status": "Status",
"successMessage": "You Have Successfully Logged Into ",
"okay": "Okay"
},
"ReceiveVcScreen": {
"header": "{{vcLabel}} details",
"save": "Save {{vcLabel}}",
@@ -276,21 +247,7 @@
"invalid": "Invalid QR Code",
"offline": "Please connect to the internet to scan QR codes using Online sharing mode",
"sent": "{{ vcLabel }} has been sent...",
"sentHint": "Waiting for receiver to save or discard your {{ vcLabel }}"
}
},
"SelectVcOverlay": {
"header": "Share {{vcLabel}}",
"chooseVc": "Choose the {{vcLabel}} you'd like to share with",
"share": "Share",
"verifyAndShare": "Verify Identity & Share"
},
"SendVcScreen": {
"reasonForSharing": "Reason for sharing (optional)",
"acceptRequest": "Accept request and choose {{vcLabel}}",
"acceptRequestAndVerify": "Accept request and verify",
"reject": "Reject",
"status": {
"sentHint": "Waiting for receiver to save or discard your {{ vcLabel }}",
"sharing": {
"title": "Sharing...",
"hint": "Please wait for the receiving device to accept or reject the share.",
@@ -304,7 +261,19 @@
"title": "Notice",
"message": "Your {{vcLabel}} was discarded by {{receiver}}"
}
}
},
"SelectVcOverlay": {
"header": "Share {{vcLabel}}",
"chooseVc": "Choose the {{vcLabel}} you'd like to share with",
"share": "Share",
"verifyAndShare": "Verify Identity & Share"
},
"SendVcScreen": {
"reasonForSharing": "Reason for sharing (optional)",
"acceptRequest": "Accept request and choose {{vcLabel}}",
"acceptRequestAndVerify": "Accept request and verify",
"reject": "Reject",
"consentToPhotoVerification": "I give consent to have my photo taken for authentication"
},
"VerifyIdentityOverlay": {

View File

@@ -15,7 +15,6 @@ import { linkTransactionResponse, VC } from '../types/vc';
import { request } from '../shared/request';
import { getJwt } from '../shared/cryptoutil/cryptoUtil';
import { getPrivateKey } from '../shared/keystore/SecureKeystore';
import getAllConfigurations from '../shared/commonprops/commonProps';
const model = createModel(
{
@@ -37,6 +36,7 @@ const model = createModel(
domainName: '',
consentClaims: ['name', 'picture'],
isSharing: {},
linkedTransactionId: '',
},
{
events: {
@@ -99,7 +99,7 @@ export const qrLoginMachine =
'expandLinkTransResp',
'setClaims',
],
target: 'WarningDomainName',
target: 'loadMyVcs',
},
],
onError: [
@@ -110,26 +110,6 @@ export const qrLoginMachine =
],
},
},
WarningDomainName: {
invoke: {
src: 'domainNameConfig',
onDone: {
actions: 'setDomainName',
target: 'showWarning',
},
},
},
showWarning: {
on: {
CONFIRM: {
target: 'loadMyVcs',
},
DISMISS: {
actions: 'forwardToParent',
target: 'waitingForData',
},
},
},
ShowError: {
on: {
DISMISS: {
@@ -164,7 +144,7 @@ export const qrLoginMachine =
faceAuth: {
on: {
FACE_VALID: {
target: 'requestConsent',
target: 'sendingAuthenticate',
},
FACE_INVALID: {
target: 'invalidIdentity',
@@ -184,6 +164,21 @@ export const qrLoginMachine =
},
},
},
sendingAuthenticate: {
invoke: {
src: 'sendAuthenticate',
onDone: {
target: 'requestConsent',
actions: 'setLinkedTransactionId',
},
onError: [
{
actions: 'SetErrorMessage',
target: 'ShowError',
},
],
},
},
requestConsent: {
on: {
CONFIRM: {
@@ -234,10 +229,6 @@ export const qrLoginMachine =
linkCode: (context, event) => event.value,
}),
setDomainName: assign({
domainName: (context, event) => event.data,
}),
loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), {
to: (context) => context.serviceRefs.store,
}),
@@ -312,6 +303,9 @@ export const qrLoginMachine =
return { ...context.isSharing };
},
}),
setLinkedTransactionId: assign({
linkedTransactionId: (context, event) => event.data as string,
}),
},
services: {
linkTransaction: async (context) => {
@@ -328,12 +322,7 @@ export const qrLoginMachine =
return response.response;
},
domainNameConfig: async (context) => {
var response = await getAllConfigurations();
return response.warningDomainName;
},
sendConsent: async (context) => {
sendAuthenticate: async (context) => {
var privateKey = await getPrivateKey(
context.selectedVc.walletBindingResponse?.walletBindingId
);
@@ -345,7 +334,7 @@ export const qrLoginMachine =
walletBindingResponse?.thumbprint
);
const resp = await request(
const response = await request(
'POST',
'/v1/idp/linked-authorization/authenticate',
{
@@ -363,16 +352,17 @@ export const qrLoginMachine =
},
}
);
return response.response.linkedTransactionId;
},
const trnId = resp.response.linkedTransactionId;
sendConsent: async (context) => {
const response = await request(
'POST',
'/v1/idp/linked-authorization/consent',
{
requestTime: String(new Date().toISOString()),
request: {
linkedTransactionId: trnId,
linkedTransactionId: context.linkedTransactionId,
acceptedClaims: context.essentialClaims.concat(
context.selectedVoluntaryClaims
),
@@ -406,10 +396,6 @@ export function selectDomainName(state: State) {
return state.context.domainName;
}
export function selectIsShowWarning(state: State) {
return state.matches('showWarning');
}
export function selectIsLinkTransaction(state: State) {
return state.matches('linkTransaction');
}
@@ -437,6 +423,9 @@ export function selectIsShowError(state: State) {
export function selectIsRequestConsent(state: State) {
return state.matches('requestConsent');
}
export function selectIsSendingAuthenticate(state: State) {
return state.matches('sendingAuthenticate');
}
export function selectIsSendingConsent(state: State) {
return state.matches('sendingConsent');
@@ -453,6 +442,9 @@ export function selectSelectedVc(state: State) {
export function selectLinkTransactionResponse(state: State) {
return state.context.linkTransactionResponse;
}
export function selectEssentialClaims(state: State) {
return state.context.essentialClaims;
}
export function selectVoluntaryClaims(state: State) {
return state.context.voluntaryClaims;

View File

@@ -3,20 +3,24 @@
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'done.invoke.QrLogin.WarningDomainName:invocation[0]': {
type: 'done.invoke.QrLogin.WarningDomainName:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.QrLogin.linkTransaction:invocation[0]': {
type: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.QrLogin.sendingAuthenticate:invocation[0]': {
type: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.QrLogin.linkTransaction:invocation[0]': {
type: 'error.platform.QrLogin.linkTransaction:invocation[0]';
data: unknown;
};
'error.platform.QrLogin.sendingAuthenticate:invocation[0]': {
type: 'error.platform.QrLogin.sendingAuthenticate:invocation[0]';
data: unknown;
};
'error.platform.QrLogin.sendingConsent:invocation[0]': {
type: 'error.platform.QrLogin.sendingConsent:invocation[0]';
data: unknown;
@@ -24,8 +28,8 @@ export interface Typegen0 {
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {
domainNameConfig: 'done.invoke.QrLogin.WarningDomainName:invocation[0]';
linkTransaction: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
sendAuthenticate: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]';
sendConsent: 'done.invoke.QrLogin.sendingConsent:invocation[0]';
};
'missingImplementations': {
@@ -37,13 +41,16 @@ export interface Typegen0 {
'eventsCausingActions': {
SetErrorMessage:
| 'error.platform.QrLogin.linkTransaction:invocation[0]'
| 'error.platform.QrLogin.sendingAuthenticate:invocation[0]'
| 'error.platform.QrLogin.sendingConsent:invocation[0]';
expandLinkTransResp: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
forwardToParent: 'DISMISS';
loadMyVcs: 'CONFIRM';
loadMyVcs: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
resetLinkTransactionId: 'GET';
resetSelectedVoluntaryClaims: 'GET';
setClaims: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
setConsentClaims: 'TOGGLE_CONSENT_CLAIM';
setDomainName: 'done.invoke.QrLogin.WarningDomainName:invocation[0]';
setLinkedTransactionId: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]';
setMyVcs: 'STORE_RESPONSE';
setScanData: 'GET';
setSelectedVc: 'SELECT_VC';
@@ -52,21 +59,20 @@ export interface Typegen0 {
'eventsCausingDelays': {};
'eventsCausingGuards': {};
'eventsCausingServices': {
domainNameConfig: 'done.invoke.QrLogin.linkTransaction:invocation[0]';
linkTransaction: 'GET';
sendAuthenticate: 'FACE_VALID';
sendConsent: 'CONFIRM';
};
'matchesStates':
| 'ShowError'
| 'WarningDomainName'
| 'done'
| 'faceAuth'
| 'invalidIdentity'
| 'linkTransaction'
| 'loadMyVcs'
| 'requestConsent'
| 'sendingAuthenticate'
| 'sendingConsent'
| 'showWarning'
| 'showvcList'
| 'success'
| 'waitingForData';

File diff suppressed because one or more lines are too long

View File

@@ -35,8 +35,7 @@ export interface Typegen0 {
sendDisconnect: 'done.invoke.request.cancelling:invocation[0]';
sendVcResponse:
| 'done.invoke.request.reviewing.accepted:invocation[0]'
| 'done.invoke.request.reviewing.rejected:invocation[0]'
| 'done.invoke.request.reviewing:invocation[0]';
| 'done.invoke.request.reviewing.rejected:invocation[0]';
verifyVp: 'done.invoke.request.reviewing.verifyingVp:invocation[0]';
};
'missingImplementations': {
@@ -88,6 +87,7 @@ export interface Typegen0 {
| 'done.invoke.request.reviewing.verifyingVp:invocation[0]';
requestReceiverInfo: 'CONNECTED';
setIncomingVc: 'VC_RECEIVED';
setPairId: 'CONNECTED';
setReceiveLogTypeDiscarded: 'CANCEL' | 'REJECT';
setReceiveLogTypeRegular: 'ACCEPT';
setReceiveLogTypeUnverified: 'FACE_INVALID';
@@ -123,7 +123,7 @@ export interface Typegen0 {
receiveVc: 'EXCHANGE_DONE';
requestBluetooth: 'BLUETOOTH_DISABLED';
sendDisconnect: 'CANCEL';
sendVcResponse: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE' | 'VC_RECEIVED';
sendVcResponse: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE';
verifyVp: never;
};
'matchesStates':

View File

@@ -348,14 +348,6 @@ export const scanMachine =
sent: {
description:
'VC data has been shared and the receiver should now be viewing it',
on: {
VC_ACCEPTED: {
target: '#scan.reviewing.accepted',
},
VC_REJECTED: {
target: '#scan.reviewing.rejected',
},
},
},
},
on: {
@@ -363,8 +355,13 @@ export const scanMachine =
target: '#scan.findingConnection',
},
VC_SENT: {
target: '#scan.reviewing.sendingVc.sent',
internal: true,
target: '.sent',
},
VC_ACCEPTED: {
target: '#scan.reviewing.accepted',
},
VC_REJECTED: {
target: '#scan.reviewing.rejected',
},
},
},
@@ -762,8 +759,11 @@ export const scanMachine =
monitorCancellation: (context) => async (callback) => {
if (context.sharingProtocol === 'ONLINE') {
await onlineSubscribe('disconnect', null, () =>
callback({ type: 'DISCONNECT' })
await onlineSubscribe(
'disconnect',
null,
() => callback({ type: 'DISCONNECT' }),
{ pairId: context.scannedQrParams.cid }
);
}
},
@@ -782,6 +782,7 @@ export const scanMachine =
discoverDevice: (context) => (callback) => {
if (context.sharingProtocol === 'OFFLINE') {
GoogleNearbyMessages.disconnect();
IdpassSmartshare.createConnection('discoverer', () => {
callback({ type: 'CONNECTED' });
});
@@ -791,26 +792,39 @@ export const scanMachine =
console.log('\n\n[scan] GNM_ERROR\n\n', kind, message)
);
await GoogleNearbyMessages.connect({
try {
IdpassSmartshare.destroyConnection();
} catch (e) {
/*pass*/
}
await GoogleNearbyMessages.connect(
Platform.select({
ios: {
apiKey: GNM_API_KEY,
discoveryMediums: ['ble'],
discoveryModes: ['scan', 'broadcast'],
});
},
default: {},
})
);
console.log('[scan] GNM connected!');
await onlineSubscribe('pairing:response', async (response) => {
await onlineSubscribe(
'pairing:response',
async (response) => {
await GoogleNearbyMessages.unpublish();
if (response === 'ok') {
if (response === context.scannedQrParams.cid) {
callback({ type: 'CONNECTED' });
}
});
},
null,
{ pairId: context.scannedQrParams.cid }
);
const pairingEvent: PairingEvent = {
type: 'pairing',
data: context.scannedQrParams,
};
await onlineSend(pairingEvent);
await onlineSend(pairingEvent, context.scannedQrParams.cid);
})();
}
},
@@ -839,10 +853,12 @@ export const scanMachine =
async (receiverInfo) => {
await GoogleNearbyMessages.unpublish();
callback({ type: 'EXCHANGE_DONE', receiverInfo });
}
},
null,
{ pairId: context.scannedQrParams.cid }
);
await onlineSend(event);
await onlineSend(event, context.scannedQrParams.cid);
})();
}
},
@@ -880,16 +896,24 @@ export const scanMachine =
});
return () => subscription?.remove();
} else {
sendVc(vc, statusCallback, () => callback({ type: 'DISCONNECT' }));
sendVc(
vc,
statusCallback,
() => callback({ type: 'DISCONNECT' }),
context.scannedQrParams.cid
);
}
},
sendDisconnect: (context) => () => {
if (context.sharingProtocol === 'ONLINE') {
onlineSend({
onlineSend(
{
type: 'disconnect',
data: 'rejected',
});
},
context.scannedQrParams.cid
);
}
},
@@ -953,7 +977,7 @@ export const scanMachine =
delays: {
CLEAR_DELAY: 250,
CANCEL_TIMEOUT: 3000,
CANCEL_TIMEOUT: 5000,
CONNECTION_TIMEOUT: (context) => {
return (context.sharingProtocol === 'ONLINE' ? 15 : 5) * 1000;
},
@@ -1091,7 +1115,8 @@ export function selectIsDisconnected(state: State) {
async function sendVc(
vc: VC,
callback: (status: SendVcStatus) => void,
disconnectCallback: () => void
disconnectCallback: () => void,
pairId: string
) {
const rawData = JSON.stringify(vc);
const chunks = chunkString(rawData, GNM_MESSAGE_LIMIT);
@@ -1116,7 +1141,8 @@ async function sendVc(
if (typeof status === 'number' && chunk < event.data.vcChunk.total) {
chunk += 1;
await GoogleNearbyMessages.unpublish();
await onlineSend({
await onlineSend(
{
type: 'send-vc',
data: {
isChunked: true,
@@ -1126,7 +1152,9 @@ async function sendVc(
rawData: chunks[chunk],
},
},
});
},
pairId
);
} else if (typeof status === 'string') {
if (status === 'ACCEPTED' || status === 'REJECTED') {
GoogleNearbyMessages.unsubscribe();
@@ -1135,16 +1163,16 @@ async function sendVc(
}
},
disconnectCallback,
{ keepAlive: true }
{ keepAlive: true, pairId }
);
await onlineSend(event);
await onlineSend(event, pairId);
} else {
const event: SendVcEvent = {
type: 'send-vc',
data: { isChunked: false, vc },
};
await onlineSubscribe(SendVcResponseType, callback);
await onlineSend(event);
await onlineSubscribe(SendVcResponseType, callback, null, { pairId });
await onlineSend(event, pairId);
}
}

View File

@@ -137,7 +137,6 @@ export interface Typegen0 {
SHARING_TIMEOUT:
| 'ACCEPT_REQUEST'
| 'FACE_VALID'
| 'VC_SENT'
| 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
};
'eventsCausingGuards': {
@@ -165,7 +164,6 @@ export interface Typegen0 {
sendVc:
| 'ACCEPT_REQUEST'
| 'FACE_VALID'
| 'VC_SENT'
| 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
};
'matchesStates':

View File

@@ -15,7 +15,6 @@ import { verifyCredential } from '../shared/vcjs/verifyCredential';
import { log } from 'xstate/lib/actions';
import {
generateKeys,
getJwt,
WalletBindingResponse,
} from '../shared/cryptoutil/cryptoUtil';
import { KeyPair } from 'react-native-rsa-native';
@@ -73,7 +72,6 @@ const model = createModel(
REFRESH: () => ({}),
REVOKE_VC: () => ({}),
ADD_WALLET_BINDING_ID: () => ({}),
BINDING_DONE: () => ({}),
CANCEL: () => ({}),
CONFIRM: () => ({}),
},
@@ -480,8 +478,13 @@ export const vcItemMachine =
invoke: {
src: 'updatePrivateKey',
onDone: {
target: 'showBindingStatus',
actions: ['updatePrivateKey', 'updateVc'],
target: 'idle',
actions: [
'storeContext',
'updatePrivateKey',
'updateVc',
'setWalletBindingErrorEmpty',
],
},
onError: {
actions: 'setWalletBindingError',
@@ -489,15 +492,6 @@ export const vcItemMachine =
},
},
},
showBindingStatus: {
entry: 'storeContext',
on: {
BINDING_DONE: {
target: 'idle',
actions: 'setWalletBindingErrorEmpty',
},
},
},
},
},
{
@@ -1022,10 +1016,6 @@ export function selectAcceptingBindingOtp(state: State) {
return state.matches('acceptingBindingOtp');
}
export function selectShowBindingStatus(state: State) {
return state.matches('showBindingStatus');
}
export function selectShowWalletBindingError(state: State) {
return state.matches('showingWalletBindingError');
}

View File

@@ -119,23 +119,23 @@ export interface Typegen0 {
'eventsCausingActions': {
clearOtp:
| ''
| 'BINDING_DONE'
| 'CANCEL'
| 'DISMISS'
| 'REVOKE_VC'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.requestingBindingOtp:invocation[0]'
| 'done.invoke.vc-item.requestingOtp:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'
| 'error.platform.vc-item.requestingLock:invocation[0]'
| 'error.platform.vc-item.requestingRevoke:invocation[0]'
| 'error.platform.vc-item.verifyingCredential:invocation[0]';
clearTransactionId:
| ''
| 'BINDING_DONE'
| 'CANCEL'
| 'DISMISS'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'
| 'error.platform.vc-item.verifyingCredential:invocation[0]';
incrementDownloadCounter: 'POLL';
@@ -170,7 +170,9 @@ export interface Typegen0 {
| 'error.platform.vc-item.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item.updatingPrivateKey:invocation[0]';
setWalletBindingErrorEmpty: 'BINDING_DONE' | 'CANCEL';
setWalletBindingErrorEmpty:
| 'CANCEL'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
setWalletBindingId: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
storeContext:
| 'CREDENTIAL_DOWNLOADED'
@@ -229,7 +231,6 @@ export interface Typegen0 {
| 'requestingOtp'
| 'requestingRevoke'
| 'revokingVc'
| 'showBindingStatus'
| 'showBindingWarning'
| 'showingWalletBindingError'
| 'storingTag'

View File

@@ -1,69 +0,0 @@
import { useSelector } from '@xstate/react';
import { useContext } from 'react';
import { ActorRefFrom } from 'xstate';
import {
selectIsRefreshingMyVcs,
selectMyVcs,
VcEvents,
} from '../../../machines/vc';
import { GlobalContext } from '../../../shared/GlobalContext';
import NetInfo from '@react-native-community/netinfo';
import { selectVcLabel } from '../../../machines/settings';
import {
selectAcceptingBindingOtp,
selectIsRequestBindingOtp,
selectOtpError,
selectShowBindingStatus,
selectWalletBindingError,
selectWalletBindingId,
VcItemEvents,
vcItemMachine,
} from '../../../machines/vcItem';
import { ModalProps } from '../../../components/ui/Modal';
export function useBindVcStatus(props: BindVcProps) {
const { appService } = useContext(GlobalContext);
const settingsService = appService.children.get('settings');
const vcService = appService.children.get('vc');
const netInfoFetch = (otp: string) => {
NetInfo.fetch().then((state) => {
if (state.isConnected) {
vcService.send(VcItemEvents.INPUT_OTP(otp));
} else {
vcService.send(VcItemEvents.DISMISS());
showToast('Request network failed');
}
});
};
return {
vcKeys: useSelector(vcService, selectMyVcs),
vcLabel: useSelector(settingsService, selectVcLabel),
isRefreshingVcs: useSelector(vcService, selectIsRefreshingMyVcs),
isBindingOtp: useSelector(vcService, selectIsRequestBindingOtp),
walletAddress: useSelector(vcService, selectWalletBindingId),
isAcceptingBindingOtp: useSelector(vcService, selectAcceptingBindingOtp),
showBindingStatus: useSelector(vcService, selectShowBindingStatus),
walletBindingError: useSelector(vcService, selectWalletBindingError),
inputOtp: (otp: string) => {
netInfoFetch(otp);
},
otpError: useSelector(vcService, selectOtpError),
BINDING_DONE: () => vcService.send(VcItemEvents.BINDING_DONE()),
INPUT_OTP: (otp: string) => vcService.send(VcItemEvents.INPUT_OTP(otp)),
DISMISS: () => vcService.send(VcItemEvents.DISMISS()),
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
};
}
function showToast(arg0: string) {
throw new Error('Function not implemented.');
}
export interface BindVcProps extends ModalProps {
bindingError: string;
onDone: () => void;
}

View File

@@ -1,55 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Column, Text, Button, Row } from '../../../components/ui';
import { Theme } from '../../../components/ui/styleUtils';
import { useBindVcStatus, BindVcProps } from './BindVcController';
import { Image } from 'react-native';
import { Modal } from '../../../components/ui/Modal';
import { Icon } from 'react-native-elements';
export const BindStatus: React.FC<BindVcProps> = (props) => {
const controller = useBindVcStatus(props);
const { t, i18n } = useTranslation('VcDetails');
var message: string = controller.walletBindingError;
return (
<Modal
isVisible={props.isVisible}
onDismiss={props.onDismiss}
headerElevation={2}
headerTitle={t('status')}
headerRight={<Icon name={''} />}>
<Column fill align="space-around" crossAlign="center" padding={'10'}>
<Column crossAlign="center">
{!controller.walletBindingError ? (
<Image source={Theme.SuccessLogo} resizeMethod="auto" />
) : (
<Row align="center" crossAlign="center" margin={'0 80 0 0'}>
<Image source={Theme.WarningLogo} resizeMethod="auto" />
<Text
margin={'0 0 0 -80'}
color={Theme.Colors.whiteText}
weight="bold">
!
</Text>
</Row>
)}
{controller.walletBindingError ? (
<Text
align="center"
color={Theme.Colors.errorMessage}
margin="16 0 0 0">
{{ message }}
</Text>
) : (
<Text align="center" margin="16 0 0 0">
{t('verificationEnabledSuccess')}
</Text>
)}
</Column>
<Button title={t('ok')} onPress={props.onDone} type="radius" />
</Column>
</Modal>
);
};

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Dimensions } from 'react-native';
import { Icon, Input, Tooltip } from 'react-native-elements';
import { Button, Column, Row, Text } from '../../../components/ui';
import { Dimensions, I18nManager } from 'react-native';
import { Icon, Input } from 'react-native-elements';
import { Button, Centered, Column, Row, Text } from '../../../components/ui';
import { Modal } from '../../../components/ui/Modal';
import { Theme } from '../../../components/ui/styleUtils';
import {
@@ -11,6 +11,7 @@ import {
import { KeyboardAvoidingView, Platform } from 'react-native';
import { useTranslation } from 'react-i18next';
import { MessageOverlay } from '../../../components/MessageOverlay';
import Tooltip from '../../../lib/react-native-elements/tooltip/Tooltip';
export const GetIdInputModal: React.FC<GetIdInputModalProps> = (props) => {
const { t } = useTranslation('GetIdInputModal');
@@ -39,6 +40,10 @@ export const GetIdInputModal: React.FC<GetIdInputModalProps> = (props) => {
color: controller.isInvalid
? Theme.Colors.errorMessage
: Theme.Colors.textValue,
textAlign: 'left',
}}
inputStyle={{
textAlign: I18nManager.isRTL ? 'right' : 'left',
}}
style={Theme.Styles.placeholder}
value={controller.id}
@@ -59,6 +64,7 @@ export const GetIdInputModal: React.FC<GetIdInputModalProps> = (props) => {
skipAndroidStatusBar={true}
onOpen={controller.ACTIVATE_ICON_COLOR}
onClose={controller.DEACTIVATE_ICON_COLOR}>
<Centered width={32} fill>
{controller.isInvalid ? (
<Icon
name="error"
@@ -78,6 +84,7 @@ export const GetIdInputModal: React.FC<GetIdInputModalProps> = (props) => {
}
/>
)}
</Centered>
</Tooltip>
}
errorStyle={{ color: Theme.Colors.errorMessage }}

View File

@@ -75,6 +75,7 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
color: controller.isInvalid
? Theme.Colors.errorMessage
: Theme.Colors.textValue,
textAlign: 'left',
}}
inputStyle={{
textAlign: I18nManager.isRTL ? 'right' : 'left',

View File

@@ -11,7 +11,6 @@ import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController';
import { useTranslation } from 'react-i18next';
import { VcDetails } from '../../components/VcDetails';
import { OtpVerification } from './MyVcs/OtpVerification';
import { BindStatus } from './MyVcs/BindVcStatus';
import { BindingVcWarningOverlay } from './MyVcs/BindingVcWarningOverlay';
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
@@ -103,15 +102,6 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
/>
)}
{controller.showBindingStatus && (
<BindStatus
isVisible={controller.showBindingStatus}
bindingError={controller.walletBindingError}
onDismiss={controller.BINDING_DONE}
onDone={controller.BINDING_DONE}
/>
)}
<BindingVcWarningOverlay
isVisible={controller.isBindingWarning}
onConfirm={controller.CONFIRM}

View File

@@ -16,11 +16,9 @@ import {
selectVc,
VcItemEvents,
vcItemMachine,
selectWalletBindingId,
selectWalletBindingError,
selectIsRequestBindingOtp,
selectAcceptingBindingOtp,
selectShowBindingStatus,
selectEmptyWalletBindingId,
isWalletBindingInProgress,
selectShowWalletBindingError,
@@ -134,7 +132,6 @@ export function useViewVcModal({
storedPasscode: useSelector(authService, selectPasscode),
isBindingOtp: useSelector(vcItemActor, selectIsRequestBindingOtp),
isAcceptingBindingOtp: useSelector(vcItemActor, selectAcceptingBindingOtp),
showBindingStatus: useSelector(vcItemActor, selectShowBindingStatus),
walletBindingError: useSelector(vcItemActor, selectWalletBindingError),
isWalletBindingPending: useSelector(
vcItemActor,
@@ -174,7 +171,6 @@ export function useViewVcModal({
EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()),
SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)),
DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()),
BINDING_DONE: () => vcItemActor.send(VcItemEvents.BINDING_DONE()),
LOCK_VC: () => vcItemActor.send(VcItemEvents.LOCK_VC()),
INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)),
CANCEL: () => vcItemActor.send(VcItemEvents.CANCEL()),

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import Markdown from 'react-native-simple-markdown';
import { useTranslation } from 'react-i18next';
import { Image, SafeAreaView, View } from 'react-native';
import { I18nManager, Image, SafeAreaView, View } from 'react-native';
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
import { Button, Text, Row, Column } from '../../components/ui';
@@ -69,7 +69,14 @@ export const Credits: React.FC<CreditsProps> = (props) => {
<View style={Theme.CreditsStyles.buttonContainer}>
<Button
type="clear"
icon={<Icon name="chevron-left" color={Theme.Colors.Icon} />}
icon={
<Icon
name={
I18nManager.isRTL ? 'chevron-right' : 'chevron-left'
}
color={Theme.Colors.Icon}
/>
}
title=""
onPress={() => setIsViewing(false)}
/>

View File

@@ -1,5 +1,11 @@
import React from 'react';
import { Dimensions, RefreshControl, SafeAreaView, View } from 'react-native';
import {
Dimensions,
I18nManager,
RefreshControl,
SafeAreaView,
View,
} from 'react-native';
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
import { Button, Column, Centered, Row, Text } from '../../components/ui';
import { VidItem } from '../../components/VidItem';
@@ -40,7 +46,14 @@ export const Revoke: React.FC<RevokeScreenProps> = (props) => {
<View style={Theme.RevokeStyles.buttonContainer}>
<Button
type="clear"
icon={<Icon name="chevron-left" color={Theme.Colors.Icon} />}
icon={
<Icon
name={
I18nManager.isRTL ? 'chevron-right' : 'chevron-left'
}
color={Theme.Colors.Icon}
/>
}
title=""
onPress={() => controller.setIsViewing(false)}
/>

View File

@@ -19,7 +19,7 @@ export const MyBindedVcs: React.FC<MyBindedVcsProps> = (props) => {
headerTitle={t('selectId')}
headerElevation={5}
onDismiss={() => {
controller.setQrLogin(false), controller.DISMISS();
controller.DISMISS();
}}>
<React.Fragment>
<Column fill style={{ display: props.isVisible ? 'flex' : 'none' }}>

View File

@@ -15,7 +15,7 @@ export const QrConsent: React.FC<QrConsentProps> = (props) => {
return (
<Modal
isVisible={controller.isRequestConsent}
isVisible={props.isVisible}
arrowLeft={<Icon name={''} />}
headerTitle={t('consent')}
headerElevation={5}
@@ -23,27 +23,14 @@ export const QrConsent: React.FC<QrConsentProps> = (props) => {
<Column
fill
align="space-between"
padding="0 24 0 24"
padding="24 24 0 24"
style={{ display: props.isVisible ? 'flex' : 'none' }}
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<ScrollView>
<Column
align="space-evenly"
crossAlign="center"
margin={'15 0 0 0'}
style={Theme.Styles.consentPageTop}
elevation={3}>
<Column>
{controller.linkTransactionResponse && (
<Row margin={'0 0 0 6'} crossAlign="center" align="center">
<Row margin={'0 0 0 9'} crossAlign="center" align="center">
<Image
source={Theme.MosipLogo}
style={{ width: 60, height: 70 }}
/>
<View style={Theme.Styles.consentDottedLine}></View>
<Image
source={
controller.logoUrl ? { uri: controller.logoUrl } : null
}
source={controller.logoUrl ? { uri: controller.logoUrl } : null}
style={{ width: 65, height: 65 }}
/>
</Row>
@@ -51,30 +38,54 @@ export const QrConsent: React.FC<QrConsentProps> = (props) => {
<Text
style={Theme.TextStyles.small}
weight="bold"
margin={'0 0 10 6'}>
margin={'10 0 0 0'}>
{controller.clientName} {t('access')}
</Text>
</Column>
<ScrollView>
<Column>
{
<Text
style={Theme.TextStyles.small}
weight="bold"
margin={'10 0 0 0'}>
{t('essentialClaims')}
</Text>
}
<Column scroll padding="10 0 0 0">
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Title>
{controller.essentialClaims.map((claim, index) => (
<Row key={index} align={'space-between'} margin={'10 0 0 20'}>
<Text
color={Theme.Colors.profileLabel}
style={Theme.TextStyles.base}>
{t('Name and Picture')}
{t(claim[0].toUpperCase() + claim.slice(1))}
</Text>
</ListItem.Title>
</ListItem.Content>
<Switch value={true} color={Theme.Colors.ProfileIconBg} />
</ListItem>
{controller.claims.map((claim, index) => {
if (claim == 'name' || claim == 'picture') {
return null;
} else {
return (
<ListItem key={index} bottomDivider>
<Text
color={Theme.Colors.GrayIcon}
style={Theme.TextStyles.small}
weight="bold"
margin={'10 0 10 6'}>
{t('required')}
</Text>
</Row>
))}
</Column>
<Column>
{
<Text
style={Theme.TextStyles.small}
weight="bold"
margin={'10 0 0 0'}>
{t('voluntaryClaims')}
</Text>
}
{controller.voluntaryClaims.map((claim, index) => (
<ListItem
key={index}
bottomDivider
containerStyle={Theme.claimsContainer.container}>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>
@@ -86,17 +97,12 @@ export const QrConsent: React.FC<QrConsentProps> = (props) => {
<Switch
value={controller.isShare[claim]}
onValueChange={() =>
controller.SELECT_CONSENT(
controller.isShare[claim],
claim
)
controller.SELECT_CONSENT(controller.isShare[claim], claim)
}
color={Theme.Colors.Icon}
/>
</ListItem>
);
}
})}
))}
</Column>
</ScrollView>
<Column
@@ -106,13 +112,13 @@ export const QrConsent: React.FC<QrConsentProps> = (props) => {
<Button
margin={'6 10 0 10'}
styles={Theme.ButtonStyles.radius}
title={'Confirm'}
title={t('allow')}
onPress={props.onConfirm}
/>
<Button
margin={'10 10 0 10'}
type="clear"
title={'Cancel'}
title={t('cancel')}
onPress={props.onCancel}
/>
</Column>

View File

@@ -6,7 +6,6 @@ import { Modal } from '../../components/ui/Modal';
import { VerifyIdentityOverlay } from '../VerifyIdentityOverlay';
import { MessageOverlay } from '../../components/MessageOverlay';
import { MyBindedVcs } from './MyBindedVcs';
import { QrLoginWarning } from './QrLoginWarning';
import { QrLoginSuccess } from './QrLoginSuccessMessage';
import { QrConsent } from './QrConsent';
import { QrLoginRef } from '../../machines/QrLoginMachine';
@@ -23,10 +22,8 @@ export const QrLogin: React.FC<QrLoginProps> = (props) => {
headerTitle={t('title')}
headerRight={<Icon name={''} />}>
<Column fill>
<QrLoginWarning
isVisible={controller.isShowWarning}
onConfirm={controller.CONFIRM}
onCancel={controller.DISMISS}
<MyBindedVcs
isVisible={controller.isShowingVcList}
service={props.service}
/>
@@ -35,7 +32,8 @@ export const QrLogin: React.FC<QrLoginProps> = (props) => {
controller.isWaitingForData ||
controller.isLoadingMyVcs ||
controller.isLinkTransaction ||
controller.isSendingConsent
controller.isSendingConsent ||
controller.isSendingAuthenticate
}
title={t('loading')}
progress
@@ -47,11 +45,6 @@ export const QrLogin: React.FC<QrLoginProps> = (props) => {
onCancel={controller.DISMISS}
/>
<MyBindedVcs
isVisible={controller.isShowingVcList}
service={props.service}
/>
<VerifyIdentityOverlay
isVisible={controller.isVerifyingIdentity}
vc={controller.selectedVc}

View File

@@ -14,15 +14,15 @@ import {
selectIsSharing,
selectIsShowError,
selectIsShowingVcList,
selectIsShowWarning,
selectIsVerifyingSuccesful,
selectIsWaitingForData,
selectLinkTransactionResponse,
selectMyVcs,
selectLogoUrl,
selectDomainName,
selectSelectedVc,
selectVoluntaryClaims,
selectIsSendingAuthenticate,
selectEssentialClaims,
} from '../../machines/QrLoginMachine';
import { selectVcLabel } from '../../machines/settings';
import { selectBindedVcs } from '../../machines/vc';
@@ -61,7 +61,8 @@ export function useQrLogin({ service }: QrLoginProps) {
),
domainName: useSelector(service, selectDomainName),
logoUrl: useSelector(service, selectLogoUrl),
claims: useSelector(service, selectVoluntaryClaims),
essentialClaims: useSelector(service, selectEssentialClaims),
voluntaryClaims: useSelector(service, selectVoluntaryClaims),
clientName: useSelector(service, selectClientName),
error: useSelector(service, selectErrorMessage),
@@ -71,12 +72,12 @@ export function useQrLogin({ service }: QrLoginProps) {
SELECT_VC,
SELECT_CONSENT,
isWaitingForData: useSelector(service, selectIsWaitingForData),
isShowWarning: useSelector(service, selectIsShowWarning),
isShowingVcList: useSelector(service, selectIsShowingVcList),
isLinkTransaction: useSelector(service, selectIsLinkTransaction),
isLoadingMyVcs: useSelector(service, selectIsloadMyVcs),
isRequestConsent: useSelector(service, selectIsRequestConsent),
isShowingError: useSelector(service, selectIsShowError),
isSendingAuthenticate: useSelector(service, selectIsSendingAuthenticate),
isSendingConsent: useSelector(service, selectIsSendingConsent),
isVerifyingIdentity: useSelector(service, selectIsisVerifyingIdentity),
isInvalidIdentity: useSelector(service, selectIsInvalidIdentity),

View File

@@ -75,6 +75,11 @@ export const ReceiveVcScreen: React.FC = () => {
onPress={controller.DISMISS}
margin={[0, 8, 0, 0]}
/>
<Button
fill
title={t('common:tryAgain')}
onPress={controller.RETRY_VERIFICATION}
/>
</Row>
</MessageOverlay>
</React.Fragment>

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { TFunction, useTranslation } from 'react-i18next';
import { Switch } from 'react-native-elements';
import { Platform } from 'react-native';
import { I18nManager, Platform } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { Centered, Button, Row, Column, Text } from '../../components/ui';

View File

@@ -63,11 +63,12 @@ export const ScanLayout: React.FC = () => {
<MessageOverlay
isVisible={controller.statusOverlay != null}
title={controller.statusOverlay?.title}
message={controller.statusOverlay?.message}
hint={controller.statusOverlay?.hint}
onCancel={controller.statusOverlay?.onCancel}
progress={!controller.isInvalid}
onBackdropPress={controller.DISMISS_INVALID}
progress={controller.statusOverlay?.progress}
onBackdropPress={controller.statusOverlay?.onBackdropPress}
/>
{controller.isDisconnected && (

View File

@@ -19,6 +19,11 @@ import {
selectIsOffline,
selectIsSent,
selectIsDisconnected,
selectIsRejected,
selectIsAccepted,
selectIsSendingVc,
selectIsSendingVcTimeout,
selectReceiverInfo,
} from '../../machines/scan';
import { selectVcLabel } from '../../machines/settings';
import { MainBottomTabParamList } from '../../routes/main';
@@ -55,6 +60,11 @@ export function useScanLayout() {
locationError.button = t('errors.locationDenied.button');
}
const DISMISS = () => scanService.send(ScanEvents.DISMISS());
const CANCEL = () => scanService.send(ScanEvents.CANCEL());
const receiverInfo = useSelector(scanService, selectReceiverInfo);
const isInvalid = useSelector(scanService, selectIsInvalid);
const isConnecting = useSelector(scanService, selectIsConnecting);
const isConnectingTimeout = useSelector(
@@ -69,48 +79,89 @@ export function useScanLayout() {
scanService,
selectIsExchangingDeviceInfoTimeout
);
const isOffline = useSelector(scanService, selectIsOffline);
const isAccepted = useSelector(scanService, selectIsAccepted);
const isRejected = useSelector(scanService, selectIsRejected);
const isSent = useSelector(scanService, selectIsSent);
const isOffline = useSelector(scanService, selectIsOffline);
const isSendingVc = useSelector(scanService, selectIsSendingVc);
const isSendingVcTimeout = useSelector(scanService, selectIsSendingVcTimeout);
const vcLabel = useSelector(settingsService, selectVcLabel);
const onCancel = () => scanService.send(ScanEvents.CANCEL());
let statusOverlay: Pick<
MessageOverlayProps,
'message' | 'hint' | 'onCancel'
'title' | 'message' | 'hint' | 'onCancel' | 'progress' | 'onBackdropPress'
> = null;
if (isConnecting) {
statusOverlay = {
message: t('status.connecting'),
progress: true,
};
} else if (isConnectingTimeout) {
statusOverlay = {
message: t('status.connecting'),
hint: t('status.connectingTimeout'),
onCancel,
progress: true,
};
} else if (isExchangingDeviceInfo) {
statusOverlay = {
message: t('status.exchangingDeviceInfo'),
progress: true,
};
} else if (isExchangingDeviceInfoTimeout) {
statusOverlay = {
message: t('status.exchangingDeviceInfo'),
hint: t('status.exchangingDeviceInfoTimeout'),
onCancel,
progress: true,
};
} else if (isSent) {
statusOverlay = {
message: t('status.sent', { vcLabel: vcLabel.singular }),
hint: t('status.sentHint', { vcLabel: vcLabel.singular }),
};
} else if (isSendingVc) {
statusOverlay = {
title: t('status.sharing.title'),
hint: t('status.sharing.hint'),
progress: true,
};
} else if (isSendingVcTimeout) {
statusOverlay = {
title: t('status.sharing.title'),
hint: t('status.sharing.timeoutHint'),
onCancel: CANCEL,
progress: true,
};
} else if (isAccepted) {
statusOverlay = {
title: t('status.accepted.title'),
message: t('status.accepted.message', {
vcLabel: vcLabel.singular,
receiver: receiverInfo.deviceName,
}),
onBackdropPress: DISMISS,
};
} else if (isRejected) {
statusOverlay = {
title: t('status.rejected.title'),
message: t('status.rejected.message', {
vcLabel: vcLabel.singular,
receiver: receiverInfo.deviceName,
}),
onBackdropPress: DISMISS,
};
} else if (isInvalid) {
statusOverlay = {
message: t('status.invalid'),
onBackdropPress: DISMISS,
};
} else if (isOffline) {
statusOverlay = {
message: t('status.offline'),
onBackdropPress: DISMISS,
};
}
@@ -154,8 +205,6 @@ export function useScanLayout() {
isDisconnected: useSelector(scanService, selectIsDisconnected),
statusOverlay,
DISMISS: () => scanService.send(ScanEvents.DISMISS()),
DISMISS_INVALID: () =>
isInvalid ? scanService.send(ScanEvents.DISMISS()) : null,
DISMISS,
};
}

View File

@@ -20,6 +20,19 @@
"invalid": "Invalid QR Code",
"offline": "Please connect to the internet to scan QR codes using Online sharing mode",
"sent": "{{ vcLabel }} has been sent...",
"sentHint": "Waiting for receiver to save or discard your {{ vcLabel }}"
"sentHint": "Waiting for receiver to save or discard your {{ vcLabel }}",
"sharing": {
"title": "Sharing...",
"hint": "Please wait for the receiving device to accept or reject the share.",
"timeoutHint": "It's taking longer than expected to share. There could be a problem with the connection."
},
"accepted": {
"title": "Success!",
"message": "Your {{vcLabel}} has been successfully shared with {{receiver}}"
},
"rejected": {
"title": "Notice",
"message": "Your {{vcLabel}} was discarded by {{receiver}}"
}
}
}

View File

@@ -3,20 +3,5 @@
"acceptRequest": "Accept request and choose {{vcLabel}}",
"acceptRequestAndVerify": "Accept request and verify",
"reject": "Reject",
"status": {
"sharing": {
"title": "Sharing...",
"hint": "Please wait for the receiving device to accept or reject the share.",
"timeoutHint": "It's taking longer than expected to share. There could be a problem with the connection."
},
"accepted": {
"title": "Success!",
"message": "Your {{vcLabel}} has been successfully shared with {{receiver}}"
},
"rejected": {
"title": "Notice",
"message": "Your {{vcLabel}} was discarded by {{receiver}}"
}
},
"consentToPhotoVerification": "I give consent to have my photo taken for authentication"
}

View File

@@ -121,46 +121,6 @@ export const SendVcScreen: React.FC = () => {
/>
</Row>
</MessageOverlay>
<MessageOverlay
isVisible={controller.isSendingVc}
title={t('status.sharing.title')}
hint={
controller.isSendingVcTimeout
? t('status.sharing.timeoutHint')
: t('status.sharing.hint')
}
onCancel={controller.isSendingVcTimeout ? controller.CANCEL : null}
progress
/>
<MessageOverlay
isVisible={controller.status != null}
title={controller.status?.title}
hint={controller.status?.hint}
onCancel={controller.status?.onCancel}
progress
/>
<MessageOverlay
isVisible={controller.isAccepted}
title={t('status.accepted.title')}
message={t('status.accepted.message', {
vcLabel: controller.vcLabel.singular,
receiver: controller.receiverInfo.deviceName,
})}
onBackdropPress={controller.DISMISS}
/>
<MessageOverlay
isVisible={controller.isRejected}
title={t('status.rejected.title')}
message={t('status.rejected.message', {
vcLabel: controller.vcLabel.singular,
receiver: controller.receiverInfo.deviceName,
})}
onBackdropPress={controller.DISMISS}
/>
</React.Fragment>
);
};

View File

@@ -1,18 +1,12 @@
import { useSelector } from '@xstate/react';
import { useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ActorRefFrom } from 'xstate';
import { MessageOverlayProps } from '../../components/MessageOverlay';
import {
ScanEvents,
selectIsAccepted,
selectReason,
selectReceiverInfo,
selectIsRejected,
selectIsSelectingVc,
selectIsSendingVc,
selectVcName,
selectIsSendingVcTimeout,
selectIsVerifyingIdentity,
selectIsInvalidIdentity,
selectSelectedVc,
@@ -29,24 +23,8 @@ export function useSendVcScreen() {
const settingsService = appService.children.get('settings');
const vcService = appService.children.get('vc');
const { t } = useTranslation('SendVcScreen');
const isSendingVc = useSelector(scanService, selectIsSendingVc);
const isSendingVcTimeout = useSelector(scanService, selectIsSendingVcTimeout);
const CANCEL = () => scanService.send(ScanEvents.CANCEL());
let status: Pick<MessageOverlayProps, 'title' | 'hint' | 'onCancel'> = null;
if (isSendingVc) {
status = {
title: t('status.sharing.title'),
};
} else if (isSendingVcTimeout) {
status = {
title: t('status.sharing.title'),
hint: t('status.sharing.timeoutHint'),
onCancel: CANCEL,
};
}
const [selectedIndex, setSelectedIndex] = useState<number>(null);
return {
@@ -60,7 +38,6 @@ export function useSendVcScreen() {
scanService.send(ScanEvents.SELECT_VC(vcData));
},
status,
receiverInfo: useSelector(scanService, selectReceiverInfo),
reason: useSelector(scanService, selectReason),
vcName: useSelector(scanService, selectVcName),
@@ -69,10 +46,6 @@ export function useSendVcScreen() {
selectedVc: useSelector(scanService, selectSelectedVc),
isSelectingVc: useSelector(scanService, selectIsSelectingVc),
isSendingVc,
isSendingVcTimeout,
isAccepted: useSelector(scanService, selectIsAccepted),
isRejected: useSelector(scanService, selectIsRejected),
isVerifyingIdentity: useSelector(scanService, selectIsVerifyingIdentity),
isInvalidIdentity: useSelector(scanService, selectIsInvalidIdentity),
isCancelling: useSelector(scanService, selectIsCancelling),

View File

@@ -5,24 +5,30 @@
// } from '@idpass/smartshare-react-native';
import Smartshare from '@idpass/smartshare-react-native';
import { ConnectionParams } from '@idpass/smartshare-react-native/lib/typescript/IdpassSmartshare';
import { getDeviceNameSync } from 'react-native-device-info';
const { IdpassSmartshare, GoogleNearbyMessages } = Smartshare;
import { DeviceInfo } from '../components/DeviceInfoList';
import { VC } from '../types/vc';
export function onlineSubscribe<T extends SmartshareEventType>(
export async function onlineSubscribe<T extends SmartshareEventType>(
eventType: T,
callback: (data: SmartshareEventData<T>) => void,
disconectCallback?: (data: SmartshareEventData<T>) => void,
config?: { keepAlive: boolean }
config?: { keepAlive?: boolean; pairId?: string }
) {
return GoogleNearbyMessages.subscribe(
(foundMessage) => {
if (__DEV__) {
console.log('\n[request] MESSAGE_FOUND', foundMessage.slice(0, 100));
console.log(
`[${getDeviceNameSync()}] MESSAGE_FOUND`,
foundMessage.slice(0, 100)
);
}
const response = SmartshareEvent.fromString<T>(foundMessage);
if (response.type === 'disconnect') {
if (response.pairId !== config?.pairId) {
return;
} else if (response.type === 'disconnect') {
GoogleNearbyMessages.unsubscribe();
disconectCallback(response.data);
} else if (response.type === eventType) {
@@ -32,20 +38,25 @@ export function onlineSubscribe<T extends SmartshareEventType>(
},
(lostMessage) => {
if (__DEV__) {
console.log('\n[request] MESSAGE_LOST', lostMessage.slice(0, 100));
console.log(
`[${getDeviceNameSync()}] MESSAGE_LOST`,
lostMessage.slice(0, 100)
);
}
}
).catch((error: Error) => {
if (error.message.includes('existing callback is already subscribed')) {
console.log('Existing callback found. Unsubscribing then retrying...');
console.log(
`${getDeviceNameSync()} Existing callback found for ${eventType}. Unsubscribing then retrying...`
);
return onlineSubscribe(eventType, callback, disconectCallback, config);
}
});
}
export function onlineSend(event: SmartshareEvents) {
export async function onlineSend(event: SmartshareEvents, pairId: string) {
return GoogleNearbyMessages.publish(
new SmartshareEvent(event.type, event.data).toString()
new SmartshareEvent(event.type, event.data, pairId).toString()
);
}
@@ -71,17 +82,27 @@ export function offlineSend(event: SmartshareEvents, callback: () => void) {
}
class SmartshareEvent<T extends SmartshareEventType> {
constructor(public type: T | string, public data: SmartshareEventData<T>) {}
constructor(
public type: T | string,
public data: SmartshareEventData<T>,
public pairId = ''
) {}
static fromString<T extends SmartshareEventType>(json: string) {
const [type, data] = json.split('\n');
return new SmartshareEvent<T>(type, data ? JSON.parse(data) : undefined);
const [pairId, type, data] = json.split('\n');
return new SmartshareEvent<T>(
type,
data ? JSON.parse(data) : undefined,
pairId
);
}
toString() {
return this.data != null
const message =
this.data != null
? this.type + '\n' + JSON.stringify(this.data)
: this.type;
return [this.pairId, message].join('\n');
}
}
@@ -92,7 +113,7 @@ export interface PairingEvent {
export interface PairingResponseEvent {
type: 'pairing:response';
data: 'ok';
data: string;
}
export interface ExchangeReceiverInfoEvent {