Vitalik 9 months ago
parent
commit
5856700cd2
  1. 16
      App.tsx
  2. 38
      android/app/src/main/AndroidManifest.xml
  3. BIN
      android/app/src/main/assets/fonts/fontello.ttf
  4. 7
      android/app/src/main/file_viewer_provider_paths.xml
  5. 2
      android/app/src/main/java/com/taskme2/MainApplication.java
  6. 2
      android/build.gradle
  7. 5
      android/settings.gradle
  8. 10
      ios/Podfile.lock
  9. 4
      ios/taskme2.xcodeproj/project.pbxproj
  10. 9
      package-lock.json
  11. 1
      package.json
  12. 2
      src/App.tsx
  13. BIN
      src/assets/fonts/fontello.ttf
  14. 52
      src/config/default-chat-bg.config.ts
  15. 14
      src/config/fontello.json
  16. 3
      src/modules/account/components/change-date-of-birthday-moda.component.tsx
  17. 5
      src/modules/account/components/fake-date-input-with-modal.component.tsx
  18. 4
      src/modules/account/hooks/use-account-editor.hook.ts
  19. 33
      src/modules/chats/hooks/use-send-files.hook.ts
  20. 13
      src/modules/contacts/enums/contact-field-key.enum.ts
  21. 3
      src/modules/contacts/enums/index.ts
  22. 1
      src/modules/contacts/hooks/index.ts
  23. 49
      src/modules/contacts/hooks/use-contact-copy.hook.ts
  24. 14
      src/modules/contacts/hooks/use-contact-detail.hook.ts
  25. 5
      src/modules/contacts/screens/contact-detail.screen.tsx
  26. 1
      src/modules/root/index.tsx
  27. 2
      src/modules/root/smart-components/fancybox.smart-component.tsx
  28. 6
      src/modules/root/smart-components/index.ts
  29. 31
      src/services/system/fs.service.ts
  30. 2
      src/services/system/media.service.ts
  31. 5
      src/services/system/notification.service.ts
  32. 8
      src/shared/components/elements/avatar.component.tsx
  33. 73
      src/shared/components/elements/icon.component.tsx
  34. 2
      src/shared/components/elements/index.ts
  35. 26
      src/shared/components/forms/form-text-input-with-icon.component.tsx
  36. 26
      src/shared/components/forms/touchable-fake-input.atom.tsx
  37. 10
      src/shared/components/plugins/chat/chat-item-file.component.tsx
  38. 8
      src/shared/components/plugins/chat/chat-item-image.component.tsx
  39. 28
      src/shared/components/texts/avatar-title.component.tsx
  40. 27
      src/shared/enums/file-type.enum.ts
  41. 11
      src/shared/helpers/copy-to-buffer.helper.ts
  42. 4
      src/shared/helpers/fs.helpers.ts
  43. 1
      src/shared/helpers/index.ts
  44. 20
      src/shared/interfaces/contact.interfaces.ts

16
App.tsx

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import React from 'react';
import {SafeAreaView, Text} from 'react-native';
import React from 'react'
import { SafeAreaView, Text } from 'react-native'
function App(): JSX.Element {
return (
<SafeAreaView>
<Text>test</Text>
</SafeAreaView>
);
return (
<SafeAreaView>
<Text>test</Text>
</SafeAreaView>
)
}
export default App;
export default App

38
android/app/src/main/AndroidManifest.xml

@ -5,6 +5,8 @@ @@ -5,6 +5,8 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
@ -55,5 +57,39 @@ @@ -55,5 +57,39 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="com.vinzscam.reactnativefileviewer.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_viewer_provider_paths"
/>
</provider>
</application>
</manifest>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="application/pdf" />
</intent>
</queries>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="text/plain" />
</intent>
</queries>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="application/msword" />
</intent>
</queries>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
</intent>
</queries>
</manifest>

BIN
android/app/src/main/assets/fonts/fontello.ttf

Binary file not shown.

7
android/app/src/main/file_viewer_provider_paths.xml

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="/" />
<external-files-path name="external_files" path="" />
<external-path name="external" path="." />
<cache-path name="cache" path="/" />
</paths>

2
android/app/src/main/java/com/taskme2/MainApplication.java

