mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-10 05:58:01 -05:00
Merge pull request #51 from idpass/29-fix-location
App should ask for permission to location access when opening Scan QR page - added allow access button
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { Camera } from 'expo-camera';
|
||||
import { BarCodeEvent, BarCodeScanner } from 'expo-barcode-scanner';
|
||||
import { Linking, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
import { Button, Text } from './ui';
|
||||
import { Column, Button, Text } from './ui';
|
||||
import { GlobalContext } from '../shared/GlobalContext';
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { selectIsActive } from '../machines/app';
|
||||
@@ -22,17 +22,12 @@ const styles = StyleSheet.create({
|
||||
scanner: {
|
||||
height: 400,
|
||||
width: '100%',
|
||||
margin: 'auto'
|
||||
margin: 'auto',
|
||||
},
|
||||
buttonContainer: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
flipButtonContainer: {
|
||||
position: 'absolute',
|
||||
width: '80%',
|
||||
top: '110%',
|
||||
},
|
||||
buttonStyle: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
@@ -95,13 +90,13 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
|
||||
<Camera
|
||||
style={styles.scanner}
|
||||
barCodeScannerSettings={{
|
||||
barcodeTypes: [BarCodeScanner.Constants.BarCodeType.qr]
|
||||
barcodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
|
||||
}}
|
||||
onBarCodeScanned={scanned ? undefined : onBarcodeScanned}
|
||||
type={type}
|
||||
/>
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.flipButtonContainer}>
|
||||
<Column margin="24 0">
|
||||
<TouchableOpacity
|
||||
style={styles.button}
|
||||
onPress={() => {
|
||||
@@ -113,7 +108,7 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
|
||||
}}>
|
||||
<Icon name="flip-camera-ios" color={Colors.Black} size={64} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Column>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
getDeviceName,
|
||||
getDeviceNameSync,
|
||||
} from 'react-native-device-info';
|
||||
import { EventFrom, spawn, StateFrom } from 'xstate';
|
||||
import { EventFrom, spawn, StateFrom, send } from 'xstate';
|
||||
import { createModel } from 'xstate/lib/model';
|
||||
import { authMachine, createAuthMachine } from './auth';
|
||||
import { createSettingsMachine, settingsMachine } from './settings';
|
||||
@@ -14,7 +14,7 @@ import { createVidMachine, vidMachine } from './vid';
|
||||
import { createActivityLogMachine, activityLogMachine } from './activityLog';
|
||||
import { createRequestMachine, requestMachine } from './request';
|
||||
import { createScanMachine, scanMachine } from './scan';
|
||||
import { respond } from 'xstate/lib/actions';
|
||||
import { pure, respond } from 'xstate/lib/actions';
|
||||
import { AppServices } from '../shared/GlobalContext';
|
||||
|
||||
const model = createModel(
|
||||
@@ -98,8 +98,12 @@ export const appMachine = model.createMachine(
|
||||
initial: 'checking',
|
||||
states: {
|
||||
checking: {},
|
||||
active: {},
|
||||
inactive: {},
|
||||
active: {
|
||||
entry: ['forwardToServices'],
|
||||
},
|
||||
inactive: {
|
||||
entry: ['forwardToServices'],
|
||||
},
|
||||
},
|
||||
},
|
||||
network: {
|
||||
@@ -113,8 +117,12 @@ export const appMachine = model.createMachine(
|
||||
initial: 'checking',
|
||||
states: {
|
||||
checking: {},
|
||||
online: {},
|
||||
offline: {},
|
||||
online: {
|
||||
entry: ['forwardToServices'],
|
||||
},
|
||||
offline: {
|
||||
entry: ['forwardToServices'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -123,6 +131,12 @@ export const appMachine = model.createMachine(
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
forwardToServices: pure((context, event) =>
|
||||
Object.values(context.serviceRefs).map((serviceRef) =>
|
||||
send({ ...event, type: `APP_${event.type}` }, { to: serviceRef })
|
||||
)
|
||||
),
|
||||
|
||||
requestDeviceInfo: respond((context) => ({
|
||||
type: 'RECEIVE_DEVICE_INFO',
|
||||
info: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import SmartShare from '@idpass/smartshare-react-native';
|
||||
import LocationEnabler from 'react-native-location-enabler';
|
||||
import { EventFrom, send, sendParent, StateFrom } from 'xstate';
|
||||
import { createModel } from 'xstate/lib/model';
|
||||
import { EmitterSubscription } from 'react-native';
|
||||
import { EmitterSubscription, Linking, PermissionsAndroid } from 'react-native';
|
||||
import { DeviceInfo } from '../components/DeviceInfoList';
|
||||
import { Message } from '../shared/Message';
|
||||
import { getDeviceNameSync } from 'react-native-device-info';
|
||||
@@ -44,8 +44,10 @@ const model = createModel(
|
||||
UPDATE_REASON: (reason: string) => ({ reason }),
|
||||
LOCATION_ENABLED: () => ({}),
|
||||
LOCATION_DISABLED: () => ({}),
|
||||
LOCATION_REQUEST: () => ({}),
|
||||
UPDATE_VID_NAME: (vidName: string) => ({ vidName }),
|
||||
STORE_RESPONSE: (response: any) => ({ response }),
|
||||
APP_ACTIVE: () => ({}),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -73,26 +75,44 @@ export const scanMachine = model.createMachine(
|
||||
},
|
||||
checkingLocationService: {
|
||||
invoke: {
|
||||
src: 'checkLocationService',
|
||||
src: 'checkLocationStatus',
|
||||
},
|
||||
on: {
|
||||
LOCATION_ENABLED: '.enabled',
|
||||
},
|
||||
initial: 'checking',
|
||||
initial: 'checkingStatus',
|
||||
states: {
|
||||
checking: {
|
||||
checkingStatus: {
|
||||
on: {
|
||||
LOCATION_DISABLED: 'requesting',
|
||||
LOCATION_ENABLED: 'checkingPermission',
|
||||
LOCATION_DISABLED: 'requestingToEnable',
|
||||
},
|
||||
},
|
||||
requesting: {
|
||||
entry: ['requestLocationService'],
|
||||
requestingToEnable: {
|
||||
entry: ['requestToEnableLocation'],
|
||||
on: {
|
||||
LOCATION_DISABLED: '#locationDenied',
|
||||
LOCATION_ENABLED: 'checkingPermission',
|
||||
LOCATION_DISABLED: 'disabled',
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
always: '#clearingConnection',
|
||||
checkingPermission: {
|
||||
invoke: {
|
||||
src: 'checkLocationPermission',
|
||||
},
|
||||
on: {
|
||||
LOCATION_ENABLED: '#clearingConnection',
|
||||
LOCATION_DISABLED: 'denied',
|
||||
},
|
||||
},
|
||||
denied: {
|
||||
on: {
|
||||
LOCATION_REQUEST: {
|
||||
actions: ['openSettings'],
|
||||
},
|
||||
APP_ACTIVE: 'checkingPermission',
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
on: {
|
||||
LOCATION_REQUEST: 'requestingToEnable',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -117,9 +137,6 @@ export const scanMachine = model.createMachine(
|
||||
],
|
||||
},
|
||||
},
|
||||
locationDenied: {
|
||||
id: 'locationDenied',
|
||||
},
|
||||
preparingToConnect: {
|
||||
entry: ['requestSenderInfo'],
|
||||
on: {
|
||||
@@ -225,7 +242,7 @@ export const scanMachine = model.createMachine(
|
||||
senderInfo: (_, event: ReceiveDeviceInfoEvent) => event.info,
|
||||
}),
|
||||
|
||||
requestLocationService: (context) => {
|
||||
requestToEnableLocation: (context) => {
|
||||
LocationEnabler.requestResolutionSettings(context.locationConfig);
|
||||
},
|
||||
|
||||
@@ -301,10 +318,40 @@ export const scanMachine = model.createMachine(
|
||||
}),
|
||||
{ to: (context) => context.serviceRefs.activityLog }
|
||||
),
|
||||
|
||||
openSettings: () => {
|
||||
Linking.openSettings();
|
||||
},
|
||||
},
|
||||
|
||||
services: {
|
||||
checkLocationService: (context) => (callback) => {
|
||||
checkLocationPermission: () => async (callback) => {
|
||||
try {
|
||||
// TODO: a more reliable way to wait for animation to finish when app becomes active
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
|
||||
const response = await PermissionsAndroid.request(
|
||||
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
||||
{
|
||||
title: 'Location access',
|
||||
message:
|
||||
'Location access is required for the scanning functionality.',
|
||||
buttonNegative: 'Cancel',
|
||||
buttonPositive: 'OK',
|
||||
}
|
||||
);
|
||||
|
||||
if (response === 'granted') {
|
||||
callback(model.events.LOCATION_ENABLED());
|
||||
} else {
|
||||
callback(model.events.LOCATION_DISABLED());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
checkLocationStatus: (context) => (callback) => {
|
||||
const listener = LocationEnabler.addListener(({ locationEnabled }) => {
|
||||
if (locationEnabled) {
|
||||
callback(model.events.LOCATION_ENABLED());
|
||||
@@ -459,6 +506,10 @@ export function selectInvalid(state: State) {
|
||||
return state.matches('invalid');
|
||||
}
|
||||
|
||||
export function selectLocationDenied(state: State) {
|
||||
return state.matches('locationDenied');
|
||||
export function selectIsLocationDenied(state: State) {
|
||||
return state.matches('checkingLocationService.denied');
|
||||
}
|
||||
|
||||
export function selectIsLocationDisabled(state: State) {
|
||||
return state.matches('checkingLocationService.disabled');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { QrScanner } from '../../components/QrScanner';
|
||||
import { Column, Text } from '../../components/ui';
|
||||
import { Button, Column, Text } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { MainRouteProps } from '../../routes/main';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
@@ -11,21 +11,27 @@ export const ScanScreen: React.FC<MainRouteProps> = (props) => {
|
||||
const controller = useScanScreen(props);
|
||||
|
||||
return (
|
||||
<Column fill padding="98 24" backgroundColor={Colors.LightGrey}>
|
||||
<Column>
|
||||
<Text align="center">Scan QR Code</Text>
|
||||
{controller.isLocationDenied && (
|
||||
<Column fill padding="98 24 24 24" backgroundColor={Colors.LightGrey}>
|
||||
<Text align="center">Scan QR Code</Text>
|
||||
|
||||
{controller.isLocationDisabled || controller.isLocationDenied ? (
|
||||
<Column fill align="space-between">
|
||||
<Text align="center" margin="16 0" color={Colors.Red}>
|
||||
Location access is required for the scanning functionality.
|
||||
{controller.locationError.message}
|
||||
</Text>
|
||||
)}
|
||||
</Column>
|
||||
{!controller.isEmpty ? (
|
||||
<Column fill padding="16 0" crossAlign="center">
|
||||
{controller.isScanning ? (
|
||||
<QrScanner onQrFound={controller.SCAN} />
|
||||
) : null}
|
||||
<Button
|
||||
title={controller.locationError.button}
|
||||
onPress={controller.LOCATION_REQUEST}
|
||||
/>
|
||||
</Column>
|
||||
) : null}
|
||||
|
||||
{!controller.isEmpty ? (
|
||||
controller.isScanning && (
|
||||
<Column fill padding="16 0" crossAlign="center">
|
||||
<QrScanner onQrFound={controller.SCAN} />
|
||||
</Column>
|
||||
)
|
||||
) : (
|
||||
<Text align="center" margin="16 0" color={Colors.Red}>
|
||||
No sharable {controller.vidLabel.plural} are available.
|
||||
@@ -36,7 +42,7 @@ export const ScanScreen: React.FC<MainRouteProps> = (props) => {
|
||||
isVisible={controller.statusMessage !== ''}
|
||||
message={controller.statusMessage}
|
||||
hasProgress={!controller.isInvalid}
|
||||
onBackdropPress={controller.onDismissInvalid}
|
||||
onBackdropPress={controller.isInvalid && controller.DISMISS}
|
||||
/>
|
||||
|
||||
<SendVidModal
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useContext, useEffect } from 'react';
|
||||
import {
|
||||
ScanEvents,
|
||||
selectInvalid,
|
||||
selectLocationDenied,
|
||||
selectIsLocationDisabled,
|
||||
selectIsLocationDenied,
|
||||
selectReviewing,
|
||||
selectScanning,
|
||||
selectStatusMessage,
|
||||
@@ -22,6 +23,20 @@ export function useScanScreen({ navigation }: MainRouteProps) {
|
||||
const shareableVids = useSelector(vidService, selectShareableVids);
|
||||
const isInvalid = useSelector(scanService, selectInvalid);
|
||||
|
||||
const isLocationDisabled = useSelector(scanService, selectIsLocationDisabled);
|
||||
const isLocationDenied = useSelector(scanService, selectIsLocationDenied);
|
||||
|
||||
const locationError = { message: '', button: '' };
|
||||
if (isLocationDisabled) {
|
||||
locationError.message =
|
||||
'Location services must be enabled for the scanning functionality';
|
||||
locationError.button = 'Enable location services';
|
||||
} else if (isLocationDenied) {
|
||||
locationError.message =
|
||||
'Location permission is required for the scanning functionality';
|
||||
locationError.button = 'Allow access to location';
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptions = [
|
||||
navigation.addListener('focus', () =>
|
||||
@@ -45,26 +60,19 @@ export function useScanScreen({ navigation }: MainRouteProps) {
|
||||
}, []);
|
||||
|
||||
return {
|
||||
locationError,
|
||||
statusMessage: useSelector(scanService, selectStatusMessage),
|
||||
vidLabel: useSelector(settingsService, selectVidLabel),
|
||||
|
||||
onDismissInvalid: () => {
|
||||
if (isInvalid) {
|
||||
DISMISS();
|
||||
}
|
||||
},
|
||||
|
||||
isInvalid,
|
||||
isEmpty: !shareableVids.length,
|
||||
isLocationDisabled,
|
||||
isLocationDenied,
|
||||
isScanning: useSelector(scanService, selectScanning),
|
||||
isReviewing: useSelector(scanService, selectReviewing),
|
||||
isLocationDenied: useSelector(scanService, selectLocationDenied),
|
||||
|
||||
DISMISS,
|
||||
DISMISS: () => scanService.send(ScanEvents.DISMISS()),
|
||||
LOCATION_REQUEST: () => scanService.send(ScanEvents.LOCATION_REQUEST()),
|
||||
SCAN: (qrCode: string) => scanService.send(ScanEvents.SCAN(qrCode)),
|
||||
};
|
||||
|
||||
function DISMISS() {
|
||||
scanService.send(ScanEvents.DISMISS());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user