Skip to content

Commit

Permalink
feat: added local configs screen to support dynamic configs
Browse files Browse the repository at this point in the history
  • Loading branch information
bang9 committed Oct 30, 2024
1 parent d37f9e2 commit 6993f29
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 8 deletions.
8 changes: 4 additions & 4 deletions sample/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import React from 'react';
import { AppRegistry, LogBox } from 'react-native';
import { AppRegistry, I18nManager, LogBox } from 'react-native';
import { withTouchReload } from 'react-native-touch-reload';
// import Sendbird from 'sendbird';

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

import { name as appName } from './app.json';
import AppRoot from './src/App';
import { withUIKitLocalConfigs } from './src/context/uikitLocalConfigs';
import { withAppearance } from './src/hooks/useAppearance';
import './src/libs/notification';

// Sendbird.setLogLevel(Sendbird.LogLevel.DEBUG);
Logger.setLogLevel('warn');
LogBox.ignoreLogs(['UIKit Warning', 'FileViewer > params.deleteMessage (Function)']);
I18nManager.allowRTL(true);

const App = withTouchReload(withAppearance(AppRoot));
const App = withTouchReload(withAppearance(withUIKitLocalConfigs(AppRoot)));
function HeadlessCheck({ isHeadless }) {
if (isHeadless) return null;
return <App />;
Expand Down
10 changes: 7 additions & 3 deletions sample/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Notifee from '@notifee/react-native';
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
import React, { useEffect } from 'react';
import React, { useContext, useEffect } from 'react';
import { AppState } from 'react-native';

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

import { UIKitLocalConfigsContext } from './context/uikitLocalConfigs';
// import LogView from './components/LogView';
import { APP_ID } from './env';
import { GetTranslucent, RootStack, SetSendbirdSDK, platformServices } from './factory';
Expand Down Expand Up @@ -45,10 +46,12 @@ import {
SignInScreen,
StorybookScreen,
ThemeColorsScreen,
UIKitConfigsScreen,
} from './screens';
import FileViewerScreen from './screens/uikit/FileViewerScreen';

const App = () => {
const { localConfigs } = useContext(UIKitLocalConfigsContext);
const { scheme } = useAppearance();
const isLightTheme = scheme === 'light';

Expand All @@ -62,8 +65,8 @@ const App = () => {
groupChannel: {
enableMention: true,
typingIndicatorTypes: new Set([TypingIndicatorType.Text, TypingIndicatorType.Bubble]),
replyType: 'thread',
threadReplySelectType: 'thread',
replyType: localConfigs.replyType,
threadReplySelectType: localConfigs.threadReplySelectType,
},
groupChannelList: {
enableTypingIndicator: true,
Expand Down Expand Up @@ -185,6 +188,7 @@ const Navigations = () => {
</RootStack.Group>

<RootStack.Group screenOptions={{ headerShown: true }}>
<RootStack.Screen name={Routes.UIKitConfigs} component={UIKitConfigsScreen} />
<RootStack.Screen name={Routes.ThemeColors} component={ThemeColorsScreen} />
<RootStack.Screen name={Routes.Palette} component={PaletteScreen} />
<RootStack.Screen name={Routes.Storybook} component={StorybookScreen} />
Expand Down
60 changes: 60 additions & 0 deletions sample/src/context/uikitLocalConfigs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { createContext, useState } from 'react';

import { uikitLocalConfigStorage } from '../factory/mmkv';

const KEY = 'uikitOptions';
const defaultOptions = {
rtl: false,
replyType: 'thread' as 'none' | 'thread' | 'quote_reply',
threadReplySelectType: 'thread' as 'thread' | 'parent',
};

type ContextValue = typeof defaultOptions;

export const UIKitLocalConfigsContext = createContext<{
localConfigs: ContextValue;
setLocalConfigs: React.Dispatch<React.SetStateAction<ContextValue>>;
}>({
localConfigs: defaultOptions,
setLocalConfigs: () => {},
});

export const UIKitLocalConfigsProvider = ({ children }: React.PropsWithChildren) => {
const [state, setState] = useState<ContextValue>(() => {
const data = uikitLocalConfigStorage.getString(KEY);
if (data) {
try {
return JSON.parse(data);
} catch {
return defaultOptions;
}
} else {
return defaultOptions;
}
});

return (
<UIKitLocalConfigsContext.Provider
value={{
localConfigs: state,
setLocalConfigs: (value) => {
setState((prev) => {
const next = typeof value === 'function' ? value(prev) : value;
uikitLocalConfigStorage.set(KEY, JSON.stringify(next));
return next;
});
},
}}
>
{children}
</UIKitLocalConfigsContext.Provider>
);
};

export const withUIKitLocalConfigs = (Component: React.ComponentType<object>) => {
return (props: object) => (
<UIKitLocalConfigsProvider>
<Component {...props} />
</UIKitLocalConfigsProvider>
);
};
2 changes: 2 additions & 0 deletions sample/src/factory/mmkv.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MMKV } from 'react-native-mmkv';

export const mmkv = new MMKV();

export const uikitLocalConfigStorage = new MMKV({ id: 'uikit.local.config' });
5 changes: 5 additions & 0 deletions sample/src/libs/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum Routes {
SignIn = 'SignIn',
Home = 'Home',

UIKitConfigs = 'UIKitConfigs',
Storybook = 'Storybook',
Palette = 'Palette',
ThemeColors = 'ThemeColors',
Expand Down Expand Up @@ -63,6 +64,10 @@ export type RouteParamsUnion =
route: Routes.Home;
params: undefined;
}
| {
route: Routes.UIKitConfigs;
params: undefined;
}
| {
route: Routes.Storybook;
params: undefined;
Expand Down
8 changes: 7 additions & 1 deletion sample/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ const HomeItems = [
desc: 'Live streams, Open community chat',
route: Routes.OpenChannelTabs,
},
{
image: undefined,
title: 'UIKit local configs',
desc: '',
route: Routes.UIKitConfigs,
},
{
image: undefined,
title: 'Storybook',
Expand Down Expand Up @@ -128,7 +134,7 @@ const styles = StyleSheet.create({
customSampleButton: {
alignItems: 'flex-start',
paddingHorizontal: 24,
paddingVertical: 22,
paddingVertical: 16,
elevation: 4,
shadowColor: 'black',
shadowOpacity: 0.15,
Expand Down
102 changes: 102 additions & 0 deletions sample/src/screens/UIKitLocalConfigsScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { useContext } from 'react';
import { I18nManager, Pressable, ScrollView, View } from 'react-native';

import { Icon, Switch, Text } from '@sendbird/uikit-react-native-foundation';

import { UIKitLocalConfigsContext } from '../context/uikitLocalConfigs';

const Divider = () => <View style={{ height: 1, backgroundColor: '#dcdcdc' }} />;

const UIKitLocalConfigsScreen = () => {
const { localConfigs, setLocalConfigs } = useContext(UIKitLocalConfigsContext);
return (
<ScrollView
style={{ backgroundColor: 'white' }}
contentContainerStyle={{
flexGrow: 1,
paddingHorizontal: 16,
paddingVertical: 12,
gap: 20,
backgroundColor: 'white',
}}
>
<Switchable
title={'RTL'}
description={'Right to left, please restart the app to apply the changes'}
value={localConfigs.rtl}
onChange={(value) => {
I18nManager.forceRTL(value);
setLocalConfigs((prev) => ({ ...prev, rtl: value }));
}}
/>
<Divider />

<Selectable
title={'Reply Type'}
value={localConfigs.replyType}
values={['none', 'thread', 'quote_reply']}
onChange={(value) => setLocalConfigs((prev) => ({ ...prev, replyType: value }))}
/>
{localConfigs.replyType === 'thread' && (
<View style={{ marginStart: 12, flexDirection: 'row' }}>
<View style={{ width: 4, height: '100%', backgroundColor: '#dcdcdc', marginEnd: 12 }} />
<Selectable
title={'Reply Select Type'}
value={localConfigs.threadReplySelectType}
values={['thread', 'parent']}
onChange={(value) => setLocalConfigs((prev) => ({ ...prev, threadReplySelectType: value }))}
/>
</View>
)}
</ScrollView>
);
};

const Switchable = ({
title,
description,
value,
onChange,
}: {
value: boolean;
onChange: (value: boolean) => void;
title?: string;
description?: string;
}) => {
return (
<View style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>
{!!title && <Text h2>{title}</Text>}
{!!description && <Text caption2>{description}</Text>}

<Switch value={value} onChangeValue={onChange} />
</View>
);
};

interface SelectableProps<T> {
value: T;
values: T[];
onChange: (value: T) => void;
title?: string;
}
const Selectable = <T extends string>({ title, values, value, onChange }: SelectableProps<T>) => {
return (
<View style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>
{!!title && <Text h2>{title}</Text>}
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{values.map((v) => (
<Pressable
key={v}
onPress={() => onChange(v)}
style={{ flexDirection: 'row', alignItems: 'center', gap: 4, marginEnd: 8 }}
>
<Icon icon={value === v ? 'checkbox-on' : 'checkbox-off'} size={24} />
<Text caption1>{v}</Text>
</Pressable>
))}
</View>
</View>
);
};

export default UIKitLocalConfigsScreen;
1 change: 1 addition & 0 deletions sample/src/screens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as SignInScreen } from './SignInScreen';
export { default as HomeScreen } from './HomeScreen';
export { default as StorybookScreen } from './StorybookScreen';
export { default as ErrorInfoScreen } from './ErrorInfoScreen';
export { default as UIKitConfigsScreen } from './UIKitLocalConfigsScreen';

0 comments on commit 6993f29

Please sign in to comment.