@ -24,7 +24,7 @@ public class MainApplication extends Application implements ReactApplication { @@ -24,7 +24,7 @@ public class MainApplication extends Application implements ReactApplication {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
// packages.add();
return packages;
}

2
android/build.gradle

@ -9,6 +9,8 @@ buildscript { @@ -9,6 +9,8 @@ buildscript {
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
pdfViewerVersion = "3.2.0-beta.1"
pdfViewerRepo = "com.github.mhiew"
}
repositories {
google()

5
android/settings.gradle

@ -4,4 +4,7 @@ include ':app' @@ -4,4 +4,7 @@ include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')
include ':react-native-config'
project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android')
project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android')
include ':react-native-file-viewer'
project(':react-native-file-viewer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-file-viewer/android')

10
ios/Podfile.lock

@ -557,7 +557,7 @@ PODS: @@ -557,7 +557,7 @@ PODS:
- React-Core
- rn-fetch-blob (0.12.0):
- React-Core
- RNAudioRecorderPlayer (3.5.3):
- RNAudioRecorderPlayer (3.6.6):
- React-Core
- RNCAsyncStorage (1.21.0):
- React-Core
@ -571,6 +571,8 @@ PODS: @@ -571,6 +571,8 @@ PODS:
- React-Core
- SDWebImage (~> 5.11.1)
- SDWebImageWebPCoder (~> 0.8.4)
- RNFileViewer (2.1.5):
- React-Core
- RNFS (2.20.0):
- React-Core
- RNGestureHandler (2.14.0):
@ -710,6 +712,7 @@ DEPENDENCIES: @@ -710,6 +712,7 @@ DEPENDENCIES:
- "RNCPicker (from `../node_modules/@react-native-picker/picker`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNFastImage (from `../node_modules/react-native-fast-image`)
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
@ -874,6 +877,8 @@ EXTERNAL SOURCES: @@ -874,6 +877,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-device-info"
RNFastImage:
:path: "../node_modules/react-native-fast-image"
RNFileViewer:
:path: "../node_modules/react-native-file-viewer"
RNFS:
:path: "../node_modules/react-native-fs"
RNGestureHandler:
@ -973,12 +978,13 @@ SPEC CHECKSUMS: @@ -973,12 +978,13 @@ SPEC CHECKSUMS:
ReactCommon: 38824bfffaf4c51fbe03a2730b4fd874ef34d67b
ReactNativeExceptionHandler: b11ff67c78802b2f62eed0e10e75cb1ef7947c60
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNAudioRecorderPlayer: 6638283925b46808310c9fdf803ebabc3abdbadc
RNAudioRecorderPlayer: f790fc1afb118552ae6285d60adde52ee6b5d9ef
RNCAsyncStorage: 618d03a5f52fbccb3d7010076bc54712844c18ef
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCPicker: b18aaf30df596e9b1738e7c1f9ee55402a229dca
RNDeviceInfo: db5c64a060e66e5db3102d041ebe3ef307a85120
RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741
RNImageCropPicker: d9616a0cb9b72e8551ff94a7a5021fbd29050aa5

4
ios/taskme2.xcodeproj/project.pbxproj

@ -909,7 +909,7 @@ @@ -909,7 +909,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = taskme2/taskme2.entitlements;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_TEAM = HQ3J3TDPR2;
DEVELOPMENT_TEAM = VCUZPZ9254;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = taskme2/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Task me ;)";
@ -946,7 +946,7 @@ @@ -946,7 +946,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = taskme2/taskme2.entitlements;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_TEAM = HQ3J3TDPR2;
DEVELOPMENT_TEAM = VCUZPZ9254;
INFOPLIST_FILE = taskme2/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Task me ;)";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";

9
package-lock.json generated

@ -53,6 +53,7 @@ @@ -53,6 +53,7 @@
"react-native-exception-handler": "^2.10.10",
"react-native-expire-storage": "^0.0.3",
"react-native-fast-image": "^8.6.3",
"react-native-file-viewer": "^2.1.5",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.11.0",
"react-native-gifted-chat": "^2.4.0",
@ -15593,6 +15594,14 @@ @@ -15593,6 +15594,14 @@
"react-native": ">=0.60.0"
}
},
"node_modules/react-native-file-viewer": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/react-native-file-viewer/-/react-native-file-viewer-2.1.5.tgz",
"integrity": "sha512-MGC6sx9jsqHdefhVQ6o0akdsPGpkXgiIbpygb2Sg4g4bh7v6K1cardLV1NwGB9A6u1yICOSDT/MOC//9Ez6EUg==",
"peerDependencies": {
"react-native": ">=0.47"
}
},
"node_modules/react-native-flipper": {
"version": "0.164.0",
"resolved": "https://registry.npmjs.org/react-native-flipper/-/react-native-flipper-0.164.0.tgz",

1
package.json

@ -63,6 +63,7 @@ @@ -63,6 +63,7 @@
"react-native-exception-handler": "^2.10.10",
"react-native-expire-storage": "^0.0.3",
"react-native-fast-image": "^8.6.3",
"react-native-file-viewer": "^2.1.5",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.11.0",
"react-native-gifted-chat": "^2.4.0",

2
src/App.tsx

@ -2,7 +2,7 @@ import React, { FC, useEffect } from 'react' @@ -2,7 +2,7 @@ import React, { FC, useEffect } from 'react'
import { Provider } from 'react-redux'
import { Navigation } from './modules/root'
import store from './store'
// import './services/system/reactron.service'
import './services/system/reactron.service'
import { ThemeProvider } from './shared/themes'
// import Orientation from 'react-native-orientation-locker'
import { AppState, LogBox } from 'react-native'

BIN
src/assets/fonts/fontello.ttf

Binary file not shown.

52
src/config/default-chat-bg.config.ts

@ -1,29 +1,29 @@ @@ -1,29 +1,29 @@
import { IChatBackgroundData } from './../modules/settings/interfaces/chat-backgrounds.interfaces';
import { ChatBgKeys } from "@/shared";
import { IChatBackgroundData } from './../modules/settings/interfaces/chat-backgrounds.interfaces'
import { ChatBgKeys } from '@/shared'
export const defaultChatBgConfig: IChatBackgroundData[] = [
{
id: ChatBgKeys.DEF_BG_1,
img: require('@/assets/images/static-chat-bg/bg-6.jpg'),
},
{
id: ChatBgKeys.DEF_BG_2,
img: require('@/assets/images/static-chat-bg/bg-2.jpg'),
},
{
id: ChatBgKeys.DEF_BG_3,
img: require('@/assets/images/static-chat-bg/bg-3.jpg'),
},
{
id: ChatBgKeys.DEF_BG_4,
img: require('@/assets/images/static-chat-bg/bg-4.jpg'),
},
{
id: ChatBgKeys.DEF_BG_5,
img: require('@/assets/images/static-chat-bg/bg-5.jpg'),
},
{
id: ChatBgKeys.DEF_BG_6,
img: require('@/assets/images/static-chat-bg/bg-1.jpg'),
},
{
id: ChatBgKeys.DEF_BG_1,
img: require('@/assets/images/static-chat-bg/bg-6.jpg'),
},
{
id: ChatBgKeys.DEF_BG_2,
img: require('@/assets/images/static-chat-bg/bg-2.jpg'),
},
{
id: ChatBgKeys.DEF_BG_3,
img: require('@/assets/images/static-chat-bg/bg-3.jpg'),
},
{
id: ChatBgKeys.DEF_BG_4,
img: require('@/assets/images/static-chat-bg/bg-4.jpg'),
},
{
id: ChatBgKeys.DEF_BG_5,
img: require('@/assets/images/static-chat-bg/bg-5.jpg'),
},
{
id: ChatBgKeys.DEF_BG_6,
img: require('@/assets/images/static-chat-bg/bg-1.jpg'),
},
]

14
src/config/fontello.json

@ -1041,6 +1041,20 @@ @@ -1041,6 +1041,20 @@
"search": [
"zip"
]
},
{
"uid": "cbc6e7a91680faee971b2dd7dc856852",
"css": "copy",
"code": 59466,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M272.7 127C263.9 129.4 254.3 139.3 252 148.5 248.8 161.3 253 172.4 264.4 181.1L270.6 185.8 542 186.3 813.3 186.7 813.7 458.2 814.2 729.7 819.3 736.1C826.5 745 833.2 748.5 843.1 748.6 854.4 748.7 864 743.2 869.7 733.3L874.2 725.8V149.2L870.5 142.2A29.4 29.4 0 0 0 850.8 126.9C841.8 124.4 282.1 124.5 272.7 127M147.7 252C137.9 254.7 129 264.5 126.7 275.3 125.3 281.8 125 358.8 125.3 567.5L125.8 850.8 130.3 858.3A33.6 33.6 0 0 0 141.9 870L149.2 874.2H725.8L733.1 870A33.6 33.6 0 0 0 744.8 858.3L749.2 850.8V274.2L745.5 267.2A29.4 29.4 0 0 0 725.8 251.9C716.8 249.4 157.1 249.5 147.7 252M688.3 562.5V813.3H186.7V311.7H688.3V562.5",
"width": 1000
},
"search": [
"copy"
]
}
]
}

