Vitalik
9 months ago
20 changed files with 321 additions and 109 deletions
@ -0,0 +1,180 @@
@@ -0,0 +1,180 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */ |
||||
import React, { useCallback } from 'react' |
||||
import { |
||||
$size, |
||||
Avatar, |
||||
IShortUser, |
||||
IconComponent, |
||||
RouteKey, |
||||
ScreenLayout, |
||||
SearchForm, |
||||
Txt, |
||||
appEvents, |
||||
hasImageUrl, |
||||
useNav, |
||||
useTheme, |
||||
} from '@/shared' |
||||
import { useFetchUsersList } from '@/modules/users/hooks' |
||||
import { |
||||
TouchableOpacity, |
||||
StyleSheet, |
||||
FlatList, |
||||
ActivityIndicator, |
||||
Platform, |
||||
} from 'react-native' |
||||
import { PartialTheme } from '@/shared/themes/interfaces' |
||||
import { chatManager } from '@/managers' |
||||
import { simpleDispatch } from '@/store/store-helpers' |
||||
import { SelectChat } from '@/store/chats' |
||||
import { useSelector } from 'react-redux' |
||||
import { selectAccount } from '@/store/account' |
||||
|
||||
export const CreatePersonalScreen = () => { |
||||
const account = useSelector(selectAccount) |
||||
const nav = useNav() |
||||
const { styles, theme } = useTheme(createStyles) |
||||
const { |
||||
items, |
||||
searchString, |
||||
setSearchVal, |
||||
loadMore, |
||||
resetFlatList, |
||||
isLoading, |
||||
isLoadingNext, |
||||
} = useFetchUsersList({ |
||||
type: 'all', |
||||
execludeUserId: [account.id], |
||||
}) |
||||
|
||||
const onPressMessage = async userId => { |
||||
try { |
||||
const chatId = await chatManager.getPersonalChatId({ |
||||
userId, |
||||
}) |
||||
simpleDispatch(new SelectChat({ id: chatId })) |
||||
chatManager.readChat.bind(chatManager)(chatId) |
||||
|
||||
nav.navigate(RouteKey.Conversation) |
||||
} catch (e) { |
||||
appEvents.emit('openInfoModal', { |
||||
title: 'Сталась помилка!', |
||||
message: 'Спробуйте будь-ласка пізніше.', |
||||
onPressOk: () => {}, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
const renderItem = useCallback(({ item }: { item: IShortUser }) => { |
||||
return ( |
||||
<TouchableOpacity |
||||
style={styles.container} |
||||
onPress={() => onPressMessage(item.id)}> |
||||
<Avatar |
||||
imageUrl={hasImageUrl(item.avatarUrl, item.fullName)} |
||||
maxHeight={$size(35, 32)} |
||||
maxWidth={$size(35, 32)} |
||||
textStyle={styles.avatarLabel} |
||||
/> |
||||
<Txt style={styles.userName}>{item.fullName}</Txt> |
||||
<IconComponent |
||||
name="chatcircledots-1" |
||||
size={24} |
||||
color="#9F2843" |
||||
/> |
||||
</TouchableOpacity> |
||||
) |
||||
}, []) |
||||
|
||||
const renderFooter = useCallback(() => { |
||||
if (!isLoading && isLoadingNext) |
||||
return ( |
||||
<ActivityIndicator |
||||
color={theme.$loaderPrimary} |
||||
style={{ marginTop: 20 }} |
||||
/> |
||||
) |
||||
else return null |
||||
}, [isLoading, isLoadingNext, theme]) |
||||
|
||||
const renderEmpty = useCallback(() => { |
||||
if (isLoading || isLoadingNext) { |
||||
return <ActivityIndicator color={theme.$loaderPrimary} /> |
||||
} else { |
||||
return <Txt style={styles.emptyText}>Користувачі відсутні</Txt> |
||||
} |
||||
}, [isLoading, isLoadingNext]) |
||||
|
||||
const keyExtractor = useCallback(item => `${item.id}`, []) |
||||
|
||||
return ( |
||||
<ScreenLayout |
||||
horizontalPadding={0} |
||||
header={{ |
||||
goBack: nav.goBack, |
||||
title: 'Новий персональний чат', |
||||
style: { |
||||
marginBottom: $size(20, 18), |
||||
paddingTop: $size(10, 10), |
||||
}, |
||||
}}> |
||||
<> |
||||
<SearchForm |
||||
containerStyle={styles.searchContainer} |
||||
searchValue={searchString} |
||||
placeholder={'Знайдіть контакт'} |
||||
onChange={setSearchVal} |
||||
/> |
||||
<FlatList |
||||
style={{ flex: 1 }} |
||||
data={items} |
||||
renderItem={renderItem} |
||||
contentContainerStyle={{ paddingHorizontal: $size(16) }} |
||||
keyExtractor={keyExtractor} |
||||
initialNumToRender={10} |
||||
onEndReachedThreshold={0.4} |
||||
refreshing={Platform.select({ |
||||
ios: false, |
||||
android: isLoading, |
||||
})} |
||||
onEndReached={loadMore} |
||||
onRefresh={resetFlatList} |
||||
showsVerticalScrollIndicator={false} |
||||
showsHorizontalScrollIndicator={false} |
||||
ListEmptyComponent={renderEmpty} |
||||
ListFooterComponent={renderFooter} |
||||
/> |
||||
</> |
||||
</ScreenLayout> |
||||
) |
||||
} |
||||
|
||||
const createStyles = (theme: PartialTheme) => |
||||
StyleSheet.create({ |
||||
container: { |
||||
flexDirection: 'row', |
||||
alignItems: 'center', |
||||
height: $size(65, 60), |
||||
borderTopWidth: 0.3, |
||||
borderTopColor: theme.$border, |
||||
}, |
||||
userName: { |
||||
fontSize: $size(16, 14), |
||||
fontWeight: '400', |
||||
marginLeft: $size(20, 16), |
||||
marginRight: 'auto', |
||||
color: theme.$textPrimary, |
||||
}, |
||||
checkBox: { |
||||
position: 'absolute', |
||||
right: 0, |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
}, |
||||
searchContainer: { |
||||
borderBottomWidth: 0, |
||||
}, |
||||
avatarLabel: { |
||||
fontSize: $size(20, 18), |
||||
fontWeight: '500', |
||||
}, |
||||
}) |
@ -1,4 +1,5 @@
@@ -1,4 +1,5 @@
|
||||
export * from './chats.screen' |
||||
export * from './group-chat-detail.screen' |
||||
export * from './create-group.screen' |
||||
export * from './forward-message.screen' |
||||
export * from './forward-message.screen' |
||||
export * from './create-personal.screen' |
||||
|
@ -1,16 +1,16 @@
@@ -1,16 +1,16 @@
|
||||
import _ from "lodash"; |
||||
import { IFilesConfig } from "../interfaces"; |
||||
import _ from 'lodash' |
||||
import { IFilesConfig } from '../interfaces' |
||||
|
||||
export const transformFilesLimitsConfig = ( |
||||
data: Partial<Record<keyof IFilesConfig, string>> |
||||
data: Partial<Record<keyof IFilesConfig, string>>, |
||||
) => { |
||||
const config: Partial<IFilesConfig> = {}; |
||||
const config: Partial<IFilesConfig> = {} |
||||
|
||||
if (_.isEmpty(data)) return config; |
||||
if (_.isEmpty(data)) return config |
||||
|
||||
Object.keys(data).map(it => { |
||||
if (!_.isNaN(Number(data[it]))) config[it] = Number(data[it]); |
||||
else config[it] = data[it].toLowerCase(); |
||||
}); |
||||
return config; |
||||
}; |
||||
Object.keys(data).map(it => { |
||||
if (!_.isNaN(Number(data[it]))) config[it] = Number(data[it]) |
||||
else config[it] = data[it].toLowerCase() |
||||
}) |
||||
return config |
||||
} |
||||
|
@ -1,9 +1,11 @@
@@ -1,9 +1,11 @@
|
||||
import moment from "moment" |
||||
import moment from 'moment' |
||||
|
||||
export const isSoonBirthday = (dateOfBirthday: string): boolean => { |
||||
const birthWithCurrentYear = moment(dateOfBirthday).year(moment().year()).toDate() |
||||
const birthWithCurrentYear = moment(dateOfBirthday) |
||||
.year(moment().year()) |
||||
.toDate() |
||||
|
||||
const diffInDays = moment(birthWithCurrentYear).diff(new Date, 'days') |
||||
const diffInDays = moment(birthWithCurrentYear).diff(new Date(), 'days') |
||||
|
||||
return diffInDays >= 0 && diffInDays < 3 |
||||
} |
||||
|
@ -1,14 +1,12 @@
@@ -1,14 +1,12 @@
|
||||
import { Alert, Linking } from "react-native"; |
||||
import { Alert, Linking } from 'react-native' |
||||
|
||||
export const callPhoneNumber = (phoneNumber: string) => Linking.openURL(`tel:${phoneNumber}`) |
||||
export const callPhoneNumber = (phoneNumber: string) => |
||||
Linking.openURL(`tel:${phoneNumber}`) |
||||
|
||||
export const sendEmail = (to: string) => Linking.openURL(`mailto:${to}`) |
||||
|
||||
|
||||
export const clearLink = (url: string) => { |
||||
|
||||
|
||||
if (url.includes('utm_source')) { |
||||
return `${url.split('utm_source')[0]}...` |
||||
} else return url |
||||
} |
||||
if (url.includes('utm_source')) { |
||||
return `${url.split('utm_source')[0]}...` |
||||
} else return url |
||||
} |
||||
|
@ -1,8 +1,8 @@
@@ -1,8 +1,8 @@
|
||||
import { ThemeContext } from '@/shared/themes'; |
||||
import { useContext } from 'react'; |
||||
import { ThemeContext } from '@/shared/themes' |
||||
import { useContext } from 'react' |
||||
|
||||
export const getCurrentThemeType = () => { |
||||
const { themeTitle } = useContext(ThemeContext) |
||||
const { themeTitle } = useContext(ThemeContext) |
||||
|
||||
return themeTitle |
||||
} |
||||
return themeTitle |
||||
} |
||||
|
@ -1,8 +1,17 @@
@@ -1,8 +1,17 @@
|
||||
export const getTitleByCount = (count: number, [form1, form2, form3]: string[]) => { |
||||
const tens = Math.abs(count) % 100 |
||||
const units = tens % 10 |
||||
if (tens > 10 && tens < 20) { return `${count} ${form3}` } |
||||
if (units > 1 && units < 5) { return `${count} ${form2}` } |
||||
if (units == 1) { return `${count} ${form1}` } |
||||
return `${count} ${form3}` |
||||
} |
||||
export const getTitleByCount = ( |
||||
count: number, |
||||
[form1, form2, form3]: string[], |
||||
) => { |
||||
const tens = Math.abs(count) % 100 |
||||
const units = tens % 10 |
||||
if (tens > 10 && tens < 20) { |
||||
return `${count} ${form3}` |
||||
} |
||||
if (units > 1 && units < 5) { |
||||
return `${count} ${form2}` |
||||
} |
||||
if (units == 1) { |
||||
return `${count} ${form1}` |
||||
} |
||||
return `${count} ${form3}` |
||||
} |
||||
|
@ -1,15 +1,20 @@
@@ -1,15 +1,20 @@
|
||||
import { NativeTouchEvent } from "react-native"; |
||||
import { NativeTouchEvent } from 'react-native' |
||||
|
||||
const maxDeviationX = 60 |
||||
const minOffsetY = 140 |
||||
|
||||
export const getSwipeDirection = ({ locationX, locationY }: NativeTouchEvent, touchStart: { locationX: number, locationY: number }): 'up' | 'down' | 'none' => { |
||||
const offsetX = Math.abs(touchStart.locationX - locationX) |
||||
const offsetY = touchStart.locationY - locationY |
||||
const absOffsetY = Math.abs(offsetY) |
||||
export const getSwipeDirection = ( |
||||
{ locationX, locationY }: NativeTouchEvent, |
||||
touchStart: { locationX: number; locationY: number }, |
||||
): 'up' | 'down' | 'none' => { |
||||
const offsetX = Math.abs(touchStart.locationX - locationX) |
||||
const offsetY = touchStart.locationY - locationY |
||||
const absOffsetY = Math.abs(offsetY) |
||||
|
||||
if (offsetX < maxDeviationX && offsetY < 0 && absOffsetY >= minOffsetY) return 'up' |
||||
if (offsetX < maxDeviationX && offsetY > 0 && absOffsetY >= minOffsetY) return 'down' |
||||
if (offsetX < maxDeviationX && offsetY < 0 && absOffsetY >= minOffsetY) |
||||
return 'up' |
||||
if (offsetX < maxDeviationX && offsetY > 0 && absOffsetY >= minOffsetY) |
||||
return 'down' |
||||
|
||||
return 'none' |
||||
} |
||||
return 'none' |
||||
} |
||||
|
@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
|
||||
export const isImgUrl = (url: string) => { |
||||
return url.toLowerCase().match(/\.(jpeg|jpg|gif|png|heic)$/) != null; |
||||
return url.toLowerCase().match(/\.(jpeg|jpg|gif|png|heic)$/) != null |
||||
} |
||||
|
||||
export const isVideo = (url: string) => |
||||
/\.(amv|mp4|m4p|m4v|mpg|mpeg|avi)$/i.test(url) |
||||
/\.(amv|mp4|m4p|m4v|mpg|mpeg|avi)$/i.test(url) |
||||
|
@ -1,15 +1,16 @@
@@ -1,15 +1,16 @@
|
||||
import _ from "lodash" |
||||
import _ from 'lodash' |
||||
|
||||
export const compare = (basis: Record<string, string>, compared: Record<string, string>) => { |
||||
const diffIds = [] |
||||
export const compare = ( |
||||
basis: Record<string, string>, |
||||
compared: Record<string, string>, |
||||
) => { |
||||
const diffIds = [] |
||||
|
||||
if (!compared || _.isEmpty(compared)) |
||||
return Object.keys(basis) |
||||
if (!compared || _.isEmpty(compared)) return Object.keys(basis) |
||||
|
||||
Object.keys(basis).map(it => { |
||||
if (basis[it] !== compared[it]) |
||||
diffIds.push(it) |
||||
}) |
||||
Object.keys(basis).map(it => { |
||||
if (basis[it] !== compared[it]) diffIds.push(it) |
||||
}) |
||||
|
||||
return diffIds |
||||
} |
||||
return diffIds |
||||
} |
||||
|
Loading…
Reference in new issue