From 6b7a3e035f736cdc03018431e51883b46a2ac94c Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Tue, 16 May 2023 18:13:56 +0300 Subject: [PATCH 1/8] feat(chat-channel): implement base UI of channel creation screen --- .../CommonInput/hooks/useLabelAnimation.ts | 41 ++++++- src/components/Inputs/CommonInput/index.tsx | 28 +++-- src/navigation/Main.tsx | 3 + .../components/ChannelPhoto/index.tsx | 71 ++++++++++++ src/screens/ChatFlow/CreateChannel/index.tsx | 109 ++++++++++++++++++ 5 files changed, 239 insertions(+), 13 deletions(-) create mode 100644 src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx create mode 100644 src/screens/ChatFlow/CreateChannel/index.tsx diff --git a/src/components/Inputs/CommonInput/hooks/useLabelAnimation.ts b/src/components/Inputs/CommonInput/hooks/useLabelAnimation.ts index 5b930675b..42709cce9 100644 --- a/src/components/Inputs/CommonInput/hooks/useLabelAnimation.ts +++ b/src/components/Inputs/CommonInput/hooks/useLabelAnimation.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: ice License 1.0 -import {useEffect} from 'react'; +import {useCallback, useEffect} from 'react'; +import {LayoutChangeEvent} from 'react-native'; import { interpolate, useAnimatedStyle, @@ -11,6 +12,32 @@ import { export const useLabelAnimation = (isFocused: boolean, text?: string) => { const focusAnimation = useSharedValue(text ? 1 : 0); + const bodyHeight = useSharedValue(0); + + const labelHeight = useSharedValue(0); + + const onLayoutBody = useCallback( + ({ + nativeEvent: { + layout: {height}, + }, + }: LayoutChangeEvent) => { + bodyHeight.value = height; + }, + [bodyHeight], + ); + + const onLayoutLabel = useCallback( + ({ + nativeEvent: { + layout: {height}, + }, + }: LayoutChangeEvent) => { + labelHeight.value = height; + }, + [labelHeight], + ); + useEffect(() => { focusAnimation.value = withTiming(isFocused || text ? 1 : 0); }, [focusAnimation, isFocused, text]); @@ -19,10 +46,18 @@ export const useLabelAnimation = (isFocused: boolean, text?: string) => { fontSize: interpolate(focusAnimation.value, [0, 1], [16, 12]), transform: [ { - translateY: interpolate(focusAnimation.value, [0, 1], [0, -14]), + translateY: interpolate( + focusAnimation.value, + [0, 1], + [(bodyHeight.value - labelHeight.value) / 2, -6], + ), }, ], })); - return {animatedStyle}; + return { + animatedStyle, + onLayoutBody, + onLayoutLabel, + }; }; diff --git a/src/components/Inputs/CommonInput/index.tsx b/src/components/Inputs/CommonInput/index.tsx index 3786fea69..b66369c64 100644 --- a/src/components/Inputs/CommonInput/index.tsx +++ b/src/components/Inputs/CommonInput/index.tsx @@ -58,7 +58,10 @@ export const CommonInput = ({ const [isFocused, setIsFocused] = useState(false); const inputRef = useRef(null); - const {animatedStyle} = useLabelAnimation(isFocused, value); + const {animatedStyle, onLayoutBody, onLayoutLabel} = useLabelAnimation( + isFocused, + value, + ); return ( - {icon} - + {icon ? {icon} : null} + {prefix} {onChange ? ( @@ -113,7 +116,8 @@ export const CommonInput = ({ styles.label, errorText ? styles.label_error : null, animatedStyle, - ]}> + ]} + onLayout={onLayoutLabel}> {errorText || label} @@ -150,14 +154,15 @@ export const CommonInput = ({ const RESULT_ICON_SIZE = rem(20); const styles = StyleSheet.create({ container: { - paddingHorizontal: rem(20), - height: rem(56), - borderWidth: 1, - borderRadius: rem(16), - backgroundColor: COLORS.wildSand, + paddingVertical: rem(12), + paddingHorizontal: rem(16), + minHeight: rem(56), flexDirection: 'row', alignItems: 'center', + borderWidth: 1, + borderRadius: rem(16), borderColor: COLORS.wildSand, + backgroundColor: COLORS.wildSand, }, container_error: { borderColor: COLORS.attention, @@ -165,9 +170,11 @@ const styles = StyleSheet.create({ container_focused: { borderColor: COLORS.congressBlue, }, + iconContainer: { + marginRight: rem(10), + }, body: { flex: 1, - marginLeft: rem(10), justifyContent: 'center', }, inputWrapper: { @@ -183,6 +190,7 @@ const styles = StyleSheet.create({ }, label: { position: 'absolute', + top: 0, left: 0, ...font(16, 20, 'medium', 'secondary'), }, diff --git a/src/navigation/Main.tsx b/src/navigation/Main.tsx index a12889166..78dee7ae1 100644 --- a/src/navigation/Main.tsx +++ b/src/navigation/Main.tsx @@ -17,6 +17,7 @@ import { } from '@react-navigation/bottom-tabs'; import {NavigatorScreenParams} from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; +import {CreateChannel} from '@screens/ChatFlow/CreateChannel'; import {BalanceHistory} from '@screens/HomeFlow/BalanceHistory'; import {Home} from '@screens/HomeFlow/Home'; import { @@ -134,6 +135,7 @@ export type MainStackParamList = { ProfilePrivacyEditStep1: undefined; ProfilePrivacyEditStep2: undefined; ProfilePrivacyEditStep3: undefined; + 'Chat/CreateChannel': undefined; }; export type HomeTabStackParamList = { @@ -399,6 +401,7 @@ export function MainNavigator() { options={modalOptions} component={JoinTelegramPopUp} /> + ); } diff --git a/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx new file mode 100644 index 000000000..81b993a6f --- /dev/null +++ b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {CameraIcon} from '@svg/CameraIcon'; +import {font} from '@utils/styles'; +import React, {useState} from 'react'; +import {Image, StyleSheet, Text, View} from 'react-native'; +import {rem} from 'rn-units'; + +export const CHANNEL_PHOTO_SIZE = rem(110); + +export const ChannelPhoto = () => { + const [photoUri, setPhotoUri] = useState(''); + + const onSelectPhoto = () => { + setPhotoUri('https://picsum.photos/200/300'); + }; + + return ( + + + {photoUri ? ( + + ) : ( + + + + )} + + + + {photoUri ? '_Change photo' : '_Add photo'} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center', + }, + + photoContainer: { + width: CHANNEL_PHOTO_SIZE, + height: CHANNEL_PHOTO_SIZE, + borderRadius: rem(20), + borderWidth: rem(4.5), + borderColor: COLORS.white, + backgroundColor: COLORS.white, + overflow: 'hidden', + }, + photo: { + ...StyleSheet.absoluteFillObject, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#E3EBF8', + }, + + text: { + marginTop: rem(8), + ...font(17, 20.4, 'semibold', 'white'), + }, +}); diff --git a/src/screens/ChatFlow/CreateChannel/index.tsx b/src/screens/ChatFlow/CreateChannel/index.tsx new file mode 100644 index 000000000..cca2e8c41 --- /dev/null +++ b/src/screens/ChatFlow/CreateChannel/index.tsx @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {PrimaryButton} from '@components/Buttons/PrimaryButton'; +import {CommonInput} from '@components/Inputs/CommonInput'; +import {LinesBackground} from '@components/LinesBackground'; +import {COLORS} from '@constants/colors'; +import {commonStyles} from '@constants/styles'; +import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; +import {useScrollShadow} from '@hooks/useScrollShadow'; +import {Header} from '@navigation/components/Header'; +import React, {useState} from 'react'; +import {StyleSheet, View} from 'react-native'; +import Animated from 'react-native-reanimated'; +import {rem} from 'rn-units'; + +import {CHANNEL_PHOTO_SIZE, ChannelPhoto} from './components/ChannelPhoto'; + +export const CreateChannel = () => { + const safeAreaInsets = useSafeAreaInsets(); + + const {scrollHandler, shadowStyle} = useScrollShadow(); + + const [title, setTitle] = useState(''); + + const [description, setDescription] = useState(''); + + return ( + +
+ + + + + + + + + + + + + + + + + + + + ); +}; + +const CONTAINER_BORDER_RADIUS = rem(30); + +const styles = StyleSheet.create({ + contentContainerStyle: { + paddingTop: rem(16), + flexGrow: 1, + backgroundColor: COLORS.white, + }, + + headerContainer: { + paddingBottom: rem(24) + CONTAINER_BORDER_RADIUS, + alignItems: 'center', + }, + headerBackground: { + ...StyleSheet.absoluteFillObject, + top: CHANNEL_PHOTO_SIZE / 2, + borderTopStartRadius: CONTAINER_BORDER_RADIUS, + borderTopEndRadius: CONTAINER_BORDER_RADIUS, + backgroundColor: 'blue', + }, + + contentContainer: { + marginTop: -CONTAINER_BORDER_RADIUS, + paddingTop: rem(24), + paddingHorizontal: rem(16), + flex: 1, + borderTopStartRadius: CONTAINER_BORDER_RADIUS, + borderTopEndRadius: CONTAINER_BORDER_RADIUS, + backgroundColor: COLORS.white, + }, + + item: { + marginTop: rem(24), + }, +}); From 529101f901db51197b18a8c3d0bb030441bc5c64 Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Wed, 17 May 2023 18:46:22 +0300 Subject: [PATCH 2/8] refactor: reuse code of style for KeyboardAvoider --- src/components/KeyboardAvoider/index.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/components/KeyboardAvoider/index.tsx b/src/components/KeyboardAvoider/index.tsx index c596bfda6..5cfe2fb84 100644 --- a/src/components/KeyboardAvoider/index.tsx +++ b/src/components/KeyboardAvoider/index.tsx @@ -1,11 +1,8 @@ // SPDX-License-Identifier: ice License 1.0 +import {commonStyles} from '@constants/styles'; import React, {ReactNode} from 'react'; -import { - KeyboardAvoidingView, - KeyboardAvoidingViewProps, - StyleSheet, -} from 'react-native'; +import {KeyboardAvoidingView, KeyboardAvoidingViewProps} from 'react-native'; import {isIOS} from 'rn-units'; type Props = { @@ -14,13 +11,9 @@ type Props = { export const KeyboardAvoider = ({children, ...props}: Props) => ( {children} ); - -const styles = StyleSheet.create({ - flex: {flex: 1}, -}); From 1f7ad9997e9ab552252f0342d0a937fd0a498bb6 Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Wed, 17 May 2023 18:52:03 +0300 Subject: [PATCH 3/8] chore: add ability to use ref for CommonInput component --- src/components/Inputs/CommonInput/index.tsx | 239 +++++++++++--------- 1 file changed, 129 insertions(+), 110 deletions(-) diff --git a/src/components/Inputs/CommonInput/index.tsx b/src/components/Inputs/CommonInput/index.tsx index b66369c64..ee57427d9 100644 --- a/src/components/Inputs/CommonInput/index.tsx +++ b/src/components/Inputs/CommonInput/index.tsx @@ -6,7 +6,13 @@ import {COLORS} from '@constants/colors'; import {CheckMarkThinIcon} from '@svg/CheckMarkThinIcon'; import {t} from '@translations/i18n'; import {font} from '@utils/styles'; -import React, {ReactNode, useRef, useState} from 'react'; +import React, { + forwardRef, + ReactNode, + useImperativeHandle, + useRef, + useState, +} from 'react'; import { ActivityIndicator, StyleProp, @@ -22,6 +28,10 @@ import { import Animated from 'react-native-reanimated'; import {isAndroid, rem} from 'rn-units'; +export type CommonInputRef = { + focus(): void; +}; + export type CommonInputProps = TextInputProps & { label: string; value: string; @@ -37,119 +47,128 @@ export type CommonInputProps = TextInputProps & { postfix?: ReactNode; }; -export const CommonInput = ({ - label, - value, - errorText, - validated, - loading, - icon, - prefix, - postfix, - onChange, - onBlur, - onFocus, - onChangeText, - containerStyle, - editable = true, - style, - ...textInputProps -}: CommonInputProps) => { - const [isFocused, setIsFocused] = useState(false); - const inputRef = useRef(null); +export const CommonInput = forwardRef( + ( + { + label, + value, + errorText, + validated, + loading, + icon, + prefix, + postfix, + onChange, + onBlur, + onFocus, + onChangeText, + containerStyle, + editable = true, + style, + ...textInputProps + }, + ref, + ) => { + const [isFocused, setIsFocused] = useState(false); + const inputRef = useRef(null); - const {animatedStyle, onLayoutBody, onLayoutLabel} = useLabelAnimation( - isFocused, - value, - ); + useImperativeHandle(ref, () => ({ + focus: () => inputRef.current?.focus(), + })); - return ( - (onChange ? onChange() : inputRef?.current?.focus())}> - - {icon ? {icon} : null} - - - {prefix} - {onChange ? ( - - {value} - - ) : ( - { - setIsFocused(false); - onBlur?.(event); - }} - onFocus={event => { - setIsFocused(true); - onFocus?.(event); - }} - onChangeText={newValue => { - onChangeText?.(newValue); - }} - {...textInputProps} - /> - )} + const {animatedStyle, onLayoutBody, onLayoutLabel} = useLabelAnimation( + isFocused, + value, + ); + + return ( + (onChange ? onChange() : inputRef?.current?.focus())}> + + {icon ? {icon} : null} + + + {prefix} + {onChange ? ( + + {value} + + ) : ( + { + setIsFocused(false); + onBlur?.(event); + }} + onFocus={event => { + setIsFocused(true); + onFocus?.(event); + }} + onChangeText={newValue => { + onChangeText?.(newValue); + }} + {...textInputProps} + /> + )} + + + {errorText || label} + - - {errorText || label} - + {onChange && ( + + + {t('button.change').toUpperCase()} + + + )} + {loading && } + {(!!errorText || validated) && !loading && ( + + {errorText ? ( + ! + ) : ( + + )} + + )} + {postfix} - {onChange && ( - - - {t('button.change').toUpperCase()} - - - )} - {loading && } - {(!!errorText || validated) && !loading && ( - - {errorText ? ( - ! - ) : ( - - )} - - )} - {postfix} - - - ); -}; + + ); + }, +); const RESULT_ICON_SIZE = rem(20); const styles = StyleSheet.create({ From f4d49766670affc1062dda8a5a393db3259a7dd0 Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Wed, 17 May 2023 18:54:36 +0300 Subject: [PATCH 4/8] feat(chat-channel): add ConfigRow component, improve layout of create channel screen --- src/assets/svg/AdminIcon.tsx | 22 +++++++ src/assets/svg/SpeakerphoneIcon.tsx | 28 +++++++++ .../components/ConfigRow/index.tsx | 60 +++++++++++++++++++ src/screens/ChatFlow/CreateChannel/index.tsx | 46 +++++++++++--- 4 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 src/assets/svg/AdminIcon.tsx create mode 100644 src/assets/svg/SpeakerphoneIcon.tsx create mode 100644 src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx diff --git a/src/assets/svg/AdminIcon.tsx b/src/assets/svg/AdminIcon.tsx new file mode 100644 index 000000000..898bfde2f --- /dev/null +++ b/src/assets/svg/AdminIcon.tsx @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {COLORS} from '@constants/colors'; +import React from 'react'; +import Svg, {Path, SvgProps} from 'react-native-svg'; +import {rem} from 'rn-units'; + +export const AdminIcon = ({color = COLORS.white, ...props}: SvgProps) => ( + + + +); diff --git a/src/assets/svg/SpeakerphoneIcon.tsx b/src/assets/svg/SpeakerphoneIcon.tsx new file mode 100644 index 000000000..e89dddf3e --- /dev/null +++ b/src/assets/svg/SpeakerphoneIcon.tsx @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {COLORS} from '@constants/colors'; +import React from 'react'; +import Svg, {Path, SvgProps} from 'react-native-svg'; +import {rem} from 'rn-units'; + +export const SpeakerphoneIcon = ({ + color = COLORS.white, + ...props +}: SvgProps) => ( + + + +); diff --git a/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx b/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx new file mode 100644 index 000000000..61980072c --- /dev/null +++ b/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {ChevronIcon} from '@svg/ChevronIcon'; +import {font} from '@utils/styles'; +import React from 'react'; +import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native'; +import {rem} from 'rn-units'; + +interface Props { + style?: StyleProp; + Icon: React.FC<{width: number; height: number; color: string}>; + title: string; + value: string | number; + onPress(): void; +} + +export const ConfigRow = ({style, Icon, title, value, onPress}: Props) => { + return ( + + + + + + {title} + + {value} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + }, + iconContainer: { + width: rem(44), + height: rem(44), + justifyContent: 'center', + alignItems: 'center', + borderRadius: rem(12), + backgroundColor: COLORS.aliceBlue, + }, + title: { + marginLeft: rem(12), + flex: 1, + ...font(16, 19.2, 'black', 'primaryDark'), + }, + value: { + marginLeft: rem(12), + ...font(16, 19.2, 'regular', 'primaryLight'), + }, + arrow: { + marginLeft: rem(8), + }, +}); diff --git a/src/screens/ChatFlow/CreateChannel/index.tsx b/src/screens/ChatFlow/CreateChannel/index.tsx index cca2e8c41..5771f1c75 100644 --- a/src/screens/ChatFlow/CreateChannel/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/index.tsx @@ -1,31 +1,37 @@ // SPDX-License-Identifier: ice License 1.0 import {PrimaryButton} from '@components/Buttons/PrimaryButton'; -import {CommonInput} from '@components/Inputs/CommonInput'; +import {CommonInput, CommonInputRef} from '@components/Inputs/CommonInput'; +import {KeyboardAvoider} from '@components/KeyboardAvoider'; import {LinesBackground} from '@components/LinesBackground'; import {COLORS} from '@constants/colors'; import {commonStyles} from '@constants/styles'; import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; import {useScrollShadow} from '@hooks/useScrollShadow'; import {Header} from '@navigation/components/Header'; -import React, {useState} from 'react'; +import {AdminIcon} from '@svg/AdminIcon'; +import {SpeakerphoneIcon} from '@svg/SpeakerphoneIcon'; +import React, {useRef, useState} from 'react'; import {StyleSheet, View} from 'react-native'; import Animated from 'react-native-reanimated'; import {rem} from 'rn-units'; import {CHANNEL_PHOTO_SIZE, ChannelPhoto} from './components/ChannelPhoto'; +import {ConfigRow} from './components/ConfigRow'; export const CreateChannel = () => { const safeAreaInsets = useSafeAreaInsets(); const {scrollHandler, shadowStyle} = useScrollShadow(); + const refDescription = useRef(null); + const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); return ( - +
{ - + refDescription.current?.focus()} + /> { scrollEnabled={false} /> + {}} + /> + + {}} + /> + - + {}} + /> - + ); }; @@ -90,7 +123,6 @@ const styles = StyleSheet.create({ top: CHANNEL_PHOTO_SIZE / 2, borderTopStartRadius: CONTAINER_BORDER_RADIUS, borderTopEndRadius: CONTAINER_BORDER_RADIUS, - backgroundColor: 'blue', }, contentContainer: { From 9c9b7803aea10cd04eafa6e6621375f1fdd6c236 Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Thu, 18 May 2023 17:39:45 +0300 Subject: [PATCH 5/8] feat(chat-channel): add locale strings, use action sheet for channel avatar selection --- src/components/Inputs/CommonInput/index.tsx | 1 + src/hooks/useActionSheetUpdateAvatar.ts | 9 +++- src/navigation/Main.tsx | 8 ++++ .../components/ChannelPhoto/index.tsx | 45 ++++++++++++++----- src/screens/ChatFlow/CreateChannel/index.tsx | 45 ++++++++++++++----- src/translations/locales/en.json | 17 +++++++ src/translations/locales/en.json.d.ts | 9 ++++ 7 files changed, 110 insertions(+), 24 deletions(-) diff --git a/src/components/Inputs/CommonInput/index.tsx b/src/components/Inputs/CommonInput/index.tsx index ee57427d9..428434983 100644 --- a/src/components/Inputs/CommonInput/index.tsx +++ b/src/components/Inputs/CommonInput/index.tsx @@ -70,6 +70,7 @@ export const CommonInput = forwardRef( ref, ) => { const [isFocused, setIsFocused] = useState(false); + const inputRef = useRef(null); useImperativeHandle(ref, () => ({ diff --git a/src/hooks/useActionSheetUpdateAvatar.ts b/src/hooks/useActionSheetUpdateAvatar.ts index a268c4eb1..4091653ed 100644 --- a/src/hooks/useActionSheetUpdateAvatar.ts +++ b/src/hooks/useActionSheetUpdateAvatar.ts @@ -13,11 +13,16 @@ import {useEffect, useState} from 'react'; export type CroppedImage = {mime: string; path: string}; type Props = { + title?: string; onChange: (image: CroppedImage | null) => void; uri?: string; }; -export const useActionSheetUpdateAvatar = ({onChange, uri}: Props) => { +export const useActionSheetUpdateAvatar = ({ + title = t('settings.profile_photo.edit'), + onChange, + uri, +}: Props) => { const navigation = useNavigation>(); @@ -32,7 +37,7 @@ export const useActionSheetUpdateAvatar = ({onChange, uri}: Props) => { const onEditPress = () => { navigation.navigate('ActionSheet', { - title: t('settings.profile_photo.edit'), + title, buttons: [ { icon: ImageIcon, diff --git a/src/navigation/Main.tsx b/src/navigation/Main.tsx index 78dee7ae1..496cdc9b4 100644 --- a/src/navigation/Main.tsx +++ b/src/navigation/Main.tsx @@ -136,6 +136,12 @@ export type MainStackParamList = { ProfilePrivacyEditStep2: undefined; ProfilePrivacyEditStep3: undefined; 'Chat/CreateChannel': undefined; + 'Chat/ChannelType': { + channelId: string | null; + }; + 'Chat/ChannelAdmins': { + channelId: string | null; + }; }; export type HomeTabStackParamList = { @@ -402,6 +408,8 @@ export function MainNavigator() { component={JoinTelegramPopUp} /> + + ); } diff --git a/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx index 81b993a6f..ab5ffe8cb 100644 --- a/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx @@ -2,30 +2,51 @@ import {Touchable} from '@components/Touchable'; import {COLORS} from '@constants/colors'; +import { + CroppedImage, + useActionSheetUpdateAvatar, +} from '@hooks/useActionSheetUpdateAvatar'; import {CameraIcon} from '@svg/CameraIcon'; +import {t} from '@translations/i18n'; import {font} from '@utils/styles'; -import React, {useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {Image, StyleSheet, Text, View} from 'react-native'; import {rem} from 'rn-units'; export const CHANNEL_PHOTO_SIZE = rem(110); export const ChannelPhoto = () => { - const [photoUri, setPhotoUri] = useState(''); + const [uri, setUri] = useState(''); - const onSelectPhoto = () => { - setPhotoUri('https://picsum.photos/200/300'); - }; + const onChange = useCallback((image: CroppedImage | null) => { + setUri(image?.path ?? ''); + }, []); + + const {onEditPress, localImage} = useActionSheetUpdateAvatar({ + title: t('chat.createChannel.addChannelPhoto'), + onChange, + uri, + }); + + const imageSource = useMemo(() => { + const currentUri = uri || localImage?.path; + + if (!currentUri) { + return null; + } + + return { + uri: currentUri, + }; + }, [localImage?.path, uri]); return ( - + - {photoUri ? ( + {imageSource ? ( ) : ( @@ -36,7 +57,9 @@ export const ChannelPhoto = () => { - {photoUri ? '_Change photo' : '_Add photo'} + {imageSource + ? t('chat.createChannel.buttons.changePhoto') + : t('chat.createChannel.buttons.addPhoto')} ); diff --git a/src/screens/ChatFlow/CreateChannel/index.tsx b/src/screens/ChatFlow/CreateChannel/index.tsx index 5771f1c75..806811557 100644 --- a/src/screens/ChatFlow/CreateChannel/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/index.tsx @@ -9,8 +9,12 @@ import {commonStyles} from '@constants/styles'; import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; import {useScrollShadow} from '@hooks/useScrollShadow'; import {Header} from '@navigation/components/Header'; +import {MainStackParamList} from '@navigation/Main'; +import {useNavigation} from '@react-navigation/native'; +import {NativeStackNavigationProp} from '@react-navigation/native-stack'; import {AdminIcon} from '@svg/AdminIcon'; import {SpeakerphoneIcon} from '@svg/SpeakerphoneIcon'; +import {t} from '@translations/i18n'; import React, {useRef, useState} from 'react'; import {StyleSheet, View} from 'react-native'; import Animated from 'react-native-reanimated'; @@ -22,6 +26,9 @@ import {ConfigRow} from './components/ConfigRow'; export const CreateChannel = () => { const safeAreaInsets = useSafeAreaInsets(); + const navigation = + useNavigation>(); + const {scrollHandler, shadowStyle} = useScrollShadow(); const refDescription = useRef(null); @@ -30,11 +37,17 @@ export const CreateChannel = () => { const [description, setDescription] = useState(''); + const [channelType, _setChannelType] = useState<'public' | 'private'>( + 'public', + ); + + const [admins, _setAdmins] = useState(['currentUser']); + return (
@@ -59,7 +72,7 @@ export const CreateChannel = () => { { { {}} + title={t('chat.createChannel.labels.channelType')} + value={channelType} + onPress={() => { + navigation.navigate('Chat/ChannelType', { + channelId: null, + }); + }} /> {}} + title={t('chat.createChannel.labels.administrators')} + value={admins.length} + onPress={() => { + navigation.navigate('Chat/ChannelAdmins', { + channelId: null, + }); + }} /> {}} + text={t('chat.createChannel.buttons.createChannel')} + onPress={() => { + navigation.goBack(); + }} /> diff --git a/src/translations/locales/en.json b/src/translations/locales/en.json index b0c8abc52..df77a951c 100644 --- a/src/translations/locales/en.json +++ b/src/translations/locales/en.json @@ -893,5 +893,22 @@ "description_part1": "Following us on Instagram is a great way to stay in the loop and connect with other users who are also passionate about ice", "description_part2": "Plus, you'll be the first to know about any new features or promotions that we're launching." } + }, + "chat": { + "createChannel": { + "title": "Creating a channel", + "addChannelPhoto": "Add channel photo", + "labels": { + "administrators": "Administrators", + "channelType": "Channel type", + "description": "Description", + "title": "Title" + }, + "buttons": { + "createChannel": "Create channel", + "addPhoto": "Add photo", + "changePhoto": "Change photo" + } + } } } diff --git a/src/translations/locales/en.json.d.ts b/src/translations/locales/en.json.d.ts index d99060cb2..1ffb722fd 100644 --- a/src/translations/locales/en.json.d.ts +++ b/src/translations/locales/en.json.d.ts @@ -583,4 +583,13 @@ export type Translations = { 'social_media.instagram.title': null; 'social_media.instagram.description_part1': null; 'social_media.instagram.description_part2': null; + 'chat.createChannel.title': null; + 'chat.createChannel.addChannelPhoto': null; + 'chat.createChannel.labels.administrators': null; + 'chat.createChannel.labels.channelType': null; + 'chat.createChannel.labels.description': null; + 'chat.createChannel.labels.title': null; + 'chat.createChannel.buttons.createChannel': null; + 'chat.createChannel.buttons.addPhoto': null; + 'chat.createChannel.buttons.changePhoto': null; }; From b08f30278fa6883d0e86cb370839019523f2fb22 Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Fri, 19 May 2023 14:53:14 +0300 Subject: [PATCH 6/8] feat(chat-channel): add screen for channel type selection --- src/navigation/Main.tsx | 11 +- .../ChatFlow/ChannelTypeSelect/index.tsx | 131 ++++++++++++++++++ .../components/ChannelPhoto/index.tsx | 6 +- src/screens/ChatFlow/CreateChannel/index.tsx | 14 +- src/translations/locales/en.json | 22 ++- src/translations/locales/en.json.d.ts | 22 +-- 6 files changed, 178 insertions(+), 28 deletions(-) create mode 100644 src/screens/ChatFlow/ChannelTypeSelect/index.tsx diff --git a/src/navigation/Main.tsx b/src/navigation/Main.tsx index 496cdc9b4..a92deb1a6 100644 --- a/src/navigation/Main.tsx +++ b/src/navigation/Main.tsx @@ -17,6 +17,7 @@ import { } from '@react-navigation/bottom-tabs'; import {NavigatorScreenParams} from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; +import {ChannelTypeSelect} from '@screens/ChatFlow/ChannelTypeSelect'; import {CreateChannel} from '@screens/ChatFlow/CreateChannel'; import {BalanceHistory} from '@screens/HomeFlow/BalanceHistory'; import {Home} from '@screens/HomeFlow/Home'; @@ -407,9 +408,13 @@ export function MainNavigator() { options={modalOptions} component={JoinTelegramPopUp} /> - - - + + + ); } diff --git a/src/screens/ChatFlow/ChannelTypeSelect/index.tsx b/src/screens/ChatFlow/ChannelTypeSelect/index.tsx new file mode 100644 index 000000000..320db2fbd --- /dev/null +++ b/src/screens/ChatFlow/ChannelTypeSelect/index.tsx @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {stopPropagation} from '@components/KeyboardDismiss'; +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {SCREEN_SIDE_OFFSET} from '@constants/styles'; +import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; +import {MainStackParamList} from '@navigation/Main'; +import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; +import {InfoOutlineIcon} from '@svg/InfoOutlineIcon'; +import {RoundCheckboxActiveIcon} from '@svg/RoundCheckboxActiveIcon'; +import {RoundCheckboxInactiveIcon} from '@svg/RoundCheckboxInactiveIcon'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import React, {useCallback, useState} from 'react'; +import {StyleSheet, Text, TouchableWithoutFeedback, View} from 'react-native'; +import Animated, {SlideInDown} from 'react-native-reanimated'; +import {rem} from 'rn-units'; + +type ChannelType = 'public' | 'private'; + +const CHANNEL_TYPES: ChannelType[] = ['public', 'private']; + +export const ChannelTypeSelect = () => { + const { + params: {channelId}, + } = useRoute>(); + + const navigation = useNavigation(); + + const safeAreaInsets = useSafeAreaInsets(); + + const [channelType, setChannelType] = useState('public'); + + const renderChannelType = useCallback( + (type: ChannelType) => { + return ( + { + console.log(`Set '${channelType}' channel type for ${channelId}`); + + setChannelType(type); + + navigation.goBack(); + }}> + {channelType === type ? ( + + ) : ( + + )} + + {t(`chat.channel.type.${type}`)} + + ); + }, + [channelId, channelType, navigation], + ); + + return ( + + + + {t('chat.channel_type.title')} + + {CHANNEL_TYPES.map(renderChannelType)} + + + + + {t('chat.channel_type.info')} + + + + + ); +}; + +const styles = StyleSheet.create({ + background: { + flex: 1, + justifyContent: 'flex-end', + backgroundColor: COLORS.transparentBackground, + }, + container: { + paddingTop: rem(30), + paddingHorizontal: SCREEN_SIDE_OFFSET, + borderTopLeftRadius: rem(20), + borderTopRightRadius: rem(20), + backgroundColor: COLORS.white, + }, + titleText: { + ...font(14, 16.8, 'semibold', 'primaryDark'), + }, + + itemContainer: { + marginTop: rem(24), + flexDirection: 'row', + alignItems: 'center', + }, + itemText: { + marginLeft: rem(12), + ...font(12, 14.4, 'regular', 'secondary'), + }, + + infoContainer: { + marginTop: rem(24), + padding: rem(16), + flexDirection: 'row', + alignItems: 'center', + borderRadius: rem(16), + backgroundColor: COLORS.aliceBlue, + }, + infoText: { + marginLeft: rem(12), + ...font(12, 14.4, 'regular', 'secondary'), + }, +}); diff --git a/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx index ab5ffe8cb..35f6f89c7 100644 --- a/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx @@ -23,7 +23,7 @@ export const ChannelPhoto = () => { }, []); const {onEditPress, localImage} = useActionSheetUpdateAvatar({ - title: t('chat.createChannel.addChannelPhoto'), + title: t('chat.create_channel.add_channel_photo'), onChange, uri, }); @@ -58,8 +58,8 @@ export const ChannelPhoto = () => { {imageSource - ? t('chat.createChannel.buttons.changePhoto') - : t('chat.createChannel.buttons.addPhoto')} + ? t('chat.create_channel.buttons.change_photo') + : t('chat.create_channel.buttons.add_photo')} ); diff --git a/src/screens/ChatFlow/CreateChannel/index.tsx b/src/screens/ChatFlow/CreateChannel/index.tsx index 806811557..f65aa7e9b 100644 --- a/src/screens/ChatFlow/CreateChannel/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/index.tsx @@ -47,7 +47,7 @@ export const CreateChannel = () => {
@@ -72,7 +72,7 @@ export const CreateChannel = () => { { { { navigation.navigate('Chat/ChannelType', { channelId: null, @@ -104,7 +104,7 @@ export const CreateChannel = () => { { navigation.navigate('Chat/ChannelAdmins', { @@ -117,7 +117,7 @@ export const CreateChannel = () => { { navigation.goBack(); }} diff --git a/src/translations/locales/en.json b/src/translations/locales/en.json index df77a951c..ac76dfae3 100644 --- a/src/translations/locales/en.json +++ b/src/translations/locales/en.json @@ -895,20 +895,30 @@ } }, "chat": { - "createChannel": { + "channel": { + "type": { + "public": "Public", + "private": "Private" + } + }, + "create_channel": { "title": "Creating a channel", - "addChannelPhoto": "Add channel photo", + "add_channel_photo": "Add channel photo", "labels": { "administrators": "Administrators", - "channelType": "Channel type", + "channel_type": "Channel type", "description": "Description", "title": "Title" }, "buttons": { - "createChannel": "Create channel", - "addPhoto": "Add photo", - "changePhoto": "Change photo" + "create_channel": "Create channel", + "add_photo": "Add photo", + "change_photo": "Change photo" } + }, + "channel_type": { + "title": "Choose type of channel", + "info": "Public channels can be found through search and any user can subscribe to them" } } } diff --git a/src/translations/locales/en.json.d.ts b/src/translations/locales/en.json.d.ts index 1ffb722fd..76b7b2420 100644 --- a/src/translations/locales/en.json.d.ts +++ b/src/translations/locales/en.json.d.ts @@ -583,13 +583,17 @@ export type Translations = { 'social_media.instagram.title': null; 'social_media.instagram.description_part1': null; 'social_media.instagram.description_part2': null; - 'chat.createChannel.title': null; - 'chat.createChannel.addChannelPhoto': null; - 'chat.createChannel.labels.administrators': null; - 'chat.createChannel.labels.channelType': null; - 'chat.createChannel.labels.description': null; - 'chat.createChannel.labels.title': null; - 'chat.createChannel.buttons.createChannel': null; - 'chat.createChannel.buttons.addPhoto': null; - 'chat.createChannel.buttons.changePhoto': null; + 'chat.channel.type.public': null; + 'chat.channel.type.private': null; + 'chat.create_channel.title': null; + 'chat.create_channel.add_channel_photo': null; + 'chat.create_channel.labels.administrators': null; + 'chat.create_channel.labels.channel_type': null; + 'chat.create_channel.labels.description': null; + 'chat.create_channel.labels.title': null; + 'chat.create_channel.buttons.create_channel': null; + 'chat.create_channel.buttons.add_photo': null; + 'chat.create_channel.buttons.change_photo': null; + 'chat.channel_type.title': null; + 'chat.channel_type.info': null; }; From b4cb0f5ffc0d142eb3dd20556470006f244de1f2 Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Wed, 24 May 2023 16:30:26 +0300 Subject: [PATCH 7/8] feat(chat-channel): improve fonts system, add chat administrators screen UI --- src/constants/fonts.ts | 43 ++++-- src/navigation/Main.tsx | 9 +- .../AddAdministratorButton/index.tsx | 57 ++++++++ .../components/UserItem/index.tsx | 112 +++++++++++++++ .../ChatFlow/ChannelAdministrators/index.tsx | 134 ++++++++++++++++++ .../ChatFlow/ChannelTypeSelect/index.tsx | 6 +- .../components/ChannelPhoto/index.tsx | 4 +- .../components/ConfigRow/index.tsx | 4 +- src/screens/ChatFlow/CreateChannel/index.tsx | 2 +- .../components/Calendar/index.tsx | 12 +- src/translations/locales/en.json | 15 ++ src/translations/locales/en.json.d.ts | 5 + src/utils/styles.ts | 6 +- 13 files changed, 379 insertions(+), 30 deletions(-) create mode 100644 src/screens/ChatFlow/ChannelAdministrators/components/AddAdministratorButton/index.tsx create mode 100644 src/screens/ChatFlow/ChannelAdministrators/components/UserItem/index.tsx create mode 100644 src/screens/ChatFlow/ChannelAdministrators/index.tsx diff --git a/src/constants/fonts.ts b/src/constants/fonts.ts index 31aa678cd..60f166c84 100644 --- a/src/constants/fonts.ts +++ b/src/constants/fonts.ts @@ -1,31 +1,52 @@ // SPDX-License-Identifier: ice License 1.0 -export type FontWight = keyof typeof FONT_WEIGHTS; +type ValueOf = T[keyof T]; export const FONT_WEIGHTS = { hairline: '100', + '100': '100', + thin: '200', + '200': '200', + light: '300', + '300': '300', + regular: '400', + '400': '400', + medium: '500', + '500': '500', + semibold: '600', + '600': '600', + bold: '700', + '700': '700', + heavy: '800', + '800': '800', + black: '900', + '900': '900', } as const; export type FontFamily = 'primary'; -export const FONTS: {[font in FontFamily]: {[width in FontWight]: string}} = { +export const FONTS: { + [font in FontFamily]: { + [weight in ValueOf]: string; + }; +} = { primary: { - hairline: 'Lato-Hairline', // 100 - thin: 'Lato-Thin', // 200 - light: 'Lato-Light', // 300 - regular: 'Lato-Regular', // 400 - medium: 'Lato-Medium', // 500 - semibold: 'Lato-Semibold', // 600 - bold: 'Lato-Bold', // 700 - heavy: 'Lato-Heavy', // 800 - black: 'Lato-Black', // 900 + '100': 'Lato-Hairline', + '200': 'Lato-Thin', + '300': 'Lato-Light', + '400': 'Lato-Regular', + '500': 'Lato-Medium', + '600': 'Lato-Semibold', + '700': 'Lato-Bold', + '800': 'Lato-Heavy', + '900': 'Lato-Black', }, }; diff --git a/src/navigation/Main.tsx b/src/navigation/Main.tsx index a92deb1a6..e3ff12651 100644 --- a/src/navigation/Main.tsx +++ b/src/navigation/Main.tsx @@ -17,6 +17,7 @@ import { } from '@react-navigation/bottom-tabs'; import {NavigatorScreenParams} from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; +import {ChannelAdministrators} from '@screens/ChatFlow/ChannelAdministrators'; import {ChannelTypeSelect} from '@screens/ChatFlow/ChannelTypeSelect'; import {CreateChannel} from '@screens/ChatFlow/CreateChannel'; import {BalanceHistory} from '@screens/HomeFlow/BalanceHistory'; @@ -140,7 +141,7 @@ export type MainStackParamList = { 'Chat/ChannelType': { channelId: string | null; }; - 'Chat/ChannelAdmins': { + 'Chat/ChannelAdministrators': { channelId: string | null; }; }; @@ -414,7 +415,11 @@ export function MainNavigator() { component={ChannelTypeSelect} options={modalOptions} /> - + ); } diff --git a/src/screens/ChatFlow/ChannelAdministrators/components/AddAdministratorButton/index.tsx b/src/screens/ChatFlow/ChannelAdministrators/components/AddAdministratorButton/index.tsx new file mode 100644 index 000000000..3752bb63e --- /dev/null +++ b/src/screens/ChatFlow/ChannelAdministrators/components/AddAdministratorButton/index.tsx @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {AdminIcon} from '@svg/AdminIcon'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import React, {useCallback} from 'react'; +import {StyleSheet, Text, View} from 'react-native'; +import {rem} from 'rn-units'; + +interface Props { + channelId: string | null; +} + +export const AddAdministratorButton = ({}: Props) => { + const onPress = useCallback(() => { + // Add administrator to channelId + }, []); + + return ( + + + + + + + {t('chat.channel_administrators.buttons.add_administrator')} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: rem(16), + flexDirection: 'row', + alignItems: 'center', + }, + iconContainer: { + width: rem(46), + height: rem(46), + justifyContent: 'center', + alignItems: 'center', + borderRadius: rem(12), + backgroundColor: COLORS.aliceBlue, + }, + text: { + marginLeft: rem(12), + ...font(16, 19.2, '900', 'primaryDark'), + flex: 1, + }, +}); diff --git a/src/screens/ChatFlow/ChannelAdministrators/components/UserItem/index.tsx b/src/screens/ChatFlow/ChannelAdministrators/components/UserItem/index.tsx new file mode 100644 index 000000000..b9a793928 --- /dev/null +++ b/src/screens/ChatFlow/ChannelAdministrators/components/UserItem/index.tsx @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Avatar} from '@components/Avatar/Avatar'; +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {MainStackParamList} from '@navigation/Main'; +import {useNavigation} from '@react-navigation/native'; +import {NativeStackNavigationProp} from '@react-navigation/native-stack'; +import {userIdSelector} from '@store/modules/Account/selectors'; +import {BinIcon} from '@svg/BinIcon'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import React, {memo, useCallback} from 'react'; +import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native'; +import {useSelector} from 'react-redux'; +import {rem} from 'rn-units'; + +interface Props { + style?: StyleProp; + userId: string; +} + +export const UserItem = memo(({style, userId}: Props) => { + const navigation = + useNavigation>(); + + const currentUserId = useSelector(userIdSelector); + + const userAvatarUri = 'https://i.pravatar.cc/300?img=1'; + + const userName = 'User Name'; + + const phoneNumber = '123456789'; + + const onRemove = useCallback(() => { + navigation.navigate('PopUp', { + title: t( + 'chat.channel_administrators.dialogs.delete_administrator.title', + ), + message: t( + 'chat.channel_administrators.dialogs.delete_administrator.message', + ), + buttons: [ + { + text: t('button.cancel'), + preset: 'outlined', + }, + { + text: t( + 'chat.channel_administrators.dialogs.delete_administrator.buttons.delete', + ), + preset: 'destructive', + onPress: () => { + // Remove userId from administrators + }, + }, + ], + }); + }, [navigation]); + + return ( + + + + + + {userName} + + + {phoneNumber ? ( + {phoneNumber} + ) : null} + + + {currentUserId !== userId && ( + + + + )} + + ); +}); + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: rem(16), + flexDirection: 'row', + alignItems: 'center', + }, + + body: { + marginLeft: rem(12), + flex: 1, + justifyContent: 'center', + }, + name: { + ...font(16, null, '700', 'primaryDark'), + }, + phoneNumber: { + paddingTop: rem(3), + ...font(13.5, null, '500', 'emperor'), + }, + + binContainer: { + marginLeft: rem(12), + }, +}); diff --git a/src/screens/ChatFlow/ChannelAdministrators/index.tsx b/src/screens/ChatFlow/ChannelAdministrators/index.tsx new file mode 100644 index 000000000..64a35f00a --- /dev/null +++ b/src/screens/ChatFlow/ChannelAdministrators/index.tsx @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: ice License 1.0 + +import {Touchable} from '@components/Touchable'; +import {COLORS} from '@constants/colors'; +import {commonStyles} from '@constants/styles'; +import BottomSheet, {BottomSheetFlatList} from '@gorhom/bottom-sheet'; +import {useSafeAreaFrame} from '@hooks/useSafeAreaFrame'; +import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; +import {MainStackParamList} from '@navigation/Main'; +import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; +import {userIdSelector} from '@store/modules/Account/selectors'; +import {t} from '@translations/i18n'; +import {font} from '@utils/styles'; +import React, {useCallback, useMemo, useState} from 'react'; +import {LayoutChangeEvent, ListRenderItem} from 'react-native'; +import {StyleSheet, Text, View} from 'react-native'; +import {useSelector} from 'react-redux'; +import {rem} from 'rn-units'; + +import {AddAdministratorButton} from './components/AddAdministratorButton'; +import {UserItem} from './components/UserItem'; + +export const ChannelAdministrators = () => { + const { + params: {channelId}, + } = useRoute>(); + + const navigation = useNavigation(); + + const safeAreaInsets = useSafeAreaInsets(); + + const frame = useSafeAreaFrame(); + + const [contentHeight, setContentHeight] = useState(0); + + const [headerHeight, setHeaderHeight] = useState(0); + + const curentUserId = useSelector(userIdSelector); + + const data = useMemo(() => [curentUserId, 'user1', 'user2'], [curentUserId]); + + const snapPoints = useMemo(() => { + const snapPoint = contentHeight + headerHeight || 1; + + return [Math.min(snapPoint, frame.height + safeAreaInsets.bottom)]; + }, [contentHeight, frame.height, headerHeight, safeAreaInsets.bottom]); + + const onLayoutHeader = useCallback( + ({ + nativeEvent: { + layout: {height}, + }, + }: LayoutChangeEvent) => { + setHeaderHeight(height); + }, + [], + ); + + const onContentSizeChange = useCallback((width: number, height: number) => { + setContentHeight(height); + }, []); + + const renderHeader = useCallback(() => { + return ; + }, [channelId]); + + const renderItem: ListRenderItem = useCallback( + ({item: userId}) => { + return ; + }, + [], + ); + + return ( + + + + + + + + {t('chat.channel_administrators.title')} + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + background: { + flex: 1, + justifyContent: 'flex-end', + backgroundColor: COLORS.transparentBackground, + }, + + titleContainer: { + paddingTop: rem(27), + paddingBottom: rem(20), + paddingHorizontal: rem(16), + }, + titleText: { + ...font(14, 16.8, '600', 'primaryDark'), + textTransform: 'uppercase', + flexGrow: 1, + }, + + item: { + marginTop: rem(16), + }, +}); diff --git a/src/screens/ChatFlow/ChannelTypeSelect/index.tsx b/src/screens/ChatFlow/ChannelTypeSelect/index.tsx index 320db2fbd..3b73fe9ca 100644 --- a/src/screens/ChatFlow/ChannelTypeSelect/index.tsx +++ b/src/screens/ChatFlow/ChannelTypeSelect/index.tsx @@ -103,7 +103,7 @@ const styles = StyleSheet.create({ backgroundColor: COLORS.white, }, titleText: { - ...font(14, 16.8, 'semibold', 'primaryDark'), + ...font(14, 16.8, '600', 'primaryDark'), }, itemContainer: { @@ -113,7 +113,7 @@ const styles = StyleSheet.create({ }, itemText: { marginLeft: rem(12), - ...font(12, 14.4, 'regular', 'secondary'), + ...font(12, 14.4, '400', 'secondary'), }, infoContainer: { @@ -126,6 +126,6 @@ const styles = StyleSheet.create({ }, infoText: { marginLeft: rem(12), - ...font(12, 14.4, 'regular', 'secondary'), + ...font(12, 14.4, '400', 'secondary'), }, }); diff --git a/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx index 35f6f89c7..00bcce32c 100644 --- a/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/components/ChannelPhoto/index.tsx @@ -84,11 +84,11 @@ const styles = StyleSheet.create({ ...StyleSheet.absoluteFillObject, justifyContent: 'center', alignItems: 'center', - backgroundColor: '#E3EBF8', + backgroundColor: COLORS.secondaryFaint, }, text: { marginTop: rem(8), - ...font(17, 20.4, 'semibold', 'white'), + ...font(17, 20.4, '600', 'white'), }, }); diff --git a/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx b/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx index 61980072c..e5de2bd2d 100644 --- a/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx @@ -48,11 +48,11 @@ const styles = StyleSheet.create({ title: { marginLeft: rem(12), flex: 1, - ...font(16, 19.2, 'black', 'primaryDark'), + ...font(16, 19.2, '900', 'primaryDark'), }, value: { marginLeft: rem(12), - ...font(16, 19.2, 'regular', 'primaryLight'), + ...font(16, 19.2, '400', 'primaryLight'), }, arrow: { marginLeft: rem(8), diff --git a/src/screens/ChatFlow/CreateChannel/index.tsx b/src/screens/ChatFlow/CreateChannel/index.tsx index f65aa7e9b..b3e601fce 100644 --- a/src/screens/ChatFlow/CreateChannel/index.tsx +++ b/src/screens/ChatFlow/CreateChannel/index.tsx @@ -107,7 +107,7 @@ export const CreateChannel = () => { title={t('chat.create_channel.labels.administrators')} value={admins.length} onPress={() => { - navigation.navigate('Chat/ChannelAdmins', { + navigation.navigate('Chat/ChannelAdministrators', { channelId: null, }); }} diff --git a/src/screens/Modals/DateSelector/components/Calendar/index.tsx b/src/screens/Modals/DateSelector/components/Calendar/index.tsx index 5e5cd5d48..2297e216b 100644 --- a/src/screens/Modals/DateSelector/components/Calendar/index.tsx +++ b/src/screens/Modals/DateSelector/components/Calendar/index.tsx @@ -44,16 +44,16 @@ export const Calendar = memo( const theme: Theme = useMemo( () => ({ textDayFontSize: rem(12), - textDayFontWeight: FONT_WEIGHTS.medium, - textDayFontFamily: FONTS.primary.medium, + textDayFontWeight: FONT_WEIGHTS['500'], + textDayFontFamily: FONTS.primary['500'], dayTextColor: COLORS.primaryDark, textMonthFontSize: rem(15), - textMonthFontWeight: FONT_WEIGHTS.regular, - textMonthFontFamily: FONTS.primary.regular, + textMonthFontWeight: FONT_WEIGHTS['400'], + textMonthFontFamily: FONTS.primary['400'], monthTextColor: COLORS.primaryDark, textDayHeaderFontSize: rem(12), - textDayHeaderFontWeight: FONT_WEIGHTS.semibold, - textDayHeaderFontFamily: FONTS.primary.semibold, + textDayHeaderFontWeight: FONT_WEIGHTS['600'], + textDayHeaderFontFamily: FONTS.primary['600'], textSectionTitleColor: COLORS.secondary, }), [], diff --git a/src/translations/locales/en.json b/src/translations/locales/en.json index ac76dfae3..f457a9168 100644 --- a/src/translations/locales/en.json +++ b/src/translations/locales/en.json @@ -919,6 +919,21 @@ "channel_type": { "title": "Choose type of channel", "info": "Public channels can be found through search and any user can subscribe to them" + }, + "channel_administrators": { + "title": "Channel Management", + "buttons": { + "add_administrator": "Add administrator" + }, + "dialogs": { + "delete_administrator": { + "title": "Delete administrator?", + "message": "Delete this channel administrator?", + "buttons": { + "delete": "Delete" + } + } + } } } } diff --git a/src/translations/locales/en.json.d.ts b/src/translations/locales/en.json.d.ts index 76b7b2420..dd14ed575 100644 --- a/src/translations/locales/en.json.d.ts +++ b/src/translations/locales/en.json.d.ts @@ -596,4 +596,9 @@ export type Translations = { 'chat.create_channel.buttons.change_photo': null; 'chat.channel_type.title': null; 'chat.channel_type.info': null; + 'chat.channel_administrators.title': null; + 'chat.channel_administrators.buttons.add_administrator': null; + 'chat.channel_administrators.dialogs.delete_administrator.title': null; + 'chat.channel_administrators.dialogs.delete_administrator.message': null; + 'chat.channel_administrators.dialogs.delete_administrator.buttons.delete': null; }; diff --git a/src/utils/styles.ts b/src/utils/styles.ts index d9a0476e7..ae417c4c3 100644 --- a/src/utils/styles.ts +++ b/src/utils/styles.ts @@ -2,7 +2,7 @@ import {COLORS} from '@constants/colors'; // eslint-disable-next-line no-restricted-imports -import {FontFamily, FONTS, FontWight} from '@constants/fonts'; +import {FONT_WEIGHTS, FontFamily, FONTS} from '@constants/fonts'; import {isRTL} from '@translations/i18n'; import {TextStyle} from 'react-native'; import {rem} from 'rn-units'; @@ -10,7 +10,7 @@ import {rem} from 'rn-units'; export const font = ( fontSize: number, lineHeight?: number | null, - fontWeight: FontWight = 'regular', + fontWeight: keyof typeof FONT_WEIGHTS = 'regular', color: keyof typeof COLORS = 'white', textAlign: TextStyle['textAlign'] = 'left', fontFamily: FontFamily = 'primary', @@ -18,7 +18,7 @@ export const font = ( return { fontSize: rem(fontSize), lineHeight: lineHeight != null ? rem(lineHeight) : undefined, - fontFamily: FONTS[fontFamily][fontWeight], + fontFamily: FONTS[fontFamily][FONT_WEIGHTS[fontWeight]], color: COLORS[color], textAlign, }; From 9009ecd39c851e9e22b94b7e3d5b9309a5865a65 Mon Sep 17 00:00:00 2001 From: ice-atlas <121009572+ice-atlas@users.noreply.github.com> Date: Tue, 30 May 2023 16:25:36 +0300 Subject: [PATCH 8/8] feat(chat-channel): implement base UI for channel editing --- src/navigation/Main.tsx | 11 ++- .../components/ChannelPhoto/index.tsx | 6 +- .../components/ConfigRow/index.tsx | 0 .../{CreateChannel => EditChannel}/index.tsx | 72 +++++++++++++++---- src/translations/locales/en.json | 20 ++++-- src/translations/locales/en.json.d.ts | 24 ++++--- 6 files changed, 102 insertions(+), 31 deletions(-) rename src/screens/ChatFlow/{CreateChannel => EditChannel}/components/ChannelPhoto/index.tsx (92%) rename src/screens/ChatFlow/{CreateChannel => EditChannel}/components/ConfigRow/index.tsx (100%) rename src/screens/ChatFlow/{CreateChannel => EditChannel}/index.tsx (68%) diff --git a/src/navigation/Main.tsx b/src/navigation/Main.tsx index e3ff12651..1cf0d9c25 100644 --- a/src/navigation/Main.tsx +++ b/src/navigation/Main.tsx @@ -19,7 +19,7 @@ import {NavigatorScreenParams} from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; import {ChannelAdministrators} from '@screens/ChatFlow/ChannelAdministrators'; import {ChannelTypeSelect} from '@screens/ChatFlow/ChannelTypeSelect'; -import {CreateChannel} from '@screens/ChatFlow/CreateChannel'; +import {EditChannel} from '@screens/ChatFlow/EditChannel'; import {BalanceHistory} from '@screens/HomeFlow/BalanceHistory'; import {Home} from '@screens/HomeFlow/Home'; import { @@ -137,7 +137,12 @@ export type MainStackParamList = { ProfilePrivacyEditStep1: undefined; ProfilePrivacyEditStep2: undefined; ProfilePrivacyEditStep3: undefined; - 'Chat/CreateChannel': undefined; + 'Chat/EditChannel': { + /** + * null for new channel (create channel flow) + */ + channelId: string | null; + }; 'Chat/ChannelType': { channelId: string | null; }; @@ -409,7 +414,7 @@ export function MainNavigator() { options={modalOptions} component={JoinTelegramPopUp} /> - + { }, []); const {onEditPress, localImage} = useActionSheetUpdateAvatar({ - title: t('chat.create_channel.add_channel_photo'), + title: t('chat.edit_channel.add_channel_photo'), onChange, uri, }); @@ -58,8 +58,8 @@ export const ChannelPhoto = () => { {imageSource - ? t('chat.create_channel.buttons.change_photo') - : t('chat.create_channel.buttons.add_photo')} + ? t('chat.edit_channel.buttons.change_photo') + : t('chat.edit_channel.buttons.add_photo')} ); diff --git a/src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx b/src/screens/ChatFlow/EditChannel/components/ConfigRow/index.tsx similarity index 100% rename from src/screens/ChatFlow/CreateChannel/components/ConfigRow/index.tsx rename to src/screens/ChatFlow/EditChannel/components/ConfigRow/index.tsx diff --git a/src/screens/ChatFlow/CreateChannel/index.tsx b/src/screens/ChatFlow/EditChannel/index.tsx similarity index 68% rename from src/screens/ChatFlow/CreateChannel/index.tsx rename to src/screens/ChatFlow/EditChannel/index.tsx index b3e601fce..8bb0ad44a 100644 --- a/src/screens/ChatFlow/CreateChannel/index.tsx +++ b/src/screens/ChatFlow/EditChannel/index.tsx @@ -4,18 +4,20 @@ import {PrimaryButton} from '@components/Buttons/PrimaryButton'; import {CommonInput, CommonInputRef} from '@components/Inputs/CommonInput'; import {KeyboardAvoider} from '@components/KeyboardAvoider'; import {LinesBackground} from '@components/LinesBackground'; +import {Touchable} from '@components/Touchable'; import {COLORS} from '@constants/colors'; import {commonStyles} from '@constants/styles'; import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; import {useScrollShadow} from '@hooks/useScrollShadow'; import {Header} from '@navigation/components/Header'; import {MainStackParamList} from '@navigation/Main'; -import {useNavigation} from '@react-navigation/native'; +import {RouteProp} from '@react-navigation/native'; import {NativeStackNavigationProp} from '@react-navigation/native-stack'; import {AdminIcon} from '@svg/AdminIcon'; +import {BinIcon} from '@svg/BinIcon'; import {SpeakerphoneIcon} from '@svg/SpeakerphoneIcon'; import {t} from '@translations/i18n'; -import React, {useRef, useState} from 'react'; +import React, {useCallback, useRef, useState} from 'react'; import {StyleSheet, View} from 'react-native'; import Animated from 'react-native-reanimated'; import {rem} from 'rn-units'; @@ -23,11 +25,15 @@ import {rem} from 'rn-units'; import {CHANNEL_PHOTO_SIZE, ChannelPhoto} from './components/ChannelPhoto'; import {ConfigRow} from './components/ConfigRow'; -export const CreateChannel = () => { - const safeAreaInsets = useSafeAreaInsets(); +interface Props { + navigation: NativeStackNavigationProp; + route: RouteProp; +} + +export const EditChannel = ({navigation, route}: Props) => { + const {channelId} = route.params; - const navigation = - useNavigation>(); + const safeAreaInsets = useSafeAreaInsets(); const {scrollHandler, shadowStyle} = useScrollShadow(); @@ -43,13 +49,51 @@ export const CreateChannel = () => { const [admins, _setAdmins] = useState(['currentUser']); + const onDeleteChannel = useCallback(() => { + navigation.navigate('PopUp', { + title: t('chat.edit_channel.dialogs.delete_channel.title'), + message: t('chat.edit_channel.dialogs.delete_channel.message'), + buttons: [ + { + text: t('button.cancel'), + preset: 'outlined', + }, + { + text: t('chat.edit_channel.dialogs.delete_channel.buttons.delete'), + preset: 'destructive', + onPress: () => { + // TODO: Close all screens related to this channelId (if not null) + navigation.goBack(); + }, + }, + ], + }); + }, [navigation]); + + const renderDeleteChannelButton = useCallback(() => { + if (!channelId) { + return null; + } + + return ( + + + + ); + }, [channelId, onDeleteChannel]); + return (
{ { { { navigation.navigate('Chat/ChannelType', { @@ -104,7 +148,7 @@ export const CreateChannel = () => { { navigation.navigate('Chat/ChannelAdministrators', { @@ -117,7 +161,11 @@ export const CreateChannel = () => { { navigation.goBack(); }} diff --git a/src/translations/locales/en.json b/src/translations/locales/en.json index f457a9168..f491f9a94 100644 --- a/src/translations/locales/en.json +++ b/src/translations/locales/en.json @@ -901,19 +901,31 @@ "private": "Private" } }, - "create_channel": { - "title": "Creating a channel", + "edit_channel": { + "title_create": "Creating a channel", + "title_edit": "Channel editing", "add_channel_photo": "Add channel photo", "labels": { "administrators": "Administrators", "channel_type": "Channel type", "description": "Description", + "invitation_link": "Invitation link", "title": "Title" }, "buttons": { - "create_channel": "Create channel", "add_photo": "Add photo", - "change_photo": "Change photo" + "change_photo": "Change photo", + "create_channel": "Create channel", + "save_changes": "Save changes" + }, + "dialogs": { + "delete_channel": { + "title": "Delete channel?", + "message": "All publications and subscribers will be lost. Do you really want to delete the channel?", + "buttons": { + "delete": "Delete" + } + } } }, "channel_type": { diff --git a/src/translations/locales/en.json.d.ts b/src/translations/locales/en.json.d.ts index dd14ed575..38013b241 100644 --- a/src/translations/locales/en.json.d.ts +++ b/src/translations/locales/en.json.d.ts @@ -585,15 +585,21 @@ export type Translations = { 'social_media.instagram.description_part2': null; 'chat.channel.type.public': null; 'chat.channel.type.private': null; - 'chat.create_channel.title': null; - 'chat.create_channel.add_channel_photo': null; - 'chat.create_channel.labels.administrators': null; - 'chat.create_channel.labels.channel_type': null; - 'chat.create_channel.labels.description': null; - 'chat.create_channel.labels.title': null; - 'chat.create_channel.buttons.create_channel': null; - 'chat.create_channel.buttons.add_photo': null; - 'chat.create_channel.buttons.change_photo': null; + 'chat.edit_channel.title_create': null; + 'chat.edit_channel.title_edit': null; + 'chat.edit_channel.add_channel_photo': null; + 'chat.edit_channel.labels.administrators': null; + 'chat.edit_channel.labels.channel_type': null; + 'chat.edit_channel.labels.description': null; + 'chat.edit_channel.labels.invitation_link': null; + 'chat.edit_channel.labels.title': null; + 'chat.edit_channel.buttons.add_photo': null; + 'chat.edit_channel.buttons.change_photo': null; + 'chat.edit_channel.buttons.create_channel': null; + 'chat.edit_channel.buttons.save_changes': null; + 'chat.edit_channel.dialogs.delete_channel.title': null; + 'chat.edit_channel.dialogs.delete_channel.message': null; + 'chat.edit_channel.dialogs.delete_channel.buttons.delete': null; 'chat.channel_type.title': null; 'chat.channel_type.info': null; 'chat.channel_administrators.title': null;