Browse Source

feature | send voice msg btn & modal

merge-requests/273/head
Denis Kurmansky 3 years ago
parent
commit
373c5d011c
  1. 2
      android/app/src/main/AndroidManifest.xml
  2. 2
      android/build.gradle
  3. 13
      declaration.d.ts
  4. 41
      ios/Podfile.lock
  5. 2
      ios/taskme/Info.plist
  6. 15804
      package-lock.json
  7. 2
      package.json
  8. 2
      src/modules/chats/screens/chat.tsx
  9. 1
      src/modules/media/consts/index.ts
  10. 6
      src/modules/media/consts/record-audio-modal.consts.ts
  11. 1
      src/modules/media/index.ts
  12. 1
      src/modules/media/interfaces/index.ts
  13. 3
      src/modules/media/interfaces/record-audio-modal.interfaces.ts
  14. 1
      src/modules/media/smart-components/index.ts
  15. 169
      src/modules/media/smart-components/record-audio-modal.smart-component.tsx
  16. 2
      src/modules/root/index.tsx
  17. 1
      src/services/system/index.ts
  18. 34
      src/services/system/media-permissions.service.ts
  19. 40
      src/services/system/media.service.ts
  20. 9
      src/shared/components/plugins/chat/chat-bar.component.tsx
  21. 142
      src/shared/components/plugins/chat/chat-send-button.component.tsx
  22. 2
      src/shared/events/index.ts
  23. 7
      src/shared/themes/dark/chat.ts
  24. 7
      src/shared/themes/interfaces/chat.interface.ts
  25. 7
      src/shared/themes/light/chat.ts
  26. 53
      yarn.lock

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

@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA"/>
<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-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />

2
android/build.gradle

