diff --git a/android/build.gradle b/android/build.gradle
index b3ed68a727..e774a75a79 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -14,7 +14,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath("com.android.tools.build:gradle:4.1.0")
+ classpath("com.android.tools.build:gradle:4.2.2")
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
diff --git a/locales/en.json b/locales/en.json
index b219e3de1f..fb3f1c556c 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -197,7 +197,7 @@
},
"rejected": {
"title": "Notice",
- "message": "You rejected {{sender}}'s {{vcLabel}}"
+ "message": "You discarded {{sender}}'s {{vcLabel}}"
},
"disconnected": {
"title": "Disconnected",
@@ -240,7 +240,9 @@
"exchangingDeviceInfo": "Exchanging device info...",
"exchangingDeviceInfoTimeout": "It's taking a while to exchange device info. You may have to reconnect.",
"invalid": "Invalid QR Code",
- "offline": "Please connect to the internet to scan QR codes using Online sharing mode"
+ "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": {
@@ -266,7 +268,7 @@
},
"rejected": {
"title": "Notice",
- "message": "Your {{vcLabel}} was rejected by {{receiver}}"
+ "message": "Your {{vcLabel}} was discarded by {{receiver}}"
}
},
"consentToPhotoVerification": "I give consent to have my photo taken for authentication"
diff --git a/locales/fil.json b/locales/fil.json
index 251283948e..e9ff77b861 100644
--- a/locales/fil.json
+++ b/locales/fil.json
@@ -197,7 +197,7 @@
},
"rejected": {
"title": "Paunawa",
- "message": "Tinanggihan mo ang {{vcLabel}} ni {{sender}}"
+ "message": "Iwinaksi ang {{vcLabel}} ni {{sender}}"
},
"disconnected": {
"title": "Nadiskonekta",
@@ -240,7 +240,9 @@
"exchangingDeviceInfo": "Nagpapalitan ng impormasyon ng device...",
"exchangingDeviceInfoTimeout": "Medyo nagtatagal ang paglabas ng impormasyon ng device. Bukas ba ang ibang device para sa mga koneksyon?",
"invalid": "Di-wasto ang QR Code",
- "offline": "Mangyaring kumonekta sa internet upang makapag-scan ng QR codes na gumagamit ng Online sharing mode"
+ "offline": "Mangyaring kumonekta sa internet upang makapag-scan ng QR codes na gumagamit ng Online sharing mode",
+ "sent": "Naibahagi na ang {{vcLabel}}...",
+ "sentHint": "Iniintay ang nakatanggap na itabi o iwaksi ang iyong {{vcLabel}}"
}
},
"SelectVcOverlay": {
@@ -266,7 +268,7 @@
},
"rejected": {
"title": "Pansinin",
- "message": "Ang iyong {{vcLabel}} ay tinanggihan ng {{receiver}}"
+ "message": "Iwinaksi ni {{receiver}} ang iyong {{vcLabel}}"
}
},
"consentToPhotoVerification": "Nagbibigay ako ng pahintulot na kunin ang aking larawan para sa pagpapatunay"
diff --git a/machines/biometrics.ts b/machines/biometrics.ts
index b23a7b7a42..7177276714 100644
--- a/machines/biometrics.ts
+++ b/machines/biometrics.ts
@@ -1,6 +1,7 @@
import { createModel } from 'xstate/lib/model';
import * as LocalAuthentication from 'expo-local-authentication';
import { EventFrom, MetaObject, StateFrom } from 'xstate';
+import { Platform } from 'react-native';
// ----- CREATE MODEL ---------------------------------------------------------
const model = createModel(
@@ -97,6 +98,9 @@ export const biometricsMachine = model.createMachine(
authenticating: {
invoke: {
src: () => async () => {
+ if (Platform.OS === 'android') {
+ await LocalAuthentication.cancelAuthenticate();
+ }
const res = await LocalAuthentication.authenticateAsync({
promptMessage: 'Biometric Authentication',
@@ -112,6 +116,9 @@ export const biometricsMachine = model.createMachine(
actions: ['setStatus'],
},
},
+ on: {
+ AUTHENTICATE: 'authenticating',
+ },
},
reauthenticating: {
@@ -145,6 +152,7 @@ export const biometricsMachine = model.createMachine(
SET_IS_AVAILABLE: {
target: '#biometrics.available',
},
+ AUTHENTICATE: 'authenticating',
},
},
@@ -186,6 +194,9 @@ export const biometricsMachine = model.createMachine(
},
},
},
+ on: {
+ AUTHENTICATE: 'authenticating',
+ },
},
},
},
diff --git a/machines/request.ts b/machines/request.ts
index 35d53c7f4d..b8aa07ae44 100644
--- a/machines/request.ts
+++ b/machines/request.ts
@@ -304,6 +304,12 @@ export const requestMachine =
},
reviewing: {
+ invoke: {
+ src: 'sendVcResponse',
+ data: {
+ status: 'RECEIVED',
+ },
+ },
exit: 'disconnect',
initial: 'idle',
states: {
@@ -399,7 +405,7 @@ export const requestMachine =
},
},
accepted: {
- entry: ['sendVcReceived', 'logReceived'],
+ entry: ['updateReceivedVcs', 'logReceived'],
invoke: {
src: 'sendVcResponse',
data: {
@@ -634,7 +640,7 @@ export const requestMachine =
{ to: (context) => context.serviceRefs.activityLog }
),
- sendVcReceived: send(
+ updateReceivedVcs: send(
(context) => {
return VcEvents.VC_RECEIVED(VC_ITEM_STORE_KEY(context.incomingVc));
},
@@ -796,7 +802,7 @@ export const requestMachine =
}
},
- sendVcResponse: (context, _event, meta) => () => {
+ sendVcResponse: (context, _event, meta) => async () => {
const event: SendVcResponseEvent = {
type: 'send-vc:response',
data: meta.data.status,
@@ -807,7 +813,8 @@ export const requestMachine =
// pass
});
} else {
- onlineSend(event);
+ await GoogleNearbyMessages.unpublish();
+ await onlineSend(event);
}
},
diff --git a/machines/request.typegen.ts b/machines/request.typegen.ts
index 7c1ebdca15..7869bdd552 100644
--- a/machines/request.typegen.ts
+++ b/machines/request.typegen.ts
@@ -35,7 +35,8 @@ 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.rejected:invocation[0]'
+ | 'done.invoke.request.reviewing:invocation[0]';
verifyVp: 'done.invoke.request.reviewing.verifyingVp:invocation[0]';
};
'missingImplementations': {
@@ -86,7 +87,6 @@ export interface Typegen0 {
| 'FACE_VALID'
| 'done.invoke.request.reviewing.verifyingVp:invocation[0]';
requestReceiverInfo: 'CONNECTED';
- sendVcReceived: 'STORE_RESPONSE';
setIncomingVc: 'VC_RECEIVED';
setReceiveLogTypeDiscarded: 'CANCEL' | 'REJECT';
setReceiveLogTypeRegular: 'ACCEPT';
@@ -96,6 +96,7 @@ export interface Typegen0 {
setSenderInfo: 'EXCHANGE_DONE';
storeVc: 'STORE_RESPONSE';
switchProtocol: 'SWITCH_PROTOCOL';
+ updateReceivedVcs: 'STORE_RESPONSE';
};
'eventsCausingDelays': {
CANCEL_TIMEOUT: 'CANCEL';
@@ -122,7 +123,7 @@ export interface Typegen0 {
receiveVc: 'EXCHANGE_DONE';
requestBluetooth: 'BLUETOOTH_DISABLED';
sendDisconnect: 'CANCEL';
- sendVcResponse: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE';
+ sendVcResponse: 'CANCEL' | 'REJECT' | 'STORE_RESPONSE' | 'VC_RECEIVED';
verifyVp: never;
};
'matchesStates':
diff --git a/machines/scan.ts b/machines/scan.ts
index 297bf84316..a9f8213953 100644
--- a/machines/scan.ts
+++ b/machines/scan.ts
@@ -61,6 +61,7 @@ const model = createModel(
VERIFY_AND_ACCEPT_REQUEST: () => ({}),
VC_ACCEPTED: () => ({}),
VC_REJECTED: () => ({}),
+ VC_SENT: () => ({}),
CANCEL: () => ({}),
DISMISS: () => ({}),
CONNECTED: () => ({}),
@@ -232,12 +233,15 @@ export const scanMachine =
initial: 'selectingVc',
states: {
selectingVc: {
+ invoke: {
+ src: 'monitorCancellation',
+ },
on: {
UPDATE_REASON: {
actions: 'setReason',
},
DISCONNECT: {
- target: '#scan.findingConnection',
+ target: '#scan.disconnected',
},
SELECT_VC: {
actions: 'setSelectedVc',
@@ -256,6 +260,7 @@ export const scanMachine =
actions: 'toggleShouldVerifyPresence',
},
},
+ exit: ['onlineUnsubscribe'],
},
cancelling: {
invoke: {
@@ -291,16 +296,26 @@ 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: {
DISCONNECT: {
target: '#scan.findingConnection',
},
- VC_ACCEPTED: {
- target: 'accepted',
- },
- VC_REJECTED: {
- target: 'rejected',
+ VC_SENT: {
+ target: '#scan.reviewing.sendingVc.sent',
+ internal: true,
},
},
},
@@ -610,6 +625,10 @@ export const scanMachine =
shouldVerifyPresence: false,
}),
}),
+
+ onlineUnsubscribe: () => {
+ GoogleNearbyMessages.unsubscribe();
+ },
},
services: {
@@ -649,6 +668,14 @@ export const scanMachine =
}
},
+ monitorCancellation: (context) => async (callback) => {
+ if (context.sharingProtocol === 'ONLINE') {
+ await onlineSubscribe('disconnect', null, () =>
+ callback({ type: 'DISCONNECT' })
+ );
+ }
+ },
+
checkLocationStatus: () => (callback) => {
checkLocation(
() => callback(model.events.LOCATION_ENABLED()),
@@ -737,10 +764,15 @@ export const scanMachine =
};
const statusCallback = (status: SendVcStatus) => {
+ console.log('[scan] statusCallback', status);
if (typeof status === 'number') return;
- callback({
- type: status === 'ACCEPTED' ? 'VC_ACCEPTED' : 'VC_REJECTED',
- });
+ if (status === 'RECEIVED') {
+ callback({ type: 'VC_SENT' });
+ } else {
+ callback({
+ type: status === 'ACCEPTED' ? 'VC_ACCEPTED' : 'VC_REJECTED',
+ });
+ }
};
if (context.sharingProtocol === 'OFFLINE') {
@@ -896,6 +928,10 @@ export function selectIsRejected(state: State) {
return state.matches('reviewing.rejected');
}
+export function selectIsSent(state: State) {
+ return state.matches('reviewing.sendingVc.sent');
+}
+
export function selectIsInvalid(state: State) {
return state.matches('invalid');
}
@@ -928,6 +964,10 @@ export function selectIsOffline(state: State) {
return state.matches('offline');
}
+export function selectIsDisconnected(state: State) {
+ return state.matches('disconnected');
+}
+
async function sendVc(
vc: VC,
callback: (status: SendVcStatus) => void,
@@ -968,7 +1008,9 @@ async function sendVc(
},
});
} else if (typeof status === 'string') {
- GoogleNearbyMessages.unsubscribe();
+ if (status === 'ACCEPTED' || status === 'REJECTED') {
+ GoogleNearbyMessages.unsubscribe();
+ }
callback(status);
}
},
diff --git a/machines/scan.typegen.ts b/machines/scan.typegen.ts
index 7f0381a54b..bc6373a9cd 100644
--- a/machines/scan.typegen.ts
+++ b/machines/scan.typegen.ts
@@ -8,6 +8,10 @@ export interface Typegen0 {
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
+ 'error.platform.scan.reviewing.creatingVp:invocation[0]': {
+ type: 'error.platform.scan.reviewing.creatingVp:invocation[0]';
+ data: unknown;
+ };
'xstate.after(CANCEL_TIMEOUT)#scan.reviewing.cancelling': {
type: 'xstate.after(CANCEL_TIMEOUT)#scan.reviewing.cancelling';
};
@@ -33,6 +37,7 @@ export interface Typegen0 {
createVp: 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
discoverDevice: 'done.invoke.scan.connecting:invocation[0]';
exchangeDeviceInfo: 'done.invoke.scan.exchangingDeviceInfo:invocation[0]';
+ monitorCancellation: 'done.invoke.scan.reviewing.selectingVc:invocation[0]';
monitorConnection: 'done.invoke.scan:invocation[0]';
sendDisconnect: 'done.invoke.scan.reviewing.cancelling:invocation[0]';
sendVc: 'done.invoke.scan.reviewing.sendingVc:invocation[0]';
@@ -73,6 +78,14 @@ export interface Typegen0 {
| 'xstate.stop';
logFailedVerification: 'FACE_INVALID';
logShared: 'VC_ACCEPTED';
+ onlineUnsubscribe:
+ | 'ACCEPT_REQUEST'
+ | 'CANCEL'
+ | 'DISCONNECT'
+ | 'SCREEN_BLUR'
+ | 'SCREEN_FOCUS'
+ | 'VERIFY_AND_ACCEPT_REQUEST'
+ | 'xstate.stop';
openSettings: 'LOCATION_REQUEST';
registerLoggers:
| 'DISCONNECT'
@@ -110,6 +123,7 @@ export interface Typegen0 {
SHARING_TIMEOUT:
| 'ACCEPT_REQUEST'
| 'FACE_VALID'
+ | 'VC_SENT'
| 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
};
'eventsCausingGuards': {
@@ -125,11 +139,17 @@ export interface Typegen0 {
exchangeDeviceInfo:
| 'CONNECTED'
| 'xstate.after(CONNECTION_TIMEOUT)#scan.exchangingDeviceInfo';
+ monitorCancellation:
+ | 'CANCEL'
+ | 'DISMISS'
+ | 'EXCHANGE_DONE'
+ | 'error.platform.scan.reviewing.creatingVp:invocation[0]';
monitorConnection: 'xstate.init';
sendDisconnect: 'CANCEL';
sendVc:
| 'ACCEPT_REQUEST'
| 'FACE_VALID'
+ | 'VC_SENT'
| 'done.invoke.scan.reviewing.creatingVp:invocation[0]';
};
'matchesStates':
@@ -163,6 +183,7 @@ export interface Typegen0 {
| 'reviewing.selectingVc'
| 'reviewing.sendingVc'
| 'reviewing.sendingVc.inProgress'
+ | 'reviewing.sendingVc.sent'
| 'reviewing.sendingVc.timeout'
| 'reviewing.verifyingIdentity'
| {
@@ -184,7 +205,7 @@ export interface Typegen0 {
| 'selectingVc'
| 'sendingVc'
| 'verifyingIdentity'
- | { sendingVc?: 'inProgress' | 'timeout' };
+ | { sendingVc?: 'inProgress' | 'sent' | 'timeout' };
};
'tags': never;
}
diff --git a/machines/vc.typegen.ts b/machines/vc.typegen.ts
index e5b7210f2e..7c9759a5d2 100644
--- a/machines/vc.typegen.ts
+++ b/machines/vc.typegen.ts
@@ -8,9 +8,9 @@ export interface Typegen0 {
'invokeSrcNameMap': {};
'missingImplementations': {
actions: never;
- services: never;
- guards: never;
delays: never;
+ guards: never;
+ services: never;
};
'eventsCausingActions': {
getReceivedVcsResponse: 'GET_RECEIVED_VCS';
@@ -24,11 +24,11 @@ export interface Typegen0 {
setMyVcs: 'STORE_RESPONSE';
setReceivedVcs: 'STORE_RESPONSE';
};
- 'eventsCausingServices': {};
+ 'eventsCausingDelays': {};
'eventsCausingGuards': {
hasExistingReceivedVc: 'VC_RECEIVED';
};
- 'eventsCausingDelays': {};
+ 'eventsCausingServices': {};
'matchesStates':
| 'init'
| 'init.myVcs'
diff --git a/screens/Request/RequestScreen.strings.json b/screens/Request/RequestScreen.strings.json
index f0a448cded..8ea8570510 100644
--- a/screens/Request/RequestScreen.strings.json
+++ b/screens/Request/RequestScreen.strings.json
@@ -10,7 +10,7 @@
},
"rejected": {
"title": "Notice",
- "message": "You rejected {{sender}}'s {{vcLabel}}"
+ "message": "You discarded {{sender}}'s {{vcLabel}}"
},
"disconnected": {
"title": "Disconnected",
diff --git a/screens/Scan/ScanLayout.tsx b/screens/Scan/ScanLayout.tsx
index d2cc808634..6cec676c01 100644
--- a/screens/Scan/ScanLayout.tsx
+++ b/screens/Scan/ScanLayout.tsx
@@ -10,6 +10,7 @@ import { useScanLayout } from './ScanLayoutController';
import { LanguageSelector } from '../../components/LanguageSelector';
import { ScanScreen } from './ScanScreen';
import { I18nManager, Platform } from 'react-native';
+import { Message } from '../../components/Message';
const ScanStack = createNativeStackNavigator();
@@ -68,6 +69,14 @@ export const ScanLayout: React.FC = () => {
progress={!controller.isInvalid}
onBackdropPress={controller.DISMISS_INVALID}
/>
+
+ {controller.isDisconnected && (
+
+ )}
);
};
diff --git a/screens/Scan/ScanLayoutController.ts b/screens/Scan/ScanLayoutController.ts
index 63c30c4fda..728aed4dde 100644
--- a/screens/Scan/ScanLayoutController.ts
+++ b/screens/Scan/ScanLayoutController.ts
@@ -16,6 +16,8 @@ import {
selectIsReviewing,
selectIsScanning,
selectIsOffline,
+ selectIsSent,
+ selectIsDisconnected,
} from '../../machines/scan';
import { selectVcLabel } from '../../machines/settings';
import { MainBottomTabParamList } from '../../routes/main';
@@ -30,6 +32,8 @@ type ScanLayoutNavigation = NavigationProp<
ScanStackParamList & MainBottomTabParamList
>;
+// TODO: refactor
+// eslint-disable-next-line sonarjs/cognitive-complexity
export function useScanLayout() {
const { t } = useTranslation('ScanScreen');
const { appService } = useContext(GlobalContext);
@@ -65,6 +69,9 @@ export function useScanLayout() {
selectIsExchangingDeviceInfoTimeout
);
const isOffline = useSelector(scanService, selectIsOffline);
+ const isSent = useSelector(scanService, selectIsSent);
+
+ const vcLabel = useSelector(settingsService, selectVcLabel);
const onCancel = () => scanService.send(ScanEvents.CANCEL());
let statusOverlay: Pick<
@@ -91,6 +98,11 @@ export function useScanLayout() {
hint: t('status.exchangingDeviceInfoTimeout'),
onCancel,
};
+ } else if (isSent) {
+ statusOverlay = {
+ message: t('status.sent', { vcLabel: vcLabel.singular }),
+ hint: t('status.sentHint', { vcLabel: vcLabel.singular }),
+ };
} else if (isInvalid) {
statusOverlay = {
message: t('status.invalid'),
@@ -130,12 +142,14 @@ export function useScanLayout() {
}, [isDone, isReviewing, isScanning]);
return {
- vcLabel: useSelector(settingsService, selectVcLabel),
+ vcLabel,
isInvalid,
isDone,
+ isDisconnected: useSelector(scanService, selectIsDisconnected),
statusOverlay,
+ DISMISS: () => scanService.send(ScanEvents.DISMISS()),
DISMISS_INVALID: () =>
isInvalid ? scanService.send(ScanEvents.DISMISS()) : null,
};
diff --git a/screens/Scan/ScanScreen.strings.json b/screens/Scan/ScanScreen.strings.json
index 083d38c5f8..67044c05d7 100644
--- a/screens/Scan/ScanScreen.strings.json
+++ b/screens/Scan/ScanScreen.strings.json
@@ -18,6 +18,8 @@
"exchangingDeviceInfo": "Exchanging device info...",
"exchangingDeviceInfoTimeout": "It's taking a while to exchange device info. You may have to reconnect.",
"invalid": "Invalid QR Code",
- "offline": "Please connect to the internet to scan QR codes using Online sharing mode"
+ "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 }}"
}
}
\ No newline at end of file
diff --git a/screens/Scan/SendVcScreen.strings.json b/screens/Scan/SendVcScreen.strings.json
index cbd0955285..a8adf7afb8 100644
--- a/screens/Scan/SendVcScreen.strings.json
+++ b/screens/Scan/SendVcScreen.strings.json
@@ -15,7 +15,7 @@
},
"rejected": {
"title": "Notice",
- "message": "Your {{vcLabel}} was rejected by {{receiver}}"
+ "message": "Your {{vcLabel}} was discarded by {{receiver}}"
}
},
"consentToPhotoVerification": "I give consent to have my photo taken for authentication"
diff --git a/shared/smartshare.ts b/shared/smartshare.ts
index 73efe7c2c8..9924c220f4 100644
--- a/shared/smartshare.ts
+++ b/shared/smartshare.ts
@@ -23,6 +23,7 @@ export function onlineSubscribe(
}
const response = SmartshareEvent.fromString(foundMessage);
if (response.type === 'disconnect') {
+ GoogleNearbyMessages.unsubscribe();
disconectCallback(response.data);
} else if (response.type === eventType) {
!config?.keepAlive && GoogleNearbyMessages.unsubscribe();
@@ -34,7 +35,12 @@ export function onlineSubscribe(
console.log('\n[request] 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...');
+ return onlineSubscribe(eventType, callback, disconectCallback, config);
+ }
+ });
}
export function onlineSend(event: SmartshareEvents) {
@@ -113,7 +119,7 @@ export interface SendVcEvent {
};
}
-export type SendVcStatus = 'ACCEPTED' | 'REJECTED';
+export type SendVcStatus = 'ACCEPTED' | 'REJECTED' | 'RECEIVED';
export interface SendVcResponseEvent {
type: 'send-vc:response';
data: SendVcStatus | number;