@@ -15,54 +15,96 @@ limitations under the License.
15
15
*/
16
16
17
17
import { logger } from "matrix-js-sdk/src/logger" ;
18
- import { ClientEvent , IMyDevice , Room } from "matrix-js-sdk/src/matrix" ;
19
- import { useCallback , useEffect , useState } from "react" ;
18
+ import { ClientEvent , MatrixClient } from "matrix-js-sdk/src/matrix" ;
19
+ import { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
20
20
21
21
import { MatrixClientPeg } from "../MatrixClientPeg" ;
22
+ import { Notifier } from "../Notifier" ;
22
23
import DMRoomMap from "../utils/DMRoomMap" ;
23
- import { useEventEmitter } from "./useEventEmitter" ;
24
24
25
25
export interface UserOnboardingContext {
26
- avatar : string | null ;
27
- myDevice : string ;
28
- devices : IMyDevice [ ] ;
29
- dmRooms : { [ userId : string ] : Room } ;
26
+ hasAvatar : boolean ;
27
+ hasDevices : boolean ;
28
+ hasDmRooms : boolean ;
29
+ hasNotificationsEnabled : boolean ;
30
30
}
31
31
32
- export function useUserOnboardingContext ( ) : UserOnboardingContext | null {
33
- const [ context , setContext ] = useState < UserOnboardingContext | null > ( null ) ;
32
+ const USER_ONBOARDING_CONTEXT_INTERVAL = 5000 ;
33
+
34
+ /**
35
+ * Returns a persistent, non-changing reference to a function
36
+ * This function proxies all its calls to the current value of the given input callback
37
+ *
38
+ * This allows you to use the current value of e.g., a state in a callback that’s used by e.g., a useEventEmitter or
39
+ * similar hook without re-registering the hook when the state changes
40
+ * @param value changing callback
41
+ */
42
+ function useRefOf < T extends any [ ] , R > ( value : ( ...values : T ) => R ) : ( ...values : T ) => R {
43
+ const ref = useRef ( value ) ;
44
+ ref . current = value ;
45
+ return useCallback (
46
+ ( ...values : T ) => ref . current ( ...values ) ,
47
+ [ ] ,
48
+ ) ;
49
+ }
34
50
51
+ function useUserOnboardingContextValue < T > ( defaultValue : T , callback : ( cli : MatrixClient ) => Promise < T > ) : T {
52
+ const [ value , setValue ] = useState < T > ( defaultValue ) ;
35
53
const cli = MatrixClientPeg . get ( ) ;
36
- const handler = useCallback ( async ( ) => {
37
- try {
38
- const profile = await cli . getProfileInfo ( cli . getUserId ( ) ) ;
39
54
40
- const myDevice = cli . getDeviceId ( ) ;
41
- const devices = await cli . getDevices ( ) ;
55
+ const handler = useRefOf ( callback ) ;
42
56
43
- const dmRooms = DMRoomMap . shared ( ) . getUniqueRoomsWithIndividuals ( ) ?? { } ;
44
- setContext ( {
45
- avatar : profile ?. avatar_url ?? null ,
46
- myDevice,
47
- devices : devices . devices ,
48
- dmRooms : dmRooms ,
49
- } ) ;
50
- } catch ( e ) {
51
- logger . warn ( "Could not load context for user onboarding task list: " , e ) ;
52
- setContext ( null ) ;
57
+ useEffect ( ( ) => {
58
+ if ( value ) {
59
+ return ;
53
60
}
54
- } , [ cli ] ) ;
55
61
56
- useEventEmitter ( cli , ClientEvent . AccountData , handler ) ;
57
- useEffect ( ( ) => {
58
- const handle = setInterval ( handler , 2000 ) ;
59
- handler ( ) ;
62
+ let handle : number | null = null ;
63
+ let enabled = true ;
64
+ const repeater = async ( ) => {
65
+ if ( handle !== null ) {
66
+ clearTimeout ( handle ) ;
67
+ handle = null ;
68
+ }
69
+ setValue ( await handler ( cli ) ) ;
70
+ if ( enabled ) {
71
+ handle = setTimeout ( repeater , USER_ONBOARDING_CONTEXT_INTERVAL ) ;
72
+ }
73
+ } ;
74
+ repeater ( ) . catch ( err => logger . warn ( "could not update user onboarding context" , err ) ) ;
75
+ cli . on ( ClientEvent . AccountData , repeater ) ;
60
76
return ( ) => {
61
- if ( handle ) {
62
- clearInterval ( handle ) ;
77
+ enabled = false ;
78
+ cli . off ( ClientEvent . AccountData , repeater ) ;
79
+ if ( handle !== null ) {
80
+ clearTimeout ( handle ) ;
81
+ handle = null ;
63
82
}
64
83
} ;
65
- } , [ handler ] ) ;
84
+ } , [ cli , handler , value ] ) ;
85
+ return value ;
86
+ }
87
+
88
+ export function useUserOnboardingContext ( ) : UserOnboardingContext | null {
89
+ const hasAvatar = useUserOnboardingContextValue ( false , async ( cli ) => {
90
+ const profile = await cli . getProfileInfo ( cli . getUserId ( ) ) ;
91
+ return Boolean ( profile ?. avatar_url ) ;
92
+ } ) ;
93
+ const hasDevices = useUserOnboardingContextValue ( false , async ( cli ) => {
94
+ const myDevice = cli . getDeviceId ( ) ;
95
+ const devices = await cli . getDevices ( ) ;
96
+ return Boolean ( devices . devices . find ( device => device . device_id !== myDevice ) ) ;
97
+ } ) ;
98
+ const hasDmRooms = useUserOnboardingContextValue ( false , async ( ) => {
99
+ const dmRooms = DMRoomMap . shared ( ) . getUniqueRoomsWithIndividuals ( ) ?? { } ;
100
+ return Boolean ( Object . keys ( dmRooms ) . length ) ;
101
+ } ) ;
102
+ const hasNotificationsEnabled = useUserOnboardingContextValue ( false , async ( ) => {
103
+ return Notifier . isPossible ( ) ;
104
+ } ) ;
66
105
67
- return context ;
106
+ return useMemo (
107
+ ( ) => ( { hasAvatar, hasDevices, hasDmRooms, hasNotificationsEnabled } ) ,
108
+ [ hasAvatar , hasDevices , hasDmRooms , hasNotificationsEnabled ] ,
109
+ ) ;
68
110
}
0 commit comments