3
src/modules/account/components/change-date-of-birthday-moda.component.tsx

@ -2,7 +2,6 @@ import React, { FC, useEffect, useState } from 'react' @@ -2,7 +2,6 @@ import React, { FC, useEffect, useState } from 'react'
import { $size, BottomModal, Button } from '@/shared'
import RBSheet from 'react-native-raw-bottom-sheet'
import { Platform, StyleSheet, View } from 'react-native'
import { PartialTheme } from '@/shared/themes/interfaces'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import DatePicker from 'react-native-date-picker'
@ -53,7 +52,7 @@ export const ChangeDateOfBirthdayModal: FC<IProps> = ({ @@ -53,7 +52,7 @@ export const ChangeDateOfBirthdayModal: FC<IProps> = ({
)
}
const createStyles = (theme: PartialTheme) =>
const createStyles = () =>
StyleSheet.create({
container: {
width: '100%',

5
src/modules/account/components/fake-date-input-with-modal.component.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import { $size, TouchableFakeInput } from '@/shared'
import { TouchableFakeInput } from '@/shared'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import { PartialTheme } from '@/shared/themes/interfaces'
import moment from 'moment'
import 'moment/locale/uk'
import React, { FC, useRef } from 'react'
@ -52,7 +51,7 @@ export const FakeDateInputWithModal: FC<IProps> = ({ @@ -52,7 +51,7 @@ export const FakeDateInputWithModal: FC<IProps> = ({
)
}
const createStyles = (theme: PartialTheme) =>
const createStyles = () =>
StyleSheet.create({
container: {
width: '100%',

4
src/modules/account/hooks/use-account-editor.hook.ts

@ -128,7 +128,9 @@ export const useAccountEditor = () => { @@ -128,7 +128,9 @@ export const useAccountEditor = () => {
onPressOk: () => {},
})
} catch (e: any) {
const message = e.response?.data?.key ? getMessageByExceptionKey(e.response?.data?.key) : 'Спробуйте будь-ласка пізніше.'
const message = e.response?.data?.key
? getMessageByExceptionKey(e.response?.data?.key)
: 'Спробуйте будь-ласка пізніше.'
appEvents.emit('openInfoModal', {
title: 'Сталась помилка!',
message,

33
src/modules/chats/hooks/use-send-files.hook.ts

@ -3,10 +3,7 @@ import _ from 'lodash' @@ -3,10 +3,7 @@ import _ from 'lodash'
import { Alert } from 'react-native'
import { useState } from 'react'
import { appEvents, IChatMessage } from '@/shared'
import {
alertFileSizeExceeded,
checkFileSize,
} from '@/shared/helpers'
import { alertFileSizeExceeded, checkFileSize } from '@/shared/helpers'
import { chatMessagesService } from '@/services/domain'
import {
ISendFileMessage,
@ -23,16 +20,21 @@ interface IProps { @@ -23,16 +20,21 @@ interface IProps {
export const useSendFiles = ({ replyToMessage, onSend }: IProps) => {
const [isSending, setSending] = useState<boolean>(false)
const { chatFilesSize, chatVideosSize } = useSelector(getFilesConfig)
const handlePressGallery = async (chatId: number) => {
try {
setSending(true)
const items = await mediaService.openMultiplePicker()
if (_.isEmpty(items)) return
const { allowed, exceeded } = checkFileSize(items, chatFilesSize, chatVideosSize)
const { allowed, exceeded } = checkFileSize(
items,
chatFilesSize,
chatVideosSize,
)
if (!_.isEmpty(exceeded)) alertFileSizeExceeded(exceeded, chatFilesSize, chatVideosSize)
if (!_.isEmpty(exceeded))
alertFileSizeExceeded(exceeded, chatFilesSize, chatVideosSize)
if (_.isEmpty(allowed)) return
onSend()
@ -67,9 +69,14 @@ export const useSendFiles = ({ replyToMessage, onSend }: IProps) => { @@ -67,9 +69,14 @@ export const useSendFiles = ({ replyToMessage, onSend }: IProps) => {
uri: it.uri,
}))
const { allowed, exceeded } = checkFileSize(prepared, chatFilesSize, chatVideosSize)
const { allowed, exceeded } = checkFileSize(
prepared,
chatFilesSize,
chatVideosSize,
)
if (!_.isEmpty(exceeded)) alertFileSizeExceeded(exceeded, chatFilesSize, chatVideosSize)
if (!_.isEmpty(exceeded))
alertFileSizeExceeded(exceeded, chatFilesSize, chatVideosSize)
if (_.isEmpty(allowed)) return
onSend()
@ -79,7 +86,8 @@ export const useSendFiles = ({ replyToMessage, onSend }: IProps) => { @@ -79,7 +86,8 @@ export const useSendFiles = ({ replyToMessage, onSend }: IProps) => {
chatId,
files: [file],
}
if (replyToMessage) dataToSend.replyToId = Number(replyToMessage.id)
if (replyToMessage)
dataToSend.replyToId = Number(replyToMessage.id)
await chatMessagesService.sendFileMessage(dataToSend)
} catch (e) {
console.log('SEND FILE ERROR', e)
@ -165,7 +173,10 @@ export const useSendFiles = ({ replyToMessage, onSend }: IProps) => { @@ -165,7 +173,10 @@ export const useSendFiles = ({ replyToMessage, onSend }: IProps) => {
})
}
const handlePressMenuItem = async (itemName: string, chatId: number | string) => {
const handlePressMenuItem = async (
itemName: string,
chatId: number | string,
) => {
const menuHandler = {
gallery: handlePressGallery,
camera: handlePressCamera,

13
src/modules/contacts/enums/contact-field-key.enum.ts

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
export enum ContactFieldKeyEnum {
firstName = "Ім'я",
lastName = 'Фамілія',
fullName = "Повне ім'я",
position = 'Посада',
factoryName = 'Місце роботи',
avatarUrl = 'Фото профілю',
dateOfBirthday = 'День народження',
workPhoneNumber = 'Робочий номер телефону',
personalPhoneNumber = 'Персональний номер',
innerPhoneNumber = 'Внутрішній номер телефону',
email = 'Електрона пошта',
}

3
src/modules/contacts/enums/index.ts

@ -1 +1,2 @@ @@ -1 +1,2 @@
export * from './contact-detail.enums'
export * from './contact-detail.enums'
export * from './contact-field-key.enum'

1
src/modules/contacts/hooks/index.ts

@ -1,2 +1,3 @@ @@ -1,2 +1,3 @@
export * from './use-fetch-contacts.hook'
export * from './use-contact-detail.hook'
export * from './use-contact-copy.hook'

49
src/modules/contacts/hooks/use-contact-copy.hook.ts

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
import { IContacForCopy, appEvents } from '@/shared'
import { CopyToBuffer } from '@/shared/helpers'
import * as _ from 'lodash'
import { ContactFieldKeyEnum } from '../enums'
export const useContactCopy = (contact: IContacForCopy) => {
const formatContactForCopy = (contacData: IContacForCopy): string => {
const formatData = _.omit(contacData, [
'userId',
'isSoonBirthday',
'chatId',
'status',
])
let resultString = ''
for (const key in formatData) {
resultString += `${ContactFieldKeyEnum[key]}: ${formatData[key]}\n`
}
return resultString
}
const actionCopy = () => {
appEvents.emit('openActionSheet', {
items: [
{
name: "Скопіювати І'мя та Прізвище",
onPress: () => CopyToBuffer(`${contact.fullName}`),
},
{
name: 'Скопіювати Посаду',
onPress: () =>
CopyToBuffer(
`${contact.factoryName}, ${contact.position}`,
),
},
{
name: 'Скопіювати всі данні користувача',
onPress: () => CopyToBuffer(formatContactForCopy(contact)),
},
],
})
}
return {
actionCopy,
}
}

14
src/modules/contacts/hooks/use-contact-detail.hook.ts

@ -35,15 +35,19 @@ export const useContactDetail = (id: number) => { @@ -35,15 +35,19 @@ export const useContactDetail = (id: number) => {
if (contactDetail) {
const { info: contactDetailInfo } = contactDetail
const dateToRender = contactDetailInfo?.dateOfBirth ? moment(contactDetailInfo?.dateOfBirth).format(
'DD MMMM',
) : ''
const dateToRender = contactDetailInfo?.dateOfBirth
? moment(contactDetailInfo?.dateOfBirth).format('DD MMMM')
: ''
return {
userId: contactDetailInfo?.userId,
firstName: contactDetailInfo.firstName,
lastName: contactDetailInfo?.lastName,
fullName: createContactName(contactDetailInfo.firstName, contactDetailInfo.middleName, contactDetailInfo.lastName),
fullName: createContactName(
contactDetailInfo.firstName,
contactDetailInfo.middleName,
contactDetailInfo.lastName,
),
position: contactDetailInfo?.position,
factoryName: contactDetail?.factoryName,
avatarUrl: hasImageUrl(
@ -57,7 +61,7 @@ export const useContactDetail = (id: number) => { @@ -57,7 +61,7 @@ export const useContactDetail = (id: number) => {
isSoonBirthday: isSoonBirthday(contactDetailInfo?.dateOfBirth),
email: contactDetail?.email,
chatId: contactDetail?.chatId,
status: contactDetail?.status
status: contactDetail?.status,
}
}
}, [contactDetail])

5
src/modules/contacts/screens/contact-detail.screen.tsx

@ -14,7 +14,7 @@ import { ContactsSpeakings } from '../components' @@ -14,7 +14,7 @@ import { ContactsSpeakings } from '../components'
import { BirthdayIndicator } from '../atoms'
import { PartialTheme } from '@/shared/themes/interfaces'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import { useContactDetail } from '../hooks'
import { useContactCopy, useContactDetail } from '../hooks'
import { ContactDetailFieldType } from '../enums'
import { useSelector } from 'react-redux'
import { selectId } from '@/store/account'
@ -35,6 +35,7 @@ export const ContactDetailScreen: FC<IProps> = ({ navigation, route }) => { @@ -35,6 +35,7 @@ export const ContactDetailScreen: FC<IProps> = ({ navigation, route }) => {
const { contactId } = route.params
const { contact, isLoading, infoFieldsConfig, onPress } =
useContactDetail(contactId)
const { actionCopy } = useContactCopy(contact)
const onPressMessage = async () => {
try {
@ -74,6 +75,7 @@ export const ContactDetailScreen: FC<IProps> = ({ navigation, route }) => { @@ -74,6 +75,7 @@ export const ContactDetailScreen: FC<IProps> = ({ navigation, route }) => {
if (contact?.status !== EUserStatus.Deleted && it.value)
onPress(type, it.value)
}}
needCopy
/>
)),
[contact],
@ -115,6 +117,7 @@ export const ContactDetailScreen: FC<IProps> = ({ navigation, route }) => { @@ -115,6 +117,7 @@ export const ContactDetailScreen: FC<IProps> = ({ navigation, route }) => {
indicator={avatarBirthdayIndicator}
containerStyle={styles.avatarImageWrap}
style={styles.avatarContainer}
onPressHeader={actionCopy}
/>
{contact?.status !== EUserStatus.Deleted && (

1
src/modules/root/index.tsx

@ -112,7 +112,6 @@ export const Navigation: FC = () => { @@ -112,7 +112,6 @@ export const Navigation: FC = () => {
<ActionSheet />
<FancyboxSmart />
<FullscreenVideoSmart />
{GlobalComponents}
</>
)

2
src/modules/root/smart-components/fancybox.smart-component.tsx

@ -13,7 +13,7 @@ export interface FancyboxProps { @@ -13,7 +13,7 @@ export interface FancyboxProps {
}
export const FancyboxSmart = () => {
const { styles, theme } = useTheme(createStyles)
const { styles } = useTheme(createStyles)
const [url, setUrl] = useState(null)
useEventsListener(

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

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
export * from './confirm-modal.smart-component';
export * from './info-modal.smart-component';
export * from './confirm-modal.smart-component'
export * from './info-modal.smart-component'
export * from './date-picker.smart-component'
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 './fullscreen-video.smart-component'

31
src/services/system/fs.service.ts

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
import { Alert, Platform } from 'react-native'
import { appEvents, IFile } from '@/shared'
import { appEvents, FileType, IFile } from '@/shared'
import {
createUniqueFileName,
getFileExtension,
getFileName,
getFileUri,
getNameFromFileUrl,
getUrlExtension,
isAndroid,
isImage,
} from '@/shared/helpers'
@ -15,6 +16,8 @@ import mime from 'mime' @@ -15,6 +16,8 @@ import mime from 'mime'
import { mediaPermissionsService } from './media-permissions.service'
import { saveToCameraRoll } from '@/services/system/camera-roll.service'
import Share from 'react-native-share'
import FileViewer from 'react-native-file-viewer'
import * as _ from 'lodash'
interface IWriteFileData {
content: string
fileName: string
@ -313,6 +316,31 @@ export const shareFileOutside = async ( @@ -313,6 +316,31 @@ export const shareFileOutside = async (
}
}
const validFileType = [FileType.DOC, FileType.DOCX, FileType.PDF]
const previewFile = (url: string) => {
try {
const extension = getUrlExtension(url)
if (!_.includes(validFileType, extension)) throw new Error()
const localFile = `${RNFS.DocumentDirectoryPath}/temporaryfile.${extension}`
const options = {
fromUrl: url,
toFile: localFile,
}
RNFS.downloadFile(options).promise.then(() =>
FileViewer.open(localFile),
)
} catch (error) {
appEvents.emit('openInfoModal', {
title: 'Сталась помилка!',
message: 'Цей тип файлу не підтримується!',
onPressOk: () => {},
})
}
}
export const fsService = {
getFileStat,
writeFile,
@ -325,4 +353,5 @@ export const fsService = { @@ -325,4 +353,5 @@ export const fsService = {
shareFile,
shareFileOutside,
createPath,
previewFile,
}

2
src/services/system/media.service.ts

@ -27,7 +27,7 @@ const permissions = Platform.select({ @@ -27,7 +27,7 @@ const permissions = Platform.select({
},
android: {
camera: PERMISSIONS.ANDROID.CAMERA,
gallery: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
gallery: PERMISSIONS.ANDROID.READ_MEDIA_IMAGES,
},
})

5
src/services/system/notification.service.ts

@ -4,7 +4,7 @@ import { INotification } from '@/shared/interfaces' @@ -4,7 +4,7 @@ import { INotification } from '@/shared/interfaces'
import { NotificationKeys, RouteKey } from '@/shared/enums'
import { SelectChat } from '@/store/chats'
import { simpleDispatch } from '@/store/store-helpers'
import { AppState, Platform } from 'react-native'
import { AppState, PermissionsAndroid, Platform } from 'react-native'
import OneSignal from 'react-native-onesignal'
import { DeviceInfoService } from './device-info.service'
import { navigate } from './navigation.service'
@ -140,6 +140,9 @@ export const init = async () => { @@ -140,6 +140,9 @@ export const init = async () => {
},
)
} else {
PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
)
OneSignal.getDeviceState()
.then(device => {
resolve(device)

8
src/shared/components/elements/avatar.component.tsx

@ -1,13 +1,7 @@ @@ -1,13 +1,7 @@
import React, { FC } from 'react'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import { PartialTheme } from '@/shared/themes/interfaces'
import {
StyleSheet,
TextStyle,
View,
ViewStyle,
Image,
} from 'react-native'
import { StyleSheet, TextStyle, View, ViewStyle, Image } from 'react-native'
import { Txt } from './txt.component'
interface AvatarProps {

73
src/shared/components/elements/icon.component.tsx

@ -1,45 +1,38 @@ @@ -1,45 +1,38 @@
import React from 'react';
import { createIconSetFromFontello } from 'react-native-vector-icons';
import { fontelloConfig } from '@/config/fontello.config';
import { StyleSheet, TouchableOpacity, ViewStyle } from 'react-native';
const Icon = createIconSetFromFontello(fontelloConfig);
import React from 'react'
import { createIconSetFromFontello } from 'react-native-vector-icons'
import { fontelloConfig } from '@/config/fontello.config'
import { TouchableOpacity, ViewStyle } from 'react-native'
const Icon = createIconSetFromFontello(fontelloConfig)
interface IProps {
name: string
size: number
color?: string
style?: any
onPress?: () => void
btnStyle?: ViewStyle
name: string
size: number
color?: string
style?: any
onPress?: () => void
btnStyle?: ViewStyle
}
export const IconComponent = ({
onPress,
...props
}: IProps) => {
if (onPress) {
return (
<TouchableOpacity onPress={onPress} style={[ props.btnStyle]}>
<Icon
name={props.name}
size={props.size}
color={props.color}
style={props.style}
/>
</TouchableOpacity>
)
} else {
return (
<Icon
name={props.name}
size={props.size}
color={props.color}
style={props.style}
/>
)
}
export const IconComponent = ({ onPress, ...props }: IProps) => {
if (onPress) {
return (
<TouchableOpacity onPress={onPress} style={[props.btnStyle]}>
<Icon
name={props.name}
size={props.size}
color={props.color}
style={props.style}
/>
</TouchableOpacity>
)
} else {
return (
<Icon
name={props.name}
size={props.size}
color={props.color}
style={props.style}
/>
)
}
}
const styles = StyleSheet.create({
})

2
src/shared/components/elements/index.ts

@ -10,4 +10,4 @@ export * from './not-found.component' @@ -10,4 +10,4 @@ export * from './not-found.component'
export * from './loading.component'
export * from './check-indicator.component'
export * from './remote-image.component'
export * from './selected-row-square-item.component'
export * from './selected-row-square-item.component'

26
src/shared/components/forms/form-text-input-with-icon.component.tsx

@ -1,5 +1,10 @@ @@ -1,5 +1,10 @@
import React, { FC } from 'react'
import { $size, callPhoneNumber, sendEmail } from '@/shared/helpers'
import {
$size,
CopyToBuffer,
callPhoneNumber,
sendEmail,
} from '@/shared/helpers'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import { PartialTheme } from '@/shared/themes/interfaces'
import {
@ -26,6 +31,7 @@ interface IProps { @@ -26,6 +31,7 @@ interface IProps {
keybordType?: KeyboardTypeOptions
inputProps?: TextInputProps
error?: string
needCopy?: boolean
}
export const TextInputWithIcon: FC<IProps> = ({
@ -40,6 +46,7 @@ export const TextInputWithIcon: FC<IProps> = ({ @@ -40,6 +46,7 @@ export const TextInputWithIcon: FC<IProps> = ({
keybordType,
inputProps = {},
error,
needCopy = false,
}) => {
const isDefaultType = type === 'default'
const viewPointerEventType = isDefaultType ? 'auto' : 'none'
@ -59,6 +66,14 @@ export const TextInputWithIcon: FC<IProps> = ({ @@ -59,6 +66,14 @@ export const TextInputWithIcon: FC<IProps> = ({
<View
pointerEvents={viewPointerEventType}
style={onCls(Boolean(error), 'inputWrap')}>
{needCopy && (
<IconComponent
name={iconName}
size={$size(20, 18)}
style={styles.iconLeft}
color={theme.iconComponent.$primaryColor}
/>
)}
<TextInput
editable={editable}
style={styles.input}
@ -68,12 +83,12 @@ export const TextInputWithIcon: FC<IProps> = ({ @@ -68,12 +83,12 @@ export const TextInputWithIcon: FC<IProps> = ({
keyboardType={keybordType}
{...inputProps}
/>
<IconComponent
name={iconName}
name={needCopy ? 'copy' : iconName}
size={$size(20, 18)}
style={styles.icon}
btnStyle={styles.icon}
color={theme.iconComponent.$primaryColor}
onPress={() => CopyToBuffer(value)}
/>
</View>
</Pressable>
@ -123,4 +138,7 @@ const createStyles = (theme: PartialTheme) => @@ -123,4 +138,7 @@ const createStyles = (theme: PartialTheme) =>
color: theme.$errorText,
fontSize: $size(12),
},
iconLeft: {
marginLeft: $size(15),
},
})

26
src/shared/components/forms/touchable-fake-input.atom.tsx

@ -11,7 +11,7 @@ import { @@ -11,7 +11,7 @@ import {
ViewStyle,
} from 'react-native'
import { IconComponent, Txt } from '../elements'
import { $size } from '@/shared/helpers'
import { $size, CopyToBuffer } from '@/shared/helpers'
interface IProps {
label: string
@ -22,6 +22,7 @@ interface IProps { @@ -22,6 +22,7 @@ interface IProps {
txtStyle?: TextStyle
error?: string
disabled?: boolean
needCopy?: boolean
}
export const TouchableFakeInput: FC<IProps> = ({
@ -33,6 +34,7 @@ export const TouchableFakeInput: FC<IProps> = ({ @@ -33,6 +34,7 @@ export const TouchableFakeInput: FC<IProps> = ({
txtStyle,
disabled,
error,
needCopy = false,
}) => {
const { styles, theme, onCls } = useTheme(createStyles)
@ -44,13 +46,22 @@ export const TouchableFakeInput: FC<IProps> = ({ @@ -44,13 +46,22 @@ export const TouchableFakeInput: FC<IProps> = ({
disabled={disabled}
style={onCls(Boolean(error), 'inputWrap')}
onPress={onPress}>
{needCopy && (
<IconComponent
name={iconName}
size={$size(20, 18)}
style={styles.iconLeft}
color={theme.iconComponent.$primaryColor}
/>
)}
<Text style={[styles.value, txtStyle]}>{value}</Text>
<IconComponent
name={iconName}
name={needCopy ? 'copy' : iconName}
size={$size(20, 18)}
style={styles.icon}
btnStyle={styles.icon}
color={theme.iconComponent.$primaryColor}
onPress={() => CopyToBuffer(value)}
/>
</TouchableOpacity>
@ -66,9 +77,8 @@ const createStyles = (theme: PartialTheme) => @@ -66,9 +77,8 @@ const createStyles = (theme: PartialTheme) =>
marginBottom: $size(10, 8),
},
inputWrap: {
alignItems: 'flex-start',
justifyContent: 'center',
// flexDirection: 'row',
alignItems: 'center',
flexDirection: 'row',
backgroundColor: theme.input.$bg,
borderRadius: 10,
@ -101,4 +111,8 @@ const createStyles = (theme: PartialTheme) => @@ -101,4 +111,8 @@ const createStyles = (theme: PartialTheme) =>
color: theme.$errorText,
fontSize: $size(12),
},
iconLeft: {
marginTop: $size(-5),
paddingRight: $size(15),
},
})

10
src/shared/components/plugins/chat/chat-item-file.component.tsx

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import { fsService } from '@/services/system'
import { BYTES_IN_KILOBYTE } from '@/shared/consts'
import {
$size,
@ -14,6 +13,7 @@ import { StyleSheet, TouchableOpacity, View } from 'react-native' @@ -14,6 +13,7 @@ import { StyleSheet, TouchableOpacity, View } from 'react-native'
import { IconComponent, Txt } from '../../elements'
import { ChatItem } from './chat-item.component'
import { IChatMessage } from './interfaces'
import { fsService } from '@/services/system'
interface ChatItemFileProps extends IChatMessage {
onLongPress?: () => void
@ -35,18 +35,16 @@ export const ChatItemFile: FC<ChatItemFileProps> = props => { @@ -35,18 +35,16 @@ export const ChatItemFile: FC<ChatItemFileProps> = props => {
return `${convertToMegabytes(props.content.size)} MБ - `
}, [props.content.size])
const downloadFile = useCallback(() => {
const previewFile = useCallback(() => {
const content = props.content
fsService.shareFile(content?.fileUrl, content?.name)
fsService.previewFile(content?.fileUrl)
}, [props.content])
const iconName = getIconNameByExtension(props?.content?.fileUrl)
return (
<ChatItem {...props}>
<TouchableOpacity
style={styles.content}
onPress={() => downloadFile()}>
<TouchableOpacity style={styles.content} onPress={previewFile}>
<View style={styles.iconContainer}>
<IconComponent
color={theme?.chats?.message?.$fileIcon}

8
src/shared/components/plugins/chat/chat-item-image.component.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { appEvents } from '@/shared/events'
import { useTheme } from '@/shared/hooks'
import { PartialTheme } from '@/shared/themes/interfaces'
import React, { FC, useState } from 'react'
import React, { FC } from 'react'
import { StyleSheet, Dimensions, TouchableOpacity } from 'react-native'
import { RemoteImage } from '../../elements'
import { ChatItem } from './chat-item.component'
@ -17,12 +17,6 @@ interface ChatItemImageProps extends IChatMessage { @@ -17,12 +17,6 @@ interface ChatItemImageProps extends IChatMessage {
const width = (Dimensions.get('screen').width * 60) / 100
const calcHeight = (height: number, originWidth: number) => {
if (!height || !originWidth) return width
const persent = (height / originWidth) * 100
return (persent / 100) * width
}
export const ChatItemImage: FC<ChatItemImageProps> = props => {
const { styles } = useTheme(createStyles)
// const [height, setHeight] = useState(width)

28
src/shared/components/texts/avatar-title.component.tsx

@ -2,7 +2,13 @@ import React, { FC } from 'react' @@ -2,7 +2,13 @@ import React, { FC } from 'react'
import { useTheme } from '@/shared/hooks/use-theme.hook'
import { PartialTheme } from '@/shared/themes/interfaces'
import { StyleSheet, TextStyle, View, ViewStyle } from 'react-native'
import {
StyleSheet,
TextStyle,
TouchableOpacity,
View,
ViewStyle,
} from 'react-native'
import { Avatar, Txt } from '../elements'
import { $size } from '@/shared/helpers'
@ -18,6 +24,7 @@ interface IProps { @@ -18,6 +24,7 @@ interface IProps {
indicator?: () => JSX.Element
containerStyle?: ViewStyle
style?: ViewStyle
onPressHeader: () => void
}
export const AvatarTitle: FC<IProps> = ({
@ -32,6 +39,7 @@ export const AvatarTitle: FC<IProps> = ({ @@ -32,6 +39,7 @@ export const AvatarTitle: FC<IProps> = ({
indicator,
containerStyle,
style,
onPressHeader,
}) => {
const { styles } = useTheme(createStyles)
@ -49,13 +57,19 @@ export const AvatarTitle: FC<IProps> = ({ @@ -49,13 +57,19 @@ export const AvatarTitle: FC<IProps> = ({
{indicator ? indicator() : null}
</View>
<Txt numberOfLines={2} style={[styles.title, textStyle]}>
{text}
</Txt>
<TouchableOpacity
onPress={onPressHeader}
style={{ alignItems: 'center' }}>
<Txt numberOfLines={2} style={[styles.title, textStyle]}>
{text}
</Txt>
{subTitle ? (
<Txt style={[styles.subTitle, subTitleStyle]}>{subTitle}</Txt>
) : null}
{subTitle ? (
<Txt style={[styles.subTitle, subTitleStyle]}>
{subTitle}
</Txt>
) : null}
</TouchableOpacity>
</View>
)
}

27
src/shared/enums/file-type.enum.ts

@ -1,14 +1,15 @@ @@ -1,14 +1,15 @@
export enum FileType {
DOC = "doc",
GIF = "gif",
JPEG = "jpeg",
JPG = "jpg",
MP3 = "mp3",
MP4 = "mp4",
PDF = "pdf",
PNG = "png",
SVG = "svg",
TXT = "txt",
XLS = "xls",
ZIP = "zip"
}
DOC = 'doc',
GIF = 'gif',
JPEG = 'jpeg',
JPG = 'jpg',
MP3 = 'mp3',
MP4 = 'mp4',
PDF = 'pdf',
PNG = 'png',
SVG = 'svg',
TXT = 'txt',
XLS = 'xls',
ZIP = 'zip',
DOCX = 'docx',
}

11
src/shared/helpers/copy-to-buffer.helper.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
import Clipboard from '@react-native-community/clipboard'
import { appEvents } from '../events'
export const CopyToBuffer = (value: string) => {
Clipboard.setString(value)
appEvents.emit('openInfoModal', {
title: '',
message: 'Данні скопійовано в буфер обміну',
pressButtonText: 'Продовжити',
})
}

4
src/shared/helpers/fs.helpers.ts

@ -269,3 +269,7 @@ export const getIconNameByExtension = (name: string) => { @@ -269,3 +269,7 @@ export const getIconNameByExtension = (name: string) => {
if (iconByType) return iconByType
return 'others'
}
export const getUrlExtension = (link: string) => {
return link.split(/[#?]/)[0].split('.').pop().trim()
}

1
src/shared/helpers/index.ts

@ -20,3 +20,4 @@ export * from './transforms.helpers' @@ -20,3 +20,4 @@ export * from './transforms.helpers'
export * from './url.helpers'
export * from './versions.helper'
export * from './configs.helpers'
export * from './copy-to-buffer.helper'

20
src/shared/interfaces/contact.interfaces.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { EUserStatus } from "../enums"
import { EUserStatus } from '../enums'
export interface IContact {
userId: number
@ -28,3 +28,21 @@ export interface IContactDetail { @@ -28,3 +28,21 @@ export interface IContactDetail {
innerPhoneNumber?: string
}
}
export interface IContacForCopy {
userId?: number
firstName: string
lastName: string
fullName: string
position: string
factoryName: string
avatarUrl: string
dateOfBirthday: string
workPhoneNumber: string
personalPhoneNumber: string
innerPhoneNumber: string
isSoonBirthday?: boolean
email: string
chatId?: number
status?: string
}

Loading…
Cancel
Save