Vitalik 9 months ago
parent
commit
30d58df200
  1. 71
      package-lock.json
  2. 4
      package.json
  3. 5
      src/modules/account/components/change-phone-number-modal.component.tsx
  4. 1
      src/modules/auth/screens/sign-in.screen.tsx
  5. 9
      src/modules/chats/hooks/use-send-sticker.hook.ts
  6. 6
      src/modules/media/smart-components/select-sticker.smart-component.tsx
  7. 1
      src/modules/root/index.tsx
  8. 1
      src/modules/root/smart-components/index.ts
  9. 157
      src/modules/root/smart-components/select-country-phone.smart-component.tsx
  10. 59
      src/shared/components/forms/form-phone.component.tsx
  11. 11
      src/shared/components/modals/bottom-modal.smart-component.tsx
  12. 3
      src/shared/components/plugins/masked-input.component.tsx
  13. 4
      src/shared/events/index.ts
  14. 12
      src/shared/helpers/text.helpers.ts
  15. 24
      src/shared/hooks/use-events-listener.hook.ts
  16. 4
      src/shared/themes/interfaces/index.ts

71
package-lock.json generated

@ -45,6 +45,7 @@ @@ -45,6 +45,7 @@
"react-native-calendars": "^1.1298.0",
"react-native-config": "^1.5.1",
"react-native-controlled-mentions": "^2.2.5",
"react-native-country-flag": "^2.0.2",
"react-native-date-picker": "^4.2.13",
"react-native-device-info": "^10.6.0",
"react-native-document-picker": "^9.0.1",
@ -88,7 +89,8 @@ @@ -88,7 +89,8 @@
"redux": "^4.2.1",
"rn-fetch-blob": "^0.12.0",
"socket.io-client": "^4.5.0",
"validate.js": "^0.13.1"
"validate.js": "^0.13.1",
"world-countries": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
@ -8741,6 +8743,16 @@ @@ -8741,6 +8743,16 @@
"node": ">= 0.8"
}
},
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -10441,6 +10453,19 @@ @@ -10441,6 +10453,19 @@
"node": ">=10.17.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"optional": true,
"peer": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -15303,6 +15328,33 @@ @@ -15303,6 +15328,33 @@
}
}
},
"node_modules/react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"optional": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
},
"peerDependencies": {
"react": "^16.14.0"
}
},
"node_modules/react-dom/node_modules/scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"optional": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"node_modules/react-freeze": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz",
@ -15492,6 +15544,11 @@ @@ -15492,6 +15544,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/react-native-country-flag": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-native-country-flag/-/react-native-country-flag-2.0.2.tgz",
"integrity": "sha512-5LMWxS79ZQ0Q9ntYgDYzWp794+HcQGXQmzzZNBR1AT7z5HcJHtX7rlk8RHi7RVzfp5gW6plWSZ4dKjRpu/OafQ=="
},
"node_modules/react-native-date-picker": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/react-native-date-picker/-/react-native-date-picker-4.3.5.tgz",
@ -17437,6 +17494,13 @@ @@ -17437,6 +17494,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"optional": true,
"peer": true
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@ -18866,6 +18930,11 @@ @@ -18866,6 +18930,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/world-countries": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/world-countries/-/world-countries-5.0.0.tgz",
"integrity": "sha512-wAfOT9Y5i/xnxNOdKJKXdOCw9Q3yQLahBUeuRol+s+o20F6h2a4tLEbJ1lBCYwEQ30Sf9Meqeipk1gib3YwF5w=="
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

4
package.json

@ -55,6 +55,7 @@ @@ -55,6 +55,7 @@
"react-native-calendars": "^1.1298.0",
"react-native-config": "^1.5.1",
"react-native-controlled-mentions": "^2.2.5",
"react-native-country-flag": "^2.0.2",
"react-native-date-picker": "^4.2.13",
"react-native-device-info": "^10.6.0",
"react-native-document-picker": "^9.0.1",
@ -98,7 +99,8 @@ @@ -98,7 +99,8 @@
"redux": "^4.2.1",
"rn-fetch-blob": "^0.12.0",
"socket.io-client": "^4.5.0",
"validate.js": "^0.13.1"
"validate.js": "^0.13.1",
"world-countries": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",

