Skip to content

Commit dfb9322

Browse files
committed
feat: added an alert to go to app settings when permission is not granted (QM-1799)
1 parent f786487 commit dfb9322

File tree

9 files changed

+135
-18
lines changed

9 files changed

+135
-18
lines changed

packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelInput/SendInput.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import {
55
Icon,
66
TextInput,
77
createStyleSheet,
8+
useAlert,
89
useBottomSheet,
910
useToast,
1011
useUIKitTheme,
1112
} from '@sendbird/uikit-react-native-foundation';
1213
import { conditionChaining } from '@sendbird/uikit-utils';
1314

1415
import { useLocalization, usePlatformService } from '../../../../hooks/useContext';
16+
import SBUError from '../../../../libs/SBUError';
17+
import SBUUtils from '../../../../libs/SBUUtils';
1518
import type { GroupChannelProps } from '../../types';
1619

1720
type SendInputProps = GroupChannelProps['Input'] & {
@@ -21,9 +24,10 @@ type SendInputProps = GroupChannelProps['Input'] & {
2124
};
2225
const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabled }: SendInputProps) => {
2326
const { STRINGS } = useLocalization();
24-
const { openSheet } = useBottomSheet();
2527
const { fileService } = usePlatformService();
2628
const { colors } = useUIKitTheme();
29+
const { openSheet } = useBottomSheet();
30+
const { alert } = useAlert();
2731
const toast = useToast();
2832

2933
const onPressSend = () => {
@@ -39,7 +43,17 @@ const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabl
3943
onPress: async () => {
4044
const photo = await fileService.openCamera({
4145
mediaType: 'all',
42-
onOpenFailureWithToastMessage: () => toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error'),
46+
onOpenFailure: (error) => {
47+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
48+
alert({
49+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
50+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('camera', 'UIKitSample'),
51+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
52+
});
53+
} else {
54+
toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error');
55+
}
56+
},
4357
});
4458

4559
if (photo) {
@@ -54,7 +68,17 @@ const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabl
5468
const photo = await fileService.openMediaLibrary({
5569
selectionLimit: 1,
5670
mediaType: 'all',
57-
onOpenFailureWithToastMessage: () => toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error'),
71+
onOpenFailure: (error) => {
72+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
73+
alert({
74+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
75+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('device storage', 'UIKitSample'),
76+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
77+
});
78+
} else {
79+
toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error');
80+
}
81+
},
5882
});
5983

6084
if (photo && photo[0]) {
@@ -67,7 +91,7 @@ const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabl
6791
icon: 'document',
6892
onPress: async () => {
6993
const file = await fileService.openDocument({
70-
onOpenFailureWithToastMessage: () => toast.show(STRINGS.TOAST.OPEN_FILES_ERROR, 'error'),
94+
onOpenFailure: () => toast.show(STRINGS.TOAST.OPEN_FILES_ERROR, 'error'),
7195
});
7296

7397
if (file) {

packages/uikit-react-native/src/domain/groupChannelSettings/module/moduleContext.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414

1515
import ProviderLayout from '../../../components/ProviderLayout';
1616
import { useLocalization, usePlatformService, useSendbirdChat } from '../../../hooks/useContext';
17+
import SBUError from '../../../libs/SBUError';
18+
import SBUUtils from '../../../libs/SBUUtils';
1719
import type { GroupChannelSettingsContextsType, GroupChannelSettingsModule } from '../types';
1820

1921
export const GroupChannelSettingsContexts: GroupChannelSettingsContextsType = {
@@ -84,7 +86,17 @@ export const GroupChannelSettingsContextsProvider: GroupChannelSettingsModule['P
8486
onPress: async () => {
8587
const file = await fileService.openCamera({
8688
mediaType: 'photo',
87-
onOpenFailureWithToastMessage: () => toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error'),
89+
onOpenFailure: (error) => {
90+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
91+
alert({
92+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
93+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('camera', 'UIKitSample'),
94+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
95+
});
96+
} else {
97+
toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error');
98+
}
99+
},
88100
});
89101
if (!file) return;
90102

@@ -99,7 +111,17 @@ export const GroupChannelSettingsContextsProvider: GroupChannelSettingsModule['P
99111
const files = await fileService.openMediaLibrary({
100112
selectionLimit: 1,
101113
mediaType: 'photo',
102-
onOpenFailureWithToastMessage: () => toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error'),
114+
onOpenFailure: (error) => {
115+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
116+
alert({
117+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
118+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('device storage', 'UIKitSample'),
119+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
120+
});
121+
} else {
122+
toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error');
123+
}
124+
},
103125
});
104126
if (!files || !files[0]) return;
105127

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
enum SBUErrorCode {
2+
ERR_UNKNOWN = 90000000,
3+
4+
// Platform service - 91001000 ~
5+
ERR_PERMISSIONS_DENIED = 91001000,
6+
ERR_DEVICE_UNAVAILABLE,
7+
}
8+
9+
export default class SBUError extends Error {
10+
static CODE = SBUErrorCode;
11+
12+
static get UNKNOWN() {
13+
return new SBUError(SBUErrorCode.ERR_UNKNOWN);
14+
}
15+
16+
static get PERMISSIONS_DENIED() {
17+
return new SBUError(SBUErrorCode.ERR_PERMISSIONS_DENIED);
18+
}
19+
static get DEVICE_UNAVAILABLE() {
20+
return new SBUError(SBUErrorCode.ERR_DEVICE_UNAVAILABLE);
21+
}
22+
23+
constructor(public code: SBUErrorCode, message?: string) {
24+
super(message);
25+
}
26+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Linking, Platform } from 'react-native';
2+
3+
export default class SBUUtils {
4+
static openSettings() {
5+
Linking.openSettings().catch(() => {
6+
if (Platform.OS === 'ios') Linking.openURL('App-Prefs:root');
7+
});
8+
}
9+
}

packages/uikit-react-native/src/localization/StringSet.type.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ export interface StringSet {
149149
};
150150
DIALOG: {
151151
ALERT_DEFAULT_OK: string;
152+
153+
ALERT_PERMISSIONS_TITLE: string;
154+
ALERT_PERMISSIONS_MESSAGE: (permission: string, appName: string) => string;
155+
ALERT_PERMISSIONS_OK: string;
156+
152157
PROMPT_DEFAULT_OK: string;
153158
PROMPT_DEFAULT_CANCEL: string;
154159
PROMPT_DEFAULT_PLACEHOLDER: string;
@@ -316,6 +321,11 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
316321
},
317322
DIALOG: {
318323
ALERT_DEFAULT_OK: 'OK',
324+
ALERT_PERMISSIONS_TITLE: 'Allow permission',
325+
ALERT_PERMISSIONS_MESSAGE: (permission, appName = 'Application') => {
326+
return `${appName} need permission to access your ${permission}. Go to Settings to allow access`;
327+
},
328+
ALERT_PERMISSIONS_OK: 'SETTINGS',
319329
PROMPT_DEFAULT_OK: 'Submit',
320330
PROMPT_DEFAULT_CANCEL: 'Cancel',
321331
PROMPT_DEFAULT_PLACEHOLDER: 'Enter',

packages/uikit-react-native/src/platform/createFileService.expo.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type * as ExpoMediaLibrary from 'expo-media-library';
55

66
import { getFileExtension, getFileType } from '@sendbird/uikit-utils';
77

8+
import SBUError from '../libs/SBUError';
89
import type { ExpoMediaLibraryPermissionResponse, ExpoPermissionResponse } from '../utils/expoPermissionGranted';
910
import expoPermissionGranted from '../utils/expoPermissionGranted';
1011
import fileTypeGuard from '../utils/fileTypeGuard';
@@ -55,7 +56,7 @@ const createExpoFileService = ({
5556
if (!hasPermission) {
5657
const granted = await this.requestCameraPermission();
5758
if (!granted) {
58-
options?.onOpenFailureWithToastMessage?.();
59+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
5960
return null;
6061
}
6162
}
@@ -89,7 +90,7 @@ const createExpoFileService = ({
8990
if (!hasPermission) {
9091
const granted = await this.requestMediaLibraryPermission('read');
9192
if (!granted) {
92-
options?.onOpenFailureWithToastMessage?.();
93+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
9394
return null;
9495
}
9596
}
@@ -123,8 +124,8 @@ const createExpoFileService = ({
123124
if (response.type === 'cancel') return null;
124125
const { mimeType, uri, size, name } = response;
125126
return fileTypeGuard({ uri, size, name, type: mimeType });
126-
} catch {
127-
options?.onOpenFailureWithToastMessage?.();
127+
} catch (e) {
128+
options?.onOpenFailure?.(SBUError.UNKNOWN, e);
128129
return null;
129130
}
130131
}

packages/uikit-react-native/src/platform/createFileService.native.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { Permission } from 'react-native-permissions';
88

99
import { getFileExtension, getFileType } from '@sendbird/uikit-utils';
1010

11+
import SBUError from '../libs/SBUError';
1112
import fileTypeGuard from '../utils/fileTypeGuard';
1213
import nativePermissionGranted from '../utils/nativePermissionGranted';
1314
import type {
@@ -87,7 +88,7 @@ const createNativeFileService = ({
8788
if (!hasPermission) {
8889
const granted = await this.requestCameraPermission();
8990
if (!granted) {
90-
options?.onOpenFailureWithToastMessage?.();
91+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
9192
return null;
9293
}
9394
}
@@ -109,7 +110,7 @@ const createNativeFileService = ({
109110
});
110111
if (response.didCancel) return null;
111112
if (response.errorCode === 'camera_unavailable') {
112-
options?.onOpenFailureWithToastMessage?.();
113+
options?.onOpenFailure?.(SBUError.DEVICE_UNAVAILABLE, new Error(response.errorMessage));
113114
return null;
114115
}
115116

@@ -126,7 +127,7 @@ const createNativeFileService = ({
126127
if (!hasPermission) {
127128
const granted = await this.requestMediaLibraryPermission();
128129
if (!granted) {
129-
options?.onOpenFailureWithToastMessage?.();
130+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
130131
return null;
131132
}
132133
}
@@ -148,7 +149,7 @@ const createNativeFileService = ({
148149
});
149150
if (response.didCancel) return null;
150151
if (response.errorCode === 'camera_unavailable') {
151-
options?.onOpenFailureWithToastMessage?.();
152+
options?.onOpenFailure?.(SBUError.DEVICE_UNAVAILABLE, new Error(response.errorMessage));
152153
return null;
153154
}
154155

@@ -162,7 +163,7 @@ const createNativeFileService = ({
162163
return fileTypeGuard({ uri, size, name, type });
163164
} catch (e) {
164165
if (!documentPickerModule.isCancel(e) && documentPickerModule.isInProgress(e)) {
165-
options?.onOpenFailureWithToastMessage?.();
166+
options?.onOpenFailure?.(SBUError.UNKNOWN, e);
166167
}
167168
return null;
168169
}

packages/uikit-react-native/src/platform/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type SBUError from '../libs/SBUError';
2+
13
export type Unsubscribe = () => void | undefined;
24
export type DownloadedPath = string;
35
export type FilePickerResponse = FileType | null;
@@ -23,7 +25,7 @@ export interface ClipboardServiceInterface {
2325
export interface FileServiceInterface extends FilePickerServiceInterface, FileSystemServiceInterface {}
2426

2527
export interface OpenResultListener {
26-
onOpenFailureWithToastMessage?: () => void;
28+
onOpenFailure?: (error: SBUError, originError?: unknown) => void;
2729
}
2830
export interface OpenMediaLibraryOptions extends OpenResultListener {
2931
selectionLimit?: number;

sample/src/screens/uikit-app/GroupChannelTabs/SettingsScreen.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
useUIKitTheme,
1919
} from '@sendbird/uikit-react-native-foundation';
2020
import { useBottomSheet } from '@sendbird/uikit-react-native-foundation';
21+
import SBUError from '@sendbird/uikit-react-native/src/libs/SBUError';
22+
import SBUUtils from '@sendbird/uikit-react-native/src/libs/SBUUtils';
2123

2224
import { useAppNavigation } from '../../../hooks/useAppNavigation';
2325
import useAppearance from '../../../hooks/useAppearance';
@@ -56,7 +58,17 @@ const SettingsScreen = () => {
5658
onPress: async () => {
5759
const photo = await fileService.openCamera({
5860
mediaType: 'photo',
59-
onOpenFailureWithToastMessage: () => toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error'),
61+
onOpenFailure: (error) => {
62+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
63+
alert({
64+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
65+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('camera', 'UIKitSample'),
66+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
67+
});
68+
} else {
69+
toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error');
70+
}
71+
},
6072
});
6173

6274
if (!photo) return;
@@ -70,7 +82,17 @@ const SettingsScreen = () => {
7082
const files = await fileService.openMediaLibrary({
7183
selectionLimit: 1,
7284
mediaType: 'photo',
73-
onOpenFailureWithToastMessage: () => toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error'),
85+
onOpenFailure: (error) => {
86+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
87+
alert({
88+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
89+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('device storage', 'UIKitSample'),
90+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
91+
});
92+
} else {
93+
toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error');
94+
}
95+
},
7496
});
7597
if (!files || !files[0]) return;
7698

0 commit comments

Comments
 (0)