@codeleap/mobile 1.9.26 → 1.9.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Backdrop.d.ts +66 -0
- package/dist/components/Backdrop.js +70 -0
- package/dist/components/Backdrop.js.map +1 -0
- package/dist/components/Button.d.ts +11 -4
- package/dist/components/Button.js +17 -6
- package/dist/components/Button.js.map +1 -1
- package/dist/components/EmptyPlaceholder.d.ts +12 -0
- package/dist/components/EmptyPlaceholder.js +54 -0
- package/dist/components/EmptyPlaceholder.js.map +1 -0
- package/dist/components/FileInput.js +0 -2
- package/dist/components/FileInput.js.map +1 -1
- package/dist/components/Gap.d.ts +8 -0
- package/dist/components/Gap.js +60 -0
- package/dist/components/Gap.js.map +1 -0
- package/dist/components/Image.js +2 -2
- package/dist/components/Image.js.map +1 -1
- package/dist/components/List.d.ts +25 -6
- package/dist/components/List.js +20 -3
- package/dist/components/List.js.map +1 -1
- package/dist/components/Modal/index.d.ts +2 -1
- package/dist/components/Modal/index.js +27 -27
- package/dist/components/Modal/index.js.map +1 -1
- package/dist/components/Modal/styles.d.ts +4 -15
- package/dist/components/Modal/styles.js +27 -23
- package/dist/components/Modal/styles.js.map +1 -1
- package/dist/components/NewModal/index.d.ts +27 -0
- package/dist/components/NewModal/index.js +99 -0
- package/dist/components/NewModal/index.js.map +1 -0
- package/dist/components/NewModal/styles.d.ts +57 -0
- package/dist/components/NewModal/styles.js +58 -0
- package/dist/components/NewModal/styles.js.map +1 -0
- package/dist/components/NewPager/index.js +3 -7
- package/dist/components/NewPager/index.js.map +1 -1
- package/dist/components/Overlay.js +13 -9
- package/dist/components/Overlay.js.map +1 -1
- package/dist/components/Scroll.d.ts +4 -2
- package/dist/components/Scroll.js.map +1 -1
- package/dist/components/SegmentedControl/index.d.ts +42 -0
- package/dist/components/SegmentedControl/index.js +137 -0
- package/dist/components/SegmentedControl/index.js.map +1 -0
- package/dist/components/SegmentedControl/styles.d.ts +54 -0
- package/dist/components/SegmentedControl/styles.js +36 -0
- package/dist/components/SegmentedControl/styles.js.map +1 -0
- package/dist/components/SegmentedControl.d.ts +5 -0
- package/dist/components/SegmentedControl.js +32 -0
- package/dist/components/SegmentedControl.js.map +1 -0
- package/dist/components/Select/index.js +1 -1
- package/dist/components/Select/index.js.map +1 -1
- package/dist/components/Text.d.ts +8 -3
- package/dist/components/Text.js +12 -5
- package/dist/components/Text.js.map +1 -1
- package/dist/components/TextInput.d.ts +4 -2
- package/dist/components/TextInput.js +2 -2
- package/dist/components/TextInput.js.map +1 -1
- package/dist/components/Touchable.d.ts +5 -3
- package/dist/components/Touchable.js +26 -19
- package/dist/components/Touchable.js.map +1 -1
- package/dist/components/View.js +1 -1
- package/dist/components/View.js.map +1 -1
- package/dist/components/_Modal/index.d.ts +27 -0
- package/dist/components/_Modal/index.js +114 -0
- package/dist/components/_Modal/index.js.map +1 -0
- package/dist/components/_Modal/styles.d.ts +64 -0
- package/dist/components/_Modal/styles.js +60 -0
- package/dist/components/_Modal/styles.js.map +1 -0
- package/dist/components/components.d.ts +5 -1
- package/dist/components/components.js +5 -1
- package/dist/components/components.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/textInputMask.d.ts +4 -7
- package/dist/modules/types/textInputMask.d.ts +5 -10
- package/dist/utils/ModalManager/components.d.ts +12 -0
- package/dist/utils/ModalManager/components.js +86 -0
- package/dist/utils/ModalManager/components.js.map +1 -0
- package/dist/utils/ModalManager/context.d.ts +47 -0
- package/dist/utils/ModalManager/context.js +196 -0
- package/dist/utils/ModalManager/context.js.map +1 -0
- package/dist/utils/ModalManager/index.d.ts +10 -0
- package/dist/utils/ModalManager/index.js +12 -0
- package/dist/utils/ModalManager/index.js.map +1 -0
- package/dist/utils/PermissionManager/components.d.ts +18 -0
- package/dist/utils/PermissionManager/components.js +52 -0
- package/dist/utils/PermissionManager/components.js.map +1 -0
- package/dist/utils/PermissionManager/context.d.ts +52 -0
- package/dist/utils/PermissionManager/context.js +325 -0
- package/dist/utils/PermissionManager/context.js.map +1 -0
- package/dist/utils/PermissionManager/index.d.ts +4 -0
- package/dist/utils/PermissionManager/index.js +9 -0
- package/dist/utils/PermissionManager/index.js.map +1 -0
- package/dist/utils/PermissionManager/types.d.ts +13 -0
- package/dist/utils/PermissionManager/types.js +3 -0
- package/dist/utils/PermissionManager/types.js.map +1 -0
- package/dist/utils/hooks.d.ts +6 -0
- package/dist/utils/hooks.js +62 -0
- package/dist/utils/hooks.js.map +1 -0
- package/package.json +2 -1
- package/src/components/Backdrop.tsx +77 -0
- package/src/components/Button.tsx +31 -8
- package/src/components/EmptyPlaceholder.tsx +53 -0
- package/src/components/FileInput.tsx +2 -2
- package/src/components/Gap.tsx +40 -0
- package/src/components/Image.tsx +3 -2
- package/src/components/List.tsx +50 -6
- package/src/components/Modal/index.tsx +39 -49
- package/src/components/Modal/styles.ts +36 -39
- package/src/components/NewPager/index.tsx +5 -7
- package/src/components/Overlay.tsx +22 -13
- package/src/components/Pager/index.tsx +19 -19
- package/src/components/Pager/styles.ts +6 -7
- package/src/components/Scroll.tsx +3 -1
- package/src/components/SegmentedControl/index.tsx +182 -0
- package/src/components/SegmentedControl/styles.ts +65 -0
- package/src/components/Select/index.tsx +1 -2
- package/src/components/Text.tsx +23 -10
- package/src/components/TextInput.tsx +4 -2
- package/src/components/Touchable.tsx +31 -20
- package/src/components/View.tsx +1 -1
- package/src/components/_Modal/index.tsx +162 -0
- package/src/components/_Modal/styles.ts +125 -0
- package/src/components/components.ts +5 -1
- package/src/index.ts +6 -0
- package/src/modules/imageCropPicker.d.ts +497 -0
- package/src/modules/index.d.ts +186 -0
- package/src/modules/types/textInputMask.ts +6 -10
- package/src/utils/ModalManager/components.tsx +69 -0
- package/src/utils/ModalManager/context.tsx +247 -0
- package/src/utils/ModalManager/index.ts +13 -0
- package/src/utils/PermissionManager/context.tsx +299 -0
- package/src/utils/PermissionManager/index.ts +20 -0
- package/src/utils/PermissionManager/types.ts +24 -0
- package/src/utils/hooks.ts +65 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { usePrevious, onMount, onUpdate, PropsOf } from '@codeleap/common'
|
|
3
|
+
import { useModalContext } from './context'
|
|
4
|
+
import { Portal } from '@gorhom/portal'
|
|
5
|
+
import { Modal as _Modal } from '../../components/Modal'
|
|
6
|
+
|
|
7
|
+
export type ManagedModalProps<T = PropsOf<typeof _Modal>> = Omit<T, 'visible' | 'toggle'> & {
|
|
8
|
+
id?: string
|
|
9
|
+
initialVisible ?: boolean
|
|
10
|
+
parent?: string
|
|
11
|
+
absolute?: boolean
|
|
12
|
+
visible?: boolean
|
|
13
|
+
toggle?: PropsOf<typeof _Modal>['toggle']
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Modal:React.FC<ManagedModalProps> = ({
|
|
17
|
+
children,
|
|
18
|
+
id,
|
|
19
|
+
absolute = true,
|
|
20
|
+
initialVisible = false,
|
|
21
|
+
parent,
|
|
22
|
+
...props
|
|
23
|
+
}) => {
|
|
24
|
+
|
|
25
|
+
const modalId = id
|
|
26
|
+
const modals = useModalContext()
|
|
27
|
+
|
|
28
|
+
onMount(() => {
|
|
29
|
+
if (!modalId) return
|
|
30
|
+
modals.setModal(modalId, {
|
|
31
|
+
attachedTo: [],
|
|
32
|
+
attachments: [],
|
|
33
|
+
visible: initialVisible,
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const prevParent = usePrevious(parent)
|
|
38
|
+
|
|
39
|
+
onUpdate(() => {
|
|
40
|
+
if (!modalId) return
|
|
41
|
+
if (!!parent || !!prevParent) {
|
|
42
|
+
modals.attach(modalId, parent)
|
|
43
|
+
}
|
|
44
|
+
}, [parent])
|
|
45
|
+
|
|
46
|
+
const visible = modalId ? modals.isVisible(modalId) : props.visible
|
|
47
|
+
|
|
48
|
+
const ctxProps = modals?.state?.[modalId]?.props
|
|
49
|
+
const content = (
|
|
50
|
+
<_Modal {...props} {...ctxProps} visible={visible} toggle={() => {
|
|
51
|
+
if (modalId) {
|
|
52
|
+
modals.toggleModal(modalId)
|
|
53
|
+
} else {
|
|
54
|
+
props?.toggle?.()
|
|
55
|
+
}
|
|
56
|
+
}}>
|
|
57
|
+
{children}
|
|
58
|
+
</_Modal>
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if (absolute) {
|
|
62
|
+
return <Portal>
|
|
63
|
+
{content}
|
|
64
|
+
</Portal>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return content
|
|
68
|
+
}
|
|
69
|
+
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { AnyFunction, onUpdate, TypeGuards, useCodeleapContext, useState } from '@codeleap/common'
|
|
3
|
+
import { PortalProvider } from '@gorhom/portal'
|
|
4
|
+
import { ModalProps } from '../../components/Modal'
|
|
5
|
+
|
|
6
|
+
export type AppModalProps = {
|
|
7
|
+
visible: boolean
|
|
8
|
+
attachments: string[]
|
|
9
|
+
attachedTo: string[]
|
|
10
|
+
props?: any
|
|
11
|
+
}
|
|
12
|
+
type TModalState = AppModalProps
|
|
13
|
+
type ModalTransitionOptions = {
|
|
14
|
+
duration?: number
|
|
15
|
+
props?: any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type TModalContext = {
|
|
19
|
+
state: Record<string, TModalState>
|
|
20
|
+
toggleModal: (name: string, setTo?: boolean, props?: any) => void
|
|
21
|
+
setModal: (name: string, to: Partial<TModalState>) => void
|
|
22
|
+
currentModal: string
|
|
23
|
+
isVisible: (name: string) => boolean
|
|
24
|
+
transition: (from: string, to: string, options?: ModalTransitionOptions) => Promise<void>
|
|
25
|
+
attach: (modal: string, to: string) => void
|
|
26
|
+
transitionDuration: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ModalContext = React.createContext({} as TModalContext)
|
|
30
|
+
|
|
31
|
+
export function Provider({ children }) {
|
|
32
|
+
const [modals, setModals] = useState<TModalContext['state']>({})
|
|
33
|
+
const currentModal = Object.keys(modals).find(name => modals[name].visible)
|
|
34
|
+
|
|
35
|
+
function isVisible(name: string) {
|
|
36
|
+
return !!modals[name]?.visible
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const toggleModal:TModalContext['toggleModal'] = (name, set?: boolean, props?: any) => {
|
|
40
|
+
const visible = isVisible(name)
|
|
41
|
+
|
|
42
|
+
const newVisible = typeof set === 'boolean' ? set : !visible
|
|
43
|
+
|
|
44
|
+
setModals((current) => {
|
|
45
|
+
const attached = newVisible ? [] : current[name].attachments.map(m => [m, { ...current[m], visible: false }])
|
|
46
|
+
return {
|
|
47
|
+
...current,
|
|
48
|
+
[name]: {
|
|
49
|
+
...current[name],
|
|
50
|
+
visible: newVisible,
|
|
51
|
+
props,
|
|
52
|
+
},
|
|
53
|
+
...Object.fromEntries(attached),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const setModal:TModalContext['setModal'] = (name, to) => {
|
|
60
|
+
|
|
61
|
+
setModals((current) => ({
|
|
62
|
+
...current,
|
|
63
|
+
[name]: {
|
|
64
|
+
...current[name],
|
|
65
|
+
...to,
|
|
66
|
+
},
|
|
67
|
+
}))
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const codeleapCtx = useCodeleapContext()
|
|
72
|
+
const defaultDuration = codeleapCtx?.Theme?.values?.transitions?.modal?.duration || 300
|
|
73
|
+
const transition:TModalContext['transition'] = (from, to, options) => {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
|
|
77
|
+
if (!from) {
|
|
78
|
+
toggleModal(to, true, options?.props)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const _options:ModalTransitionOptions = {
|
|
82
|
+
duration: defaultDuration,
|
|
83
|
+
...options,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const toVisible = isVisible(to)
|
|
87
|
+
const fromVisible = isVisible(from)
|
|
88
|
+
|
|
89
|
+
// if (!fromVisible && !toVisible) {
|
|
90
|
+
// toggleModal(to, true, options?.props)
|
|
91
|
+
// return
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
toggleModal(from, false)
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
|
|
97
|
+
toggleModal(to, true, options?.props)
|
|
98
|
+
|
|
99
|
+
resolve()
|
|
100
|
+
}, _options.duration)
|
|
101
|
+
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function attach(modal: string, to: string) {
|
|
107
|
+
setModals((modals) => {
|
|
108
|
+
const toModal = modals[to]
|
|
109
|
+
const _modal = modals[modal]
|
|
110
|
+
if (!toModal || !_modal) return modals
|
|
111
|
+
|
|
112
|
+
const isAttached = toModal.attachments.includes(modal) || _modal.attachedTo.includes(to)
|
|
113
|
+
|
|
114
|
+
const newVal = { ...modals }
|
|
115
|
+
if (isAttached) {
|
|
116
|
+
|
|
117
|
+
newVal[to].attachments = newVal[to].attachments.filter(x => x !== modal)
|
|
118
|
+
newVal[modal].attachedTo = newVal[modal].attachedTo.filter(x => x !== to)
|
|
119
|
+
|
|
120
|
+
} else {
|
|
121
|
+
|
|
122
|
+
newVal[to].attachments.push(modal)
|
|
123
|
+
newVal[modal].attachedTo.push(to)
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
return newVal
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return <ModalContext.Provider value={{
|
|
131
|
+
state: modals,
|
|
132
|
+
toggleModal,
|
|
133
|
+
setModal,
|
|
134
|
+
currentModal,
|
|
135
|
+
attach,
|
|
136
|
+
isVisible,
|
|
137
|
+
transition,
|
|
138
|
+
transitionDuration: defaultDuration,
|
|
139
|
+
|
|
140
|
+
}}>
|
|
141
|
+
<PortalProvider>
|
|
142
|
+
|
|
143
|
+
{children}
|
|
144
|
+
</PortalProvider>
|
|
145
|
+
</ModalContext.Provider>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function useModalContext() {
|
|
149
|
+
const context = React.useContext(ModalContext)
|
|
150
|
+
return context
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type UseModalSequenceOptions = {
|
|
154
|
+
onFinish?: AnyFunction
|
|
155
|
+
resetOnFinish?: boolean
|
|
156
|
+
closeLastOnFinish?: boolean
|
|
157
|
+
waitForLastToCloseBeforeCallingFinish?: boolean
|
|
158
|
+
transitionOpts?: Partial<Parameters<TModalContext['transition']>[2]>
|
|
159
|
+
autoOpen?: boolean
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function useModalSequence(ids: string[], options?: UseModalSequenceOptions) {
|
|
163
|
+
|
|
164
|
+
const _options:UseModalSequenceOptions = {
|
|
165
|
+
closeLastOnFinish: true,
|
|
166
|
+
onFinish: () => {},
|
|
167
|
+
resetOnFinish: false,
|
|
168
|
+
waitForLastToCloseBeforeCallingFinish: true,
|
|
169
|
+
...options,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const modals = useModalContext()
|
|
173
|
+
const [idx, setIdx] = useState(0)
|
|
174
|
+
|
|
175
|
+
const state = {
|
|
176
|
+
currentId: ids[idx],
|
|
177
|
+
nextId: ids[idx + 1],
|
|
178
|
+
previousId: ids[idx - 1],
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
onUpdate(() => {
|
|
182
|
+
if (_options.autoOpen && typeof ids?.[0] === 'number') {
|
|
183
|
+
if (!modals.isVisible(ids[0])) {
|
|
184
|
+
modals.toggleModal(ids[0])
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}, [_options.autoOpen, ids?.[0]])
|
|
188
|
+
|
|
189
|
+
function next(props?: any) {
|
|
190
|
+
if (idx === ids.length - 1) {
|
|
191
|
+
if (_options.closeLastOnFinish) {
|
|
192
|
+
modals.transition(ids[idx], null).then(() => {
|
|
193
|
+
if (_options.waitForLastToCloseBeforeCallingFinish) {
|
|
194
|
+
_options.onFinish()
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
if (_options.resetOnFinish) {
|
|
199
|
+
reset()
|
|
200
|
+
}
|
|
201
|
+
if (!(_options.waitForLastToCloseBeforeCallingFinish && _options.closeLastOnFinish)) {
|
|
202
|
+
_options.onFinish()
|
|
203
|
+
}
|
|
204
|
+
return
|
|
205
|
+
} else {
|
|
206
|
+
if (!state.nextId) return
|
|
207
|
+
modals.transition(ids[idx], state.nextId, props)
|
|
208
|
+
setIdx(i => i + 1)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function previous(props?: any) {
|
|
212
|
+
if (!state.previousId) return
|
|
213
|
+
|
|
214
|
+
modals.transition(ids[idx], state.previousId, props)
|
|
215
|
+
setIdx(i => i - 1)
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function goto(idxOrId: string | number, props?: any) {
|
|
220
|
+
let newId:string = null
|
|
221
|
+
if (TypeGuards.isString(idxOrId)) {
|
|
222
|
+
newId = idxOrId
|
|
223
|
+
|
|
224
|
+
} else {
|
|
225
|
+
newId = ids[idxOrId]
|
|
226
|
+
}
|
|
227
|
+
modals.transition(ids[idx], newId, {
|
|
228
|
+
props,
|
|
229
|
+
})
|
|
230
|
+
setIdx(ids.indexOf(newId))
|
|
231
|
+
}
|
|
232
|
+
function reset() {
|
|
233
|
+
setIdx(0)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
reset,
|
|
238
|
+
next,
|
|
239
|
+
previous,
|
|
240
|
+
setModal: setIdx,
|
|
241
|
+
goto,
|
|
242
|
+
currentIdx: idx,
|
|
243
|
+
...state,
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useModalContext, useModalSequence, Provider } from './context'
|
|
2
|
+
import { Modal, ManagedModalProps } from './components'
|
|
3
|
+
|
|
4
|
+
export const ModalManager = {
|
|
5
|
+
useModalContext,
|
|
6
|
+
Modal,
|
|
7
|
+
Provider,
|
|
8
|
+
useModalSequence,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
ManagedModalProps,
|
|
13
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import React, { useContext, useState } from 'react'
|
|
2
|
+
import { ModalManager } from '@codeleap/mobile'
|
|
3
|
+
import { deepEqual, onMount, onUpdate, PermissionManager, PermissionTypes, useDebounce, usePrevious } from '@codeleap/common'
|
|
4
|
+
import { AppState, Linking } from 'react-native'
|
|
5
|
+
import { PermissionConfig, PermissionModalsConfig } from './types'
|
|
6
|
+
|
|
7
|
+
type TPermissionContext = {
|
|
8
|
+
state: Record<string, PermissionTypes.PermissionStatus>
|
|
9
|
+
modalConfig: PermissionModalsConfig<any>
|
|
10
|
+
manager: PermissionManager<any, any>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PermissionContext = React.createContext({} as TPermissionContext)
|
|
14
|
+
|
|
15
|
+
type PermissionProviderProps = {
|
|
16
|
+
children: React.ReactElement
|
|
17
|
+
AppPermissions: PermissionManager<any, any>
|
|
18
|
+
modalConfig: PermissionModalsConfig<any>
|
|
19
|
+
}
|
|
20
|
+
type PermissionsRecord = PermissionManager<any, any>['values']
|
|
21
|
+
function getStatuses(state: PermissionsRecord) {
|
|
22
|
+
const statuses = Object.entries(state).map(([k, v]) => [k, v.status])
|
|
23
|
+
return Object.fromEntries(statuses)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Provider({ children, AppPermissions, modalConfig }:PermissionProviderProps) {
|
|
27
|
+
|
|
28
|
+
const [state, setState] = useState(() => getStatuses(AppPermissions.values))
|
|
29
|
+
|
|
30
|
+
onMount(() => {
|
|
31
|
+
|
|
32
|
+
AppState.addEventListener('focus', () => {
|
|
33
|
+
|
|
34
|
+
AppPermissions.update().then((vals) => {
|
|
35
|
+
const statuses = getStatuses(vals)
|
|
36
|
+
if (!deepEqual(statuses, state)) {
|
|
37
|
+
setState({ ...statuses })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return <PermissionContext.Provider value={{
|
|
46
|
+
state,
|
|
47
|
+
modalConfig: modalConfig,
|
|
48
|
+
manager: AppPermissions,
|
|
49
|
+
}}>
|
|
50
|
+
{children}
|
|
51
|
+
</PermissionContext.Provider>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type TAskManyResults<T extends string> =Record<T, PermissionTypes.PermissionStatus> & {
|
|
55
|
+
overall
|
|
56
|
+
: PermissionTypes.PermissionStatus
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type AskManyOpts<T extends string|number|symbol > = {
|
|
60
|
+
breakOnDenied?: T[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type UsePermissions<
|
|
64
|
+
_PermissionNames extends string,
|
|
65
|
+
PermissionNames extends string = `${_PermissionNames}?` | _PermissionNames> = () => TPermissionContext & {
|
|
66
|
+
askPermission: (name: PermissionNames, onResolve?: (status: PermissionTypes.PermissionStatus) => any) => any
|
|
67
|
+
askMany<T extends PermissionNames, R = TAskManyResults<T>>(
|
|
68
|
+
perms: T[],
|
|
69
|
+
onResolve?: (res:R) => any,
|
|
70
|
+
options?: AskManyOpts<T>
|
|
71
|
+
):Promise<void>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const usePermissions:UsePermissions<any> = () => {
|
|
75
|
+
const modalCtx = ModalManager.useModalContext()
|
|
76
|
+
const permissionCtx = useContext(PermissionContext)
|
|
77
|
+
|
|
78
|
+
async function askPermission(name: string, onResolve?: (status: PermissionTypes.PermissionStatus, modalName?: string) => any) {
|
|
79
|
+
const status = await permissionCtx.manager.get(name, {
|
|
80
|
+
ask: false,
|
|
81
|
+
askOnDenied: false,
|
|
82
|
+
askOnPending: false,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const permissionModalName = `permissions.${name}`
|
|
86
|
+
|
|
87
|
+
if (!status.isGranted) {
|
|
88
|
+
|
|
89
|
+
modalCtx.toggleModal(permissionModalName, true, {
|
|
90
|
+
onPermissionResolve: (status) => {
|
|
91
|
+
modalCtx.toggleModal(permissionModalName, false, {})
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
onResolve?.(status, permissionModalName)
|
|
94
|
+
|
|
95
|
+
}, modalCtx.transitionDuration)
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
} else {
|
|
100
|
+
onResolve?.(status.status as unknown as PermissionTypes.PermissionStatus)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const askMany = async (
|
|
105
|
+
perms: any[],
|
|
106
|
+
onResolve?: (res:any) => any,
|
|
107
|
+
options?: AskManyOpts<any>,
|
|
108
|
+
) => {
|
|
109
|
+
|
|
110
|
+
let prevModal = null
|
|
111
|
+
const results = {}
|
|
112
|
+
|
|
113
|
+
for (const _permission of perms) {
|
|
114
|
+
const permission = _permission.replace('?', '')
|
|
115
|
+
const status = await permissionCtx.manager.get(permission, {
|
|
116
|
+
ask: false,
|
|
117
|
+
askOnDenied: false,
|
|
118
|
+
askOnPending: false,
|
|
119
|
+
})
|
|
120
|
+
results[permission] = status.status
|
|
121
|
+
const permissionModalName = `permissions.${permission}`
|
|
122
|
+
|
|
123
|
+
if (!status.isGranted) {
|
|
124
|
+
let onOpen = null
|
|
125
|
+
|
|
126
|
+
if (prevModal) {
|
|
127
|
+
|
|
128
|
+
onOpen = () => new Promise((resolve) => {
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
console.log('transition', {
|
|
131
|
+
prevModal,
|
|
132
|
+
permissionModalName,
|
|
133
|
+
|
|
134
|
+
})
|
|
135
|
+
modalCtx.transition(prevModal, permissionModalName, {
|
|
136
|
+
props: {
|
|
137
|
+
onPermissionResolve: (status) => {
|
|
138
|
+
resolve(status)
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
} else {
|
|
145
|
+
onOpen = () => new Promise((resolve) => {
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
modalCtx.toggleModal(permissionModalName, true, {
|
|
148
|
+
onPermissionResolve: (status) => {
|
|
149
|
+
resolve(status)
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
results[permission] = await onOpen()
|
|
158
|
+
prevModal = permissionModalName
|
|
159
|
+
|
|
160
|
+
if (!_permission.endsWith('?') && results[permission] !== 'granted') {
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (prevModal) {
|
|
166
|
+
console.log(`Close prev modal`, {
|
|
167
|
+
prevModal,
|
|
168
|
+
})
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
modalCtx.toggleModal(prevModal, false, {})
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
onResolve({
|
|
174
|
+
...results,
|
|
175
|
+
overall: Object.values(results).every(x => x === 'granted') ? 'granted' : 'denied',
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
askPermission,
|
|
181
|
+
askMany,
|
|
182
|
+
...permissionCtx,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function usePermissionModal(permissionName: string) {
|
|
187
|
+
|
|
188
|
+
const modalId = `permissions.${permissionName}`
|
|
189
|
+
const modals = ModalManager.useModalContext()
|
|
190
|
+
const permissionCtx = usePermissions()
|
|
191
|
+
const modalState = modals.state[modalId]
|
|
192
|
+
|
|
193
|
+
const currentState = permissionCtx.state[permissionName]
|
|
194
|
+
const status = currentState
|
|
195
|
+
const [debouncedStatus, reset] = useDebounce(status, modals.transitionDuration * 0.5)
|
|
196
|
+
|
|
197
|
+
function getConfig(withStatus) {
|
|
198
|
+
return {
|
|
199
|
+
...permissionCtx.modalConfig[permissionName],
|
|
200
|
+
...permissionCtx.modalConfig[permissionName]?.[withStatus],
|
|
201
|
+
} as PermissionConfig
|
|
202
|
+
}
|
|
203
|
+
const config = getConfig(debouncedStatus)
|
|
204
|
+
|
|
205
|
+
function onPermissionResolve(_status?: PermissionTypes.PermissionStatus) {
|
|
206
|
+
modalState?.props?.onPermissionResolve?.(_status || status)
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
onUpdate(() => {
|
|
211
|
+
|
|
212
|
+
if (modalState?.visible && !!status) {
|
|
213
|
+
|
|
214
|
+
if (status === 'granted') {
|
|
215
|
+
reset()
|
|
216
|
+
onPermissionResolve()
|
|
217
|
+
} else {
|
|
218
|
+
|
|
219
|
+
if (!deepEqual(config, getConfig(status))) {
|
|
220
|
+
|
|
221
|
+
modals.transition(modalId, modalId, {
|
|
222
|
+
props: modalState?.props,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
}, [status, modalState?.visible])
|
|
229
|
+
|
|
230
|
+
async function onAllow() {
|
|
231
|
+
|
|
232
|
+
switch (config.onAllow) {
|
|
233
|
+
case 'ask':
|
|
234
|
+
const newStatus = await permissionCtx.manager.get(permissionName, {
|
|
235
|
+
ask: true,
|
|
236
|
+
askOnDenied: true,
|
|
237
|
+
askOnPending: true,
|
|
238
|
+
})
|
|
239
|
+
if (!newStatus.isGranted) {
|
|
240
|
+
onPermissionResolve(newStatus.status)
|
|
241
|
+
}
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
case 'openSettings':
|
|
245
|
+
default:
|
|
246
|
+
Linking.openSettings()
|
|
247
|
+
break
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function onDeny() {
|
|
253
|
+
onPermissionResolve()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
onAllow,
|
|
258
|
+
onDeny,
|
|
259
|
+
modalId,
|
|
260
|
+
permissionName,
|
|
261
|
+
modalState: {
|
|
262
|
+
...modalState,
|
|
263
|
+
|
|
264
|
+
},
|
|
265
|
+
currentState,
|
|
266
|
+
config,
|
|
267
|
+
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
type PermissionManagerArgTypes<M extends PermissionManager<any, any>> = {
|
|
272
|
+
perms: M extends PermissionManager<infer P, any> ? P extends Record<string, any> ? P : never : never
|
|
273
|
+
opts: M extends PermissionManager<any, infer O> ? O : never
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
type CreateTypedPermissionHooksArgs<
|
|
277
|
+
M extends PermissionManager<any, any>,
|
|
278
|
+
Args extends PermissionManagerArgTypes<M> = PermissionManagerArgTypes<M>,
|
|
279
|
+
ModalConfig extends PermissionModalsConfig<keyof Args['perms']> = PermissionModalsConfig<keyof Args['perms']>
|
|
280
|
+
> = {
|
|
281
|
+
modalConfig: ModalConfig
|
|
282
|
+
permissionsManager: M
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function createTypedPermissionHooks<
|
|
286
|
+
M extends PermissionManager<any, any>,
|
|
287
|
+
Args extends PermissionManagerArgTypes<M> = PermissionManagerArgTypes<M>
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
289
|
+
>(configuration: CreateTypedPermissionHooksArgs<M>) {
|
|
290
|
+
|
|
291
|
+
const _usePermissions = usePermissions as UsePermissions<Exclude<keyof Args['perms'], number | symbol>>
|
|
292
|
+
const _usePermissionModal = usePermissionModal as ((name: keyof Args['perms']) => ReturnType<typeof usePermissionModal>)
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
usePermissions: _usePermissions,
|
|
296
|
+
usePermissionModal: _usePermissionModal,
|
|
297
|
+
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { usePermissions, Provider, createTypedPermissionHooks, usePermissionModal } from './context'
|
|
2
|
+
import {
|
|
3
|
+
|
|
4
|
+
PermissionConfig,
|
|
5
|
+
PermissionModalsConfig,
|
|
6
|
+
|
|
7
|
+
} from './types'
|
|
8
|
+
export {
|
|
9
|
+
usePermissions,
|
|
10
|
+
Provider,
|
|
11
|
+
usePermissionModal,
|
|
12
|
+
createTypedPermissionHooks,
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
|
|
18
|
+
PermissionConfig,
|
|
19
|
+
PermissionModalsConfig,
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PermissionTypes } from '@codeleap/common'
|
|
2
|
+
type NonGrantedPermissionTypes = Exclude<PermissionTypes.PermissionStatus, 'granted'>
|
|
3
|
+
|
|
4
|
+
export type BasePermissionConfig = {
|
|
5
|
+
fullscreenModal?: boolean
|
|
6
|
+
dismissable?: boolean
|
|
7
|
+
title: string
|
|
8
|
+
onAllow: 'openSettings' | 'ask'
|
|
9
|
+
|
|
10
|
+
description: string[]
|
|
11
|
+
icon: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type PermissionConfig = BasePermissionConfig &
|
|
15
|
+
Partial<
|
|
16
|
+
Record<
|
|
17
|
+
NonGrantedPermissionTypes,
|
|
18
|
+
Partial<BasePermissionConfig>
|
|
19
|
+
>
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
export type PermissionModalsConfig<PermissionNames extends string|number|symbol> = Partial<
|
|
23
|
+
Record<PermissionNames, PermissionConfig>
|
|
24
|
+
>
|