Skip to content

Commit 6993f29

Browse files
committed
feat: added local configs screen to support dynamic configs
1 parent d37f9e2 commit 6993f29

File tree

8 files changed

+188
-8
lines changed

8 files changed

+188
-8
lines changed

sample/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import React from 'react';
2-
import { AppRegistry, LogBox } from 'react-native';
2+
import { AppRegistry, I18nManager, LogBox } from 'react-native';
33
import { withTouchReload } from 'react-native-touch-reload';
4-
// import Sendbird from 'sendbird';
54

65
import { Logger } from '@sendbird/uikit-utils';
76

87
import { name as appName } from './app.json';
98
import AppRoot from './src/App';
9+
import { withUIKitLocalConfigs } from './src/context/uikitLocalConfigs';
1010
import { withAppearance } from './src/hooks/useAppearance';
1111
import './src/libs/notification';
1212

13-
// Sendbird.setLogLevel(Sendbird.LogLevel.DEBUG);
1413
Logger.setLogLevel('warn');
1514
LogBox.ignoreLogs(['UIKit Warning', 'FileViewer > params.deleteMessage (Function)']);
15+
I18nManager.allowRTL(true);
1616

17-
const App = withTouchReload(withAppearance(AppRoot));
17+
const App = withTouchReload(withAppearance(withUIKitLocalConfigs(AppRoot)));
1818
function HeadlessCheck({ isHeadless }) {
1919
if (isHeadless) return null;
2020
return <App />;

sample/src/App.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import Notifee from '@notifee/react-native';
22
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
3-
import React, { useEffect } from 'react';
3+
import React, { useContext, useEffect } from 'react';
44
import { AppState } from 'react-native';
55

66
import { SendbirdUIKitContainer, TypingIndicatorType, useSendbirdChat } from '@sendbird/uikit-react-native';
77
import { DarkUIKitTheme, LightUIKitTheme } from '@sendbird/uikit-react-native-foundation';
88

9+
import { UIKitLocalConfigsContext } from './context/uikitLocalConfigs';
910
// import LogView from './components/LogView';
1011
import { APP_ID } from './env';
1112
import { GetTranslucent, RootStack, SetSendbirdSDK, platformServices } from './factory';
@@ -45,10 +46,12 @@ import {
4546
SignInScreen,
4647
StorybookScreen,
4748
ThemeColorsScreen,
49+
UIKitConfigsScreen,
4850
} from './screens';
4951
import FileViewerScreen from './screens/uikit/FileViewerScreen';
5052

5153
const App = () => {
54+
const { localConfigs } = useContext(UIKitLocalConfigsContext);
5255
const { scheme } = useAppearance();
5356
const isLightTheme = scheme === 'light';
5457

@@ -62,8 +65,8 @@ const App = () => {
6265
groupChannel: {
6366
enableMention: true,
6467
typingIndicatorTypes: new Set([TypingIndicatorType.Text, TypingIndicatorType.Bubble]),
65-
replyType: 'thread',
66-
threadReplySelectType: 'thread',
68+
replyType: localConfigs.replyType,
69+
threadReplySelectType: localConfigs.threadReplySelectType,
6770
},
6871
groupChannelList: {
6972
enableTypingIndicator: true,
@@ -185,6 +188,7 @@ const Navigations = () => {
185188
</RootStack.Group>
186189

187190
<RootStack.Group screenOptions={{ headerShown: true }}>
191+
<RootStack.Screen name={Routes.UIKitConfigs} component={UIKitConfigsScreen} />
188192
<RootStack.Screen name={Routes.ThemeColors} component={ThemeColorsScreen} />
189193
<RootStack.Screen name={Routes.Palette} component={PaletteScreen} />
190194
<RootStack.Screen name={Routes.Storybook} component={StorybookScreen} />
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { createContext, useState } from 'react';
2+
3+
import { uikitLocalConfigStorage } from '../factory/mmkv';
4+
5+
const KEY = 'uikitOptions';
6+
const defaultOptions = {
7+
rtl: false,
8+
replyType: 'thread' as 'none' | 'thread' | 'quote_reply',
9+
threadReplySelectType: 'thread' as 'thread' | 'parent',
10+
};
11+
12+
type ContextValue = typeof defaultOptions;
13+
14+
export const UIKitLocalConfigsContext = createContext<{
15+
localConfigs: ContextValue;
16+
setLocalConfigs: React.Dispatch<React.SetStateAction<ContextValue>>;
17+
}>({
18+
localConfigs: defaultOptions,
19+
setLocalConfigs: () => {},
20+
});
21+
22+
export const UIKitLocalConfigsProvider = ({ children }: React.PropsWithChildren) => {
23+
const [state, setState] = useState<ContextValue>(() => {
24+
const data = uikitLocalConfigStorage.getString(KEY);
25+
if (data) {
26+
try {
27+
return JSON.parse(data);
28+
} catch {
29+
return defaultOptions;
30+
}
31+
} else {
32+
return defaultOptions;
33+
}
34+
});
35+
36+
return (
37+
<UIKitLocalConfigsContext.Provider
38+
value={{
39+
localConfigs: state,
40+
setLocalConfigs: (value) => {
41+
setState((prev) => {
42+
const next = typeof value === 'function' ? value(prev) : value;
43+
uikitLocalConfigStorage.set(KEY, JSON.stringify(next));
44+
return next;
45+
});
46+
},
47+
}}
48+
>
49+
{children}
50+
</UIKitLocalConfigsContext.Provider>
51+
);
52+
};
53+
54+
export const withUIKitLocalConfigs = (Component: React.ComponentType<object>) => {
55+
return (props: object) => (
56+
<UIKitLocalConfigsProvider>
57+
<Component {...props} />
58+
</UIKitLocalConfigsProvider>
59+
);
60+
};

sample/src/factory/mmkv.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import { MMKV } from 'react-native-mmkv';
22

33
export const mmkv = new MMKV();
4+
5+
export const uikitLocalConfigStorage = new MMKV({ id: 'uikit.local.config' });

sample/src/libs/navigation.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum Routes {
1111
SignIn = 'SignIn',
1212
Home = 'Home',
1313

14+
UIKitConfigs = 'UIKitConfigs',
1415
Storybook = 'Storybook',
1516
Palette = 'Palette',
1617
ThemeColors = 'ThemeColors',
@@ -63,6 +64,10 @@ export type RouteParamsUnion =
6364
route: Routes.Home;
6465
params: undefined;
6566
}
67+
| {
68+
route: Routes.UIKitConfigs;
69+
params: undefined;
70+
}
6671
| {
6772
route: Routes.Storybook;
6873
params: undefined;

sample/src/screens/HomeScreen.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ const HomeItems = [
2424
desc: 'Live streams, Open community chat',
2525
route: Routes.OpenChannelTabs,
2626
},
27+
{
28+
image: undefined,
29+
title: 'UIKit local configs',
30+
desc: '',
31+
route: Routes.UIKitConfigs,
32+
},
2733
{
2834
image: undefined,
2935
title: 'Storybook',
@@ -128,7 +134,7 @@ const styles = StyleSheet.create({
128134
customSampleButton: {
129135
alignItems: 'flex-start',
130136
paddingHorizontal: 24,
131-
paddingVertical: 22,
137+
paddingVertical: 16,
132138
elevation: 4,
133139
shadowColor: 'black',
134140
shadowOpacity: 0.15,
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useContext } from 'react';
2+
import { I18nManager, Pressable, ScrollView, View } from 'react-native';
3+
4+
import { Icon, Switch, Text } from '@sendbird/uikit-react-native-foundation';
5+
6+
import { UIKitLocalConfigsContext } from '../context/uikitLocalConfigs';
7+
8+
const Divider = () => <View style={{ height: 1, backgroundColor: '#dcdcdc' }} />;
9+
10+
const UIKitLocalConfigsScreen = () => {
11+
const { localConfigs, setLocalConfigs } = useContext(UIKitLocalConfigsContext);
12+
return (
13+
<ScrollView
14+
style={{ backgroundColor: 'white' }}
15+
contentContainerStyle={{
16+
flexGrow: 1,
17+
paddingHorizontal: 16,
18+
paddingVertical: 12,
19+
gap: 20,
20+
backgroundColor: 'white',
21+
}}
22+
>
23+
<Switchable
24+
title={'RTL'}
25+
description={'Right to left, please restart the app to apply the changes'}
26+
value={localConfigs.rtl}
27+
onChange={(value) => {
28+
I18nManager.forceRTL(value);
29+
setLocalConfigs((prev) => ({ ...prev, rtl: value }));
30+
}}
31+
/>
32+
<Divider />
33+
34+
<Selectable
35+
title={'Reply Type'}
36+
value={localConfigs.replyType}
37+
values={['none', 'thread', 'quote_reply']}
38+
onChange={(value) => setLocalConfigs((prev) => ({ ...prev, replyType: value }))}
39+
/>
40+
{localConfigs.replyType === 'thread' && (
41+
<View style={{ marginStart: 12, flexDirection: 'row' }}>
42+
<View style={{ width: 4, height: '100%', backgroundColor: '#dcdcdc', marginEnd: 12 }} />
43+
<Selectable
44+
title={'Reply Select Type'}
45+
value={localConfigs.threadReplySelectType}
46+
values={['thread', 'parent']}
47+
onChange={(value) => setLocalConfigs((prev) => ({ ...prev, threadReplySelectType: value }))}
48+
/>
49+
</View>
50+
)}
51+
</ScrollView>
52+
);
53+
};
54+
55+
const Switchable = ({
56+
title,
57+
description,
58+
value,
59+
onChange,
60+
}: {
61+
value: boolean;
62+
onChange: (value: boolean) => void;
63+
title?: string;
64+
description?: string;
65+
}) => {
66+
return (
67+
<View style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>
68+
{!!title && <Text h2>{title}</Text>}
69+
{!!description && <Text caption2>{description}</Text>}
70+
71+
<Switch value={value} onChangeValue={onChange} />
72+
</View>
73+
);
74+
};
75+
76+
interface SelectableProps<T> {
77+
value: T;
78+
values: T[];
79+
onChange: (value: T) => void;
80+
title?: string;
81+
}
82+
const Selectable = <T extends string>({ title, values, value, onChange }: SelectableProps<T>) => {
83+
return (
84+
<View style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>
85+
{!!title && <Text h2>{title}</Text>}
86+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
87+
{values.map((v) => (
88+
<Pressable
89+
key={v}
90+
onPress={() => onChange(v)}
91+
style={{ flexDirection: 'row', alignItems: 'center', gap: 4, marginEnd: 8 }}
92+
>
93+
<Icon icon={value === v ? 'checkbox-on' : 'checkbox-off'} size={24} />
94+
<Text caption1>{v}</Text>
95+
</Pressable>
96+
))}
97+
</View>
98+
</View>
99+
);
100+
};
101+
102+
export default UIKitLocalConfigsScreen;

sample/src/screens/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { default as SignInScreen } from './SignInScreen';
44
export { default as HomeScreen } from './HomeScreen';
55
export { default as StorybookScreen } from './StorybookScreen';
66
export { default as ErrorInfoScreen } from './ErrorInfoScreen';
7+
export { default as UIKitConfigsScreen } from './UIKitLocalConfigsScreen';

0 commit comments

Comments
 (0)