@ -6,6 +6,7 @@ buildscript { @@ -6,6 +6,7 @@ buildscript {
minSdkVersion = 24
compileSdkVersion = 29
targetSdkVersion = 29
kotlinVersion = '1.5.0'
ndkVersion = "22.0.7026061"
}
repositories {
@ -17,6 +18,7 @@ buildscript { @@ -17,6 +18,7 @@ buildscript {
dependencies {
classpath("com.android.tools.build:gradle:4.0.1")
classpath 'com.huawei.agconnect:agcp:1.6.0.300'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

13
declaration.d.ts vendored

@ -4,3 +4,16 @@ declare module "*.svg" { @@ -4,3 +4,16 @@ declare module "*.svg" {
const content: React.FC<SvgProps>;
export default content;
}
declare module 'react-native-sound-level' {
const RNSoundLevel: {
start(): void,
onNewFrame(data: {
id: number,
value: number,
rawValue: number
}): void
stop(): void
}
export default RNSoundLevel
}

41
ios/Podfile.lock

@ -312,6 +312,8 @@ PODS: @@ -312,6 +312,8 @@ PODS:
- React-perflogger (= 0.64.3)
- ReactNativeExceptionHandler (2.10.10):
- React-Core
- RNAudioRecorderPlayer (3.4.0):
- React-Core
- RNCAsyncStorage (1.15.8):
- React-Core
- RNCClipboard (1.5.1):
@ -370,6 +372,8 @@ PODS: @@ -370,6 +372,8 @@ PODS:
- RNScreens (3.6.0):
- React-Core
- React-RCTImage
- RNSoundLevel (1.1.5):
- React
- RNSVG (9.13.6):
- React
- RNVectorIcons (8.0.0):
@ -429,6 +433,7 @@ DEPENDENCIES: @@ -429,6 +433,7 @@ DEPENDENCIES:
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- ReactNativeExceptionHandler (from `../node_modules/react-native-exception-handler`)
- RNAudioRecorderPlayer (from `../node_modules/react-native-audio-recorder-player`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
- "RNCPicker (from `../node_modules/@react-native-picker/picker`)"
@ -440,6 +445,7 @@ DEPENDENCIES: @@ -440,6 +445,7 @@ DEPENDENCIES:
- RNPermissions (from `../node_modules/react-native-permissions`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSoundLevel (from `../node_modules/react-native-sound-level`)
- RNSVG (from `../node_modules/react-native-svg`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@ -539,6 +545,8 @@ EXTERNAL SOURCES: @@ -539,6 +545,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon"
ReactNativeExceptionHandler:
:path: "../node_modules/react-native-exception-handler"
RNAudioRecorderPlayer:
:path: "../node_modules/react-native-audio-recorder-player"
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
@ -561,6 +569,8 @@ EXTERNAL SOURCES: @@ -561,6 +569,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSoundLevel:
:path: "../node_modules/react-native-sound-level"
RNSVG:
:path: "../node_modules/react-native-svg"
RNVectorIcons:
@ -577,8 +587,8 @@ SPEC CHECKSUMS: @@ -577,8 +587,8 @@ SPEC CHECKSUMS:
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
OneSignal: 29b555f5991109dc717f75d4440c565cab92511c
OneSignalXCFramework: fe027712118f2862ab193b72270487f11450ea63
Permission-Camera: 53efcbb755b0e8bdf253dbb27cc7559ccfce8480
Permission-PhotoLibrary: 7bec836dcdd04a0bfb200c314f1aae06d4476357
Permission-Camera: 9eb618fd601ae4a674b072c3b0d37f109d7b91e5
Permission-PhotoLibrary: 900e7e33012bd5e02e5859cb65d5bb2134259c64
RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
RCTRequired: d34bf57e17cb6e3b2681f4809b13843c021feb6c
RCTTypeSafety: 8dab4933124ed39bb0c1d88d74d61b1eb950f28f
@ -592,16 +602,16 @@ SPEC CHECKSUMS: @@ -592,16 +602,16 @@ SPEC CHECKSUMS:
React-jsinspector: 34e23860273a23695342f58eed3ffd3ba10c31e0
react-native-clear-cache: 28bce59b33cd809e0afe903786787b4409d1c1fb
react-native-date-picker: 201b481c94dcb7678f4712477ad026dd7793305b
react-native-document-picker: 429972f7ece4463aa5bcdd789622b3a674a3c5d1
react-native-image-picker: 5fe0a96bef4935bbdfb02f59b910bf40d5526109
react-native-netinfo: 3d3769f0d65de15c83a9bf1346f8be71de5a24bf
react-native-onesignal: 7643517fe655b94cfda8e697ec16f9b9135648b8
react-native-document-picker: 772d04a4bc5c35da9abe27b08ac271420ae3f9ef
react-native-image-picker: 27c3726557dac6e224a17c564c16cdc7fb952f79
react-native-netinfo: 877946c7b4eb85a639cf1ea31333dadc2be999a1
react-native-onesignal: 9cb94608abf49ab78fb660f4cca0cd08e4da9742
react-native-orientation-locker: 998c0744e26624407dac068c04c605b4af7304a2
react-native-pager-view: 5ab4d0b4b44d89f77310cb3eb8129745f274ce55
react-native-restart: 45c8dca02491980f2958595333cbccd6877cb57e
react-native-safe-area-context: 61c8c484a3a9e7d1fda19f7b1794b35bbfd2262a
react-native-pager-view: f21658a2e12eced35ef998250375e4e4dc9b8487
react-native-restart: 8af4579c94638f38bd9074ec477ebf087de87dc5
react-native-safe-area-context: 8465df05de8106c584b117f0e027e17174d6e02e
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
react-native-video: a4c2635d0802f983594b7057e1bce8f442f0ad28
react-native-video: 77ce22be7abff9e373527ca10106a99dea3ba56c
React-perflogger: cc76a4254d19640f1d8ad1c66fdee800414b805c
React-RCTActionSheet: 7448f049318d8d7e8a9a1ebb742ada721757eea8
React-RCTAnimation: fb9b3fa1a4a9f5e6ab01b3368693ce69860ba76a
@ -615,16 +625,19 @@ SPEC CHECKSUMS: @@ -615,16 +625,19 @@ SPEC CHECKSUMS:
React-runtimeexecutor: 493d9abb8b23c3f84e19ae221eeba92cadcb70dc
ReactCommon: 8fea6422328e2fc093e25c9fac67adbcf0f04fb4
ReactNativeExceptionHandler: b11ff67c78802b2f62eed0e10e75cb1ef7947c60
RNCAsyncStorage: e8b8d6320a0dd90eb610fb0d0b1ef90596697c69
RNAudioRecorderPlayer: 001f01049754e978c43af97162452c8563d5794a
RNCAsyncStorage: 0f655864a81214d1c5a9bf0faf79d86dc25c383e
RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
RNCPicker: c3a3d481bec16624ed84a3c2a64c92fdc5515960
RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45
RNDeviceInfo: cc7de0772378f85d8f36ae439df20f05c590a651
RNCPushNotificationIOS: 089da3b657e1e3d464f38195fd2e3069608ef5af
RNDeviceInfo: 0d6865ab0a57d9192bdd4e4f5894340b846c3e53
RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNImageCropPicker: 448d3c6e923fde3466b49caf3c2457c2a0ba02dd
RNPermissions: f7ebe52db07c00901127966ca080b4ec6a6ceb0a
RNPermissions: bf844d392fe0ecbfbd2e4ae2b88cc32f2f09b369
RNReanimated: 58e7b950e0172235ff8296dd39ec145f9577e301
RNScreens: eb0dfb2d6b21d2d7f980ad46b14eb306d2f1062e
RNSoundLevel: 3dd5d2f6431e47f806233600936deb636a0d9800
RNSVG: 8ba35cbeb385a52fd960fd28db9d7d18b4c2974f
RNVectorIcons: f67a1abce2ec73e62fe4606e8110e95a832bc859
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d

2
ios/taskme/Info.plist

@ -8,6 +8,8 @@ @@ -8,6 +8,8 @@
<string>taskme</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>NSMicrophoneUsageDescription</key>
<string>Потрібно надати доступ для запису голосовго повiдомленя.</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>

15804
package-lock.json generated

File diff suppressed because it is too large Load Diff

2
package.json

@ -37,6 +37,7 @@ @@ -37,6 +37,7 @@
"react": "17.0.1",
"react-native": "^0.64.3",
"react-native-app-badge": "^0.1.5",
"react-native-audio-recorder-player": "^3.4.0",
"react-native-autolink": "^4.0.0",
"react-native-calendars": "^1.1274.0",
"react-native-controlled-mentions": "^2.2.5",
@ -71,6 +72,7 @@ @@ -71,6 +72,7 @@
"react-native-shadow": "^1.2.2",
"react-native-shadow-2": "^5.1.2",
"react-native-shadow-view": "^0.0.1",
"react-native-sound-level": "^1.1.5",
"react-native-splash-screen": "^3.2.0",
"react-native-svg": "9.13",
"react-native-swiper": "^1.6.0",

2
src/modules/chats/screens/chat.tsx

@ -28,7 +28,7 @@ import { @@ -28,7 +28,7 @@ import {
} from '@/shared/components/plugins/chat'
import _ from 'lodash'
interface IProps extends IRouteParams {}
interface IProps extends IRouteParams { }
export const ChatConversation = ({ navigation }: IProps) => {
const accountId = useSelector(selectId)

1
src/modules/media/consts/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './record-audio-modal.consts'

6
src/modules/media/consts/record-audio-modal.consts.ts

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
import { $size } from "@/shared"
export const DEF_DB_LVL = -65
export const DEF_VOLUME_INDICATOR_SIZE = $size(65)
export const MAX_VOLUME_INDICATOR_SIZE = DEF_VOLUME_INDICATOR_SIZE + 35
export const INITIAL_PLAY_TIME = '00:00:00'

1
src/modules/media/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './smart-components'

1
src/modules/media/interfaces/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './record-audio-modal.interfaces'

3
src/modules/media/interfaces/record-audio-modal.interfaces.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export interface RecordAudioModalSettings {
send(record: any): void
}

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

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './record-audio-modal.smart-component'

169
src/modules/media/smart-components/record-audio-modal.smart-component.tsx

@ -0,0 +1,169 @@ @@ -0,0 +1,169 @@
import {
$size,
BottomModal,
IconComponent,
Txt,
useEventsListener,
useTheme,
} from '@/shared'
import { PartialTheme } from '@/shared/themes/interfaces'
import React, { FC, useMemo, useRef, useState } from 'react'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import Svg, { Path } from 'react-native-svg'
import RBSheet from 'react-native-raw-bottom-sheet'
import RNSoundLevel from 'react-native-sound-level'
import Animated, * as RNReanimated from 'react-native-reanimated'
import { mediaService } from '@/services/system'
import { RecordAudioModalSettings } from '../interfaces'
import {
DEF_DB_LVL,
DEF_VOLUME_INDICATOR_SIZE,
INITIAL_PLAY_TIME,
MAX_VOLUME_INDICATOR_SIZE,
} from '../consts'
export const RecordAudioModalSmart: FC = () => {
const { styles, theme } = useTheme(createStyles)
const [isRecording, setRecord] = useState<boolean>(false)
const [playTime, setPlayTime] = useState<string>(INITIAL_PLAY_TIME)
const animationVal = RNReanimated.useSharedValue(DEF_DB_LVL)
const sheetRef = useRef<RBSheet | null>()
const settingsRef = useRef<RecordAudioModalSettings>({
send: null,
}).current
const animatedStyles = RNReanimated.useAnimatedStyle(() => {
const size = RNReanimated.interpolate(
animationVal.value,
[DEF_DB_LVL, 0],
[DEF_VOLUME_INDICATOR_SIZE, MAX_VOLUME_INDICATOR_SIZE],
)
return {
width: size,
height: size,
borderRadius: size / 2,
}
})
useEventsListener(
'openRecordVoiceModal',
data => {
sheetRef.current.open()
settingsRef.send = data.send
},
[sheetRef.current, settingsRef],
)
const onStartRecord = async () => {
RNSoundLevel.start()
await mediaService.onStartRecord()
mediaService.addRecordBackListener(e => {
setRecord(e.isRecording)
setPlayTime(mediaService.formatRecordPlayTime(e.currentPosition))
})
RNSoundLevel.onNewFrame = data => {
animationVal.value = RNReanimated.withSpring(data.value)
}
}
const onStopRecord = async () => {
setRecord(false)
animationVal.value = RNReanimated.withSpring(DEF_DB_LVL)
RNReanimated.cancelAnimation(animationVal)
RNSoundLevel.stop()
await mediaService.onStopRecord()
}
const timer = useMemo(() => <Txt>{playTime}</Txt>, [playTime])
return (
<BottomModal
sheetRef={ref => (sheetRef.current = ref)}
height={$size(200, 190)}
onClose={() => {
setPlayTime(INITIAL_PLAY_TIME)
onStopRecord()
}}
containerStyle={styles.container}>
<Txt style={styles.title}>Запис повідомлення</Txt>
<View style={styles.actionsContainer}>
<IconComponent
name="xcircle-1"
size={$size(23)}
color={theme?.chats?.message?.$btnSend}
/>
<TouchableOpacity
style={styles.micContainer}
onPress={!isRecording ? onStartRecord : onStopRecord}>
<View style={styles.micBtn}>
<IconComponent
name="microphone-1"
size={$size(23)}
color={theme?.chats?.message?.$btnSend}
/>
</View>
<Animated.View
style={[styles.micVolumeCircle, animatedStyles]}
/>
</TouchableOpacity>
<Svg width={$size(20)} height={$size(25)} fill="none">
<Path
d="M20.581 11.346 4.745 2.478a.75.75 0 0 0-1.072.906l2.987 8.364a.75.75 0 0 1 0 .504l-2.987 8.364a.75.75 0 0 0 1.072.907l15.836-8.868a.75.75 0 0 0 0-1.31v0ZM6.75 12h6"
stroke={theme?.chats?.message?.$btnSend}
strokeWidth={1.7}
strokeLinecap="round"
strokeLinejoin="round"
transform={`translate(${$size(1, 2)}, ${$size(
0.5,
1.5,
)}), scale(${$size(0.7, 0.85)})`}
/>
</Svg>
</View>
<View>{timer}</View>
</BottomModal>
)
}
const createStyles = ({ chats: { sendVoiceMsg } }: PartialTheme) =>
StyleSheet.create({
container: {
width: '100%',
justifyContent: 'center',
alignItems: 'center',
},
title: {
top: $size(-35),
},
actionsContainer: {
width: '60%',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: $size(25),
},
micContainer: {
alignItems: 'center',
justifyContent: 'center',
},
micBtn: {
alignItems: 'center',
justifyContent: 'center',
zIndex: 1,
width: $size(52),
height: $size(52),
backgroundColor: sendVoiceMsg.micBtn.$bg,
borderRadius: $size(62) / 2,
},
micVolumeCircle: {
position: 'absolute',
backgroundColor: sendVoiceMsg.micBtn.$border,
},
})

2
src/modules/root/index.tsx

@ -35,6 +35,7 @@ import { LoadingScreen } from './screens' @@ -35,6 +35,7 @@ import { LoadingScreen } from './screens'
import _ from 'lodash'
import { selectAccount } from '@/store/account'
import { useAppBadge } from './hooks'
import { RecordAudioModalSmart } from '../media'
export const Navigation: FC = () => {
const activeModule = useSelector(selectActiveNavigationModule)
@ -109,6 +110,7 @@ export const Navigation: FC = () => { @@ -109,6 +110,7 @@ export const Navigation: FC = () => {
<FinishTaskModal />
<SelectUserModalSmart />
<SelectTaxonomiesModalSmart />
<RecordAudioModalSmart />
</>
)
}

1
src/services/system/index.ts

@ -4,3 +4,4 @@ export * from './navigation.service' @@ -4,3 +4,4 @@ export * from './navigation.service'
export * from './storage.service'
export * from './device-info.service'
export * from './real-time.service'
export * from './media.service'

34
src/services/system/media-permissions.service.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { Alert, Linking, PermissionsAndroid } from 'react-native'
import { Alert, Linking, PermissionsAndroid, Platform } from 'react-native'
import { request, check, RESULTS } from 'react-native-permissions'
const requestCameraPermission = async () => {
@ -55,6 +55,37 @@ const checkCameraPermissions = async (permission: any): Promise<boolean> => { @@ -55,6 +55,37 @@ const checkCameraPermissions = async (permission: any): Promise<boolean> => {
}
}
const requestAudioRecordAndroidPermissions = async () => {
if (Platform.OS === 'android') {
try {
const grants = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
]);
console.log('write external stroage', grants);
if (
grants['android.permission.WRITE_EXTERNAL_STORAGE'] ===
PermissionsAndroid.RESULTS.GRANTED &&
grants['android.permission.READ_EXTERNAL_STORAGE'] ===
PermissionsAndroid.RESULTS.GRANTED &&
grants['android.permission.RECORD_AUDIO'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
console.log('Permissions granted');
} else {
console.log('All required permissions not granted');
return;
}
} catch (err) {
console.warn(err);
return;
}
}
}
const needMediaPermissions = () => {
return new Promise((resolve, reject) => {
Alert.alert(
@ -76,4 +107,5 @@ const needMediaPermissions = () => { @@ -76,4 +107,5 @@ const needMediaPermissions = () => {
export const mediaPermissionsService = {
checkCameraPermissions,
requestAudioRecordAndroidPermissions
}

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

@ -3,12 +3,12 @@ import ImagePicker, { Options } from 'react-native-image-crop-picker' @@ -3,12 +3,12 @@ import ImagePicker, { Options } from 'react-native-image-crop-picker'
import {
launchImageLibrary,
CameraOptions,
launchCamera,
} from 'react-native-image-picker'
import { PERMISSIONS } from 'react-native-permissions'
import { mediaPermissionsService } from './media-permissions.service'
import DocumentPicker from 'react-native-document-picker'
import { prepareFile, prepareFiles } from '@/shared/helpers'
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
interface IPickerProps {
width: number
@ -16,6 +16,8 @@ interface IPickerProps { @@ -16,6 +16,8 @@ interface IPickerProps {
cropperCircleOverlay?: boolean
}
const audioRecorderPlayer = new AudioRecorderPlayer();
const permissions = Platform.select({
ios: {
camera: PERMISSIONS.IOS.CAMERA,
@ -131,6 +133,32 @@ const openGalleryPicker = async () => { @@ -131,6 +133,32 @@ const openGalleryPicker = async () => {
return img
}
const onStartRecord = async () => {
await mediaPermissionsService.requestAudioRecordAndroidPermissions()
return await audioRecorderPlayer.startRecorder();
};
const onStopRecord = async () => {
const result = await audioRecorderPlayer.stopRecorder();
audioRecorderPlayer.removeRecordBackListener();
return result
};
const onStartPlay = async () => {
const msg = await audioRecorderPlayer.startPlayer();
return msg
};
const onPausePlay = async () => {
await audioRecorderPlayer.pausePlayer();
};
const onStopPlay = async () => {
audioRecorderPlayer.stopPlayer();
audioRecorderPlayer.removePlayBackListener();
};
export const mediaService = {
openCamera,
openCropPicker,
@ -138,4 +166,14 @@ export const mediaService = { @@ -138,4 +166,14 @@ export const mediaService = {
openMultiplePicker,
openFilesPicker,
launchDeviceCamera,
// Audio ====
onStartRecord,
onStopRecord,
onStartPlay,
onPausePlay,
onStopPlay,
addRecordBackListener: audioRecorderPlayer.addRecordBackListener,
addPlayBackListener: audioRecorderPlayer.addPlayBackListener,
formatRecordPlayTime: audioRecorderPlayer.mmssss
}

9
src/shared/components/plugins/chat/chat-bar.component.tsx

@ -113,12 +113,8 @@ export const ChatBar: FC<ChatBarProps> = props => { @@ -113,12 +113,8 @@ export const ChatBar: FC<ChatBarProps> = props => {
} else {
return (
<ChatSendButton
buttonType={
!props.message && props.microphoneEnabled
? 'microphone'
: 'send'
}
onPress={() => props.onPressSend()}
onSend={() => props.onPressSend()}
onSendVoice={() => { }}
/>
)
}
@ -228,7 +224,6 @@ const createStyles = (theme: PartialTheme) => @@ -228,7 +224,6 @@ const createStyles = (theme: PartialTheme) =>
position: 'absolute',
bottom: 57,
minHeight: $size(50),
// maxHeight: $size(190),
marginLeft: -62,
minWidth: Dimensions.get('screen').width,
borderBottomColor: theme?.chats?.bar?.$border,

142
src/shared/components/plugins/chat/chat-send-button.component.tsx

@ -1,67 +1,125 @@ @@ -1,67 +1,125 @@
import { $size, IconComponent, useTheme } from '@/shared'
import { appEvents } from '@/shared/events'
import { PartialTheme } from '@/shared/themes/interfaces'
import React from 'react'
import { View, StyleSheet, TouchableOpacity } from 'react-native'
import React, { FC, useEffect, useRef, useState } from 'react'
import { StyleSheet, TouchableOpacity, Animated } from 'react-native'
import Svg, { Path } from 'react-native-svg'
interface ChatSendButtonProps {
onPress: () => void
buttonType: 'send' | 'microphone'
onSend: () => void
onSendVoice: () => void
}
export const ChatSendButton = (props: ChatSendButtonProps) => {
export const ChatSendButton: FC<ChatSendButtonProps> = props => {
const [touchX, setTouchX] = useState<number>()
const [isMic, setMic] = useState<boolean>(false)
const animatedVal = useRef(new Animated.Value(0)).current
const { styles, theme } = useTheme(createStyles)
if (props.buttonType === 'microphone')
return (
<View style={styles.microphoneContainer}>
<IconComponent
color={theme?.chats?.message?.$btnSend}
name="microphone-1"
size={$size(23, 25)}
/>
</View>
)
const animValSend = animatedVal.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
})
useEffect(() => {
Animated.timing(animatedVal, {
toValue: isMic ? 1 : 0,
duration: 200,
useNativeDriver: false,
}).start()
}, [isMic])
return (
<TouchableOpacity style={styles.container} onPress={props.onPress}>
<Svg
width={$size(18, 22)}
height={$size(20, 25)}
fill="none"
{...props}>
<Path
d="M20.581 11.346 4.745 2.478a.75.75 0 0 0-1.072.906l2.987 8.364a.75.75 0 0 1 0 .504l-2.987 8.364a.75.75 0 0 0 1.072.907l15.836-8.868a.75.75 0 0 0 0-1.31v0ZM6.75 12h6"
stroke={theme?.chats?.message?.$btnSend}
strokeWidth={1.7}
strokeLinecap="round"
strokeLinejoin="round"
transform={`translate(${$size(1, 2)}, ${$size(
0.5,
1.5,
)}), scale(${$size(0.8, 0.85)})`}
/>
</Svg>
<TouchableOpacity
onPress={() =>
isMic
? appEvents.emit('openRecordVoiceModal', {
send: props.onSendVoice,
})
: props.onSend()
}>
<Animated.View
style={styles.container}
onTouchStart={e => setTouchX(e.nativeEvent.pageX)}
onTouchEnd={e => {
if (
touchX - e.nativeEvent.pageX > 10 ||
touchX - e.nativeEvent.pageX < -10
)
setMic(!isMic)
}}>
<Animated.View
style={[
styles.iconContainer,
{
opacity: animatedVal,
transform: [
{
scale: animatedVal.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
}),
},
],
},
]}>
<IconComponent
color={theme?.chats?.message?.$btnSend}
name="microphone-1"
size={$size(23, 25)}
/>
</Animated.View>
<Animated.View
style={[
styles.iconContainer,
{
opacity: animValSend,
transform: [
{
scale: animValSend,
},
],
},
]}>
<Svg
width={$size(18, 22)}
height={$size(20, 25)}
fill="none"
{...props}>
<Path
d="M20.581 11.346 4.745 2.478a.75.75 0 0 0-1.072.906l2.987 8.364a.75.75 0 0 1 0 .504l-2.987 8.364a.75.75 0 0 0 1.072.907l15.836-8.868a.75.75 0 0 0 0-1.31v0ZM6.75 12h6"
stroke={theme?.chats?.message?.$btnSend}
strokeWidth={1.7}
strokeLinecap="round"
strokeLinejoin="round"
transform={`translate(${$size(1, 2)}, ${$size(
0.5,
1.5,
)}), scale(${$size(0.8, 0.85)})`}
/>
</Svg>
</Animated.View>
</Animated.View>
</TouchableOpacity>
)
}
const createStyles = (theme: PartialTheme) =>
StyleSheet.create({
container: {
maxWidth: $size(30, 35),
maxHeight: $size(30, 35),
backgroundColor: theme?.chats?.message?.$bgPrimary,
borderRadius: 100,
padding: $size(10),
justifyContent: 'center',
flexDirection: 'row',
position: 'relative',
alignItems: 'center',
marginBottom: $size(1),
},
microphoneContainer: {
justifyContent: 'center',
width: $size(30, 35),
height: $size(30, 35),
backgroundColor: theme?.chats?.message?.$bgPrimary,
borderRadius: 100,
},
iconContainer: {
position: 'absolute',
width: $size(30, 35),
height: $size(30, 35),
justifyContent: 'center',
alignItems: 'center',
},

2
src/shared/events/index.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { RecordAudioModalSettings } from '@/modules/media/interfaces'
import { Events } from 'jet-tools'
import { ChatMemberRole } from '../enums'
import { IChatMessage, IShortUser, ITaxonomyShortInfo } from '../interfaces'
@ -78,6 +79,7 @@ export type AppEvents = { @@ -78,6 +79,7 @@ export type AppEvents = {
onDeleteMessage: { chatId: number; messageId: number }
onChangeMemberRole: { memberId: number; role: ChatMemberRole }
onDeleteMember: { memberId: number }
openRecordVoiceModal: RecordAudioModalSettings
reloadChatDetail: {}
decreaseCounter: { count: number }

7
src/shared/themes/dark/chat.ts

@ -73,6 +73,13 @@ export const chatColors: ChatColors = { @@ -73,6 +73,13 @@ export const chatColors: ChatColors = {
}
},
sendVoiceMsg: {
micBtn: {
$bg: '#000000',
$border: 'rgba(0, 0, 0, 0.4)'
}
},
chatAvatar: {
$bg: '#1F1F1F',
}

7
src/shared/themes/interfaces/chat.interface.ts

@ -70,6 +70,13 @@ export interface ChatColors { @@ -70,6 +70,13 @@ export interface ChatColors {
}
},
sendVoiceMsg: {
micBtn: {
$bg: string
$border: string
}
}
chatAvatar: {
$bg: string,
}

7
src/shared/themes/light/chat.ts

@ -73,6 +73,13 @@ export const chatColors: ChatColors = { @@ -73,6 +73,13 @@ export const chatColors: ChatColors = {
}
},
sendVoiceMsg: {
micBtn: {
$bg: '#F3D6DD',
$border: '#F6EAED'
}
},
chatAvatar: {
$bg: '#F6EAED',
}

53
yarn.lock

@ -3320,6 +3320,11 @@ @@ -3320,6 +3320,11 @@
"resolved" "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz"
"version" "26.6.2"
"diff@5.0.0":
"integrity" "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w=="
"resolved" "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz"
"version" "5.0.0"
"doctrine@^2.1.0":
"integrity" "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="
"resolved" "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz"
@ -3367,6 +3372,11 @@ @@ -3367,6 +3372,11 @@
"dom-serializer" "0"
"domelementtype" "1"
"dooboolab-welcome@^1.3.2":
"integrity" "sha512-2NbMaIIURElxEf/UAoVUFlXrO+7n/FRhLCiQlk4fkbGRh9cJ3/f8VEMPveR9m4Ug2l2Zey+UCXjd6EcBqHJ5bw=="
"resolved" "https://registry.npmjs.org/dooboolab-welcome/-/dooboolab-welcome-1.3.2.tgz"
"version" "1.3.2"
"ecc-jsbn@~0.1.1":
"integrity" "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk="
"resolved" "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz"
@ -4504,7 +4514,7 @@ @@ -4504,7 +4514,7 @@
"resolved" "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
"version" "1.3.8"
"internal-slot@^1.0.3":
"internal-slot@^1.0.2", "internal-slot@^1.0.3":
"integrity" "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA=="
"resolved" "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz"
"version" "1.0.3"
@ -6945,6 +6955,13 @@ @@ -6945,6 +6955,13 @@
dependencies:
"@react-native-community/push-notification-ios" "^1.2.0"
"react-native-audio-recorder-player@^3.4.0":
"integrity" "sha512-Y3Gv33SsVSei+5+XaVfzdomcLvBzjC2cktzZumVUpcsx+7Hcz87v1pG8KIU4dv0wREF1Sv+iToVGosyk4fi5Yg=="
"resolved" "https://registry.npmjs.org/react-native-audio-recorder-player/-/react-native-audio-recorder-player-3.4.0.tgz"
"version" "3.4.0"
dependencies:
"dooboolab-welcome" "^1.3.2"
"react-native-autolink@^4.0.0":
"integrity" "sha512-58wV7lBkS+JmTDzuN4v5OqSz5cIc0lI3PlIV1WIVhxlitI1Yhl5UQWum4y31bV83faA9we7gynvi8xV/I0TWLg=="
"resolved" "https://registry.npmjs.org/react-native-autolink/-/react-native-autolink-4.0.0.tgz"
@ -6981,6 +6998,14 @@ @@ -6981,6 +6998,14 @@
"resolved" "https://registry.npmjs.org/react-native-communications/-/react-native-communications-2.2.1.tgz"
"version" "2.2.1"
"react-native-controlled-mentions@^2.2.5":
"integrity" "sha512-fNIQWZMPy9OvPKO3MeLX+J76Ld7OuvrrLn4xwXMOjsYjuEWtVBNc4t/ADxNc3woQCfuSEkST+SbafFpUwwLMIA=="
"resolved" "https://registry.npmjs.org/react-native-controlled-mentions/-/react-native-controlled-mentions-2.2.5.tgz"
"version" "2.2.5"
dependencies:
"diff" "5.0.0"
"string.prototype.matchall" "4.0.3"
"react-native-date-picker@^3.4.3":
"integrity" "sha512-YAp/U8KafgXI57ZiqaZlgWMLPcY686PQ2ejo5gTEb1EcnjSKn3Zt8Gg7+oy55cLl98gZzmz+rJlBeVIDlq4n+A=="
"resolved" "https://registry.npmjs.org/react-native-date-picker/-/react-native-date-picker-3.4.3.tgz"
@ -7222,6 +7247,11 @@ @@ -7222,6 +7247,11 @@
"resolved" "https://registry.npmjs.org/react-native-shadow/-/react-native-shadow-1.2.2.tgz"
"version" "1.2.2"
"react-native-sound-level@^1.1.5":
"integrity" "sha512-H4dVYbif/PfzMOp9IpNjJUtbODYXeHk8tI0d9mWcJcBEB/ITcP4jSvDtAOH8B/Vcu4h4Owrvsuz/Y+UfS0biuw=="
"resolved" "https://registry.npmjs.org/react-native-sound-level/-/react-native-sound-level-1.1.5.tgz"
"version" "1.1.5"
"react-native-splash-screen@^3.2.0":
"integrity" "sha512-Ls9qiNZzW/OLFoI25wfjjAcrf2DZ975hn2vr6U9gyuxi2nooVbzQeFoQS5vQcbCt9QX5NY8ASEEAtlLdIa6KVg=="
"resolved" "https://registry.npmjs.org/react-native-splash-screen/-/react-native-splash-screen-3.2.0.tgz"
@ -7318,7 +7348,7 @@ @@ -7318,7 +7348,7 @@
dependencies:
"moment" "^2.19.1"
"react-native@*", "react-native@^0.0.0-0 || ^0.60.6 || ^0.61.5 || ^0.62.2 || ^0.63.2 || ^0.64.0 || ^0.65.0 || 1000.0.0", "react-native@^0.62.0", "react-native@^0.63.4", "react-native@^0.64.3", "react-native@>= 0.46", "react-native@>=0.40.0", "react-native@>=0.42.0", "react-native@>=0.45.0", "react-native@>=0.46.0", "react-native@>=0.48.4", "react-native@>=0.50.0", "react-native@>=0.57", "react-native@>=0.57.0", "react-native@>=0.58.4", "react-native@>=0.59", "react-native@>=0.59.0-rc.0 <1.0.x", "react-native@>=0.60", "react-native@>=0.60.0", "react-native@>=0.63.3", "react-native@>=0.64.0-rc.0 || 0.0.0-*", "react-native@>=0.65.0", "react-native@0.63.2":
"react-native@*", "react-native@^0.0.0-0 || ^0.60.6 || ^0.61.5 || ^0.62.2 || ^0.63.2 || ^0.64.0 || ^0.65.0 || 1000.0.0", "react-native@^0.62.0", "react-native@^0.63.4", "react-native@^0.64.3", "react-native@>= 0.46", "react-native@>=0.25.0", "react-native@>=0.40.0", "react-native@>=0.42.0", "react-native@>=0.45.0", "react-native@>=0.46.0", "react-native@>=0.48.4", "react-native@>=0.50.0", "react-native@>=0.57", "react-native@>=0.57.0", "react-native@>=0.58.4", "react-native@>=0.59", "react-native@>=0.59.0-rc.0 <1.0.x", "react-native@>=0.60", "react-native@>=0.60.0", "react-native@>=0.63.3", "react-native@>=0.64.0-rc.0 || 0.0.0-*", "react-native@>=0.65.0", "react-native@0.63.2":
"integrity" "sha512-2OEU74U0Ek1/WeBzPbg6XDsCfjF/9fhrNX/5TFgEiBKd5mNc9LOZ/OlMmkb7iues/ZZ/oc51SbEfLRQdcW0fVw=="
"resolved" "https://registry.npmjs.org/react-native/-/react-native-0.64.3.tgz"
"version" "0.64.3"
@ -7391,7 +7421,7 @@ @@ -7391,7 +7421,7 @@
"react-shallow-renderer" "^16.13.1"
"scheduler" "^0.20.1"
"react@*", "react@^16.0.0 || ^17.0.0", "react@^16.8", "react@^16.8.0 || ^17.0.0", "react@^16.8.1", "react@^16.8.3 || ^17", "react@^16.8.6 || ^17.0.0", "react@>= 16", "react@>=16.13.1", "react@>=16.3.0", "react@>=16.6.3", "react@16 || 17", "react@16.13.1", "react@17.0.1":
"react@*", "react@^16.0.0 || ^17.0.0", "react@^16.8", "react@^16.8.0 || ^17.0.0", "react@^16.8.1", "react@^16.8.3 || ^17", "react@^16.8.6 || ^17.0.0", "react@>= 16", "react@>=16.0", "react@>=16.13.1", "react@>=16.3.0", "react@>=16.6.3", "react@>16.8.0", "react@16 || 17", "react@16.13.1", "react@17.0.1":
"integrity" "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w=="
"resolved" "https://registry.npmjs.org/react/-/react-17.0.1.tgz"
"version" "17.0.1"
@ -7542,7 +7572,7 @@ @@ -7542,7 +7572,7 @@
"extend-shallow" "^3.0.2"
"safe-regex" "^1.1.0"
"regexp.prototype.flags@^1.3.1":
"regexp.prototype.flags@^1.3.0", "regexp.prototype.flags@^1.3.1":
"integrity" "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA=="
"resolved" "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz"
"version" "1.3.1"
@ -7996,7 +8026,7 @@ @@ -7996,7 +8026,7 @@
"resolved" "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz"
"version" "0.1.1"
"side-channel@^1.0.4":
"side-channel@^1.0.3", "side-channel@^1.0.4":
"integrity" "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw=="
"resolved" "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz"
"version" "1.0.4"
@ -8346,6 +8376,19 @@ @@ -8346,6 +8376,19 @@
"regexp.prototype.flags" "^1.3.1"
"side-channel" "^1.0.4"
"string.prototype.matchall@4.0.3":
"integrity" "sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw=="
"resolved" "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz"
"version" "4.0.3"
dependencies:
"call-bind" "^1.0.0"
"define-properties" "^1.1.3"
"es-abstract" "^1.18.0-next.1"
"has-symbols" "^1.0.1"
"internal-slot" "^1.0.2"
"regexp.prototype.flags" "^1.3.0"
"side-channel" "^1.0.3"
"string.prototype.trimend@^1.0.4":
"integrity" "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A=="
"resolved" "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz"

Loading…
Cancel
Save