mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-11-29 11:12:18 +03:00
Merge pull request #2795 from ecency/nt/video-n-background-upload
Nt/video n background upload
This commit is contained in:
commit
f26d792ea5
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -12,5 +12,6 @@
|
|||||||
},
|
},
|
||||||
"workbench.colorCustomizations": {
|
"workbench.colorCustomizations": {
|
||||||
"editorUnnecessaryCode.border": "#dd7aab"
|
"editorUnnecessaryCode.border": "#dd7aab"
|
||||||
}
|
},
|
||||||
|
"java.compile.nullAnalysis.mode": "automatic"
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
arguments=--init-script /var/folders/b6/ssclzcc529ld6mllp97qvcg80000gn/T/d146c9752a26f79b52047fb6dc6ed385d064e120494f96f08ca63a317c41f94c.gradle --init-script /var/folders/b6/ssclzcc529ld6mllp97qvcg80000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
|
arguments=--init-script /var/folders/4n/09fh21tj1nz5pqqzh5ky5tb80000gn/T/d146c9752a26f79b52047fb6dc6ed385d064e120494f96f08ca63a317c41f94c.gradle --init-script /var/folders/4n/09fh21tj1nz5pqqzh5ky5tb80000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
|
||||||
auto.sync=false
|
auto.sync=true
|
||||||
build.scans.enabled=false
|
build.scans.enabled=false
|
||||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||||
connection.project.dir=
|
connection.project.dir=
|
||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
gradle.user.home=
|
gradle.user.home=
|
||||||
java.home=/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home
|
java.home=/Library/Java/JavaVirtualMachines/jdk-17.0.5.jdk/Contents/Home
|
||||||
jvm.arguments=
|
jvm.arguments=
|
||||||
offline.mode=false
|
offline.mode=false
|
||||||
override.workspace.settings=true
|
override.workspace.settings=true
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-12/"/>
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/>
|
||||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
<classpathentry kind="output" path="bin/default"/>
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
3
android/app/proguard-rules.pro
vendored
3
android/app/proguard-rules.pro
vendored
@ -14,3 +14,6 @@
|
|||||||
|
|
||||||
# -keep class com.swmansion.reanimated.** { *; }
|
# -keep class com.swmansion.reanimated.** { *; }
|
||||||
# -keep class com.facebook.react.turbomodule.** { *; }
|
# -keep class com.facebook.react.turbomodule.** { *; }
|
||||||
|
|
||||||
|
# config for rn background upload
|
||||||
|
-keep class net.gotev.uploadservice.** { *; }
|
||||||
|
@ -445,6 +445,8 @@ PODS:
|
|||||||
- glog
|
- glog
|
||||||
- react-native-background-timer (2.4.1):
|
- react-native-background-timer (2.4.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
|
- react-native-background-upload (6.6.0):
|
||||||
|
- React
|
||||||
- react-native-camera (4.2.1):
|
- react-native-camera (4.2.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-camera/RCT (= 4.2.1)
|
- react-native-camera/RCT (= 4.2.1)
|
||||||
@ -459,6 +461,8 @@ PODS:
|
|||||||
- react-native-config/App (= 1.5.1)
|
- react-native-config/App (= 1.5.1)
|
||||||
- react-native-config/App (1.5.1):
|
- react-native-config/App (1.5.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
|
- react-native-create-thumbnail (1.6.4):
|
||||||
|
- React-Core
|
||||||
- react-native-date-picker (4.2.9):
|
- react-native-date-picker (4.2.9):
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-fingerprint-scanner (6.0.0):
|
- react-native-fingerprint-scanner (6.0.0):
|
||||||
@ -651,6 +655,9 @@ PODS:
|
|||||||
- React-RCTImage
|
- React-RCTImage
|
||||||
- RNSVG (12.5.1):
|
- RNSVG (12.5.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
|
- RNTusClient (1.1.0):
|
||||||
|
- React-Core
|
||||||
|
- TUSKit (~> 1.4.2)
|
||||||
- RNVectorIcons (6.7.0):
|
- RNVectorIcons (6.7.0):
|
||||||
- React
|
- React
|
||||||
- SDWebImage (5.11.1):
|
- SDWebImage (5.11.1):
|
||||||
@ -665,6 +672,7 @@ PODS:
|
|||||||
- TOCropViewController (2.6.1)
|
- TOCropViewController (2.6.1)
|
||||||
- toolbar-android (0.2.1):
|
- toolbar-android (0.2.1):
|
||||||
- React
|
- React
|
||||||
|
- TUSKit (1.4.2)
|
||||||
- Yoga (1.14.0)
|
- Yoga (1.14.0)
|
||||||
- YogaKit (1.18.1):
|
- YogaKit (1.18.1):
|
||||||
- Yoga (~> 1.14)
|
- Yoga (~> 1.14)
|
||||||
@ -726,9 +734,11 @@ DEPENDENCIES:
|
|||||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||||
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
|
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
|
||||||
|
- react-native-background-upload (from `../node_modules/react-native-background-upload`)
|
||||||
- react-native-camera (from `../node_modules/react-native-camera`)
|
- react-native-camera (from `../node_modules/react-native-camera`)
|
||||||
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
|
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
|
||||||
- react-native-config (from `../node_modules/react-native-config`)
|
- react-native-config (from `../node_modules/react-native-config`)
|
||||||
|
- react-native-create-thumbnail (from `../node_modules/react-native-create-thumbnail`)
|
||||||
- react-native-date-picker (from `../node_modules/react-native-date-picker`)
|
- react-native-date-picker (from `../node_modules/react-native-date-picker`)
|
||||||
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
|
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
|
||||||
- react-native-flipper (from `../node_modules/react-native-flipper`)
|
- react-native-flipper (from `../node_modules/react-native-flipper`)
|
||||||
@ -777,6 +787,7 @@ DEPENDENCIES:
|
|||||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||||
- RNScreens (from `../node_modules/react-native-screens`)
|
- RNScreens (from `../node_modules/react-native-screens`)
|
||||||
- RNSVG (from `../node_modules/react-native-svg`)
|
- RNSVG (from `../node_modules/react-native-svg`)
|
||||||
|
- RNTusClient (from `../node_modules/react-native-tus-client`)
|
||||||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||||
- TcpSockets (from `../node_modules/react-native-tcp`)
|
- TcpSockets (from `../node_modules/react-native-tcp`)
|
||||||
- "toolbar-android (from `../node_modules/@react-native-community/toolbar-android`)"
|
- "toolbar-android (from `../node_modules/@react-native-community/toolbar-android`)"
|
||||||
@ -816,6 +827,7 @@ SPEC REPOS:
|
|||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
- SocketRocket
|
- SocketRocket
|
||||||
- TOCropViewController
|
- TOCropViewController
|
||||||
|
- TUSKit
|
||||||
- YogaKit
|
- YogaKit
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
@ -879,12 +891,16 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native/ReactCommon/logger"
|
:path: "../node_modules/react-native/ReactCommon/logger"
|
||||||
react-native-background-timer:
|
react-native-background-timer:
|
||||||
:path: "../node_modules/react-native-background-timer"
|
:path: "../node_modules/react-native-background-timer"
|
||||||
|
react-native-background-upload:
|
||||||
|
:path: "../node_modules/react-native-background-upload"
|
||||||
react-native-camera:
|
react-native-camera:
|
||||||
:path: "../node_modules/react-native-camera"
|
:path: "../node_modules/react-native-camera"
|
||||||
react-native-cameraroll:
|
react-native-cameraroll:
|
||||||
:path: "../node_modules/@react-native-community/cameraroll"
|
:path: "../node_modules/@react-native-community/cameraroll"
|
||||||
react-native-config:
|
react-native-config:
|
||||||
:path: "../node_modules/react-native-config"
|
:path: "../node_modules/react-native-config"
|
||||||
|
react-native-create-thumbnail:
|
||||||
|
:path: "../node_modules/react-native-create-thumbnail"
|
||||||
react-native-date-picker:
|
react-native-date-picker:
|
||||||
:path: "../node_modules/react-native-date-picker"
|
:path: "../node_modules/react-native-date-picker"
|
||||||
react-native-fingerprint-scanner:
|
react-native-fingerprint-scanner:
|
||||||
@ -981,6 +997,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native-screens"
|
:path: "../node_modules/react-native-screens"
|
||||||
RNSVG:
|
RNSVG:
|
||||||
:path: "../node_modules/react-native-svg"
|
:path: "../node_modules/react-native-svg"
|
||||||
|
RNTusClient:
|
||||||
|
:path: "../node_modules/react-native-tus-client"
|
||||||
RNVectorIcons:
|
RNVectorIcons:
|
||||||
:path: "../node_modules/react-native-vector-icons"
|
:path: "../node_modules/react-native-vector-icons"
|
||||||
TcpSockets:
|
TcpSockets:
|
||||||
@ -1049,9 +1067,11 @@ SPEC CHECKSUMS:
|
|||||||
React-jsinspector: 1c34fea1868136ecde647bc11fae9266d4143693
|
React-jsinspector: 1c34fea1868136ecde647bc11fae9266d4143693
|
||||||
React-logger: e9f407f9fdf3f3ce7749ae6f88affe63e8446019
|
React-logger: e9f407f9fdf3f3ce7749ae6f88affe63e8446019
|
||||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
||||||
|
react-native-background-upload: 7c608537f87106c93530a3a19a853afd55466823
|
||||||
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
|
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
|
||||||
react-native-cameraroll: e2917a5e62da9f10c3d525e157e25e694d2d6dfa
|
react-native-cameraroll: e2917a5e62da9f10c3d525e157e25e694d2d6dfa
|
||||||
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
|
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
|
||||||
|
react-native-create-thumbnail: e022bcdcba8a0b4529a50d3fa1a832ec921be39d
|
||||||
react-native-date-picker: c063a8967058c58a02d7d0e1d655f0453576fb0d
|
react-native-date-picker: c063a8967058c58a02d7d0e1d655f0453576fb0d
|
||||||
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
|
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
|
||||||
react-native-flipper: c33a4995958ef12a2b2f8290d63bed7adeed7634
|
react-native-flipper: c33a4995958ef12a2b2f8290d63bed7adeed7634
|
||||||
@ -1100,6 +1120,7 @@ SPEC CHECKSUMS:
|
|||||||
RNReanimated: 6668b0587bebd4b15dd849b99e5a9c70fc12ed95
|
RNReanimated: 6668b0587bebd4b15dd849b99e5a9c70fc12ed95
|
||||||
RNScreens: 4830eb40e0793b38849965cd27f4f3a7d7bc65c1
|
RNScreens: 4830eb40e0793b38849965cd27f4f3a7d7bc65c1
|
||||||
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
|
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
|
||||||
|
RNTusClient: b90393226531c118c4716a2b71128e3b9d9c77ee
|
||||||
RNVectorIcons: 368d6d8b8301224e5ffb6254191f4f8876c2be0d
|
RNVectorIcons: 368d6d8b8301224e5ffb6254191f4f8876c2be0d
|
||||||
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
|
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
|
||||||
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
|
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
|
||||||
@ -1107,6 +1128,7 @@ SPEC CHECKSUMS:
|
|||||||
TcpSockets: 4ef55305239923b343ed0a378b1fac188b1373b0
|
TcpSockets: 4ef55305239923b343ed0a378b1fac188b1373b0
|
||||||
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
||||||
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
|
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
|
||||||
|
TUSKit: 4bcc2fe13e1b4d6c3bfbaca57d64e64c1be31201
|
||||||
Yoga: 92d086bb705a41cc588599b51db726ba7b1d341c
|
Yoga: 92d086bb705a41cc588599b51db726ba7b1d341c
|
||||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
"domain-browser": "^1.1.1",
|
"domain-browser": "^1.1.1",
|
||||||
"events": "^1.0.0",
|
"events": "^1.0.0",
|
||||||
"hive-uri": "^0.2.5",
|
"hive-uri": "^0.2.5",
|
||||||
"hivesigner": "^3.2.7",
|
"hivesigner": "^3.3.4",
|
||||||
"https-browserify": "~0.0.0",
|
"https-browserify": "~0.0.0",
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
"jsc-android": "^241213.1.0",
|
"jsc-android": "^241213.1.0",
|
||||||
@ -104,10 +104,12 @@
|
|||||||
"react-native-animatable": "^1.3.3",
|
"react-native-animatable": "^1.3.3",
|
||||||
"react-native-autoheight-webview": "^1.5.8",
|
"react-native-autoheight-webview": "^1.5.8",
|
||||||
"react-native-background-timer": "^2.4.1",
|
"react-native-background-timer": "^2.4.1",
|
||||||
|
"react-native-background-upload": "^6.6.0",
|
||||||
"react-native-bootsplash": "^4.3.2",
|
"react-native-bootsplash": "^4.3.2",
|
||||||
"react-native-camera": "^4.2.1",
|
"react-native-camera": "^4.2.1",
|
||||||
"react-native-chart-kit": "^6.11.0",
|
"react-native-chart-kit": "^6.11.0",
|
||||||
"react-native-config": "^1.5.1",
|
"react-native-config": "^1.5.1",
|
||||||
|
"react-native-create-thumbnail": "^1.6.4",
|
||||||
"react-native-crypto": "^2.2.0",
|
"react-native-crypto": "^2.2.0",
|
||||||
"react-native-date-picker": "^4.2.0",
|
"react-native-date-picker": "^4.2.0",
|
||||||
"react-native-device-info": "^10.7.0",
|
"react-native-device-info": "^10.7.0",
|
||||||
@ -154,6 +156,7 @@
|
|||||||
"react-native-svg": "^12.1.1",
|
"react-native-svg": "^12.1.1",
|
||||||
"react-native-swiper": "^1.6.0-rc.3",
|
"react-native-swiper": "^1.6.0-rc.3",
|
||||||
"react-native-tcp": "^4.0.0",
|
"react-native-tcp": "^4.0.0",
|
||||||
|
"react-native-tus-client": "^1.1.0",
|
||||||
"react-native-udp": "^4.1.4",
|
"react-native-udp": "^4.1.4",
|
||||||
"react-native-unique-id": "^2.0.0",
|
"react-native-unique-id": "^2.0.0",
|
||||||
"react-native-vector-icons": "^6.6.0",
|
"react-native-vector-icons": "^6.6.0",
|
||||||
@ -212,6 +215,7 @@
|
|||||||
"prettier": "^2.0.2",
|
"prettier": "^2.0.2",
|
||||||
"prettier-eslint": "^9.0.1",
|
"prettier-eslint": "^9.0.1",
|
||||||
"react-native-codegen": "^0.0.13",
|
"react-native-codegen": "^0.0.13",
|
||||||
|
"react-query-native-devtools": "^4.0.0",
|
||||||
"react-test-renderer": "18.1.0",
|
"react-test-renderer": "18.1.0",
|
||||||
"reactotron-react-native": "^5.0.3",
|
"reactotron-react-native": "^5.0.3",
|
||||||
"reactotron-redux": "^3.1.3",
|
"reactotron-redux": "^3.1.3",
|
||||||
|
3726
patches/react-native-create-thumbnail+1.6.4.patch
Normal file
3726
patches/react-native-create-thumbnail+1.6.4.patch
Normal file
File diff suppressed because it is too large
Load Diff
3715
patches/react-native-tus-client+1.1.0.patch
Normal file
3715
patches/react-native-tus-client+1.1.0.patch
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,6 @@ import { debounce, isArray } from 'lodash';
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { FlatList, Text, View } from 'react-native';
|
import { FlatList, Text, View } from 'react-native';
|
||||||
|
|
||||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
@ -12,6 +11,7 @@ import { lookupAccounts } from '../../providers/hive/dhive';
|
|||||||
import { setBeneficiaries as setBeneficiariesAction } from '../../redux/actions/editorActions';
|
import { setBeneficiaries as setBeneficiariesAction } from '../../redux/actions/editorActions';
|
||||||
import { TEMP_BENEFICIARIES_ID } from '../../redux/constants/constants';
|
import { TEMP_BENEFICIARIES_ID } from '../../redux/constants/constants';
|
||||||
import { Beneficiary } from '../../redux/reducers/editorReducer';
|
import { Beneficiary } from '../../redux/reducers/editorReducer';
|
||||||
|
import { BENEFICIARY_SRC_ENCODER } from '../../providers/speak/constants';
|
||||||
|
|
||||||
interface BeneficiarySelectionContentProps {
|
interface BeneficiarySelectionContentProps {
|
||||||
draftId: string;
|
draftId: string;
|
||||||
@ -20,6 +20,7 @@ interface BeneficiarySelectionContentProps {
|
|||||||
label?: string;
|
label?: string;
|
||||||
labelStyle?: string;
|
labelStyle?: string;
|
||||||
powerDownBeneficiaries?: Beneficiary[];
|
powerDownBeneficiaries?: Beneficiary[];
|
||||||
|
encodingBeneficiaries?: Beneficiary[];
|
||||||
handleSaveBeneficiary?: (beneficiaries: Beneficiary[]) => void;
|
handleSaveBeneficiary?: (beneficiaries: Beneficiary[]) => void;
|
||||||
handleRemoveBeneficiary?: (beneficiary: Beneficiary) => void;
|
handleRemoveBeneficiary?: (beneficiary: Beneficiary) => void;
|
||||||
}
|
}
|
||||||
@ -31,6 +32,7 @@ const BeneficiarySelectionContent = ({
|
|||||||
setDisableDone,
|
setDisableDone,
|
||||||
powerDown,
|
powerDown,
|
||||||
powerDownBeneficiaries,
|
powerDownBeneficiaries,
|
||||||
|
encodingBeneficiaries,
|
||||||
handleSaveBeneficiary,
|
handleSaveBeneficiary,
|
||||||
handleRemoveBeneficiary,
|
handleRemoveBeneficiary,
|
||||||
}: BeneficiarySelectionContentProps) => {
|
}: BeneficiarySelectionContentProps) => {
|
||||||
@ -59,10 +61,8 @@ const BeneficiarySelectionContent = ({
|
|||||||
}, [powerDownBeneficiaries]);
|
}, [powerDownBeneficiaries]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draftId) {
|
initBeneficiaries();
|
||||||
readTempBeneficiaries();
|
}, [draftId, encodingBeneficiaries]);
|
||||||
}
|
|
||||||
}, [draftId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDisableDone(newEditable);
|
setDisableDone(newEditable);
|
||||||
@ -88,25 +88,27 @@ const BeneficiarySelectionContent = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const readTempBeneficiaries = async () => {
|
const initBeneficiaries = async () => {
|
||||||
if (beneficiariesMap) {
|
const _draftId = draftId || TEMP_BENEFICIARIES_ID;
|
||||||
const savedBeneficiareis = beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID];
|
|
||||||
const tempBeneficiaries =
|
|
||||||
savedBeneficiareis && savedBeneficiareis.length
|
|
||||||
? [DEFAULT_BENEFICIARY, ...beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID]]
|
|
||||||
: [DEFAULT_BENEFICIARY];
|
|
||||||
|
|
||||||
if (isArray(tempBeneficiaries) && tempBeneficiaries.length > 0) {
|
let savedBeneficiareis: Beneficiary[] = [DEFAULT_BENEFICIARY, ...(encodingBeneficiaries || [])];
|
||||||
// weight correction algorithm.
|
|
||||||
let othersWeight = 0;
|
if (beneficiariesMap && beneficiariesMap[_draftId]) {
|
||||||
tempBeneficiaries.forEach((item, index) => {
|
const _cachedBenef = beneficiariesMap[_draftId];
|
||||||
if (index > 0) {
|
const _filteredBenef = _cachedBenef.filter((bene) => bene.src !== BENEFICIARY_SRC_ENCODER);
|
||||||
othersWeight += item.weight;
|
savedBeneficiareis = [...savedBeneficiareis, ..._filteredBenef];
|
||||||
}
|
}
|
||||||
});
|
|
||||||
tempBeneficiaries[0].weight = 10000 - othersWeight;
|
if (savedBeneficiareis?.length > 1) {
|
||||||
setBeneficiaries(tempBeneficiaries);
|
// weight correction algorithm.
|
||||||
}
|
let othersWeight = 0;
|
||||||
|
savedBeneficiareis.forEach((item, index) => {
|
||||||
|
if (index > 0) {
|
||||||
|
othersWeight += item.weight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
savedBeneficiareis[0].weight = 10000 - othersWeight;
|
||||||
|
setBeneficiaries(savedBeneficiareis);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -344,7 +346,7 @@ const BeneficiarySelectionContent = ({
|
|||||||
wrapperStyle={styles.usernameFormInputWrapper}
|
wrapperStyle={styles.usernameFormInputWrapper}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{!_isCurrentUser ? (
|
{!_isCurrentUser && item.src !== BENEFICIARY_SRC_ENCODER ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
name="close"
|
name="close"
|
||||||
iconType="MaterialCommunityIcons"
|
iconType="MaterialCommunityIcons"
|
||||||
|
@ -11,11 +11,12 @@ import Animated, { EasingNode, Extrapolate } from 'react-native-reanimated';
|
|||||||
import { IconButton, UploadsGalleryModal } from '../..';
|
import { IconButton, UploadsGalleryModal } from '../..';
|
||||||
import styles from '../styles/editorToolbarStyles';
|
import styles from '../styles/editorToolbarStyles';
|
||||||
import { useAppSelector } from '../../../hooks';
|
import { useAppSelector } from '../../../hooks';
|
||||||
import { MediaInsertData } from '../../uploadsGalleryModal/container/uploadsGalleryModal';
|
import { MediaInsertData, Modes } from '../../uploadsGalleryModal/container/uploadsGalleryModal';
|
||||||
import Formats from './formats/formats';
|
import Formats from './formats/formats';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
insertedMediaUrls: string[];
|
draftId?: string;
|
||||||
|
postBody: string;
|
||||||
paramFiles: any[];
|
paramFiles: any[];
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
isPreviewActive: boolean;
|
isPreviewActive: boolean;
|
||||||
@ -28,7 +29,8 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const EditorToolbar = ({
|
export const EditorToolbar = ({
|
||||||
insertedMediaUrls,
|
draftId,
|
||||||
|
postBody,
|
||||||
paramFiles,
|
paramFiles,
|
||||||
isEditing,
|
isEditing,
|
||||||
isPreviewActive,
|
isPreviewActive,
|
||||||
@ -46,7 +48,6 @@ export const EditorToolbar = ({
|
|||||||
const extensionHeight = useRef(0);
|
const extensionHeight = useRef(0);
|
||||||
|
|
||||||
const [isExtensionVisible, setIsExtensionVisible] = useState(false);
|
const [isExtensionVisible, setIsExtensionVisible] = useState(false);
|
||||||
|
|
||||||
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
|
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -78,13 +79,28 @@ export const EditorToolbar = ({
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const _showUploadsExtension = () => {
|
const _showUploadsExtension = (mode: Modes) => {
|
||||||
if (isExtensionVisible && uploadsGalleryModalRef.current) {
|
if (!uploadsGalleryModalRef.current) {
|
||||||
_hideExtension();
|
return;
|
||||||
} else if (uploadsGalleryModalRef.current) {
|
|
||||||
uploadsGalleryModalRef.current.toggleModal(true);
|
|
||||||
_revealExtension();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _curMode = uploadsGalleryModalRef.current.getMode();
|
||||||
|
|
||||||
|
if (!isExtensionVisible || _curMode !== mode) {
|
||||||
|
uploadsGalleryModalRef.current.toggleModal(true, mode);
|
||||||
|
_revealExtension();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hideExtension();
|
||||||
|
};
|
||||||
|
|
||||||
|
const _showImageUploads = () => {
|
||||||
|
_showUploadsExtension(Modes.MODE_IMAGE);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _showVideoUploads = () => {
|
||||||
|
_showUploadsExtension(Modes.MODE_VIDEO);
|
||||||
};
|
};
|
||||||
|
|
||||||
// handles extension closing
|
// handles extension closing
|
||||||
@ -183,8 +199,9 @@ export const EditorToolbar = ({
|
|||||||
>
|
>
|
||||||
{isExtensionVisible && <View style={styles.indicator} />}
|
{isExtensionVisible && <View style={styles.indicator} />}
|
||||||
<UploadsGalleryModal
|
<UploadsGalleryModal
|
||||||
|
draftId={draftId}
|
||||||
ref={uploadsGalleryModalRef}
|
ref={uploadsGalleryModalRef}
|
||||||
insertedMediaUrls={insertedMediaUrls}
|
postBody={postBody}
|
||||||
isPreviewActive={isPreviewActive}
|
isPreviewActive={isPreviewActive}
|
||||||
paramFiles={paramFiles}
|
paramFiles={paramFiles}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
@ -243,14 +260,23 @@ export const EditorToolbar = ({
|
|||||||
iconType="MaterialCommunityIcons"
|
iconType="MaterialCommunityIcons"
|
||||||
name="text-short"
|
name="text-short"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={_showUploadsExtension}
|
onPress={_showImageUploads}
|
||||||
style={styles.rightIcons}
|
style={styles.rightIcons}
|
||||||
size={20}
|
size={18}
|
||||||
iconStyle={styles.icon}
|
iconStyle={styles.icon}
|
||||||
iconType="FontAwesome"
|
iconType="FontAwesome"
|
||||||
name="image"
|
name="image"
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
onPress={_showVideoUploads}
|
||||||
|
style={styles.rightIcons}
|
||||||
|
size={26}
|
||||||
|
iconStyle={styles.icon}
|
||||||
|
iconType="MaterialCommunityIcons"
|
||||||
|
name="video-outline"
|
||||||
|
/>
|
||||||
<View style={styles.clearButtonWrapper}>
|
<View style={styles.clearButtonWrapper}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
MediaInsertData,
|
MediaInsertData,
|
||||||
MediaInsertStatus,
|
MediaInsertStatus,
|
||||||
|
Modes,
|
||||||
} from '../../../uploadsGalleryModal/container/uploadsGalleryModal';
|
} from '../../../uploadsGalleryModal/container/uploadsGalleryModal';
|
||||||
import { replaceBetween } from './utils';
|
import { replaceBetween } from './utils';
|
||||||
|
|
||||||
@ -23,13 +24,14 @@ export default async ({ text, selection, setTextAndSelection, items }: Args) =>
|
|||||||
// calclulate change of cursor position
|
// calclulate change of cursor position
|
||||||
|
|
||||||
const imagePrefix = '!';
|
const imagePrefix = '!';
|
||||||
|
|
||||||
const placeholderPrefix = 'Uploading... ';
|
const placeholderPrefix = 'Uploading... ';
|
||||||
|
|
||||||
let newText = text;
|
let newText = text;
|
||||||
let newSelection = selection;
|
let newSelection = selection;
|
||||||
|
|
||||||
const _insertFormatedString = (text, value) => {
|
const _insertFormatedString = (text, value, mode) => {
|
||||||
const formatedText = `\n${imagePrefix}[${text}](${value})\n`;
|
const formatedText = `\n${mode === Modes.MODE_VIDEO ? '' : imagePrefix}[${text}](${value})\n`;
|
||||||
newText = replaceBetween(newText, newSelection, formatedText);
|
newText = replaceBetween(newText, newSelection, formatedText);
|
||||||
const newIndex = newText && newText.indexOf(value, newSelection.start) + value.length + 2;
|
const newIndex = newText && newText.indexOf(value, newSelection.start) + value.length + 2;
|
||||||
newSelection = {
|
newSelection = {
|
||||||
@ -80,7 +82,7 @@ export default async ({ text, selection, setTextAndSelection, items }: Args) =>
|
|||||||
// means placeholder is preset is needs replacing
|
// means placeholder is preset is needs replacing
|
||||||
_replaceFormatedString(_placeholder, item.url);
|
_replaceFormatedString(_placeholder, item.url);
|
||||||
} else if (item.url) {
|
} else if (item.url) {
|
||||||
_insertFormatedString(item.text, item.url);
|
_insertFormatedString(item.text, item.url, item.mode);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import React, { useState, useRef, useEffect, useCallback, Fragment } from 'react';
|
import { postBodySummary, renderPostBody } from '@ecency/render-helper';
|
||||||
|
import { debounce, get } from 'lodash';
|
||||||
|
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Text,
|
|
||||||
Platform,
|
Platform,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { renderPostBody, postBodySummary } from '@ecency/render-helper';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { get, debounce } from 'lodash';
|
|
||||||
import Animated, { BounceInRight } from 'react-native-reanimated';
|
import Animated, { BounceInRight } from 'react-native-reanimated';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Icon } from '../../icon';
|
import { Icon } from '../../icon';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
@ -21,34 +21,34 @@ import { toggleAccountsBottomSheet } from '../../../redux/actions/uiAction';
|
|||||||
|
|
||||||
// Components
|
// Components
|
||||||
import {
|
import {
|
||||||
|
InsertLinkModal,
|
||||||
|
Modal,
|
||||||
PostBody,
|
PostBody,
|
||||||
TextInput,
|
SnippetsModal,
|
||||||
UserAvatar,
|
SummaryArea,
|
||||||
TitleArea,
|
|
||||||
TagArea,
|
TagArea,
|
||||||
TagInput,
|
TagInput,
|
||||||
SummaryArea,
|
TextInput,
|
||||||
Modal,
|
TitleArea,
|
||||||
SnippetsModal,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
InsertLinkModal,
|
UserAvatar,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
import styles from '../styles/markdownEditorStyles';
|
import { useAppSelector } from '../../../hooks';
|
||||||
import applySnippet from '../children/formats/applySnippet';
|
import { walkthrough } from '../../../redux/constants/walkthroughConstants';
|
||||||
import { MainButton } from '../../mainButton';
|
|
||||||
import isAndroidOreo from '../../../utils/isAndroidOreo';
|
import isAndroidOreo from '../../../utils/isAndroidOreo';
|
||||||
import { OptionsModal } from '../../atoms';
|
import { OptionsModal } from '../../atoms';
|
||||||
import { walkthrough } from '../../../redux/constants/walkthroughConstants';
|
import { MainButton } from '../../mainButton';
|
||||||
import { MediaInsertData } from '../../uploadsGalleryModal/container/uploadsGalleryModal';
|
import { MediaInsertData } from '../../uploadsGalleryModal/container/uploadsGalleryModal';
|
||||||
import { EditorToolbar } from '../children/editorToolbar';
|
import { EditorToolbar } from '../children/editorToolbar';
|
||||||
import { extractImageUrls } from '../../../utils/editor';
|
import applySnippet from '../children/formats/applySnippet';
|
||||||
import { useAppSelector } from '../../../hooks';
|
import styles from '../styles/markdownEditorStyles';
|
||||||
|
|
||||||
// const MIN_BODY_INPUT_HEIGHT = 300;
|
// const MIN_BODY_INPUT_HEIGHT = 300;
|
||||||
|
|
||||||
const MarkdownEditorView = ({
|
const MarkdownEditorView = ({
|
||||||
|
draftId,
|
||||||
paramFiles,
|
paramFiles,
|
||||||
draftBody,
|
draftBody,
|
||||||
intl,
|
intl,
|
||||||
@ -79,8 +79,6 @@ const MarkdownEditorView = ({
|
|||||||
const [isSnippetsOpen, setIsSnippetsOpen] = useState(false);
|
const [isSnippetsOpen, setIsSnippetsOpen] = useState(false);
|
||||||
const [showDraftLoadButton, setShowDraftLoadButton] = useState(false);
|
const [showDraftLoadButton, setShowDraftLoadButton] = useState(false);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [insertedMediaUrls, setInsertedMediaUrls] = useState([]);
|
|
||||||
// const [isDraftUpdated, setIsDraftupdated] = useState(false);
|
|
||||||
|
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
const clearRef = useRef<any>(null);
|
const clearRef = useRef<any>(null);
|
||||||
@ -192,10 +190,6 @@ const MarkdownEditorView = ({
|
|||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
handleBodyChange(bodyTextRef.current);
|
handleBodyChange(bodyTextRef.current);
|
||||||
handleFormUpdate('body', bodyTextRef.current);
|
handleFormUpdate('body', bodyTextRef.current);
|
||||||
const urls = extractImageUrls({ body: bodyTextRef.current });
|
|
||||||
if (urls.length !== insertedMediaUrls.length) {
|
|
||||||
setInsertedMediaUrls(urls);
|
|
||||||
}
|
|
||||||
}, 500),
|
}, 500),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -437,7 +431,8 @@ const MarkdownEditorView = ({
|
|||||||
{_renderFloatingDraftButton()}
|
{_renderFloatingDraftButton()}
|
||||||
|
|
||||||
<EditorToolbar
|
<EditorToolbar
|
||||||
insertedMediaUrls={insertedMediaUrls}
|
draftId={draftId}
|
||||||
|
postBody={bodyTextRef.current}
|
||||||
isPreviewActive={isPreviewActive}
|
isPreviewActive={isPreviewActive}
|
||||||
paramFiles={paramFiles}
|
paramFiles={paramFiles}
|
||||||
setIsUploading={setIsUploading}
|
setIsUploading={setIsUploading}
|
||||||
|
@ -4,7 +4,7 @@ import { Alert, KeyboardAvoidingView, Platform, View } from 'react-native';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { TextInput } from '..';
|
import { TextInput } from '..';
|
||||||
import { Snippet } from '../../models';
|
import { Snippet } from '../../models';
|
||||||
import { useSnippetsMutation } from '../../providers/queries';
|
import { editorQueries } from '../../providers/queries';
|
||||||
import { TextButton } from '../buttons';
|
import { TextButton } from '../buttons';
|
||||||
import Modal from '../modal';
|
import Modal from '../modal';
|
||||||
import styles from './snippetEditorModalStyles';
|
import styles from './snippetEditorModalStyles';
|
||||||
@ -19,7 +19,7 @@ const SnippetEditorModal = ({}, ref) => {
|
|||||||
const titleInputRef = useRef(null);
|
const titleInputRef = useRef(null);
|
||||||
const bodyInputRef = useRef(null);
|
const bodyInputRef = useRef(null);
|
||||||
|
|
||||||
const snippetsMutation = useSnippetsMutation();
|
const snippetsMutation = editorQueries.useSnippetsMutation();
|
||||||
|
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [body, setBody] = useState('');
|
const [body, setBody] = useState('');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { Alert, Text, View } from 'react-native';
|
import { Alert, Text, View } from 'react-native';
|
||||||
import { useSnippetDeleteMutation } from '../../providers/queries';
|
import { editorQueries } from '../../providers/queries';
|
||||||
import IconButton from '../iconButton';
|
import IconButton from '../iconButton';
|
||||||
import styles from './snippetsModalStyles';
|
import styles from './snippetsModalStyles';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ interface SnippetItemProps {
|
|||||||
|
|
||||||
const SnippetItem = ({ id, title, body, index, onEditPress }: SnippetItemProps) => {
|
const SnippetItem = ({ id, title, body, index, onEditPress }: SnippetItemProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const snippetsDeleteMutation = useSnippetDeleteMutation();
|
const snippetsDeleteMutation = editorQueries.useSnippetDeleteMutation();
|
||||||
|
|
||||||
const _onRemovePress = () => {
|
const _onRemovePress = () => {
|
||||||
// asks for remvoe confirmation and run remove routing upon confirming
|
// asks for remvoe confirmation and run remove routing upon confirming
|
||||||
|
@ -10,7 +10,7 @@ import SnippetEditorModal, {
|
|||||||
import SnippetItem from './snippetItem';
|
import SnippetItem from './snippetItem';
|
||||||
import { Snippet } from '../../models';
|
import { Snippet } from '../../models';
|
||||||
import { useAppSelector } from '../../hooks';
|
import { useAppSelector } from '../../hooks';
|
||||||
import { useSnippetsQuery } from '../../providers/queries';
|
import { editorQueries } from '../../providers/queries';
|
||||||
|
|
||||||
interface SnippetsModalProps {
|
interface SnippetsModalProps {
|
||||||
handleOnSelect: (snippetText: string) => void;
|
handleOnSelect: (snippetText: string) => void;
|
||||||
@ -22,7 +22,7 @@ const SnippetsModal = ({ handleOnSelect }: SnippetsModalProps) => {
|
|||||||
|
|
||||||
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
||||||
|
|
||||||
const snippetsQuery = useSnippetsQuery();
|
const snippetsQuery = editorQueries.useSnippetsQuery();
|
||||||
|
|
||||||
// render list item for snippet and handle actions;
|
// render list item for snippet and handle actions;
|
||||||
const _renderItem = ({ item, index }: { item: Snippet; index: number }) => {
|
const _renderItem = ({ item, index }: { item: Snippet; index: number }) => {
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { proxifyImageSrc } from '@ecency/render-helper';
|
||||||
|
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import { default as AnimatedView, ZoomIn } from 'react-native-reanimated';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { Icon } from '../..';
|
||||||
|
import styles from './uploadsGalleryModalStyles';
|
||||||
|
import { MediaItem } from '../../../providers/ecency/ecency.types';
|
||||||
|
import { ThreeSpeakStatus } from '../../../providers/speak/speak.types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
item: MediaItem;
|
||||||
|
insertedMediaUrls: string[];
|
||||||
|
isDeleteMode: boolean;
|
||||||
|
isDeleting: boolean;
|
||||||
|
deleteIds: string[];
|
||||||
|
isExpandedMode: boolean;
|
||||||
|
onPress: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MediaPreviewItem = ({
|
||||||
|
item,
|
||||||
|
insertedMediaUrls,
|
||||||
|
isDeleteMode,
|
||||||
|
isDeleting,
|
||||||
|
deleteIds,
|
||||||
|
isExpandedMode,
|
||||||
|
onPress,
|
||||||
|
}: Props) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const thumbUrl =
|
||||||
|
item.thumbUrl || proxifyImageSrc(item.url, 600, 500, Platform.OS === 'ios' ? 'match' : 'webp');
|
||||||
|
let isInsertedTimes = 0;
|
||||||
|
insertedMediaUrls?.forEach((url) => (isInsertedTimes += url === item.url ? 1 : 0));
|
||||||
|
const isToBeDeleted = deleteIds.indexOf(item._id) >= 0;
|
||||||
|
const transformStyle = {
|
||||||
|
transform: isToBeDeleted ? [{ scaleX: 0.7 }, { scaleY: 0.7 }] : [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const _renderStatus = () =>
|
||||||
|
item.speakData && (
|
||||||
|
<View style={{ ...styles.statusContainer, right: isExpandedMode ? 8 : 0 }}>
|
||||||
|
<Text style={styles.statusText}>
|
||||||
|
{intl.formatMessage({ id: `uploads_modal.${item.speakData?.status}` })}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const _renderMinus = () =>
|
||||||
|
isDeleteMode && (
|
||||||
|
<AnimatedView.View entering={ZoomIn} style={styles.minusContainer}>
|
||||||
|
<Icon
|
||||||
|
color={EStyleSheet.value('$pureWhite')}
|
||||||
|
iconType="MaterialCommunityIcons"
|
||||||
|
name="minus"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
</AnimatedView.View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const _renderCounter = () =>
|
||||||
|
isInsertedTimes > 0 &&
|
||||||
|
!isDeleteMode && (
|
||||||
|
<AnimatedView.View entering={ZoomIn} style={styles.counterContainer}>
|
||||||
|
<Text style={styles.counterText}>{isInsertedTimes}</Text>
|
||||||
|
</AnimatedView.View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const _renderLoading = () =>
|
||||||
|
(item.speakData?.status === ThreeSpeakStatus.PREPARING ||
|
||||||
|
item.speakData?.status === ThreeSpeakStatus.ENCODING) && (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={onPress} disabled={isDeleting}>
|
||||||
|
<View style={transformStyle}>
|
||||||
|
<FastImage
|
||||||
|
source={{ uri: thumbUrl }}
|
||||||
|
style={isExpandedMode ? styles.gridMediaItem : styles.mediaItem}
|
||||||
|
/>
|
||||||
|
{_renderCounter()}
|
||||||
|
{_renderStatus()}
|
||||||
|
{_renderMinus()}
|
||||||
|
{_renderLoading()}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,261 @@
|
|||||||
|
import React, { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { View, Text, TouchableOpacity, Image, Alert } from 'react-native';
|
||||||
|
import ActionSheet from 'react-native-actions-sheet';
|
||||||
|
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||||
|
import { Video as VideoType } from 'react-native-image-crop-picker';
|
||||||
|
import Video from 'react-native-video';
|
||||||
|
import { createThumbnail } from 'react-native-create-thumbnail';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import ImagePicker, { Options } from 'react-native-image-crop-picker';
|
||||||
|
import { FlashList } from '@shopify/flash-list';
|
||||||
|
import * as Progress from 'react-native-progress';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import styles from '../styles/speakUploaderModal.styles';
|
||||||
|
import { MainButton } from '../../mainButton';
|
||||||
|
import { uploadFile, uploadVideoInfo } from '../../../providers/speak/speak';
|
||||||
|
import { useAppSelector } from '../../../hooks';
|
||||||
|
import QUERIES from '../../../providers/queries/queryKeys';
|
||||||
|
import Icon from '../../icon';
|
||||||
|
import getWindowDimensions from '../../../utils/getWindowDimensions';
|
||||||
|
import { TextButton } from '../../buttons';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
setIsUploading: (flag: boolean) => void;
|
||||||
|
isUploading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SpeakUploaderModal = forwardRef(({ setIsUploading, isUploading }: Props, ref) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const sheetModalRef = useRef();
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||||
|
const pinHash = useAppSelector((state) => state.application.pin);
|
||||||
|
|
||||||
|
const [selectedThumb, setSelectedThumb] = useState(null);
|
||||||
|
const [availableThumbs, setAvailableThumbs] = useState([]);
|
||||||
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
|
|
||||||
|
const [selectedVido, setSelectedVideo] = useState<VideoType | null>(null);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
showUploader: async (_video: VideoType) => {
|
||||||
|
if (sheetModalRef.current) {
|
||||||
|
sheetModalRef.current.setModalVisible(true);
|
||||||
|
|
||||||
|
if (_video) {
|
||||||
|
if (!_video.filename) {
|
||||||
|
_video.filename = _video.path.split('/').pop();
|
||||||
|
}
|
||||||
|
setSelectedVideo(_video);
|
||||||
|
setSelectedThumb(null);
|
||||||
|
setUploadProgress(0);
|
||||||
|
|
||||||
|
const thumbs = [];
|
||||||
|
const _diff = _video.duration / 5;
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const _thumb = await createThumbnail({
|
||||||
|
url: _video.sourceURL || _video.path,
|
||||||
|
timeStamp: i * _diff,
|
||||||
|
});
|
||||||
|
thumbs.push(_thumb);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAvailableThumbs(thumbs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const _startUpload = async () => {
|
||||||
|
if (!selectedVido || isUploading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsUploading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { filename, size, duration } = selectedVido;
|
||||||
|
|
||||||
|
const _onProgress = (progress) => {
|
||||||
|
console.log('Upload progress', progress);
|
||||||
|
setUploadProgress(progress);
|
||||||
|
};
|
||||||
|
|
||||||
|
const videoId = await uploadFile(selectedVido, _onProgress);
|
||||||
|
|
||||||
|
let thumbId: any = '';
|
||||||
|
if (selectedThumb) {
|
||||||
|
thumbId = await uploadFile(selectedThumb);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('updating video information', videoId, thumbId);
|
||||||
|
|
||||||
|
const response = await uploadVideoInfo(
|
||||||
|
currentAccount,
|
||||||
|
pinHash,
|
||||||
|
filename,
|
||||||
|
size,
|
||||||
|
videoId,
|
||||||
|
thumbId,
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
queryClient.invalidateQueries([QUERIES.MEDIA.GET_VIDEOS]);
|
||||||
|
|
||||||
|
if (sheetModalRef.current) {
|
||||||
|
sheetModalRef.current.setModalVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('response after updating video information', response);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Video upload failed', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsUploading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _onClosePress = () => {
|
||||||
|
sheetModalRef.current?.setModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _handleOpenImagePicker = () => {
|
||||||
|
const _options: Options = {
|
||||||
|
includeBase64: true,
|
||||||
|
mediaType: 'photo',
|
||||||
|
smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'],
|
||||||
|
};
|
||||||
|
|
||||||
|
ImagePicker.openPicker(_options)
|
||||||
|
.then((items) => {
|
||||||
|
if (items && !Array.isArray(items)) {
|
||||||
|
items = [items];
|
||||||
|
}
|
||||||
|
setSelectedThumb(items[0]);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
Alert.alert('Fail', `Thumb selection failed, ${e.message}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const _renderThumbSelection = () => {
|
||||||
|
const _renderThumb = (uri, onPress) => (
|
||||||
|
<TouchableOpacity onPress={onPress} disabled={isUploading}>
|
||||||
|
<Image source={uri && { uri }} style={styles.thumbnail} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
|
const _renderThumbItem = ({ item }) => {
|
||||||
|
const _onPress = () => {
|
||||||
|
setSelectedThumb(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
return _renderThumb(item.path || '', _onPress);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _renderHeader = () => (
|
||||||
|
<View style={styles.selectedThumbContainer}>
|
||||||
|
<>
|
||||||
|
{_renderThumb(selectedThumb?.path || '', _handleOpenImagePicker)}
|
||||||
|
<Icon
|
||||||
|
iconType="MaterialCommunityIcons"
|
||||||
|
style={{ position: 'absolute', top: 16, left: 8 }}
|
||||||
|
name="pencil"
|
||||||
|
color={EStyleSheet.value('$iconColor')}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
<View style={styles.thumbSeparator} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.imageContainer}>
|
||||||
|
<Text style={styles.label}>{intl.formatMessage({ id: 'uploads_modal.select_thumb' })}</Text>
|
||||||
|
<FlashList
|
||||||
|
horizontal={true}
|
||||||
|
ListHeaderComponent={_renderHeader}
|
||||||
|
data={availableThumbs.slice()}
|
||||||
|
renderItem={_renderThumbItem}
|
||||||
|
keyExtractor={(item, index) => item.path + index}
|
||||||
|
estimatedItemSize={128}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _renderUploadProgress = () => {
|
||||||
|
return (
|
||||||
|
<Progress.Bar
|
||||||
|
style={{ alignSelf: 'center', marginBottom: 12, borderWidth: 0 }}
|
||||||
|
progress={uploadProgress}
|
||||||
|
color={EStyleSheet.value('$primaryBlue')}
|
||||||
|
unfilledColor={EStyleSheet.value('$primaryLightBackground')}
|
||||||
|
width={getWindowDimensions().width - 40}
|
||||||
|
indeterminate={uploadProgress === 1 && isUploading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _renderActionPanel = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.actionPanel}>
|
||||||
|
<TextButton
|
||||||
|
text={intl.formatMessage({ id: 'alert.close' })}
|
||||||
|
onPress={_onClosePress}
|
||||||
|
textStyle={styles.btnTxtClose}
|
||||||
|
style={styles.btnClose}
|
||||||
|
/>
|
||||||
|
<MainButton
|
||||||
|
style={{}}
|
||||||
|
onPress={_startUpload}
|
||||||
|
text={intl.formatMessage({
|
||||||
|
id: `uploads_modal.${isUploading ? 'uploading' : 'start_upload'}`,
|
||||||
|
})}
|
||||||
|
isDisable={isUploading}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _renderFormContent = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.contentContainer}>
|
||||||
|
{!!selectedVido && (
|
||||||
|
<Video
|
||||||
|
source={{
|
||||||
|
uri: selectedVido?.sourceURL || selectedVido?.path,
|
||||||
|
}}
|
||||||
|
repeat={true}
|
||||||
|
resizeMode="contain"
|
||||||
|
fullscreen={false}
|
||||||
|
paused={isUploading}
|
||||||
|
style={styles.mediaPlayer}
|
||||||
|
volume={0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{_renderThumbSelection()}
|
||||||
|
{_renderUploadProgress()}
|
||||||
|
{_renderActionPanel()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionSheet
|
||||||
|
ref={sheetModalRef}
|
||||||
|
gestureEnabled={true}
|
||||||
|
closeOnTouchBackdrop={true}
|
||||||
|
hideUnderlay
|
||||||
|
containerStyle={styles.sheetContent}
|
||||||
|
indicatorColor={EStyleSheet.value('$iconColor')}
|
||||||
|
>
|
||||||
|
{_renderFormContent()}
|
||||||
|
</ActionSheet>
|
||||||
|
);
|
||||||
|
});
|
@ -1,54 +1,57 @@
|
|||||||
import { proxifyImageSrc } from '@ecency/render-helper';
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import {
|
import { ActivityIndicator, Alert, Keyboard, Text, TouchableOpacity, View } from 'react-native';
|
||||||
ActivityIndicator,
|
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||||
Alert,
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
Keyboard,
|
|
||||||
Platform,
|
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from 'react-native';
|
|
||||||
import Animated, {
|
import Animated, {
|
||||||
default as AnimatedView,
|
default as AnimatedView,
|
||||||
|
EasingNode,
|
||||||
SlideInRight,
|
SlideInRight,
|
||||||
SlideOutRight,
|
SlideOutRight,
|
||||||
ZoomIn,
|
|
||||||
EasingNode,
|
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
import { useDispatch } from 'react-redux';
|
||||||
import FastImage from 'react-native-fast-image';
|
|
||||||
import { FlatList } from 'react-native-gesture-handler';
|
|
||||||
import { Icon, IconButton } from '../..';
|
import { Icon, IconButton } from '../..';
|
||||||
import { UploadedMedia } from '../../../models';
|
import { MediaItem } from '../../../providers/ecency/ecency.types';
|
||||||
|
import { editorQueries, speakQueries } from '../../../providers/queries';
|
||||||
|
import { MediaPreviewItem } from './mediaPreviewItem';
|
||||||
import styles, {
|
import styles, {
|
||||||
COMPACT_HEIGHT,
|
COMPACT_HEIGHT,
|
||||||
EXPANDED_HEIGHT,
|
EXPANDED_HEIGHT,
|
||||||
MAX_HORIZONTAL_THUMBS,
|
MAX_HORIZONTAL_THUMBS,
|
||||||
} from './uploadsGalleryModalStyles';
|
} from './uploadsGalleryModalStyles';
|
||||||
import { useMediaDeleteMutation } from '../../../providers/queries';
|
import { ThreeSpeakStatus } from '../../../providers/speak/speak.types';
|
||||||
|
import { toastNotification } from '../../../redux/actions/uiAction';
|
||||||
|
import { useAppSelector } from '../../../hooks';
|
||||||
|
import { Modes } from '../container/uploadsGalleryModal';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
mode: Modes;
|
||||||
insertedMediaUrls: string[];
|
insertedMediaUrls: string[];
|
||||||
mediaUploads: any[];
|
mediaUploads: MediaItem[];
|
||||||
isAddingToUploads: boolean;
|
isAddingToUploads: boolean;
|
||||||
insertMedia: (map: Map<number, boolean>) => void;
|
insertMedia: (map: Map<number, boolean>) => void;
|
||||||
handleOpenGallery: (addToUploads?: boolean) => void;
|
handleOpenGallery: (addToUploads?: boolean) => void;
|
||||||
|
handleOpenSpeakUploader: () => void;
|
||||||
handleOpenCamera: () => void;
|
handleOpenCamera: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UploadsGalleryContent = ({
|
const UploadsGalleryContent = ({
|
||||||
|
mode,
|
||||||
insertedMediaUrls,
|
insertedMediaUrls,
|
||||||
mediaUploads,
|
mediaUploads,
|
||||||
isAddingToUploads,
|
isAddingToUploads,
|
||||||
insertMedia,
|
insertMedia,
|
||||||
handleOpenGallery,
|
handleOpenGallery,
|
||||||
handleOpenCamera,
|
handleOpenCamera,
|
||||||
|
handleOpenSpeakUploader,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const deleteMediaMutation = useMediaDeleteMutation();
|
const deleteMediaMutation = editorQueries.useMediaDeleteMutation();
|
||||||
|
const speakMutations = speakQueries.useSpeakMutations();
|
||||||
|
|
||||||
|
const allowSpkPublishing = useAppSelector((state) => state.editor.allowSpkPublishing);
|
||||||
|
|
||||||
const [deleteIds, setDeleteIds] = useState<string[]>([]);
|
const [deleteIds, setDeleteIds] = useState<string[]>([]);
|
||||||
const [isDeleteMode, setIsDeleteMode] = useState(false);
|
const [isDeleteMode, setIsDeleteMode] = useState(false);
|
||||||
@ -56,7 +59,10 @@ const UploadsGalleryContent = ({
|
|||||||
|
|
||||||
const animatedHeightRef = useRef(new Animated.Value(COMPACT_HEIGHT));
|
const animatedHeightRef = useRef(new Animated.Value(COMPACT_HEIGHT));
|
||||||
|
|
||||||
const isDeleting = deleteMediaMutation.isLoading;
|
const isDeleting =
|
||||||
|
mode === Modes.MODE_IMAGE
|
||||||
|
? deleteMediaMutation.isLoading
|
||||||
|
: speakMutations.deleteVideoMutation.isLoading;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isExpandedMode) {
|
if (isExpandedMode) {
|
||||||
@ -65,12 +71,28 @@ const UploadsGalleryContent = ({
|
|||||||
}, [isExpandedMode]);
|
}, [isExpandedMode]);
|
||||||
|
|
||||||
const _deleteMedia = async () => {
|
const _deleteMedia = async () => {
|
||||||
deleteMediaMutation.mutate(deleteIds, {
|
const _options = {
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
setIsDeleteMode(false);
|
setIsDeleteMode(false);
|
||||||
setDeleteIds([]);
|
setDeleteIds([]);
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case Modes.MODE_VIDEO:
|
||||||
|
const _permlinks: string[] = [];
|
||||||
|
deleteIds.forEach((_id) => {
|
||||||
|
const mediaItem = mediaUploads.find((item) => item._id === _id);
|
||||||
|
if (mediaItem?.speakData) {
|
||||||
|
_permlinks.push(mediaItem.speakData.permlink);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
speakMutations.deleteVideoMutation.mutate(_permlinks, _options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
deleteMediaMutation.mutate(deleteIds, _options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const _onDeletePress = async () => {
|
const _onDeletePress = async () => {
|
||||||
@ -101,60 +123,72 @@ const UploadsGalleryContent = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// render list item for snippet and handle actions;
|
// render list item for snippet and handle actions;
|
||||||
const _renderItem = ({ item, index }: { item: UploadedMedia; index: number }) => {
|
const _renderItem = ({ item, index }: { item: MediaItem; index: number }) => {
|
||||||
|
// avoid rendering unpublihsed videos in allow publishing state is false
|
||||||
|
if (
|
||||||
|
!allowSpkPublishing &&
|
||||||
|
item.speakData &&
|
||||||
|
item.speakData.status !== ThreeSpeakStatus.PUBLISHED
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const _onPress = () => {
|
const _onPress = () => {
|
||||||
if (isDeleteMode) {
|
if (isDeleteMode) {
|
||||||
const idIndex = deleteIds.indexOf(item._id);
|
const deleteId = item._id;
|
||||||
|
|
||||||
|
const idIndex = deleteIds.indexOf(deleteId);
|
||||||
if (idIndex >= 0) {
|
if (idIndex >= 0) {
|
||||||
deleteIds.splice(idIndex, 1);
|
deleteIds.splice(idIndex, 1);
|
||||||
} else {
|
} else {
|
||||||
deleteIds.push(item._id);
|
deleteIds.push(deleteId);
|
||||||
}
|
}
|
||||||
setDeleteIds([...deleteIds]);
|
setDeleteIds([...deleteIds]);
|
||||||
} else {
|
} else {
|
||||||
insertMedia(new Map([[index, true]]));
|
let insertError: Error | null = null;
|
||||||
|
if (item.speakData) {
|
||||||
|
switch (item.speakData.status) {
|
||||||
|
case ThreeSpeakStatus.READY:
|
||||||
|
// check if a ready video is already inserted
|
||||||
|
insertedMediaUrls.forEach((url) => {
|
||||||
|
const _mediaItem = mediaUploads.find(
|
||||||
|
(item) => item.url === url && item.speakData?.status === ThreeSpeakStatus.READY,
|
||||||
|
);
|
||||||
|
if (_mediaItem) {
|
||||||
|
insertError = new Error('Can only have on unpublised speak speak per post');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ThreeSpeakStatus.PREPARING:
|
||||||
|
case ThreeSpeakStatus.ENCODING:
|
||||||
|
// interupt video insertion is it's still under processing
|
||||||
|
insertError = new Error('Please wait while video is being processed');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log('Skipping corner check for published video');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!insertError) {
|
||||||
|
insertMedia(new Map([[index, true]]));
|
||||||
|
} else {
|
||||||
|
dispatch(toastNotification(insertError.message));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const thumbUrl = proxifyImageSrc(item.url, 600, 500, Platform.OS === 'ios' ? 'match' : 'webp');
|
|
||||||
let isInsertedTimes = 0;
|
|
||||||
insertedMediaUrls?.forEach((url) => (isInsertedTimes += url === item.url ? 1 : 0));
|
|
||||||
const isToBeDeleted = deleteIds.indexOf(item._id) >= 0;
|
|
||||||
const transformStyle = {
|
|
||||||
transform: isToBeDeleted ? [{ scaleX: 0.7 }, { scaleY: 0.7 }] : [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const _renderMinus = () =>
|
|
||||||
isDeleteMode && (
|
|
||||||
<AnimatedView.View entering={ZoomIn} style={styles.minusContainer}>
|
|
||||||
<Icon
|
|
||||||
color={EStyleSheet.value('$pureWhite')}
|
|
||||||
iconType="MaterialCommunityIcons"
|
|
||||||
name="minus"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
</AnimatedView.View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const _renderCounter = () =>
|
|
||||||
isInsertedTimes > 0 &&
|
|
||||||
!isDeleteMode && (
|
|
||||||
<AnimatedView.View entering={ZoomIn} style={styles.counterContainer}>
|
|
||||||
<Text style={styles.counterText}>{isInsertedTimes}</Text>
|
|
||||||
</AnimatedView.View>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={_onPress} disabled={isDeleting}>
|
<MediaPreviewItem
|
||||||
<View style={transformStyle}>
|
item={item}
|
||||||
<FastImage
|
insertedMediaUrls={insertedMediaUrls}
|
||||||
source={{ uri: thumbUrl }}
|
deleteIds={deleteIds}
|
||||||
style={isExpandedMode ? styles.gridMediaItem : styles.mediaItem}
|
isDeleteMode={isDeleteMode}
|
||||||
/>
|
isDeleting={isDeleting}
|
||||||
{_renderCounter()}
|
isExpandedMode={isExpandedMode}
|
||||||
{_renderMinus()}
|
onPress={_onPress}
|
||||||
</View>
|
/>
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -188,11 +222,25 @@ const UploadsGalleryContent = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _renderSelectButtons = (
|
||||||
|
<>
|
||||||
|
{_renderSelectButton(
|
||||||
|
mode === Modes.MODE_VIDEO ? 'video-box' : 'image',
|
||||||
|
'Gallery',
|
||||||
|
handleOpenGallery,
|
||||||
|
)}
|
||||||
|
{_renderSelectButton('camera', 'Camera', handleOpenCamera)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const _renderHeaderContent = () => (
|
const _renderHeaderContent = () => (
|
||||||
<View style={{ ...styles.buttonsContainer, paddingVertical: isExpandedMode ? 8 : 0 }}>
|
<View style={{ ...styles.buttonsContainer, paddingVertical: isExpandedMode ? 8 : 0 }}>
|
||||||
<View style={styles.selectButtonsContainer}>
|
<View style={styles.selectButtonsContainer}>
|
||||||
{_renderSelectButton('image', 'Gallery', handleOpenGallery)}
|
{mode === Modes.MODE_IMAGE
|
||||||
{_renderSelectButton('camera', 'Camera', handleOpenCamera)}
|
? _renderSelectButtons
|
||||||
|
: isAddingToUploads
|
||||||
|
? _renderSelectButton('progress-upload', 'Uploading', handleOpenSpeakUploader)
|
||||||
|
: _renderSelectButtons}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.pillBtnContainer}>
|
<View style={styles.pillBtnContainer}>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -205,6 +253,7 @@ const UploadsGalleryContent = ({
|
|||||||
handleOpenGallery(true);
|
handleOpenGallery(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{
|
style={{
|
||||||
...styles.uploadsActionBtn,
|
...styles.uploadsActionBtn,
|
||||||
|
@ -180,7 +180,7 @@ export default EStyleSheet.create({
|
|||||||
|
|
||||||
minusContainer: {
|
minusContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 16,
|
top: 12,
|
||||||
left: 16,
|
left: 16,
|
||||||
backgroundColor: '$primaryRed',
|
backgroundColor: '$primaryRed',
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
@ -189,7 +189,7 @@ export default EStyleSheet.create({
|
|||||||
|
|
||||||
counterContainer: {
|
counterContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 16,
|
top: 12,
|
||||||
left: 16,
|
left: 16,
|
||||||
backgroundColor: '$primaryLightBackground',
|
backgroundColor: '$primaryLightBackground',
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
@ -205,6 +205,24 @@ export default EStyleSheet.create({
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
} as TextStyle,
|
} as TextStyle,
|
||||||
|
|
||||||
|
statusContainer: {
|
||||||
|
backgroundColor: '$primaryBlue',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 8,
|
||||||
|
right: 0,
|
||||||
|
borderBottomLeftRadius: 16,
|
||||||
|
borderBottomRightRadius: 16,
|
||||||
|
padding: 2,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
} as ViewStyle,
|
||||||
|
|
||||||
|
statusText: {
|
||||||
|
color: '$pureWhite',
|
||||||
|
fontSize: 14,
|
||||||
|
} as TextStyle,
|
||||||
|
|
||||||
checkStyle: {
|
checkStyle: {
|
||||||
backgroundColor: '$white',
|
backgroundColor: '$white',
|
||||||
} as ViewStyle,
|
} as ViewStyle,
|
||||||
@ -222,4 +240,14 @@ export default EStyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
} as ViewStyle,
|
} as ViewStyle,
|
||||||
|
|
||||||
|
loadingContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
} as ViewStyle,
|
||||||
});
|
});
|
||||||
|
@ -1,23 +1,34 @@
|
|||||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { Alert, AlertButton } from 'react-native';
|
import { Alert, AlertButton } from 'react-native';
|
||||||
import ImagePicker, { Image } from 'react-native-image-crop-picker';
|
import ImagePicker, { Image, Options, Video } from 'react-native-image-crop-picker';
|
||||||
import RNHeicConverter from 'react-native-heic-converter';
|
import RNHeicConverter from 'react-native-heic-converter';
|
||||||
import { openSettings } from 'react-native-permissions';
|
import { openSettings } from 'react-native-permissions';
|
||||||
import bugsnapInstance from '../../../config/bugsnag';
|
import bugsnapInstance from '../../../config/bugsnag';
|
||||||
import { getImages } from '../../../providers/ecency/ecency';
|
|
||||||
import UploadsGalleryContent from '../children/uploadsGalleryContent';
|
import UploadsGalleryContent from '../children/uploadsGalleryContent';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '../../../hooks';
|
import { useAppDispatch, useAppSelector } from '../../../hooks';
|
||||||
import { delay, extractFilenameFromPath } from '../../../utils/editor';
|
import {
|
||||||
|
delay,
|
||||||
|
extract3SpeakIds,
|
||||||
|
extractFilenameFromPath,
|
||||||
|
extractImageUrls,
|
||||||
|
} from '../../../utils/editor';
|
||||||
import showLoginAlert from '../../../utils/showLoginAlert';
|
import showLoginAlert from '../../../utils/showLoginAlert';
|
||||||
import { useMediaQuery, useMediaUploadMutation } from '../../../providers/queries';
|
import { editorQueries, speakQueries } from '../../../providers/queries';
|
||||||
import { showActionModal } from '../../../redux/actions/uiAction';
|
import { showActionModal } from '../../../redux/actions/uiAction';
|
||||||
|
import { MediaItem } from '../../../providers/ecency/ecency.types';
|
||||||
|
import { SpeakUploaderModal } from '../children/speakUploaderModal';
|
||||||
|
|
||||||
export interface UploadsGalleryModalRef {
|
export interface UploadsGalleryModalRef {
|
||||||
showModal: () => void;
|
showModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Modes {
|
||||||
|
MODE_IMAGE = 0,
|
||||||
|
MODE_VIDEO = 1,
|
||||||
|
}
|
||||||
|
|
||||||
export enum MediaInsertStatus {
|
export enum MediaInsertStatus {
|
||||||
UPLOADING = 'UPLOADING',
|
UPLOADING = 'UPLOADING',
|
||||||
READY = 'READY',
|
READY = 'READY',
|
||||||
@ -29,12 +40,13 @@ export interface MediaInsertData {
|
|||||||
filename?: string;
|
filename?: string;
|
||||||
text: string;
|
text: string;
|
||||||
status: MediaInsertStatus;
|
status: MediaInsertStatus;
|
||||||
|
mode: Modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UploadsGalleryModalProps {
|
interface UploadsGalleryModalProps {
|
||||||
insertedMediaUrls: string[];
|
draftId?: string;
|
||||||
|
postBody: string;
|
||||||
paramFiles: any[];
|
paramFiles: any[];
|
||||||
username: string;
|
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
isPreviewActive: boolean;
|
isPreviewActive: boolean;
|
||||||
allowMultiple?: boolean;
|
allowMultiple?: boolean;
|
||||||
@ -46,9 +58,9 @@ interface UploadsGalleryModalProps {
|
|||||||
export const UploadsGalleryModal = forwardRef(
|
export const UploadsGalleryModal = forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
insertedMediaUrls,
|
draftId,
|
||||||
|
postBody,
|
||||||
paramFiles,
|
paramFiles,
|
||||||
username,
|
|
||||||
isEditing,
|
isEditing,
|
||||||
isPreviewActive,
|
isPreviewActive,
|
||||||
allowMultiple,
|
allowMultiple,
|
||||||
@ -61,33 +73,42 @@ export const UploadsGalleryModal = forwardRef(
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const mediaQuery = useMediaQuery();
|
const imageUploadsQuery = editorQueries.useMediaQuery();
|
||||||
const mediaUploadMutation = useMediaUploadMutation();
|
const videoUploadsQuery = speakQueries.useVideoUploadsQuery();
|
||||||
|
|
||||||
|
const mediaUploadMutation = editorQueries.useMediaUploadMutation();
|
||||||
|
|
||||||
const pendingInserts = useRef<MediaInsertData[]>([]);
|
const pendingInserts = useRef<MediaInsertData[]>([]);
|
||||||
|
const speakUploaderRef = useRef<SpeakUploaderModal>();
|
||||||
|
|
||||||
const [mediaUploads, setMediaUploads] = useState([]);
|
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [isAddingToUploads, setIsAddingToUploads] = useState(false);
|
const [isAddingToUploads, setIsAddingToUploads] = useState(false);
|
||||||
|
const [mode, setMode] = useState<Modes>(Modes.MODE_IMAGE);
|
||||||
|
const [mediaUrls, setMediaUrls] = useState<string[]>([]);
|
||||||
|
|
||||||
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
||||||
|
|
||||||
|
const mediaUploadsQuery = mode === Modes.MODE_VIDEO ? videoUploadsQuery : imageUploadsQuery;
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
toggleModal: (value: boolean) => {
|
toggleModal: (value: boolean, _mode: Modes = mode) => {
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
showLoginAlert({ intl });
|
showLoginAlert({ intl });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === showModal) {
|
if (value === showModal && _mode === mode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
_getMediaUploads();
|
_getMediaUploads(_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMode(_mode);
|
||||||
setShowModal(value);
|
setShowModal(value);
|
||||||
},
|
},
|
||||||
|
getMode: () => mode,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -122,21 +143,55 @@ export const UploadsGalleryModal = forwardRef(
|
|||||||
}, [isEditing]);
|
}, [isEditing]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
_getMediaUploads(); // get media uploads when there is new update
|
_getMediaUploads(mode); // get media uploads when there is new update
|
||||||
}, [mediaQuery.data]);
|
}, [mediaUploadsQuery.data, mode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showModal) {
|
||||||
|
let _urls: string[] = [];
|
||||||
|
if (mode === Modes.MODE_VIDEO) {
|
||||||
|
const _vidIds = extract3SpeakIds({ body: postBody });
|
||||||
|
_urls = _vidIds.map((id) => {
|
||||||
|
const mediaItem = mediaUploadsQuery.data.find((item) => item._id === id);
|
||||||
|
return mediaItem?.url;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_urls = extractImageUrls({ body: postBody });
|
||||||
|
}
|
||||||
|
setMediaUrls(_urls);
|
||||||
|
}
|
||||||
|
}, [postBody, showModal, mode]);
|
||||||
|
|
||||||
const _handleOpenImagePicker = (addToUploads?: boolean) => {
|
const _handleOpenImagePicker = (addToUploads?: boolean) => {
|
||||||
ImagePicker.openPicker({
|
const _vidMode = mode === Modes.MODE_VIDEO;
|
||||||
includeBase64: true,
|
|
||||||
multiple: allowMultiple || true,
|
if (_vidMode && isAddingToUploads) {
|
||||||
mediaType: 'photo',
|
speakUploaderRef.current.showUploader();
|
||||||
smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'],
|
return;
|
||||||
})
|
}
|
||||||
.then((images) => {
|
|
||||||
if (images && !Array.isArray(images)) {
|
const _options: Options = _vidMode
|
||||||
images = [images];
|
? {
|
||||||
|
mediaType: 'video',
|
||||||
|
smartAlbums: ['UserLibrary', 'Favorites', 'Videos'],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
includeBase64: true,
|
||||||
|
multiple: allowMultiple || true,
|
||||||
|
mediaType: 'photo',
|
||||||
|
smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'],
|
||||||
|
};
|
||||||
|
|
||||||
|
ImagePicker.openPicker(_options)
|
||||||
|
.then((items) => {
|
||||||
|
if (items && !Array.isArray(items)) {
|
||||||
|
items = [items];
|
||||||
|
}
|
||||||
|
if (_vidMode) {
|
||||||
|
_handleVideoSelection(items[0]);
|
||||||
|
} else {
|
||||||
|
_handleMediaOnSelected(items, !addToUploads);
|
||||||
}
|
}
|
||||||
_handleMediaOnSelected(images, !addToUploads);
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
_handleMediaOnSelectFailure(e);
|
_handleMediaOnSelectFailure(e);
|
||||||
@ -144,12 +199,29 @@ export const UploadsGalleryModal = forwardRef(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const _handleOpenCamera = () => {
|
const _handleOpenCamera = () => {
|
||||||
ImagePicker.openCamera({
|
const _vidMode = mode === Modes.MODE_VIDEO;
|
||||||
includeBase64: true,
|
|
||||||
mediaType: 'photo',
|
if (_vidMode && isAddingToUploads) {
|
||||||
})
|
speakUploaderRef.current.showUploader();
|
||||||
.then((image) => {
|
return;
|
||||||
_handleMediaOnSelected([image], true);
|
}
|
||||||
|
|
||||||
|
const _options: Options = _vidMode
|
||||||
|
? {
|
||||||
|
mediaType: 'video',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
includeBase64: true,
|
||||||
|
mediaType: 'photo',
|
||||||
|
};
|
||||||
|
|
||||||
|
ImagePicker.openCamera(_options)
|
||||||
|
.then((media) => {
|
||||||
|
if (_vidMode) {
|
||||||
|
_handleVideoSelection(media);
|
||||||
|
} else {
|
||||||
|
_handleMediaOnSelected([media], true);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
_handleMediaOnSelectFailure(e);
|
_handleMediaOnSelectFailure(e);
|
||||||
@ -299,6 +371,12 @@ export const UploadsGalleryModal = forwardRef(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _handleVideoSelection = (video: Video) => {
|
||||||
|
// show video upload modal,
|
||||||
|
// allow thumbnail selection and uplaods
|
||||||
|
speakUploaderRef.current.showUploader(video);
|
||||||
|
};
|
||||||
|
|
||||||
const _handleMediaOnSelectFailure = (error) => {
|
const _handleMediaOnSelectFailure = (error) => {
|
||||||
let title = intl.formatMessage({ id: 'alert.something_wrong' });
|
let title = intl.formatMessage({ id: 'alert.something_wrong' });
|
||||||
let body = error.message || JSON.stringify(error);
|
let body = error.message || JSON.stringify(error);
|
||||||
@ -336,6 +414,15 @@ export const UploadsGalleryModal = forwardRef(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _handleOpenSpeakUploader = () => {
|
||||||
|
speakUploaderRef.current.showUploader();
|
||||||
|
};
|
||||||
|
|
||||||
|
const _setIsSpeakUploading = (flag: boolean) => {
|
||||||
|
setIsUploading(flag);
|
||||||
|
setIsAddingToUploads(flag);
|
||||||
|
};
|
||||||
|
|
||||||
const _handleMediaInsertion = (data: MediaInsertData) => {
|
const _handleMediaInsertion = (data: MediaInsertData) => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
pendingInserts.current.push(data);
|
pendingInserts.current.push(data);
|
||||||
@ -345,14 +432,9 @@ export const UploadsGalleryModal = forwardRef(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// fetch images from server
|
// fetch images from server
|
||||||
const _getMediaUploads = async () => {
|
const _getMediaUploads = async (_mode: Modes = mode) => {
|
||||||
try {
|
try {
|
||||||
if (username) {
|
mediaUploadsQuery.refetch();
|
||||||
console.log(`getting images for: ${username}`);
|
|
||||||
const images = await getImages();
|
|
||||||
console.log('images received', images);
|
|
||||||
setMediaUploads(images || []);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to get images');
|
console.warn('Failed to get images');
|
||||||
}
|
}
|
||||||
@ -365,30 +447,46 @@ export const UploadsGalleryModal = forwardRef(
|
|||||||
|
|
||||||
map.forEach((value, index) => {
|
map.forEach((value, index) => {
|
||||||
console.log(index);
|
console.log(index);
|
||||||
const item = mediaUploads[index];
|
const item: MediaItem = mediaUploadsQuery.data[index];
|
||||||
data.push({
|
data.push({
|
||||||
url: item.url,
|
url: mode === Modes.MODE_VIDEO ? item.speakData?._id || '' : item.url,
|
||||||
text: '',
|
text: mode === Modes.MODE_VIDEO ? `3speak` : '',
|
||||||
status: MediaInsertStatus.READY,
|
status: MediaInsertStatus.READY,
|
||||||
|
mode,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
handleMediaInsert(data);
|
handleMediaInsert(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const data = mediaUploadsQuery.data.slice();
|
||||||
|
|
||||||
|
if (isPreviewActive) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!isPreviewActive &&
|
<>
|
||||||
showModal && (
|
{showModal && (
|
||||||
<UploadsGalleryContent
|
<UploadsGalleryContent
|
||||||
insertedMediaUrls={insertedMediaUrls}
|
mode={mode}
|
||||||
mediaUploads={mediaQuery.data.slice()}
|
draftId={draftId}
|
||||||
isAddingToUploads={isAddingToUploads}
|
insertedMediaUrls={mediaUrls}
|
||||||
getMediaUploads={_getMediaUploads}
|
mediaUploads={data}
|
||||||
insertMedia={_insertMedia}
|
isAddingToUploads={isAddingToUploads}
|
||||||
handleOpenCamera={_handleOpenCamera}
|
getMediaUploads={_getMediaUploads}
|
||||||
handleOpenGallery={_handleOpenImagePicker}
|
insertMedia={_insertMedia}
|
||||||
|
handleOpenCamera={_handleOpenCamera}
|
||||||
|
handleOpenGallery={_handleOpenImagePicker}
|
||||||
|
handleOpenSpeakUploader={_handleOpenSpeakUploader}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<SpeakUploaderModal
|
||||||
|
ref={speakUploaderRef}
|
||||||
|
isUploading={isAddingToUploads}
|
||||||
|
setIsUploading={_setIsSpeakUploading}
|
||||||
/>
|
/>
|
||||||
)
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
import { Dimensions, TextStyle, ViewStyle } from 'react-native';
|
||||||
|
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||||
|
|
||||||
|
const SCREEN_WIDTH = Dimensions.get('screen').width;
|
||||||
|
|
||||||
|
export default EStyleSheet.create({
|
||||||
|
modalStyle: {
|
||||||
|
backgroundColor: '$primaryBackgroundColor',
|
||||||
|
margin: 0,
|
||||||
|
paddingTop: 32,
|
||||||
|
paddingBottom: 8,
|
||||||
|
},
|
||||||
|
|
||||||
|
sheetContent: {
|
||||||
|
backgroundColor: '$primaryBackgroundColor',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 999,
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
paddingBottom: 40,
|
||||||
|
},
|
||||||
|
imageContainer: {
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
marginVertical: 20,
|
||||||
|
},
|
||||||
|
selectedThumbContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
thumbSeparator: {
|
||||||
|
width: 6,
|
||||||
|
marginTop: 12,
|
||||||
|
marginRight: 12,
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: '$iconColor',
|
||||||
|
},
|
||||||
|
thumbnail: {
|
||||||
|
marginTop: 10,
|
||||||
|
width: 128,
|
||||||
|
height: 72,
|
||||||
|
resizeMode: 'cover',
|
||||||
|
borderRadius: 8,
|
||||||
|
marginRight: 12,
|
||||||
|
backgroundColor: '$primaryLightBackground',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
color: '$primaryDarkGray',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
flexGrow: 1,
|
||||||
|
textAlign: 'left',
|
||||||
|
},
|
||||||
|
titleBox: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
titleInput: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#ccc',
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 8,
|
||||||
|
width: '80%',
|
||||||
|
marginTop: 10,
|
||||||
|
color: '$primaryDarkGray',
|
||||||
|
},
|
||||||
|
actionPanel: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
} as ViewStyle,
|
||||||
|
btnTxtClose: {
|
||||||
|
color: '$iconColor',
|
||||||
|
fontSize: 16,
|
||||||
|
} as TextStyle,
|
||||||
|
btnClose: {
|
||||||
|
marginRight: 12,
|
||||||
|
} as ViewStyle,
|
||||||
|
uploadButton: {
|
||||||
|
marginBottom: 24,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
alignSelf: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
mediaPlayer: {
|
||||||
|
width: SCREEN_WIDTH,
|
||||||
|
height: SCREEN_WIDTH / 1.77,
|
||||||
|
backgroundColor: 'black',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
@ -515,6 +515,7 @@
|
|||||||
"scheduled_for":"Scheduled For",
|
"scheduled_for":"Scheduled For",
|
||||||
"scheduled_immediate":"Immediate",
|
"scheduled_immediate":"Immediate",
|
||||||
"scheduled_later":"Later",
|
"scheduled_later":"Later",
|
||||||
|
"schedule_video_unsupported":"Scheduling video posts not available",
|
||||||
"settings_title":"Post Options",
|
"settings_title":"Post Options",
|
||||||
"done":"DONE",
|
"done":"DONE",
|
||||||
"draft_save_title":"Saving Draft",
|
"draft_save_title":"Saving Draft",
|
||||||
@ -557,10 +558,20 @@
|
|||||||
"btn_add":"Image",
|
"btn_add":"Image",
|
||||||
"btn_insert":"INSERT",
|
"btn_insert":"INSERT",
|
||||||
"btn_delete":"DELETE",
|
"btn_delete":"DELETE",
|
||||||
"confirm_delete":"Are you sure you want to delete images from your uploads",
|
"confirm_delete":"Are you sure you want to delete selected items from your uploads",
|
||||||
"message_failed":"Failed to upload image",
|
"message_failed":"Failed to upload image",
|
||||||
"delete_failed":"Failed to delete image",
|
"delete_failed":"Failed to delete image",
|
||||||
"failed_count":"Failed to upload {failedCount} of {totalCount} selected image(s)"
|
"failed_count":"Failed to upload {failedCount} of {totalCount} selected image(s)",
|
||||||
|
"publish_manual":"Ready",
|
||||||
|
"published":"Published",
|
||||||
|
"encoding_ipfs":"Encoding",
|
||||||
|
"encoding_preparing":"Preparing",
|
||||||
|
"deleted":"Deleted",
|
||||||
|
"start_upload":"START UPLOAD",
|
||||||
|
"uploading":"UPLOADING",
|
||||||
|
"select_thumb":"Select Thumbnail",
|
||||||
|
"warn_vid_already_added":"",
|
||||||
|
"warn_vid_in_process":""
|
||||||
},
|
},
|
||||||
"pincode": {
|
"pincode": {
|
||||||
"enter_text": "Enter PIN to unlock",
|
"enter_text": "Enter PIN to unlock",
|
||||||
@ -582,6 +593,8 @@
|
|||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"okay":"Okay",
|
"okay":"Okay",
|
||||||
"done":"Done",
|
"done":"Done",
|
||||||
|
"notice":"Notice!",
|
||||||
|
"close":"CLOSE",
|
||||||
"move_question": "Are you sure to move to drafts?",
|
"move_question": "Are you sure to move to drafts?",
|
||||||
"success_shared": "Success! Content submitted!",
|
"success_shared": "Success! Content submitted!",
|
||||||
"success_moved": "Moved to draft",
|
"success_moved": "Moved to draft",
|
||||||
|
@ -15,6 +15,12 @@ import { initQueryClient } from './providers/queries';
|
|||||||
|
|
||||||
const queryClientProviderProps = initQueryClient();
|
const queryClientProviderProps = initQueryClient();
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
import('react-query-native-devtools').then(({ addPlugin }) => {
|
||||||
|
addPlugin({ queryClient: queryClientProviderProps.client });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const _renderApp = ({ locale }) => (
|
const _renderApp = ({ locale }) => (
|
||||||
<PersistQueryClientProvider {...queryClientProviderProps}>
|
<PersistQueryClientProvider {...queryClientProviderProps}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
|
@ -43,6 +43,7 @@ const RootStack = createNativeStackNavigator();
|
|||||||
const MainStack = createNativeStackNavigator();
|
const MainStack = createNativeStackNavigator();
|
||||||
|
|
||||||
const MainStackNavigator = () => {
|
const MainStackNavigator = () => {
|
||||||
|
// TODO: remove initialRoute before PR
|
||||||
return (
|
return (
|
||||||
<MainStack.Navigator screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
|
<MainStack.Navigator screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
|
||||||
<MainStack.Screen name={ROUTES.DRAWER.MAIN} component={DrawerNavigator} />
|
<MainStack.Screen name={ROUTES.DRAWER.MAIN} component={DrawerNavigator} />
|
||||||
@ -88,7 +89,6 @@ export const StackNavigator = ({ initRoute }) => {
|
|||||||
screenOptions={{ headerShown: false, animation: 'slide_from_bottom' }}
|
screenOptions={{ headerShown: false, animation: 'slide_from_bottom' }}
|
||||||
>
|
>
|
||||||
<RootStack.Screen name={ROUTES.STACK.MAIN} component={MainStackNavigator} />
|
<RootStack.Screen name={ROUTES.STACK.MAIN} component={MainStackNavigator} />
|
||||||
|
|
||||||
<RootStack.Screen name={ROUTES.SCREENS.REGISTER} component={Register} />
|
<RootStack.Screen name={ROUTES.SCREENS.REGISTER} component={Register} />
|
||||||
<RootStack.Screen name={ROUTES.SCREENS.LOGIN} component={Login} />
|
<RootStack.Screen name={ROUTES.SCREENS.LOGIN} component={Login} />
|
||||||
<RootStack.Screen name={ROUTES.SCREENS.WELCOME} component={WelcomeScreen} />
|
<RootStack.Screen name={ROUTES.SCREENS.WELCOME} component={WelcomeScreen} />
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { QuoteItem } from '../../redux/reducers/walletReducer';
|
import { QuoteItem } from '../../redux/reducers/walletReducer';
|
||||||
|
import { ThreeSpeakVideo } from '../speak/speak.types';
|
||||||
|
|
||||||
export interface ReceivedVestingShare {
|
export interface ReceivedVestingShare {
|
||||||
delegator: string;
|
delegator: string;
|
||||||
@ -10,8 +11,10 @@ export interface ReceivedVestingShare {
|
|||||||
export interface MediaItem {
|
export interface MediaItem {
|
||||||
_id: string;
|
_id: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
thumbUrl: string;
|
||||||
created: string;
|
created: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
speakData?: ThreeSpeakVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Snippet {
|
export interface Snippet {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { Image } from 'react-native-image-crop-picker';
|
import { Image } from 'react-native-image-crop-picker';
|
||||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
import Upload, { UploadOptions } from 'react-native-background-upload';
|
||||||
import { toastNotification } from '../../redux/actions/uiAction';
|
import Config from 'react-native-config';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../../hooks';
|
||||||
|
import { toastNotification } from '../../../redux/actions/uiAction';
|
||||||
import {
|
import {
|
||||||
addFragment,
|
addFragment,
|
||||||
addImage,
|
addImage,
|
||||||
@ -11,11 +14,11 @@ import {
|
|||||||
getFragments,
|
getFragments,
|
||||||
getImages,
|
getImages,
|
||||||
updateFragment,
|
updateFragment,
|
||||||
uploadImage,
|
} from '../../ecency/ecency';
|
||||||
} from '../ecency/ecency';
|
import { MediaItem, Snippet } from '../../ecency/ecency.types';
|
||||||
import { MediaItem, Snippet } from '../ecency/ecency.types';
|
import { signImage } from '../../hive/dhive';
|
||||||
import { signImage } from '../hive/dhive';
|
import QUERIES from '../queryKeys';
|
||||||
import QUERIES from './queryKeys';
|
import bugsnapInstance from '../../../config/bugsnag';
|
||||||
|
|
||||||
interface SnippetMutationVars {
|
interface SnippetMutationVars {
|
||||||
id: string | null;
|
id: string | null;
|
||||||
@ -84,14 +87,66 @@ export const useMediaUploadMutation = () => {
|
|||||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||||
const pinCode = useAppSelector((state) => state.application.pin);
|
const pinCode = useAppSelector((state) => state.application.pin);
|
||||||
|
|
||||||
|
const _uploadMedia = async ({ media }: MediaUploadVars) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
signImage(media, currentAccount, pinCode)
|
||||||
|
.then((sign) => {
|
||||||
|
const _options: UploadOptions = {
|
||||||
|
url: `${Config.NEW_IMAGE_API}/hs/${sign}`,
|
||||||
|
path: Platform.select({
|
||||||
|
ios: `file://${media.path}`,
|
||||||
|
android: media.path.replace('file://', ''),
|
||||||
|
}),
|
||||||
|
method: 'POST',
|
||||||
|
type: 'multipart',
|
||||||
|
maxRetries: 2, // set retry count (Android only). Default 2
|
||||||
|
headers: {
|
||||||
|
Authorization: Config.NEW_IMAGE_API, // Config.NEW_IMAGE_API
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
field: 'uploaded_media',
|
||||||
|
// Below are options only supported on Android
|
||||||
|
notification: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
useUtf8Charset: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Upload starting');
|
||||||
|
return Upload.startUpload(_options);
|
||||||
|
})
|
||||||
|
.then((uploadId) => {
|
||||||
|
Upload.addListener('progress', uploadId, (data) => {
|
||||||
|
console.log(`Progress: ${data.progress}%`, data);
|
||||||
|
});
|
||||||
|
Upload.addListener('error', uploadId, (data) => {
|
||||||
|
console.log(`Error`, data);
|
||||||
|
throw data.error;
|
||||||
|
});
|
||||||
|
Upload.addListener('cancelled', uploadId, (data) => {
|
||||||
|
console.log(`Cancelled!`, data);
|
||||||
|
throw new Error('Upload Cancelled');
|
||||||
|
});
|
||||||
|
Upload.addListener('completed', uploadId, (data) => {
|
||||||
|
// data includes responseCode: number and responseBody: Object
|
||||||
|
console.log('Completed!', data);
|
||||||
|
const _respData = JSON.parse(data.responseBody);
|
||||||
|
resolve(_respData);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn('Meida Upload Failed', err);
|
||||||
|
bugsnapInstance.notify('Media upload failed', err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return useMutation<Image, undefined, MediaUploadVars>(
|
return useMutation<Image, undefined, MediaUploadVars>(
|
||||||
async ({ media }) => {
|
(vars) => {
|
||||||
console.log('uploading media', media);
|
return _uploadMedia(vars);
|
||||||
const sign = await signImage(media, currentAccount, pinCode);
|
|
||||||
return uploadImage(media, currentAccount.name, sign);
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
retry: 3,
|
|
||||||
onSuccess: (response, { addToUploads }) => {
|
onSuccess: (response, { addToUploads }) => {
|
||||||
if (addToUploads && response && response.url) {
|
if (addToUploads && response && response.url) {
|
||||||
console.log('adding image to gallery', response.url);
|
console.log('adding image to gallery', response.url);
|
4
src/providers/queries/editorQueries/index.ts
Normal file
4
src/providers/queries/editorQueries/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import * as editorQueries from './editorQueries';
|
||||||
|
import * as speakQueries from './speakQueries';
|
||||||
|
|
||||||
|
export { editorQueries, speakQueries };
|
234
src/providers/queries/editorQueries/speakQueries.ts
Normal file
234
src/providers/queries/editorQueries/speakQueries.ts
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../../hooks';
|
||||||
|
import { showActionModal, toastNotification } from '../../../redux/actions/uiAction';
|
||||||
|
import { MediaItem } from '../../ecency/ecency.types';
|
||||||
|
import {
|
||||||
|
deleteVideo,
|
||||||
|
getAllVideoStatuses,
|
||||||
|
markAsPublished,
|
||||||
|
updateSpeakVideoInfo,
|
||||||
|
} from '../../speak/speak';
|
||||||
|
import QUERIES from '../queryKeys';
|
||||||
|
import { extract3SpeakIds } from '../../../utils/editor';
|
||||||
|
import { ThreeSpeakStatus, ThreeSpeakVideo } from '../../speak/speak.types';
|
||||||
|
import bugsnapInstance from '../../../config/bugsnag';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetches and caches speak video uploads
|
||||||
|
* @returns query instance with data as array of videos as MediaItem[]
|
||||||
|
*/
|
||||||
|
export const useVideoUploadsQuery = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||||
|
const pinHash = useAppSelector((state) => state.application.pin);
|
||||||
|
|
||||||
|
const _fetchVideoUploads = async () => getAllVideoStatuses(currentAccount, pinHash);
|
||||||
|
|
||||||
|
const _setRefetchInterval = (data: MediaItem[] | undefined) => {
|
||||||
|
if (data) {
|
||||||
|
const hasPendingItem = data.find(
|
||||||
|
(item) =>
|
||||||
|
item.speakData?.status === ThreeSpeakStatus.PREPARING ||
|
||||||
|
item.speakData?.status === ThreeSpeakStatus.ENCODING,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasPendingItem) {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useQuery<MediaItem[]>([QUERIES.MEDIA.GET_VIDEOS], _fetchVideoUploads, {
|
||||||
|
initialData: [],
|
||||||
|
refetchInterval: _setRefetchInterval,
|
||||||
|
onError: () => {
|
||||||
|
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSpeakContentBuilder = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const videoUploads = useVideoUploadsQuery();
|
||||||
|
const videoPublishMetaRef = useRef<ThreeSpeakVideo | null>(null);
|
||||||
|
const thumbUrlsRef = useRef<string[]>([]);
|
||||||
|
|
||||||
|
const build = (body: string) => {
|
||||||
|
let _newBody = body;
|
||||||
|
videoPublishMetaRef.current = null;
|
||||||
|
thumbUrlsRef.current = [];
|
||||||
|
const _ids = extract3SpeakIds({ body });
|
||||||
|
const thumbUrls: string[] = [];
|
||||||
|
|
||||||
|
_ids.forEach((id) => {
|
||||||
|
const mediaItem: MediaItem | undefined = videoUploads.data.find((item) => item._id === id);
|
||||||
|
if (mediaItem) {
|
||||||
|
// check if video is unpublished, set unpublish video meta
|
||||||
|
if (mediaItem.speakData?.status !== ThreeSpeakStatus.PUBLISHED) {
|
||||||
|
if (!videoPublishMetaRef.current) {
|
||||||
|
videoPublishMetaRef.current = mediaItem.speakData;
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
showActionModal({
|
||||||
|
title: 'Fail',
|
||||||
|
body: 'Can have only one unpublished video per post',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
throw new Error('Fail');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace 3speak with actual data
|
||||||
|
const _toReplaceStr = `[3speak](${id})`;
|
||||||
|
const _replacement = `<center>[![](${mediaItem.thumbUrl})](${mediaItem.url})</center>`;
|
||||||
|
_newBody = _newBody.replace(_toReplaceStr, _replacement);
|
||||||
|
|
||||||
|
thumbUrls.push(mediaItem.thumbUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thumbUrlsRef.current = thumbUrls;
|
||||||
|
|
||||||
|
return _newBody;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
build,
|
||||||
|
videoPublishMetaRef,
|
||||||
|
thumbUrlsRef,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSpeakMutations = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||||
|
const pinCode = useAppSelector((state) => state.application.pin);
|
||||||
|
|
||||||
|
// mark as published mutations id is options, if no id is provided program marks all notifications as read;
|
||||||
|
const _mutationFn = async (id: string) => {
|
||||||
|
try {
|
||||||
|
const response = await markAsPublished(currentAccount, pinCode, id);
|
||||||
|
console.log('Speak video marked as published', response);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
bugsnapInstance.notify(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const _options: UseMutationOptions<number, unknown, string | undefined, void> = {
|
||||||
|
retry: 3,
|
||||||
|
delay: 5000,
|
||||||
|
onMutate: async (videoId) => {
|
||||||
|
// TODO: find a way to optimise mutations by avoiding too many loops
|
||||||
|
console.log('on mutate data', videoId);
|
||||||
|
|
||||||
|
// update query data
|
||||||
|
const videosCache: MediaItem[] | undefined = queryClient.getQueryData([
|
||||||
|
QUERIES.MEDIA.GET_VIDEOS,
|
||||||
|
]);
|
||||||
|
console.log('query data', videosCache);
|
||||||
|
|
||||||
|
if (!videosCache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _vidIndex = videosCache.findIndex((item) => item._id === videoId);
|
||||||
|
|
||||||
|
if (_vidIndex) {
|
||||||
|
const spkData = videosCache[_vidIndex].speakData;
|
||||||
|
if (spkData) {
|
||||||
|
spkData.status = ThreeSpeakStatus.PUBLISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient.setQueryData([QUERIES.MEDIA.GET_VIDEOS], videosCache);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess: async (status, _id) => {
|
||||||
|
console.log('on success data', status);
|
||||||
|
queryClient.invalidateQueries([QUERIES.MEDIA.GET_VIDEOS]);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// update info mutation
|
||||||
|
const _updateInfoMutationFn = async ({ id, title, body, tags }) => {
|
||||||
|
try {
|
||||||
|
// TODO: update information
|
||||||
|
const response = await updateSpeakVideoInfo(currentAccount, pinCode, body, id, title, tags);
|
||||||
|
console.log('Speak video marked as published', response);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
bugsnapInstance.notify(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const _updateInfoOptions = {
|
||||||
|
retry: 3,
|
||||||
|
onSuccess: async (status, _data) => {
|
||||||
|
console.log('on success data', status);
|
||||||
|
queryClient.invalidateQueries([QUERIES.MEDIA.GET_VIDEOS]);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// delete mutation
|
||||||
|
const _deleteMutationFn = async (permlinks: string[]) => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const i in permlinks) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await deleteVideo(currentAccount, pinCode, permlinks[i]);
|
||||||
|
}
|
||||||
|
console.log('deleted speak videos', permlinks);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
bugsnapInstance.notify(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const _deleteVideoOptions = {
|
||||||
|
retry: 3,
|
||||||
|
onSuccess: async (status, permlinks) => {
|
||||||
|
console.log('Success media deletion', status, permlinks);
|
||||||
|
const data: MediaItem[] | undefined = queryClient.getQueryData([QUERIES.MEDIA.GET_VIDEOS]);
|
||||||
|
if (data) {
|
||||||
|
const _newData = data.filter((item) => !permlinks.includes(item.speakData?.permlink));
|
||||||
|
queryClient.setQueryData([QUERIES.MEDIA.GET_VIDEOS], _newData);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient.invalidateQueries([QUERIES.MEDIA.GET_VIDEOS]);
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
console.warn('delete failing', err);
|
||||||
|
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// init mutations
|
||||||
|
const markAsPublishedMutation = useMutation(_mutationFn, _options);
|
||||||
|
const updateInfoMutation = useMutation(_updateInfoMutationFn, _updateInfoOptions);
|
||||||
|
const deleteVideoMutation = useMutation(_deleteMutationFn, _deleteVideoOptions);
|
||||||
|
|
||||||
|
return {
|
||||||
|
markAsPublishedMutation,
|
||||||
|
updateInfoMutation,
|
||||||
|
deleteVideoMutation,
|
||||||
|
};
|
||||||
|
};
|
@ -13,6 +13,7 @@ const QUERIES = {
|
|||||||
},
|
},
|
||||||
MEDIA: {
|
MEDIA: {
|
||||||
GET: 'QUERY_GET_UPLOADS',
|
GET: 'QUERY_GET_UPLOADS',
|
||||||
|
GET_VIDEOS: 'QUERY_GET_VIDEO_UPLOADS',
|
||||||
},
|
},
|
||||||
WALLET: {
|
WALLET: {
|
||||||
GET: 'QUERY_GET_ASSETS',
|
GET: 'QUERY_GET_ASSETS',
|
||||||
|
24
src/providers/speak/constants.ts
Normal file
24
src/providers/speak/constants.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export const BASE_URL_SPEAK_STUDIO = 'https://studio.3speak.tv';
|
||||||
|
|
||||||
|
export const BASE_URL_SPEAK_UPLOAD = 'https://uploads.3speak.tv/files';
|
||||||
|
|
||||||
|
export const BASE_URL_SPEAK_WATCH = 'https://3speak.tv/watch';
|
||||||
|
|
||||||
|
export const PATH_MOBILE = 'mobile';
|
||||||
|
export const PATH_LOGIN = 'login';
|
||||||
|
export const PATH_API = 'api';
|
||||||
|
|
||||||
|
export const DEFAULT_SPEAK_BENEFICIARIES = [
|
||||||
|
{
|
||||||
|
account: 'spk.beneficiary',
|
||||||
|
src: 'ENCODER_PAY',
|
||||||
|
weight: 900,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
account: 'threespeakleader',
|
||||||
|
src: 'ENCODER_PAY',
|
||||||
|
weight: 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BENEFICIARY_SRC_ENCODER = 'ENCODER_PAY';
|
13
src/providers/speak/converters.ts
Normal file
13
src/providers/speak/converters.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { MediaItem } from '../ecency/ecency.types';
|
||||||
|
import { BASE_URL_SPEAK_WATCH } from './constants';
|
||||||
|
|
||||||
|
export const convertVideoUpload = (data) => {
|
||||||
|
return {
|
||||||
|
_id: data._id,
|
||||||
|
url: `${BASE_URL_SPEAK_WATCH}?v=${data.owner}/${data.permlink}`,
|
||||||
|
thumbUrl: data.thumbUrl,
|
||||||
|
created: data.created,
|
||||||
|
timestamp: 0,
|
||||||
|
speakData: data,
|
||||||
|
} as MediaItem;
|
||||||
|
};
|
229
src/providers/speak/speak.ts
Normal file
229
src/providers/speak/speak.ts
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import hs from 'hivesigner';
|
||||||
|
import { Image, Video } from 'react-native-image-crop-picker';
|
||||||
|
import { Upload } from 'react-native-tus-client';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
import { getDigitPinCode } from '../hive/dhive';
|
||||||
|
import { ThreeSpeakVideo } from './speak.types';
|
||||||
|
import { decryptKey } from '../../utils/crypto';
|
||||||
|
import { convertVideoUpload } from './converters';
|
||||||
|
import { BASE_URL_SPEAK_STUDIO, PATH_API, PATH_LOGIN, PATH_MOBILE } from './constants';
|
||||||
|
|
||||||
|
const tusEndPoint = 'https://uploads.3speak.tv/files/';
|
||||||
|
|
||||||
|
const speakApi = axios.create({
|
||||||
|
baseURL: `${BASE_URL_SPEAK_STUDIO}/${PATH_MOBILE}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const threespeakAuth = async (currentAccount: any, pinHash: string) => {
|
||||||
|
try {
|
||||||
|
const response = await speakApi.get(
|
||||||
|
`${PATH_LOGIN}?username=${currentAccount.username}&hivesigner=true`,
|
||||||
|
{
|
||||||
|
withCredentials: false,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const memo_string = response.data.memo;
|
||||||
|
const memoDecoded = await getDecodedMemo(currentAccount.local, pinHash, memo_string);
|
||||||
|
|
||||||
|
return memoDecoded.replace('#', '');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(new Error('[3Speak auth] Failed to login'));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadVideoInfo = async (
|
||||||
|
currentAccount: any,
|
||||||
|
pinHash: string,
|
||||||
|
oFilename: string,
|
||||||
|
fileSize: number,
|
||||||
|
videoId: string,
|
||||||
|
thumbnailId: string,
|
||||||
|
duration: string,
|
||||||
|
) => {
|
||||||
|
const token = await threespeakAuth(currentAccount, pinHash);
|
||||||
|
try {
|
||||||
|
const { data } = await speakApi.post<ThreeSpeakVideo>(
|
||||||
|
`${PATH_API}/upload_info?app=ecency`,
|
||||||
|
{
|
||||||
|
filename: videoId,
|
||||||
|
oFilename,
|
||||||
|
size: fileSize,
|
||||||
|
duration,
|
||||||
|
thumbnail: thumbnailId,
|
||||||
|
isReel: false,
|
||||||
|
owner: currentAccount.username,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withCredentials: false,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllVideoStatuses = async (currentAccount: any, pinHash: string) => {
|
||||||
|
const token = await threespeakAuth(currentAccount, pinHash);
|
||||||
|
try {
|
||||||
|
const response = await speakApi.get<ThreeSpeakVideo[]>(`${PATH_API}/my-videos`, {
|
||||||
|
withCredentials: false,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mediaItems = response.data.map(convertVideoUpload);
|
||||||
|
|
||||||
|
return mediaItems;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(new Error('[3Speak video] Failed to get videos'));
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TOOD: use api during post publishing
|
||||||
|
export const updateSpeakVideoInfo = async (
|
||||||
|
currentAccount: any,
|
||||||
|
pinHash: string,
|
||||||
|
postBody: string,
|
||||||
|
videoId: string,
|
||||||
|
title: string,
|
||||||
|
tags: string[],
|
||||||
|
isNsfwC?: boolean,
|
||||||
|
) => {
|
||||||
|
const token = await threespeakAuth(currentAccount, pinHash);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
videoId,
|
||||||
|
title,
|
||||||
|
description: postBody,
|
||||||
|
isNsfwContent: isNsfwC || false,
|
||||||
|
tags_v2: tags,
|
||||||
|
};
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await speakApi.post(`${PATH_API}/update_info`, data, { headers });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const markAsPublished = async (currentAccount: any, pinHash: string, videoId: string) => {
|
||||||
|
const token = await threespeakAuth(currentAccount, pinHash);
|
||||||
|
const data = {
|
||||||
|
videoId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
speakApi
|
||||||
|
.post(`${PATH_API}/my-videos/iPublished`, data, { headers })
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 'https://studio.3speak.tv/mobile/api/video/${permlink}/delete
|
||||||
|
|
||||||
|
export const deleteVideo = async (currentAccount: any, pinHash: string, permlink: string) => {
|
||||||
|
const token = await threespeakAuth(currentAccount, pinHash);
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
speakApi
|
||||||
|
.get(`${PATH_API}/video/${permlink}/delete`, { headers })
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadFile = (media: Video | Image, onProgress?: (progress: number) => void) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const _path = Platform.select({
|
||||||
|
ios: media.path,
|
||||||
|
android: media.path.replace('file://', ''),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!_path) {
|
||||||
|
throw new Error('failed to create apporpriate path');
|
||||||
|
}
|
||||||
|
|
||||||
|
const upload = new Upload(_path, {
|
||||||
|
endpoint: tusEndPoint, // use your tus server endpoint instead
|
||||||
|
metadata: {
|
||||||
|
filename: media.filename || media.path.split('/').pop(),
|
||||||
|
filetype: media.mime,
|
||||||
|
},
|
||||||
|
onError: (error) => console.log('error', error),
|
||||||
|
onSuccess: () => {
|
||||||
|
console.log('Upload completed. File url:', upload.url);
|
||||||
|
const _videoId = upload.url.replace(tusEndPoint, '');
|
||||||
|
resolve(_videoId);
|
||||||
|
},
|
||||||
|
|
||||||
|
onProgress: (uploaded, total) => {
|
||||||
|
if (onProgress) {
|
||||||
|
onProgress(uploaded / total);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
upload.start();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Image upload failed', error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDecodedMemo = async (local, pinHash, encryptedMemo) => {
|
||||||
|
try {
|
||||||
|
const digitPinCode = getDigitPinCode(pinHash);
|
||||||
|
const token = decryptKey(local.accessToken, digitPinCode);
|
||||||
|
|
||||||
|
const client = new hs.Client({
|
||||||
|
accessToken: token,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { memoDecoded } = await client.decode(encryptedMemo);
|
||||||
|
|
||||||
|
if (!memoDecoded) {
|
||||||
|
throw new Error('Decode failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return memoDecoded;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to decode memo key', err);
|
||||||
|
}
|
||||||
|
};
|
60
src/providers/speak/speak.types.ts
Normal file
60
src/providers/speak/speak.types.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
export enum ThreeSpeakStatus {
|
||||||
|
PUBLISHED = 'published',
|
||||||
|
READY = 'publish_manual',
|
||||||
|
DELETED = 'deleted',
|
||||||
|
ENCODING = 'encoding_ipfs',
|
||||||
|
PREPARING = 'encoding_preparing',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThreeSpeakVideo {
|
||||||
|
app: string;
|
||||||
|
beneficiaries: string; // e.g. "[{\"account\":\"actifit-he\",\"weight\":100,\"src\":\"ENCODER_PAY\"}]"
|
||||||
|
category: string;
|
||||||
|
community: unknown | null;
|
||||||
|
created: string; // e.g. "2023-06-21T12:02:10.421Z"
|
||||||
|
declineRewards: boolean;
|
||||||
|
description: string;
|
||||||
|
donations: boolean;
|
||||||
|
duration: number;
|
||||||
|
encoding: Record<number, boolean>;
|
||||||
|
encodingProgress: number;
|
||||||
|
encoding_price_steem: string;
|
||||||
|
filename: string;
|
||||||
|
firstUpload: boolean;
|
||||||
|
fromMobile: boolean;
|
||||||
|
height: unknown;
|
||||||
|
hive: string;
|
||||||
|
indexed: boolean;
|
||||||
|
is3CJContent: boolean;
|
||||||
|
isNsfwContent: boolean;
|
||||||
|
isReel: boolean;
|
||||||
|
isVOD: boolean;
|
||||||
|
job_id: string;
|
||||||
|
language: string;
|
||||||
|
local_filename: string;
|
||||||
|
lowRc: boolean;
|
||||||
|
needsBlockchainUpdate: boolean;
|
||||||
|
originalFilename: string;
|
||||||
|
owner: string;
|
||||||
|
paid: boolean;
|
||||||
|
permlink: string;
|
||||||
|
postToHiveBlog: boolean;
|
||||||
|
publish_type: string;
|
||||||
|
reducedUpvote: boolean;
|
||||||
|
rewardPowerup: boolean;
|
||||||
|
size: number;
|
||||||
|
status: ThreeSpeakStatus;
|
||||||
|
tags_v2: unknown[];
|
||||||
|
thumbUrl: string;
|
||||||
|
thumbnail: string;
|
||||||
|
title: string;
|
||||||
|
updateSteem: boolean;
|
||||||
|
upload_type: string;
|
||||||
|
upvoteEligible: boolean;
|
||||||
|
video_v2: string;
|
||||||
|
views: number;
|
||||||
|
votePercent: number;
|
||||||
|
width: unknown;
|
||||||
|
__v: number;
|
||||||
|
_id: string;
|
||||||
|
}
|
@ -1,4 +1,8 @@
|
|||||||
import { SET_BENEFICIARIES, REMOVE_BENEFICIARIES } from '../constants/constants';
|
import {
|
||||||
|
SET_BENEFICIARIES,
|
||||||
|
REMOVE_BENEFICIARIES,
|
||||||
|
SET_ALLOW_SPK_PUBLISHING,
|
||||||
|
} from '../constants/constants';
|
||||||
import { Beneficiary } from '../reducers/editorReducer';
|
import { Beneficiary } from '../reducers/editorReducer';
|
||||||
|
|
||||||
export const setBeneficiaries = (draftId: string, benficiaries: Beneficiary[]) => ({
|
export const setBeneficiaries = (draftId: string, benficiaries: Beneficiary[]) => ({
|
||||||
@ -15,3 +19,8 @@ export const removeBeneficiaries = (draftId: string) => ({
|
|||||||
},
|
},
|
||||||
type: REMOVE_BENEFICIARIES,
|
type: REMOVE_BENEFICIARIES,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setAllowSpkPublishing = (allowSpkPublishing: boolean) => ({
|
||||||
|
payload: allowSpkPublishing,
|
||||||
|
type: SET_ALLOW_SPK_PUBLISHING,
|
||||||
|
});
|
||||||
|
@ -117,6 +117,7 @@ export const SET_OWN_PROFILE_TABS = 'SET_OWN_PROFILE_TABS';
|
|||||||
export const SET_BENEFICIARIES = 'SET_BENEFICIARIES';
|
export const SET_BENEFICIARIES = 'SET_BENEFICIARIES';
|
||||||
export const REMOVE_BENEFICIARIES = 'REMOVE_BENEFICIARIES';
|
export const REMOVE_BENEFICIARIES = 'REMOVE_BENEFICIARIES';
|
||||||
export const TEMP_BENEFICIARIES_ID = 'temp-beneficiaries';
|
export const TEMP_BENEFICIARIES_ID = 'temp-beneficiaries';
|
||||||
|
export const SET_ALLOW_SPK_PUBLISHING = 'SET_ALLOW_SPK_PUBLISHING';
|
||||||
|
|
||||||
// CACHE
|
// CACHE
|
||||||
export const PURGE_EXPIRED_CACHE = 'PURGE_EXPIRED_CACHE';
|
export const PURGE_EXPIRED_CACHE = 'PURGE_EXPIRED_CACHE';
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { REMOVE_BENEFICIARIES, SET_BENEFICIARIES } from '../constants/constants';
|
import {
|
||||||
|
REMOVE_BENEFICIARIES,
|
||||||
|
SET_BENEFICIARIES,
|
||||||
|
SET_ALLOW_SPK_PUBLISHING,
|
||||||
|
} from '../constants/constants';
|
||||||
|
|
||||||
export interface Beneficiary {
|
export interface Beneficiary {
|
||||||
account: string;
|
account: string;
|
||||||
@ -11,10 +15,12 @@ interface State {
|
|||||||
beneficiariesMap: {
|
beneficiariesMap: {
|
||||||
[key: string]: Beneficiary[];
|
[key: string]: Beneficiary[];
|
||||||
};
|
};
|
||||||
|
allowSpkPublishing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
beneficiariesMap: {},
|
beneficiariesMap: {},
|
||||||
|
allowSpkPublishing: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const editorReducer = (state = initialState, action) => {
|
const editorReducer = (state = initialState, action) => {
|
||||||
@ -30,6 +36,11 @@ const editorReducer = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...state, // spread operator in requried here, otherwise persist do not register change
|
...state, // spread operator in requried here, otherwise persist do not register change
|
||||||
};
|
};
|
||||||
|
case SET_ALLOW_SPK_PUBLISHING:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
allowSpkPublishing: payload,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import Animated, { FlipInEasyX } from 'react-native-reanimated';
|
import Animated, { FlipInEasyX } from 'react-native-reanimated';
|
||||||
@ -13,6 +13,9 @@ import {
|
|||||||
import styles from './postOptionsModalStyles';
|
import styles from './postOptionsModalStyles';
|
||||||
import ThumbSelectionContent from './thumbSelectionContent';
|
import ThumbSelectionContent from './thumbSelectionContent';
|
||||||
import PostDescription from './postDescription';
|
import PostDescription from './postDescription';
|
||||||
|
import { useSpeakContentBuilder } from '../../../providers/queries/editorQueries/speakQueries';
|
||||||
|
import { DEFAULT_SPEAK_BENEFICIARIES } from '../../../providers/speak/constants';
|
||||||
|
import { Beneficiary } from '../../../redux/reducers/editorReducer';
|
||||||
|
|
||||||
const REWARD_TYPES = [
|
const REWARD_TYPES = [
|
||||||
{
|
{
|
||||||
@ -71,6 +74,7 @@ const PostOptionsModal = forwardRef(
|
|||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const speakContentBuilder = useSpeakContentBuilder();
|
||||||
|
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [rewardTypeIndex, setRewardTypeIndex] = useState(0);
|
const [rewardTypeIndex, setRewardTypeIndex] = useState(0);
|
||||||
@ -79,8 +83,28 @@ const PostOptionsModal = forwardRef(
|
|||||||
const [scheduledFor, setScheduledFor] = useState('');
|
const [scheduledFor, setScheduledFor] = useState('');
|
||||||
const [disableDone, setDisableDone] = useState(false);
|
const [disableDone, setDisableDone] = useState(false);
|
||||||
|
|
||||||
// removed the useeffect causing index reset bug
|
const { encodingBeneficiaries, videoThumbUrls } = useMemo(() => {
|
||||||
|
let benefs: Beneficiary[] = [];
|
||||||
|
if (body && showModal) {
|
||||||
|
speakContentBuilder.build(body);
|
||||||
|
const unpublishedMeta = speakContentBuilder.videoPublishMetaRef.current;
|
||||||
|
if (unpublishedMeta) {
|
||||||
|
const vidBeneficiaries = JSON.parse(unpublishedMeta.beneficiaries || '[]');
|
||||||
|
benefs = [...DEFAULT_SPEAK_BENEFICIARIES, ...vidBeneficiaries];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
videoThumbUrls: speakContentBuilder.thumbUrlsRef.current,
|
||||||
|
encodingBeneficiaries: benefs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
videoThumbUrls: [],
|
||||||
|
encodingBeneficiaries: benefs,
|
||||||
|
};
|
||||||
|
}, [showModal, body]);
|
||||||
|
|
||||||
|
// removed the useeffect causing index reset bug
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!scheduleLater) {
|
if (!scheduleLater) {
|
||||||
handleScheduleChange(null);
|
handleScheduleChange(null);
|
||||||
@ -196,6 +220,7 @@ const PostOptionsModal = forwardRef(
|
|||||||
<ThumbSelectionContent
|
<ThumbSelectionContent
|
||||||
body={body}
|
body={body}
|
||||||
thumbUrl={thumbUrl}
|
thumbUrl={thumbUrl}
|
||||||
|
videoThumbUrls={videoThumbUrls}
|
||||||
isUploading={isUploading}
|
isUploading={isUploading}
|
||||||
onThumbSelection={_handleThumbIndexSelection}
|
onThumbSelection={_handleThumbIndexSelection}
|
||||||
/>
|
/>
|
||||||
@ -205,7 +230,11 @@ const PostOptionsModal = forwardRef(
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{!isEdit && (
|
{!isEdit && (
|
||||||
<BeneficiarySelectionContent draftId={draftId} setDisableDone={setDisableDone} />
|
<BeneficiarySelectionContent
|
||||||
|
draftId={draftId}
|
||||||
|
setDisableDone={setDisableDone}
|
||||||
|
encodingBeneficiaries={encodingBeneficiaries}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</KeyboardAwareScrollView>
|
</KeyboardAwareScrollView>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||||
import { getBottomSpace } from 'react-native-iphone-x-helper';
|
|
||||||
|
|
||||||
export default EStyleSheet.create({
|
export default EStyleSheet.create({
|
||||||
sheetContent: {
|
sheetContent: {
|
||||||
|
@ -13,6 +13,7 @@ import { Icon } from '../../../components';
|
|||||||
interface ThumbSelectionContentProps {
|
interface ThumbSelectionContentProps {
|
||||||
body: string;
|
body: string;
|
||||||
thumbUrl: string;
|
thumbUrl: string;
|
||||||
|
videoThumbUrls: string[];
|
||||||
isUploading: boolean;
|
isUploading: boolean;
|
||||||
onThumbSelection: (url: string) => void;
|
onThumbSelection: (url: string) => void;
|
||||||
}
|
}
|
||||||
@ -20,6 +21,7 @@ interface ThumbSelectionContentProps {
|
|||||||
const ThumbSelectionContent = ({
|
const ThumbSelectionContent = ({
|
||||||
body,
|
body,
|
||||||
thumbUrl,
|
thumbUrl,
|
||||||
|
videoThumbUrls,
|
||||||
onThumbSelection,
|
onThumbSelection,
|
||||||
isUploading,
|
isUploading,
|
||||||
}: ThumbSelectionContentProps) => {
|
}: ThumbSelectionContentProps) => {
|
||||||
@ -30,7 +32,7 @@ const ThumbSelectionContent = ({
|
|||||||
const [thumbIndex, setThumbIndex] = useState(0);
|
const [thumbIndex, setThumbIndex] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const urls = extractImageUrls({ body });
|
const urls = [...extractImageUrls({ body }), ...videoThumbUrls];
|
||||||
|
|
||||||
if (urls.length < 2) {
|
if (urls.length < 2) {
|
||||||
setNeedMore(true);
|
setNeedMore(true);
|
||||||
|
@ -10,8 +10,9 @@ import { isArray } from 'lodash';
|
|||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||||
|
import { postBodySummary } from '@ecency/render-helper';
|
||||||
import { addDraft, updateDraft, getDrafts, addSchedule } from '../../../providers/ecency/ecency';
|
import { addDraft, updateDraft, getDrafts, addSchedule } from '../../../providers/ecency/ecency';
|
||||||
import { toastNotification, setRcOffer } from '../../../redux/actions/uiAction';
|
import { toastNotification, setRcOffer, showActionModal } from '../../../redux/actions/uiAction';
|
||||||
import {
|
import {
|
||||||
postContent,
|
postContent,
|
||||||
getPurePost,
|
getPurePost,
|
||||||
@ -32,12 +33,16 @@ import {
|
|||||||
extractMetadata,
|
extractMetadata,
|
||||||
makeJsonMetadataForUpdate,
|
makeJsonMetadataForUpdate,
|
||||||
createPatch,
|
createPatch,
|
||||||
|
extract3SpeakIds,
|
||||||
} from '../../../utils/editor';
|
} from '../../../utils/editor';
|
||||||
// import { generateSignature } from '../../../utils/image';
|
|
||||||
|
|
||||||
// Component
|
// Component
|
||||||
import EditorScreen from '../screen/editorScreen';
|
import EditorScreen from '../screen/editorScreen';
|
||||||
import { removeBeneficiaries, setBeneficiaries } from '../../../redux/actions/editorActions';
|
import {
|
||||||
|
removeBeneficiaries,
|
||||||
|
setAllowSpkPublishing,
|
||||||
|
setBeneficiaries,
|
||||||
|
} from '../../../redux/actions/editorActions';
|
||||||
import { DEFAULT_USER_DRAFT_ID, TEMP_BENEFICIARIES_ID } from '../../../redux/constants/constants';
|
import { DEFAULT_USER_DRAFT_ID, TEMP_BENEFICIARIES_ID } from '../../../redux/constants/constants';
|
||||||
import {
|
import {
|
||||||
deleteDraftCacheEntry,
|
deleteDraftCacheEntry,
|
||||||
@ -50,7 +55,12 @@ import { useUserActivityMutation } from '../../../providers/queries/pointQueries
|
|||||||
import { PointActivityIds } from '../../../providers/ecency/ecency.types';
|
import { PointActivityIds } from '../../../providers/ecency/ecency.types';
|
||||||
import { usePostsCachePrimer } from '../../../providers/queries/postQueries/postQueries';
|
import { usePostsCachePrimer } from '../../../providers/queries/postQueries/postQueries';
|
||||||
import { PostTypes } from '../../../constants/postTypes';
|
import { PostTypes } from '../../../constants/postTypes';
|
||||||
import { postBodySummary } from '@ecency/render-helper';
|
import { speakQueries } from '../../../providers/queries';
|
||||||
|
import {
|
||||||
|
BENEFICIARY_SRC_ENCODER,
|
||||||
|
DEFAULT_SPEAK_BENEFICIARIES,
|
||||||
|
} from '../../../providers/speak/constants';
|
||||||
|
import { ThreeSpeakVideo } from '../../../providers/speak/speak.types';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Props Name Description Value
|
* Props Name Description Value
|
||||||
@ -96,7 +106,7 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
// Component Life Cycle Functions
|
// Component Life Cycle Functions
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
const { currentAccount, route, queryClient } = this.props;
|
const { currentAccount, route, queryClient, dispatch } = this.props;
|
||||||
const username = currentAccount && currentAccount.name ? currentAccount.name : '';
|
const username = currentAccount && currentAccount.name ? currentAccount.name : '';
|
||||||
let isReply;
|
let isReply;
|
||||||
let draftId;
|
let draftId;
|
||||||
@ -192,6 +202,9 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
this._requestKeyboardFocus();
|
this._requestKeyboardFocus();
|
||||||
|
|
||||||
this._appStateSub = AppState.addEventListener('change', this._handleAppStateChange);
|
this._appStateSub = AppState.addEventListener('change', this._handleAppStateChange);
|
||||||
|
|
||||||
|
// dispatch spk publishing status
|
||||||
|
dispatch(setAllowSpkPublishing(!isReply && !isEdit));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<any>, prevState: Readonly<any>): void {
|
componentDidUpdate(prevProps: Readonly<any>, prevState: Readonly<any>): void {
|
||||||
@ -305,6 +318,7 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
const filteredBeneficiaries = draft.meta.beneficiaries.filter(
|
const filteredBeneficiaries = draft.meta.beneficiaries.filter(
|
||||||
(item) => item.account !== currentAccount.username,
|
(item) => item.account !== currentAccount.username,
|
||||||
); // remove default beneficiary from array while saving
|
); // remove default beneficiary from array while saving
|
||||||
|
|
||||||
dispatch(setBeneficiaries(draft._id || TEMP_BENEFICIARIES_ID, filteredBeneficiaries));
|
dispatch(setBeneficiaries(draft._id || TEMP_BENEFICIARIES_ID, filteredBeneficiaries));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,18 +412,14 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
|
|
||||||
_extractBeneficiaries = () => {
|
_extractBeneficiaries = () => {
|
||||||
const { draftId } = this.state;
|
const { draftId } = this.state;
|
||||||
const { beneficiariesMap, currentAccount } = this.props;
|
const { beneficiariesMap } = this.props;
|
||||||
|
|
||||||
return (
|
return beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID] || [];
|
||||||
beneficiariesMap[draftId || TEMP_BENEFICIARIES_ID] || [
|
|
||||||
{ account: currentAccount.name, weight: 10000 },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_saveDraftToDB = async (fields, saveAsNew = false) => {
|
_saveDraftToDB = async (fields, saveAsNew = false) => {
|
||||||
const { isDraftSaved, draftId, thumbUrl, isReply, rewardType, postDescription } = this.state;
|
const { isDraftSaved, draftId, thumbUrl, isReply, rewardType, postDescription } = this.state;
|
||||||
const { currentAccount, dispatch, intl, queryClient } = this.props;
|
const { currentAccount, dispatch, intl, queryClient, speakContentBuilder } = this.props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// saves draft locallly
|
// saves draft locallly
|
||||||
@ -423,6 +433,8 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
speakContentBuilder.build(fields.body);
|
||||||
|
|
||||||
const beneficiaries = this._extractBeneficiaries();
|
const beneficiaries = this._extractBeneficiaries();
|
||||||
const postBodySummaryContent = postBodySummary(
|
const postBodySummaryContent = postBodySummary(
|
||||||
get(fields, 'body', ''),
|
get(fields, 'body', ''),
|
||||||
@ -450,13 +462,28 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
const _extractedMeta = await extractMetadata({
|
const _extractedMeta = await extractMetadata({
|
||||||
body: draftField.body,
|
body: draftField.body,
|
||||||
thumbUrl,
|
thumbUrl,
|
||||||
|
videoThumbUrls: speakContentBuilder.thumbUrlsRef.current,
|
||||||
fetchRatios: false,
|
fetchRatios: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// inject video meta for draft
|
||||||
|
const speakIds = extract3SpeakIds({ body: draftField.body });
|
||||||
|
const videos: any = {};
|
||||||
|
const videosCache: any = queryClient.getQueryData([QUERIES.MEDIA.GET_VIDEOS]);
|
||||||
|
|
||||||
|
speakIds.forEach((_id) => {
|
||||||
|
const videoItem = videosCache.find((item) => item._id === _id);
|
||||||
|
if (videoItem?.speakData) {
|
||||||
|
videos[_id] = videoItem.speakData;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const meta = Object.assign({}, _extractedMeta, {
|
const meta = Object.assign({}, _extractedMeta, {
|
||||||
tags: draftField.tags,
|
tags: draftField.tags,
|
||||||
beneficiaries,
|
beneficiaries,
|
||||||
rewardType,
|
rewardType,
|
||||||
description: postDescription ? postDescription : postBodySummaryContent,
|
description: postDescription || postBodySummaryContent,
|
||||||
|
videos: Object.keys(videos).length > 0 && videos,
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonMeta = makeJsonMetadata(meta, draftField.tags);
|
const jsonMeta = makeJsonMetadata(meta, draftField.tags);
|
||||||
@ -574,7 +601,13 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_submitPost = async ({ fields, scheduleDate }: { fields: any; scheduleDate?: string }) => {
|
_submitPost = async ({
|
||||||
|
fields: _fieldsBase,
|
||||||
|
scheduleDate,
|
||||||
|
}: {
|
||||||
|
fields: any;
|
||||||
|
scheduleDate?: string;
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
currentAccount,
|
currentAccount,
|
||||||
dispatch,
|
dispatch,
|
||||||
@ -582,27 +615,69 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
navigation,
|
navigation,
|
||||||
pinCode,
|
pinCode,
|
||||||
userActivityMutation,
|
userActivityMutation,
|
||||||
// isDefaultFooter,
|
speakContentBuilder,
|
||||||
|
speakMutations,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { rewardType, isPostSending, thumbUrl, draftId, shouldReblog } = this.state;
|
const { rewardType, isPostSending, thumbUrl, draftId, shouldReblog } = this.state;
|
||||||
|
|
||||||
const beneficiaries = this._extractBeneficiaries();
|
const fields = Object.assign({}, _fieldsBase);
|
||||||
|
let beneficiaries = this._extractBeneficiaries();
|
||||||
|
let videoPublishMeta: ThreeSpeakVideo | undefined = undefined;
|
||||||
|
|
||||||
if (isPostSending) {
|
if (isPostSending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentAccount) {
|
if (currentAccount) {
|
||||||
|
// build speak video body
|
||||||
|
try {
|
||||||
|
fields.body = speakContentBuilder.build(fields.body);
|
||||||
|
videoPublishMeta = speakContentBuilder.videoPublishMetaRef.current;
|
||||||
|
|
||||||
|
// verify and make video beneficiaries redundent
|
||||||
|
beneficiaries = beneficiaries.filter((item) => item.src !== BENEFICIARY_SRC_ENCODER);
|
||||||
|
if (videoPublishMeta) {
|
||||||
|
const encoderBene = [
|
||||||
|
...JSON.parse(videoPublishMeta.beneficiaries || '[]'),
|
||||||
|
...DEFAULT_SPEAK_BENEFICIARIES,
|
||||||
|
];
|
||||||
|
beneficiaries = [...encoderBene, ...beneficiaries];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('fail', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduleDate && videoPublishMeta) {
|
||||||
|
dispatch(
|
||||||
|
showActionModal({
|
||||||
|
title: intl.formatMessage({ id: 'alert.notice' }),
|
||||||
|
body: intl.formatMessage({ id: 'editor.schedule_video_unsupported' }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isPostSending: true,
|
isPostSending: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const meta = await extractMetadata({ body: fields.body, thumbUrl, fetchRatios: true });
|
// only require video meta for unpublished video, it will always be one
|
||||||
|
const meta = await extractMetadata({
|
||||||
|
body: fields.body,
|
||||||
|
thumbUrl,
|
||||||
|
videoThumbUrls: speakContentBuilder.thumbUrlsRef.current,
|
||||||
|
fetchRatios: true,
|
||||||
|
videoPublishMeta,
|
||||||
|
});
|
||||||
const _tags = fields.tags.filter((tag) => tag && tag !== ' ');
|
const _tags = fields.tags.filter((tag) => tag && tag !== ' ');
|
||||||
|
|
||||||
const jsonMeta = makeJsonMetadata(meta, _tags);
|
const jsonMeta = makeJsonMetadata(meta, _tags);
|
||||||
|
|
||||||
// TODO: check if permlink is available github: #314 https://github.com/ecency/ecency-mobile/pull/314
|
// TODO: check if permlink is available github: #314 https://github.com/ecency/ecency-mobile/pull/314
|
||||||
let permlink = generatePermlink(fields.title || '');
|
let permlink = videoPublishMeta
|
||||||
|
? videoPublishMeta.permlink
|
||||||
|
: generatePermlink(fields.title || '');
|
||||||
|
|
||||||
let dublicatePost;
|
let dublicatePost;
|
||||||
try {
|
try {
|
||||||
@ -629,6 +704,7 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
if (fields.tags.length === 0) {
|
if (fields.tags.length === 0) {
|
||||||
fields.tags = ['hive-125125'];
|
fields.tags = ['hive-125125'];
|
||||||
}
|
}
|
||||||
|
|
||||||
this._setScheduledPost({
|
this._setScheduledPost({
|
||||||
author,
|
author,
|
||||||
permlink,
|
permlink,
|
||||||
@ -651,6 +727,7 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
|
||||||
// track user activity for points
|
// track user activity for points
|
||||||
userActivityMutation.mutate({
|
userActivityMutation.mutate({
|
||||||
pointsTy: PointActivityIds.POST,
|
pointsTy: PointActivityIds.POST,
|
||||||
@ -673,6 +750,18 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mark unpublished video as published on 3speak if that is the case
|
||||||
|
if (videoPublishMeta) {
|
||||||
|
console.log('marking inserted video as published');
|
||||||
|
speakMutations.updateInfoMutation.mutate({
|
||||||
|
id: videoPublishMeta._id,
|
||||||
|
title: fields.title,
|
||||||
|
body: fields.body,
|
||||||
|
tags: fields.tags,
|
||||||
|
});
|
||||||
|
speakMutations.markAsPublishedMutation.mutate(videoPublishMeta._id);
|
||||||
|
}
|
||||||
|
|
||||||
// post publish updates
|
// post publish updates
|
||||||
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name));
|
dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name));
|
||||||
|
|
||||||
@ -711,8 +800,14 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_submitReply = async (fields) => {
|
_submitReply = async (fields) => {
|
||||||
const { currentAccount, pinCode, dispatch, userActivityMutation, draftsCollection } =
|
const {
|
||||||
this.props;
|
currentAccount,
|
||||||
|
pinCode,
|
||||||
|
dispatch,
|
||||||
|
userActivityMutation,
|
||||||
|
draftsCollection,
|
||||||
|
speakContentBuilder,
|
||||||
|
} = this.props;
|
||||||
const { isPostSending } = this.state;
|
const { isPostSending } = this.state;
|
||||||
|
|
||||||
if (isPostSending) {
|
if (isPostSending) {
|
||||||
@ -726,6 +821,8 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
|
|
||||||
const { post } = this.state;
|
const { post } = this.state;
|
||||||
|
|
||||||
|
fields.body = speakContentBuilder.build(fields.body);
|
||||||
|
|
||||||
const _prefix = `re-${post.author.replace(/\./g, '')}`;
|
const _prefix = `re-${post.author.replace(/\./g, '')}`;
|
||||||
const permlink = generateUniquePermlink(_prefix);
|
const permlink = generateUniquePermlink(_prefix);
|
||||||
|
|
||||||
@ -790,7 +887,7 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_submitEdit = async (fields) => {
|
_submitEdit = async (fields) => {
|
||||||
const { currentAccount, pinCode, dispatch, postCachePrimer } = this.props;
|
const { currentAccount, pinCode, dispatch, postCachePrimer, speakContentBuilder } = this.props;
|
||||||
const { post, isEdit, isPostSending, thumbUrl, isReply } = this.state;
|
const { post, isEdit, isPostSending, thumbUrl, isReply } = this.state;
|
||||||
|
|
||||||
if (isPostSending) {
|
if (isPostSending) {
|
||||||
@ -801,6 +898,10 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
isPostSending: true,
|
isPostSending: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// build speak video body
|
||||||
|
fields.body = speakContentBuilder.build(fields.body);
|
||||||
|
|
||||||
const { tags, body, title } = fields;
|
const { tags, body, title } = fields;
|
||||||
const {
|
const {
|
||||||
markdownBody: oldBody,
|
markdownBody: oldBody,
|
||||||
@ -817,7 +918,12 @@ class EditorContainer extends Component<EditorContainerProps, any> {
|
|||||||
newBody = patch;
|
newBody = patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = await extractMetadata({ body: fields.body, thumbUrl, fetchRatios: true });
|
const meta = await extractMetadata({
|
||||||
|
body: fields.body,
|
||||||
|
videoThumbUrls: speakContentBuilder.thumbUrlsRef.current,
|
||||||
|
thumbUrl,
|
||||||
|
fetchRatios: true,
|
||||||
|
});
|
||||||
|
|
||||||
let jsonMeta = {};
|
let jsonMeta = {};
|
||||||
|
|
||||||
@ -1216,6 +1322,8 @@ const mapStateToProps = (state) => ({
|
|||||||
|
|
||||||
const mapQueriesToProps = () => ({
|
const mapQueriesToProps = () => ({
|
||||||
queryClient: useQueryClient(),
|
queryClient: useQueryClient(),
|
||||||
|
speakContentBuilder: speakQueries.useSpeakContentBuilder(),
|
||||||
|
speakMutations: speakQueries.useSpeakMutations(),
|
||||||
userActivityMutation: useUserActivityMutation(),
|
userActivityMutation: useUserActivityMutation(),
|
||||||
postCachePrimer: usePostsCachePrimer(),
|
postCachePrimer: usePostsCachePrimer(),
|
||||||
});
|
});
|
||||||
|
@ -495,6 +495,7 @@ class EditorScreen extends Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
|
draftId={draftId}
|
||||||
paramFiles={paramFiles}
|
paramFiles={paramFiles}
|
||||||
componentID="body"
|
componentID="body"
|
||||||
draftBody={fields && fields.body}
|
draftBody={fields && fields.body}
|
||||||
|
@ -4,6 +4,7 @@ import { Image } from 'react-native';
|
|||||||
import VersionNumber from 'react-native-version-number';
|
import VersionNumber from 'react-native-version-number';
|
||||||
import getSlug from 'speakingurl';
|
import getSlug from 'speakingurl';
|
||||||
import { PostTypes } from '../constants/postTypes';
|
import { PostTypes } from '../constants/postTypes';
|
||||||
|
import { ThreeSpeakVideo } from '../providers/speak/speak.types';
|
||||||
|
|
||||||
export const getWordsCount = (text) =>
|
export const getWordsCount = (text) =>
|
||||||
text && typeof text === 'string' ? text.replace(/^\s+|\s+$/g, '').split(/\s+/).length : 0;
|
text && typeof text === 'string' ? text.replace(/^\s+|\s+$/g, '').split(/\s+/).length : 0;
|
||||||
@ -185,6 +186,20 @@ export const extractImageUrls = ({ body, urls }: { body?: string; urls?: string[
|
|||||||
return imgUrls;
|
return imgUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extract3SpeakIds = ({ body }) => {
|
||||||
|
if (!body) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = /\[3speak]\((.*?)\)/g;
|
||||||
|
const matches = [...body.matchAll(regex)];
|
||||||
|
|
||||||
|
const ids = matches.map((match) => match[1]);
|
||||||
|
console.log(ids);
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
export const extractFilenameFromPath = ({
|
export const extractFilenameFromPath = ({
|
||||||
path,
|
path,
|
||||||
mimeType,
|
mimeType,
|
||||||
@ -214,20 +229,24 @@ export const extractFilenameFromPath = ({
|
|||||||
export const extractMetadata = async ({
|
export const extractMetadata = async ({
|
||||||
body,
|
body,
|
||||||
thumbUrl,
|
thumbUrl,
|
||||||
|
videoThumbUrls,
|
||||||
fetchRatios,
|
fetchRatios,
|
||||||
postType,
|
postType,
|
||||||
|
videoPublishMeta,
|
||||||
}: {
|
}: {
|
||||||
body: string;
|
body: string;
|
||||||
thumbUrl?: string;
|
thumbUrl?: string;
|
||||||
|
videoThumbUrls: string[];
|
||||||
fetchRatios?: boolean;
|
fetchRatios?: boolean;
|
||||||
postType?: PostTypes;
|
postType?: PostTypes;
|
||||||
|
videoPublishMeta?: ThreeSpeakVideo;
|
||||||
}) => {
|
}) => {
|
||||||
// NOTE: keepting regex to extract usernames as reference for later usage if any
|
// NOTE: keepting regex to extract usernames as reference for later usage if any
|
||||||
// const userReg = /(^|\s)(@[a-z][-.a-z\d]+[a-z\d])/gim;
|
// const userReg = /(^|\s)(@[a-z][-.a-z\d]+[a-z\d])/gim;
|
||||||
|
|
||||||
const out = {};
|
const out: any = {};
|
||||||
const mUrls = extractUrls(body);
|
const mUrls = extractUrls(body);
|
||||||
const matchedImages = extractImageUrls({ urls: mUrls });
|
const matchedImages = [...extractImageUrls({ urls: mUrls }), ...(videoThumbUrls || [])];
|
||||||
|
|
||||||
if (matchedImages.length) {
|
if (matchedImages.length) {
|
||||||
if (thumbUrl) {
|
if (thumbUrl) {
|
||||||
@ -257,6 +276,41 @@ export const extractMetadata = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert three speak meta
|
||||||
|
if (videoPublishMeta) {
|
||||||
|
out.video = {
|
||||||
|
info: {
|
||||||
|
platform: '3speak',
|
||||||
|
title: videoPublishMeta.title,
|
||||||
|
author: videoPublishMeta.owner,
|
||||||
|
permlink: videoPublishMeta.permlink,
|
||||||
|
duration: videoPublishMeta.duration,
|
||||||
|
filesize: videoPublishMeta.size,
|
||||||
|
file: videoPublishMeta.filename,
|
||||||
|
lang: videoPublishMeta.language,
|
||||||
|
firstUpload: videoPublishMeta.firstUpload,
|
||||||
|
ipfs: null,
|
||||||
|
ipfsThumbnail: null,
|
||||||
|
video_v2: videoPublishMeta.video_v2,
|
||||||
|
sourceMap: [
|
||||||
|
{
|
||||||
|
type: 'video',
|
||||||
|
url: videoPublishMeta.video_v2,
|
||||||
|
format: 'm3u8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'thumbnail',
|
||||||
|
url: videoPublishMeta.thumbUrl,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
description: videoPublishMeta.description,
|
||||||
|
tags: videoPublishMeta.tags_v2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// setting post type, primary usecase for separating waves from other posts
|
// setting post type, primary usecase for separating waves from other posts
|
||||||
out.type = postType || PostTypes.POST;
|
out.type = postType || PostTypes.POST;
|
||||||
|
|
||||||
|
35
yarn.lock
35
yarn.lock
@ -5073,6 +5073,11 @@ flatted@^3.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
|
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
|
||||||
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
|
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
|
||||||
|
|
||||||
|
flatted@^3.2.4:
|
||||||
|
version "3.2.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf"
|
||||||
|
integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==
|
||||||
|
|
||||||
flow-parser@0.*:
|
flow-parser@0.*:
|
||||||
version "0.201.0"
|
version "0.201.0"
|
||||||
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.201.0.tgz#d2005d4dae6fddf60d30f9ae0fb49a13c9c51cfe"
|
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.201.0.tgz#d2005d4dae6fddf60d30f9ae0fb49a13c9c51cfe"
|
||||||
@ -5551,10 +5556,10 @@ hive-uri@^0.2.5:
|
|||||||
resolved "https://registry.yarnpkg.com/hive-uri/-/hive-uri-0.2.5.tgz#11f94d43b87d22c2a9c018e6ed41d303d3e0c770"
|
resolved "https://registry.yarnpkg.com/hive-uri/-/hive-uri-0.2.5.tgz#11f94d43b87d22c2a9c018e6ed41d303d3e0c770"
|
||||||
integrity sha512-oSFqa39/Frs8XaA6oAT1W/5KtkInfp8u0Yv58YGok1klu7M8rPYMg3v9m/0rdvrRLSheAYwip88l60at2kPtYA==
|
integrity sha512-oSFqa39/Frs8XaA6oAT1W/5KtkInfp8u0Yv58YGok1klu7M8rPYMg3v9m/0rdvrRLSheAYwip88l60at2kPtYA==
|
||||||
|
|
||||||
hivesigner@^3.2.7:
|
hivesigner@^3.3.4:
|
||||||
version "3.2.9"
|
version "3.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/hivesigner/-/hivesigner-3.2.9.tgz#c0b670d7c47fe04516b8986fbe2c8b5f645eaaeb"
|
resolved "https://registry.yarnpkg.com/hivesigner/-/hivesigner-3.3.4.tgz#5ab19f25821eacb6683d86ee098e3868f028497e"
|
||||||
integrity sha512-VHJTW4FD6O8ZBdFWDkOLOnC2s5rjIOoDK9Pz4RK+mbs9/l1AyF2K6Z+bVZGzSluRsc7fjThweLg4EuPEVbEQYw==
|
integrity sha512-n+fpsmc3FvkwXwhSTHJgChq97YeL2bEcJz/Awn9o+boFnIxqrxg4Y3i4sMMR8j+kJnwDV9c5JIMF4zQVxPSPJQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.9"
|
"@babel/runtime" "^7.13.9"
|
||||||
cross-fetch "^3.0.6"
|
cross-fetch "^3.0.6"
|
||||||
@ -8926,6 +8931,11 @@ react-native-background-timer@^2.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz#a3bc1cafa8c1e3aeefd0611de120298b67978a0f"
|
resolved "https://registry.yarnpkg.com/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz#a3bc1cafa8c1e3aeefd0611de120298b67978a0f"
|
||||||
integrity sha512-TE4Kiy7jUyv+hugxDxitzu38sW1NqjCk4uE5IgU2WevLv7sZacaBc6PZKOShNRPGirLl1NWkaG3LDEkdb9Um5g==
|
integrity sha512-TE4Kiy7jUyv+hugxDxitzu38sW1NqjCk4uE5IgU2WevLv7sZacaBc6PZKOShNRPGirLl1NWkaG3LDEkdb9Um5g==
|
||||||
|
|
||||||
|
react-native-background-upload@^6.6.0:
|
||||||
|
version "6.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-background-upload/-/react-native-background-upload-6.6.0.tgz#7c081f4260d16e7a416522c3622b81938ad799c8"
|
||||||
|
integrity sha512-adfOJmeO3GmPmc53cdHYWp5eTGKagk/AhkraSoQ89BHRwBWrtRQ29NN9sm2CLezBYrSQWg0W47C8TaowtYZ1LQ==
|
||||||
|
|
||||||
react-native-blob-util@^0.16.0:
|
react-native-blob-util@^0.16.0:
|
||||||
version "0.16.4"
|
version "0.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-blob-util/-/react-native-blob-util-0.16.4.tgz#eeb0e28f6fa6ecb357c10f154be3d2e66c010f62"
|
resolved "https://registry.yarnpkg.com/react-native-blob-util/-/react-native-blob-util-0.16.4.tgz#eeb0e28f6fa6ecb357c10f154be3d2e66c010f62"
|
||||||
@ -8984,6 +8994,11 @@ react-native-config@^1.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-native-config/-/react-native-config-1.5.1.tgz#73c94f511493e9b7ff9350cdf351d203a1b05acc"
|
resolved "https://registry.yarnpkg.com/react-native-config/-/react-native-config-1.5.1.tgz#73c94f511493e9b7ff9350cdf351d203a1b05acc"
|
||||||
integrity sha512-g1xNgt1tV95FCX+iWz6YJonxXkQX0GdD3fB8xQtR1GUBEqweB9zMROW77gi2TygmYmUkBI7LU4pES+zcTyK4HA==
|
integrity sha512-g1xNgt1tV95FCX+iWz6YJonxXkQX0GdD3fB8xQtR1GUBEqweB9zMROW77gi2TygmYmUkBI7LU4pES+zcTyK4HA==
|
||||||
|
|
||||||
|
react-native-create-thumbnail@^1.6.4:
|
||||||
|
version "1.6.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-create-thumbnail/-/react-native-create-thumbnail-1.6.4.tgz#90f5b0a587de6e3738a7632fe3d9a9624ed83581"
|
||||||
|
integrity sha512-JWuKXswDXtqUPfuqh6rjCVMvTSSG3kUtwvSK/YdaNU0i+nZKxeqHmt/CO2+TyI/WSUFynGVmWT1xOHhCZAFsRQ==
|
||||||
|
|
||||||
react-native-crypto-js@^1.0.0:
|
react-native-crypto-js@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-crypto-js/-/react-native-crypto-js-1.0.0.tgz#e677e022e147f41b35614416c92d655f87e2450a"
|
resolved "https://registry.yarnpkg.com/react-native-crypto-js/-/react-native-crypto-js-1.0.0.tgz#e677e022e147f41b35614416c92d655f87e2450a"
|
||||||
@ -9348,6 +9363,11 @@ react-native-tcp@^4.0.0:
|
|||||||
process "^0.11.9"
|
process "^0.11.9"
|
||||||
util "^0.10.3"
|
util "^0.10.3"
|
||||||
|
|
||||||
|
react-native-tus-client@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-tus-client/-/react-native-tus-client-1.1.0.tgz#5d1ba7e79a1b0e131426cb9ae0a502fb695730c4"
|
||||||
|
integrity sha512-rs5/JNbCWLoQZE3x/faa6+mSXAz21q75lLGC5yyJe6L4jylhBHNbJUsapvnpo7mbcOMvuai0aoTcSzGyqQ9kVQ==
|
||||||
|
|
||||||
react-native-udp@^4.1.4:
|
react-native-udp@^4.1.4:
|
||||||
version "4.1.7"
|
version "4.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-udp/-/react-native-udp-4.1.7.tgz#9fa90b772b44c991605e8191444dd2ca3259cb58"
|
resolved "https://registry.yarnpkg.com/react-native-udp/-/react-native-udp-4.1.7.tgz#9fa90b772b44c991605e8191444dd2ca3259cb58"
|
||||||
@ -9467,6 +9487,13 @@ react-navigation-redux-helpers@^4.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
invariant "^2.2.2"
|
invariant "^2.2.2"
|
||||||
|
|
||||||
|
react-query-native-devtools@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-query-native-devtools/-/react-query-native-devtools-4.0.0.tgz#97d484d024503d484652f6e64a07c188f99ae702"
|
||||||
|
integrity sha512-TKwYO0tcw744c2aI1AGL2zoLycx3mr6Apl/3p9EIxh204kWGBpF7OhPQY7OKSdPvgw1jG6wlKzfAj3asgCSZGg==
|
||||||
|
dependencies:
|
||||||
|
flatted "^3.2.4"
|
||||||
|
|
||||||
react-redux@^8.0.4:
|
react-redux@^8.0.4:
|
||||||
version "8.0.5"
|
version "8.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
|
||||||
|
Loading…
Reference in New Issue
Block a user