5
src/modules/account/components/change-phone-number-modal.component.tsx

@ -3,7 +3,6 @@ import { $size, BottomModal, Button } from '@/shared' @@ -3,7 +3,6 @@ import { $size, BottomModal, Button } from '@/shared'
import { FormPhone } from '@/shared/components/forms'
import RBSheet from 'react-native-raw-bottom-sheet'
import { StyleSheet, View } from 'react-native'
import { PartialTheme } from '@/shared/themes/interfaces'
import { useTheme } from '@/shared/hooks/use-theme.hook'
interface IProps {
@ -23,7 +22,7 @@ export const ChangePhoneNumberModal = ({ @@ -23,7 +22,7 @@ export const ChangePhoneNumberModal = ({
isLoading,
onClose = () => {},
}: IProps) => {
const { styles, theme } = useTheme(createStyles)
const { styles } = useTheme(createStyles)
const [newPhoneNumber, setNewPhoneNumber] = useState('')
return (
@ -57,7 +56,7 @@ export const ChangePhoneNumberModal = ({ @@ -57,7 +56,7 @@ export const ChangePhoneNumberModal = ({
)
}
const createStyles = (theme: PartialTheme) =>
const createStyles = () =>
StyleSheet.create({
container: {
width: '100%',

1
src/modules/auth/screens/sign-in.screen.tsx

@ -13,7 +13,6 @@ import { useAuthorization } from '../hooks' @@ -13,7 +13,6 @@ import { useAuthorization } from '../hooks'
import { PartialTheme } from '@/shared/themes/interfaces'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import { useAndroidPaddingKeyboard } from '@/shared/hooks/use-android-padding-keyboard.hook'
interface IProps extends IRouteParams {}
export const SignInScreen: FC<IProps> = ({ navigation }) => {

9
src/modules/chats/hooks/use-send-sticker.hook.ts

@ -13,10 +13,15 @@ export const useSendSticker = () => { @@ -13,10 +13,15 @@ export const useSendSticker = () => {
})
}
const sendSticker = async (chatId: number | string, stickerName: string) => {
const sendSticker = async (
chatId: number | string,
stickerName: string,
) => {
setSending(true)
try {
await chatMessageManager.sendStickerMessage.bind(chatMessageManager)({
await chatMessageManager.sendStickerMessage.bind(
chatMessageManager,
)({
chatId,
sticker: stickerName,
})

6
src/modules/media/smart-components/select-sticker.smart-component.tsx

@ -1,14 +1,12 @@ @@ -1,14 +1,12 @@
import {
$size,
BottomModal,
isAndroid,
PrimaryHeader,
Txt,
useEventsListener,
useTheme,
} from '@/shared'
import { PartialTheme } from '@/shared/themes/interfaces'
import BottomSheet, {
import {
BottomSheetBackdrop,
BottomSheetModal,
BottomSheetScrollView,
@ -18,10 +16,8 @@ import { @@ -18,10 +16,8 @@ import {
Dimensions,
Image,
Platform,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { stickersList } from '../config'

1
src/modules/root/index.tsx

@ -105,7 +105,6 @@ export const Navigation: FC = () => { @@ -105,7 +105,6 @@ export const Navigation: FC = () => {
{navigation}
</NavigationContainer>
</SafeAreaView>
<InfoModalSmartComponent />
<DatePickerSmartComponent />
<ModalPickerSmartComponent />

1
src/modules/root/smart-components/index.ts

@ -5,3 +5,4 @@ export * from './done-task.smart-component' @@ -5,3 +5,4 @@ export * from './done-task.smart-component'
export * from './modal-picker-with-pagination.smart-component'
export * from './fancybox.smart-component'
export * from './fullscreen-video.smart-component'
export * from './select-country-phone.smart-component'

157
src/modules/root/smart-components/select-country-phone.smart-component.tsx

@ -0,0 +1,157 @@ @@ -0,0 +1,157 @@
import {
BottomModal,
CheckBox,
SearchForm,
useEventsListener,
useTheme,
} from '@/shared'
import { PartialTheme } from '@/shared/themes/interfaces'
import React, { useCallback, useRef, useState } from 'react'
import {
StyleSheet,
Dimensions,
TouchableOpacity,
Text,
View,
FlatList,
} from 'react-native'
import countries, { Countries, Country } from 'world-countries'
import { $size, cutLongText } from '@/shared/helpers'
import CountryFlag from 'react-native-country-flag'
import RBSheet from 'react-native-raw-bottom-sheet'
const screenHeight = Dimensions.get('screen').height
export const SelectCountryPhone = () => {
const [list, setList] = useState<Countries | null>(null)
const sheetRef = useRef<RBSheet>(null)
const { styles } = useTheme(createStyles)
const [currentIso, setCurrentIso] = useState<string>('')
const settingsRef = useRef({
send: null,
}).current
const search = useCallback((searchTerm: string) => {
if (!!searchTerm && searchTerm.length > 0) {
setList(prev =>
prev?.filter((it: Country) =>
it.name.common
.toLowerCase()
.includes(searchTerm.toLowerCase()),
),
)
} else {
setList(countries)
}
}, [])
useEventsListener(
'selectCountryCode',
data => {
sheetRef.current.open()
settingsRef.send = data.onSelect
setCurrentIso(data.currentIso)
setList(countries)
},
[sheetRef.current, settingsRef],
)
const close = useCallback(() => {
sheetRef.current.close()
}, [])
const select = useCallback(
(iso: string, code: string) => {
setCurrentIso(iso)
settingsRef.send({ iso, code })
close()
},
[close],
)
const renderItem = ({ item }: { item: Country }) => (
<TouchableOpacity
style={styles.item}
key={item.cca2}
onPress={() =>
select(item.cca2, item.idd.root + item.idd.suffixes[0])
}>
<View style={styles.flagWrapper}>
<CountryFlag
isoCode={item.cca2}
size={$size(18)}
style={{ marginRight: 10 }}
/>
<Text style={styles.code}>
{cutLongText(
`${item?.idd?.root}${item?.idd?.suffixes[0] || ''}`,
5,
)}
</Text>
</View>
<Text style={styles.label}>
{cutLongText(item.name.common, 20)}
</Text>
<CheckBox
isChecked={item.cca2 === currentIso}
onPress={() =>
select(item.cca2, item.idd.root + item.idd.suffixes[0])
}
/>
</TouchableOpacity>
)
return (
<BottomModal
wrapperStyle={{ justifyContent: 'flex-start' }}
closeOnPressMask
sheetRef={ref => (sheetRef.current = ref)}
height={screenHeight - 250}
containerStyle={{ paddingHorizontal: 0 }}>
<View style={{ width: '100%' }}>
<SearchForm
searchValue={''}
containerStyle={styles.searchContainer}
placeholder="Знайдіть свою країну"
onChange={search}
/>
<FlatList data={list} renderItem={renderItem} />
</View>
</BottomModal>
)
}
const createStyles = (theme: PartialTheme) =>
StyleSheet.create({
container: {
backgroundColor: theme.$layoutBg,
paddingHorizontal: $size(20),
},
searchContainer: {},
item: {
backgroundColor: '#fff',
marginHorizontal: $size(20),
marginVertical: $size(8),
padding: $size(12),
borderRadius: $size(10),
flexDirection: 'row',
alignItems: 'center',
},
label: {
marginRight: 'auto',
fontSize: $size(16),
},
flag: {
fontSize: 24,
marginRight: $size(5),
},
code: {
marginRight: $size(24),
fontSize: $size(16),
overflow: 'hidden',
},
flagWrapper: {
width: 90,
flexDirection: 'row',
alignItems: 'center',
},
})

59
src/shared/components/forms/form-phone.component.tsx

@ -1,10 +1,20 @@ @@ -1,10 +1,20 @@
import React from 'react'
import { StyleSheet, TextInputProps, View, Text, ViewStyle } from 'react-native'
import React, { useState } from 'react'
import {
StyleSheet,
TextInputProps,
View,
Text,
ViewStyle,
TouchableOpacity,
} from 'react-native'
import { $size, getCurrentThemeType } from '@/shared/helpers'
import { MaskedInput } from '../plugins/masked-input.component'
import { PartialTheme } from '@/shared/themes/interfaces'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import { RoundButton } from '../buttons'
import { IconComponent } from '../elements'
import { appEvents } from '@/shared/events'
import CountryFlag from 'react-native-country-flag'
import { SelectCountryPhone } from '@/modules/root/smart-components'
interface IProps {
label?: string
@ -23,6 +33,36 @@ interface IProps { @@ -23,6 +33,36 @@ interface IProps {
export const FormPhone = (props: IProps) => {
const { styles, theme } = useTheme(createStyles)
const themeMode = getCurrentThemeType()
const [inputCode, setInputCode] = useState({ code: '380', iso: 'UA' })
const renderCustomFlagButton = () => (
<TouchableOpacity
onPress={changeCountryCode}
style={{
flexDirection: 'row',
alignItems: 'center',
marginRight: 10,
}}>
<CountryFlag
isoCode={inputCode.iso}
size={$size(18)}
style={{ marginRight: 10 }}
/>
<IconComponent
name="caretleft-2-1"
size={15}
color={theme.$secondaryText}
onPress={changeCountryCode}
/>
</TouchableOpacity>
)
const changeCountryCode = () => {
appEvents.emit('selectCountryCode', {
onSelect: params => setInputCode(params),
currentIso: inputCode.iso,
})
}
return (
<>
@ -41,29 +81,24 @@ export const FormPhone = (props: IProps) => { @@ -41,29 +81,24 @@ export const FormPhone = (props: IProps) => {
borderColor: theme.$border,
},
]}>
<RoundButton
disabled={true}
icon={props.icon || 'user-1'}
iconSize={$size(18, 14)}
iconColor={theme.iconInput.$icon}
style={styles.icon}
/>
{renderCustomFlagButton()}
<MaskedInput
type={'cel-phone'}
value={props.value}
value={props.value || inputCode.code}
name={props.name}
onChange={props.onChange}
styles={styles.input}
disabled={props.disabled}
inputProps={{
placeholder: '+38 (068) 111 22 33',
placeholder: `+${inputCode.code} 68 111 22 33`,
maxLength: 19,
}}
/>
</View>
<Text style={styles.error}>{props.error}</Text>
</View>
<SelectCountryPhone />
</>
)
}

11
src/shared/components/modals/bottom-modal.smart-component.tsx

@ -17,6 +17,7 @@ interface IProps { @@ -17,6 +17,7 @@ interface IProps {
onOpen?: () => void
closeDuration?: number
closeOnDragDown?: boolean
wrapperStyle?: ViewStyle
}
export const BottomModal: FC<IProps> = ({
@ -30,6 +31,7 @@ export const BottomModal: FC<IProps> = ({ @@ -30,6 +31,7 @@ export const BottomModal: FC<IProps> = ({
onClose,
onOpen,
closeDuration = 250,
wrapperStyle,
}) => {
const { styles } = useTheme(createStyles)
@ -44,19 +46,14 @@ export const BottomModal: FC<IProps> = ({ @@ -44,19 +46,14 @@ export const BottomModal: FC<IProps> = ({
openDuration={250}
closeDuration={closeDuration}
customStyles={{
container: { ...styles.wrapper, height },
container: { ...styles.wrapper, height, ...wrapperStyle },
draggableIcon: {
backgroundColor: '#C6C6C6',
width: $size(70),
height: $size(2),
},
}}>
<View
style={[
styles.innerContainer,
// { height: height },
containerStyle,
]}>
<View style={[styles.innerContainer, containerStyle]}>
{title ? <Txt style={styles.title}>{title}</Txt> : null}
{children}
</View>

3
src/shared/components/plugins/masked-input.component.tsx

@ -21,6 +21,7 @@ interface IProps { @@ -21,6 +21,7 @@ interface IProps {
styles?: any
inputProps?: any
disabled?: boolean
defaultValue?: string
}
export const MaskedInput = (props: IProps) => {
@ -29,7 +30,7 @@ export const MaskedInput = (props: IProps) => { @@ -29,7 +30,7 @@ export const MaskedInput = (props: IProps) => {
return {
maskType: 'BRL',
withDDD: true,
dddMask: '+99 (999) 999 99 99',
dddMask: '99 (999) 999 99 99',
}
return {}
}

4
src/shared/events/index.ts

@ -129,6 +129,10 @@ export type AppEvents = { @@ -129,6 +129,10 @@ export type AppEvents = {
message: IChatMessage
}
onAppFocused: {}
selectCountryCode: {
onSelect: (params: any) => void
currentIso: string
}
}
export type SocketEvents = {

12
src/shared/helpers/text.helpers.ts

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
export const cutLongText = (txt: string, length: number): string => {
if (txt.length > length) {
const cuttingTxt = txt.slice(0, length)
if (txt.length > length) {
const cuttingTxt = txt.slice(0, length)
return `${cuttingTxt}...`
}
return `${cuttingTxt}...`
}
return txt
return txt
}
export const getLinksFromTxt = (txt: string) =>
txt?.match(/\bhttp(s)?:(:)?\/\/\S+/gi)
txt?.match(/\bhttp(s)?:(:)?\/\/\S+/gi)

24
src/shared/hooks/use-events-listener.hook.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { AppEvents, appEvents, socketEvents, SocketEvents } from '../events';
import { useEffect } from 'react'
import { AppEvents, appEvents, socketEvents, SocketEvents } from '../events'
export const useEventsListener = <T extends keyof AppEvents>(
name: T,
@ -7,13 +7,13 @@ export const useEventsListener = <T extends keyof AppEvents>( @@ -7,13 +7,13 @@ export const useEventsListener = <T extends keyof AppEvents>(
dependencies: any[] = [],
) => {
useEffect(() => {
const fn = (data: AppEvents[T]) => action(data);
appEvents.on(name, fn);
const fn = (data: AppEvents[T]) => action(data)
appEvents.on(name, fn)
return () => appEvents.off(name, fn);
return () => appEvents.off(name, fn)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies);
};
}, dependencies)
}
export const useSocketListener = <T extends keyof SocketEvents>(
name: T,
@ -21,10 +21,10 @@ export const useSocketListener = <T extends keyof SocketEvents>( @@ -21,10 +21,10 @@ export const useSocketListener = <T extends keyof SocketEvents>(
dependencies: any[] = [],
) => {
useEffect(() => {
const fn = (data: SocketEvents[T]) => action(data);
socketEvents.on(name, fn);
const fn = (data: SocketEvents[T]) => action(data)
socketEvents.on(name, fn)
return () => socketEvents.off(name, fn);
return () => socketEvents.off(name, fn)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies);
};
}, dependencies)
}

4
src/shared/themes/interfaces/index.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { AccountColors } from './account.interface';
import { AccountColors } from './account.interface'
import { ChatColors } from './chat.interface'
import { ContactsColors } from './contacts.interface'
import { TaskCardColors } from './task-card.interface'
@ -8,7 +8,7 @@ import { NavigationColors } from './navigation.interface' @@ -8,7 +8,7 @@ import { NavigationColors } from './navigation.interface'
import { SharedComponentsColors } from './shared-components.interface'
import { SettingColors } from './setting.interface'
import { CallsColors } from './calls.interface'
import { TaskColors } from './task.interface';
import { TaskColors } from './task.interface'
export type ThemeType = 'light' | 'dark'

Loading…
Cancel
Save