mirror of
https://github.com/ecency/ecency-mobile.git
synced 2025-01-04 20:15:51 +03:00
Merge branch 'development' into l10n_development
This commit is contained in:
commit
5f58011fe8
@ -144,7 +144,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch
|
||||
versionName "3.0.24"
|
||||
versionName "3.0.26"
|
||||
resValue "string", "build_config_package", "app.esteem.mobile.android"
|
||||
multiDexEnabled true
|
||||
// react-native-image-crop-picker
|
||||
|
@ -63,6 +63,7 @@ allprojects {
|
||||
includeGroup("com.facebook.fbjni")
|
||||
includeGroup("com.henninghall.android")
|
||||
includeGroup("org.matomo.sdk")
|
||||
includeModule("com.yqritc", "android-scalablevideoview")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.24</string>
|
||||
<string>3.0.26</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2803</string>
|
||||
<string>2805</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true />
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
@ -15,10 +15,10 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.24</string>
|
||||
<string>3.0.26</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2803</string>
|
||||
<string>2805</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1129,7 +1129,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2803;
|
||||
CURRENT_PROJECT_VERSION = 2805;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = 75B6RXTKGT;
|
||||
EXCLUDED_ARCHS = "";
|
||||
@ -1208,7 +1208,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Ecency/Ecency.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2803;
|
||||
CURRENT_PROJECT_VERSION = 2805;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = 75B6RXTKGT;
|
||||
EXCLUDED_ARCHS = "";
|
||||
|
@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.24</string>
|
||||
<string>3.0.26</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
@ -15,10 +15,10 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.24</string>
|
||||
<string>3.0.26</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2803</string>
|
||||
<string>2805</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -335,7 +335,12 @@ PODS:
|
||||
- React
|
||||
- react-native-version-number (0.3.6):
|
||||
- React
|
||||
- react-native-webview (11.2.3):
|
||||
- react-native-video (5.2.0):
|
||||
- React-Core
|
||||
- react-native-video/Video (= 5.2.0)
|
||||
- react-native-video/Video (5.2.0):
|
||||
- React-Core
|
||||
- react-native-webview (11.17.1):
|
||||
- React-Core
|
||||
- React-RCTActionSheet (0.63.4):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.63.4)
|
||||
@ -496,6 +501,7 @@ DEPENDENCIES:
|
||||
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
||||
- react-native-udp (from `../node_modules/react-native-udp`)
|
||||
- react-native-version-number (from `../node_modules/react-native-version-number`)
|
||||
- react-native-video (from `../node_modules/react-native-video`)
|
||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
||||
@ -621,6 +627,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-udp"
|
||||
react-native-version-number:
|
||||
:path: "../node_modules/react-native-version-number"
|
||||
react-native-video:
|
||||
:path: "../node_modules/react-native-video"
|
||||
react-native-webview:
|
||||
:path: "../node_modules/react-native-webview"
|
||||
React-RCTActionSheet:
|
||||
@ -736,7 +744,8 @@ SPEC CHECKSUMS:
|
||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||
react-native-udp: ff9d13e523f2b58e6bc5d4d32321ac60671b5dc9
|
||||
react-native-version-number: b415bbec6a13f2df62bf978e85bc0d699462f37f
|
||||
react-native-webview: 6520e3e7b4933de76b95ef542c8d7115cf45b68e
|
||||
react-native-video: a4c2635d0802f983594b7057e1bce8f442f0ad28
|
||||
react-native-webview: 162b6453d074e0b1c7025242bb7a939b6f72b9e7
|
||||
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
|
||||
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
|
||||
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
|
||||
|
@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.24</string>
|
||||
<string>3.0.26</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2803</string>
|
||||
<string>2805</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
|
18
package.json
18
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ecency",
|
||||
"version": "3.0.24",
|
||||
"version": "3.0.26",
|
||||
"displayName": "Ecency",
|
||||
"private": true,
|
||||
"rnpm": {
|
||||
@ -18,7 +18,7 @@
|
||||
"test": "node node_modules/jest/bin/jest.js --watch",
|
||||
"lint": "eslint src/",
|
||||
"lint:fix": "eslint src/ --fix",
|
||||
"format": "prettier --write 'src/**/*.{js,jsx}' && yarn lint:fix --fix",
|
||||
"format": "prettier --write 'src/**/*.{js,jsx,ts,tsx}' && yarn lint:fix --fix",
|
||||
"lint-staged": "lint-staged",
|
||||
"clear": "watchman watch-del-all && rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-* && rm -rf node_modules/ && yarn && yarn start -- --reset-cache",
|
||||
"bump-patch": "npm version patch --no-git-tag-version",
|
||||
@ -29,12 +29,13 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@bugsnag/react-native": "^7.11.0",
|
||||
"@ecency/render-helper": "^2.2.12",
|
||||
"@ecency/render-helper": "^2.2.16",
|
||||
"@esteemapp/dhive": "0.15.0",
|
||||
"@esteemapp/react-native-autocomplete-input": "^4.2.1",
|
||||
"@esteemapp/react-native-multi-slider": "^1.1.0",
|
||||
"@esteemapp/react-native-slider": "^0.12.0",
|
||||
"@hiveio/dhive": "^1.0.1",
|
||||
"@native-html/iframe-plugin": "^2.6.1",
|
||||
"@react-native-community/async-storage": "^1.11.0",
|
||||
"@react-native-community/cameraroll": "^1.3.0",
|
||||
"@react-native-community/cli-platform-ios": "^4.10.1",
|
||||
@ -50,7 +51,7 @@
|
||||
"appcenter-analytics": "^4.1.0",
|
||||
"appcenter-crashes": "^4.1.0",
|
||||
"assert": "^1.1.1",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.21.2",
|
||||
"browserify-zlib": "~0.1.4",
|
||||
"buffer": "^6.0.3",
|
||||
"bytebuffer": "^5.0.1",
|
||||
@ -99,9 +100,10 @@
|
||||
"react-native-level-fs": "^3.0.0",
|
||||
"react-native-linear-gradient": "^2.4.2",
|
||||
"react-native-matomo-sdk": "feruzm/react-native-matomo-sdk",
|
||||
"react-native-media-controls": "^2.3.0",
|
||||
"react-native-modal": "^11.5.6",
|
||||
"react-native-modal-dropdown": "^1.0.1",
|
||||
"react-native-modal-popover": "^2.0.1",
|
||||
"react-native-modal-dropdown": "^1.0.2",
|
||||
"react-native-modal-popover": "^2.1.0",
|
||||
"react-native-modal-translucent": "^5.0.0",
|
||||
"react-native-navigation-bar-color": "^1.0.0",
|
||||
"react-native-os": "^1.0.1",
|
||||
@ -117,6 +119,7 @@
|
||||
"react-native-safe-area-context": "^3.1.9",
|
||||
"react-native-screens": "^2.9.0",
|
||||
"react-native-scrollable-tab-view": "ecency/react-native-scrollable-tab-view",
|
||||
"react-native-slider": "^0.11.0",
|
||||
"react-native-snap-carousel": "^3.8.0",
|
||||
"react-native-splash-screen": "^3.2.0",
|
||||
"react-native-svg": "^12.1.1",
|
||||
@ -127,7 +130,8 @@
|
||||
"react-native-vector-icons": "^6.6.0",
|
||||
"react-native-version": "^4.0.0",
|
||||
"react-native-version-number": "^0.3.5",
|
||||
"react-native-webview": "^11.2.1",
|
||||
"react-native-video": "^5.2.0",
|
||||
"react-native-webview": "^11.17.1",
|
||||
"react-native-youtube-iframe": "^2.1.1",
|
||||
"react-navigation": "^4.0.10",
|
||||
"react-navigation-drawer": "^2.3.3",
|
||||
|
@ -8,9 +8,11 @@ import ActionModalView, { ActionModalRef } from '../view/actionModalView';
|
||||
export interface ActionModalData {
|
||||
title:string,
|
||||
body:string,
|
||||
para?: string,
|
||||
buttons:AlertButton[],
|
||||
headerImage?:Source,
|
||||
onClosed:()=>void,
|
||||
headerContent?: React.ReactNode,
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,12 +42,19 @@ const ActionModalView = ({onClose, data}: ActionModalViewProps, ref) => {
|
||||
title,
|
||||
body,
|
||||
buttons,
|
||||
headerImage
|
||||
headerImage,
|
||||
para,
|
||||
headerContent
|
||||
} = data;
|
||||
|
||||
|
||||
const _renderContent = (
|
||||
<View style={styles.container}>
|
||||
{
|
||||
headerContent && (
|
||||
headerContent
|
||||
)
|
||||
}
|
||||
{
|
||||
headerImage && (
|
||||
<FastImage
|
||||
@ -61,7 +68,10 @@ const ActionModalView = ({onClose, data}: ActionModalViewProps, ref) => {
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{!!body && (
|
||||
<Text style={styles.bodyText}>{body}</Text>
|
||||
<>
|
||||
<Text style={styles.bodyText}>{body}</Text>
|
||||
<Text style={styles.bodyText}>{para}</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
|
@ -37,7 +37,7 @@ import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
}
|
||||
|
||||
const imgStyle = {
|
||||
width:imgWidth,
|
||||
width:imgWidth - 10,
|
||||
height:imgHeight,
|
||||
backgroundColor: onLoadCalled ? 'transparent' : EStyleSheet.value('$primaryGray')
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useState, Fragment, useRef } from 'react';
|
||||
import { View, Text, ActivityIndicator, SafeAreaView } from 'react-native';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { useSelector } from 'react-redux';
|
||||
import moment from 'moment';
|
||||
|
||||
@ -15,6 +14,7 @@ import { DateTimePicker } from '../../dateTimePicker';
|
||||
// Constants
|
||||
// Styles
|
||||
import styles from './basicHeaderStyles';
|
||||
import { OptionsModal } from '../../atoms';
|
||||
|
||||
const BasicHeaderView = ({
|
||||
disabled,
|
||||
@ -324,7 +324,7 @@ const BasicHeaderView = ({
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={settingMenuRef}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'editor.setting_schedule' }),
|
||||
@ -337,7 +337,7 @@ const BasicHeaderView = ({
|
||||
title={intl.formatMessage({ id: 'editor.options' })}
|
||||
onPress={_handleSettingMenuSelect}
|
||||
/>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={rewardMenuRef}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'editor.reward_default' }),
|
||||
|
@ -29,6 +29,8 @@ const UserListItem = ({
|
||||
isLoggedIn,
|
||||
searchValue,
|
||||
rightTooltipText,
|
||||
leftItemRenderer,
|
||||
rightItemRenderer
|
||||
}) => {
|
||||
const _handleRightButtonPress = () => {
|
||||
if(onPressRightText){
|
||||
@ -47,6 +49,7 @@ const UserListItem = ({
|
||||
onPress={() => handleOnPress && handleOnPress(username)}
|
||||
>
|
||||
<View style={[styles.voteItemWrapper, index % 2 === 1 && styles.voteItemWrapperGray]}>
|
||||
{leftItemRenderer && leftItemRenderer()}
|
||||
{itemIndex && <Text style={styles.itemIndex}>{itemIndex}</Text>}
|
||||
<UserAvatar noAction={true} style={styles.avatar} username={username} />
|
||||
<View style={styles.userDescription}>
|
||||
@ -86,7 +89,7 @@ const UserListItem = ({
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
{rightItemRenderer && rightItemRenderer()}
|
||||
{isHasRightItem &&
|
||||
isLoggedIn &&
|
||||
(isLoadingRightAction ? (
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
|
||||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
|
||||
// Components
|
||||
import { DropdownButton, PopoverWrapper, Icon, GrayWrapper } from '../../..';
|
||||
@ -30,7 +29,7 @@ const WalletLineItem = ({
|
||||
hintDescription,
|
||||
onPress,
|
||||
}) => (
|
||||
<TouchableWithoutFeedback onPress={onPress} disabled={!onPress}>
|
||||
<TouchableOpacity onPress={onPress} disabled={!onPress} activeOpacity={0.8}>
|
||||
<GrayWrapper isGray={index && index % 2 !== 0}>
|
||||
<View style={[styles.container, fitContent && styles.fitContent, style]}>
|
||||
<View style={styles.iconTextWrapper}>
|
||||
@ -104,7 +103,7 @@ const WalletLineItem = ({
|
||||
)}
|
||||
</View>
|
||||
</GrayWrapper>
|
||||
</TouchableWithoutFeedback>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
export default WalletLineItem;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, TouchableOpacity } from 'react-native';
|
||||
|
||||
import styles from './checkboxStyles';
|
||||
@ -6,6 +6,10 @@ import styles from './checkboxStyles';
|
||||
const CheckBoxView = ({ clicked, value, isChecked, style, locked }) => {
|
||||
const [isCheck, setIsCheck] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsCheck(isChecked);
|
||||
}, [isChecked]);
|
||||
|
||||
const _checkClicked = () => {
|
||||
setIsCheck(!isCheck);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Fragment, useState, useRef, useEffect } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { useIntl } from 'react-intl';
|
||||
import get from 'lodash/get';
|
||||
import { View as AnimatedView } from 'react-native-animatable';
|
||||
@ -18,6 +17,7 @@ import { TextWithIcon } from '../../basicUIElements';
|
||||
// Styles
|
||||
import styles from './commentStyles';
|
||||
import { useAppSelector } from '../../../hooks';
|
||||
import { OptionsModal } from '../../atoms';
|
||||
|
||||
const CommentView = ({
|
||||
avatarSize,
|
||||
@ -33,24 +33,29 @@ const CommentView = ({
|
||||
handleOnVotersPress,
|
||||
isLoggedIn,
|
||||
isShowComments,
|
||||
isShowMoreButton,
|
||||
mainAuthor = { mainAuthor },
|
||||
isHideImage,
|
||||
showAllComments,
|
||||
isShowSubComments,
|
||||
hideManyCommentsButton,
|
||||
openReplyThread,
|
||||
fetchedAt,
|
||||
incrementRepliesCount
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const actionSheet = useRef(null);
|
||||
|
||||
const isMuted = useAppSelector(state => state.account.currentAccount.mutes?.indexOf(comment.author) > -1);
|
||||
const lastCacheUpdate = useAppSelector((state) => state.cache.lastUpdate);
|
||||
const cachedComments = useAppSelector((state) => state.cache.comments);
|
||||
|
||||
const [_isShowSubComments, setIsShowSubComments] = useState(isShowSubComments || false);
|
||||
const [isPressedShowButton, setIsPressedShowButton] = useState(false);
|
||||
const [activeVotes, setActiveVotes] = useState([]);
|
||||
const [cacheVoteIcrement, setCacheVoteIcrement] = useState(0);
|
||||
|
||||
const [childCount, setChildCount] = useState(comment.children);
|
||||
const [replies, setReplies] = useState(comment.replies);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@ -59,8 +64,35 @@ const CommentView = ({
|
||||
}
|
||||
}, [comment]);
|
||||
|
||||
const _showSubCommentsToggle = () => {
|
||||
if(comment.replies && comment.replies.length > 0){
|
||||
|
||||
useEffect(() => {
|
||||
const postPath = `${comment.author || ''}/${comment.permlink || ''}`;
|
||||
//this conditional makes sure on targetted already fetched post is updated
|
||||
//with new cache status, this is to avoid duplicate cache merging
|
||||
if (
|
||||
lastCacheUpdate &&
|
||||
lastCacheUpdate.postPath === postPath &&
|
||||
lastCacheUpdate.type === 'comment' &&
|
||||
lastCacheUpdate.updatedAt > fetchedAt
|
||||
) {
|
||||
//TODO: update comment count and show sub comment if required;
|
||||
const cachedComment = cachedComments.get(postPath);
|
||||
if(cachedComment.updated === cachedComment.created){
|
||||
if(commentNumber > 1 && incrementRepliesCount){
|
||||
incrementRepliesCount();
|
||||
}
|
||||
setChildCount(childCount + 1);
|
||||
setReplies(replies ? [...replies, cachedComment] : [cachedComment]);
|
||||
}
|
||||
|
||||
if(!_isShowSubComments){
|
||||
_showSubCommentsToggle(true);
|
||||
}
|
||||
}
|
||||
}, [lastCacheUpdate]);
|
||||
|
||||
const _showSubCommentsToggle = (force) => {
|
||||
if(((replies && replies.length > 0) || force)){
|
||||
setIsShowSubComments(!_isShowSubComments);
|
||||
setIsPressedShowButton(true);
|
||||
} else if(openReplyThread) {
|
||||
@ -74,6 +106,13 @@ const CommentView = ({
|
||||
setCacheVoteIcrement(1);
|
||||
};
|
||||
|
||||
const _incrementRepliesCount = () => {
|
||||
if(commentNumber > 1 && incrementRepliesCount){
|
||||
incrementRepliesCount();
|
||||
}
|
||||
setChildCount(childCount + 1);
|
||||
}
|
||||
|
||||
|
||||
const _renderReadMoreButton = () => (
|
||||
<TextWithIcon
|
||||
@ -104,13 +143,14 @@ const CommentView = ({
|
||||
avatarSize={avatarSize || 24}
|
||||
author={comment.author}
|
||||
permlink={comment.permlink}
|
||||
commentCount={comment.children}
|
||||
commentCount={childCount}
|
||||
comments={comment.replies}
|
||||
isShowMoreButton={false}
|
||||
hasManyComments={commentNumber === 5 && get(comment, 'children') > 0}
|
||||
fetchPost={fetchPost}
|
||||
hideManyCommentsButton={hideManyCommentsButton}
|
||||
mainAuthor={mainAuthor}
|
||||
fetchedAt={fetchedAt}
|
||||
incrementRepliesCount={_incrementRepliesCount}
|
||||
/>
|
||||
</AnimatedView>
|
||||
|
||||
@ -137,8 +177,8 @@ const CommentView = ({
|
||||
{_renderActionPanel()}
|
||||
</View>
|
||||
{ commentNumber > 1 &&
|
||||
comment.children > 0 &&
|
||||
!comment.replies?.length &&
|
||||
childCount > 0 &&
|
||||
!replies?.length &&
|
||||
_renderReadMoreButton()
|
||||
}
|
||||
</Fragment>
|
||||
@ -193,7 +233,7 @@ const CommentView = ({
|
||||
onPress={() => handleOnEditPress && handleOnEditPress(comment)}
|
||||
iconType="MaterialIcons"
|
||||
/>
|
||||
{!comment.children && !activeVotes.length && (
|
||||
{!childCount && !activeVotes.length && (
|
||||
<Fragment>
|
||||
<IconButton
|
||||
size={20}
|
||||
@ -203,7 +243,7 @@ const CommentView = ({
|
||||
onPress={() => actionSheet.current.show()}
|
||||
iconType="MaterialIcons"
|
||||
/>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={actionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.delete' }),
|
||||
@ -222,7 +262,7 @@ const CommentView = ({
|
||||
)}
|
||||
|
||||
|
||||
{isShowMoreButton && (
|
||||
{commentNumber === 1 && childCount > 0 && (
|
||||
<View style={styles.rightButtonWrapper}>
|
||||
<TextWithIcon
|
||||
wrapperStyle={styles.rightButton}
|
||||
@ -233,7 +273,7 @@ const CommentView = ({
|
||||
iconStyle={styles.iconStyle}
|
||||
iconSize={16}
|
||||
onPress={() => _showSubCommentsToggle()}
|
||||
text={`${comment.children} ${intl.formatMessage({ id: 'comments.more_replies' })}`}
|
||||
text={`${childCount} ${intl.formatMessage({ id: 'comments.more_replies' })}`}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
@ -18,6 +18,8 @@ import ROUTES from '../../../constants/routeNames';
|
||||
|
||||
// Component
|
||||
import CommentsView from '../view/commentsView';
|
||||
import { useAppSelector } from '../../../hooks';
|
||||
import { deleteCommentCacheEntry } from '../../../redux/actions/cacheActions';
|
||||
|
||||
const CommentsContainer = ({
|
||||
author,
|
||||
@ -35,7 +37,6 @@ const CommentsContainer = ({
|
||||
commentCount,
|
||||
isLoggedIn,
|
||||
commentNumber,
|
||||
isShowMoreButton,
|
||||
mainAuthor,
|
||||
selectedPermlink: _selectedPermlink,
|
||||
isHideImage,
|
||||
@ -44,8 +45,14 @@ const CommentsContainer = ({
|
||||
showAllComments,
|
||||
hideManyCommentsButton,
|
||||
flatListProps,
|
||||
fetchedAt,
|
||||
incrementRepliesCount,
|
||||
}) => {
|
||||
const lastCacheUpdate = useAppSelector((state) => state.cache.lastUpdate);
|
||||
const cachedComments = useAppSelector((state) => state.cache.comments);
|
||||
|
||||
const [lcomments, setLComments] = useState([]);
|
||||
const [replies, setReplies] = useState(comments);
|
||||
const [selectedPermlink, setSelectedPermlink] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@ -58,6 +65,20 @@ const CommentsContainer = ({
|
||||
setLComments(shortedComments);
|
||||
}, [commentCount, selectedFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
const postPath = `${author || ''}/${permlink || ''}`;
|
||||
//this conditional makes sure on targetted already fetched post is updated
|
||||
//with new cache status, this is to avoid duplicate cache merging
|
||||
if (
|
||||
lastCacheUpdate &&
|
||||
lastCacheUpdate.postPath === postPath &&
|
||||
lastCacheUpdate.type === 'comment' &&
|
||||
lastCacheUpdate.updatedAt > fetchedAt
|
||||
) {
|
||||
_handleCachedComment();
|
||||
}
|
||||
}, [lastCacheUpdate]);
|
||||
|
||||
// Component Functions
|
||||
|
||||
const _shortComments = (sortOrder, _comments) => {
|
||||
@ -98,8 +119,8 @@ const CommentsContainer = ({
|
||||
return 0;
|
||||
},
|
||||
votes: (a, b) => {
|
||||
const keyA = a.net_votes;
|
||||
const keyB = b.net_votes;
|
||||
const keyA = a.active_votes.length;
|
||||
const keyB = b.active_votes.length;
|
||||
|
||||
if (keyA > keyB) {
|
||||
return -1;
|
||||
@ -141,20 +162,69 @@ const CommentsContainer = ({
|
||||
const _getComments = async () => {
|
||||
if (isOwnProfile) {
|
||||
fetchPost();
|
||||
} else if (author && permlink && !comments) {
|
||||
} else if (author && permlink && !replies) {
|
||||
await getComments(author, permlink, name)
|
||||
.then((__comments) => {
|
||||
//TODO: favourable place for merging comment cache
|
||||
__comments = _handleCachedComment(__comments);
|
||||
|
||||
if (selectedFilter) {
|
||||
const sortComments = _shortComments(selectedFilter, __comments);
|
||||
setLComments(sortComments);
|
||||
} else {
|
||||
setLComments(__comments);
|
||||
__comments = _shortComments(selectedFilter, __comments);
|
||||
}
|
||||
|
||||
setLComments(__comments);
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
_handleCachedComment();
|
||||
}
|
||||
};
|
||||
|
||||
const _handleCachedComment = (passedComments = null) => {
|
||||
const _comments = passedComments || replies || lcomments;
|
||||
const postPath = `${author || ''}/${permlink || ''}`;
|
||||
|
||||
if (cachedComments.has(postPath)) {
|
||||
const cachedComment = cachedComments.get(postPath);
|
||||
|
||||
var ignoreCache = false;
|
||||
var replaceAtIndex = -1;
|
||||
_comments.forEach((comment, index) => {
|
||||
if (cachedComment.permlink === comment.permlink) {
|
||||
if (cachedComment.updated < comment.updated) {
|
||||
//comment is present with latest data
|
||||
ignoreCache = true;
|
||||
console.log('Ignore cache as comment is now present');
|
||||
} else {
|
||||
//comment is present in list but data is old
|
||||
replaceAtIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//manipulate comments with cached data
|
||||
if (!ignoreCache) {
|
||||
let newComments = [];
|
||||
if (replaceAtIndex >= 0) {
|
||||
_comments[replaceAtIndex] = cachedComment;
|
||||
newComments = [..._comments];
|
||||
} else {
|
||||
newComments = [..._comments, cachedComment];
|
||||
}
|
||||
|
||||
console.log('updated comments with cached comment');
|
||||
if (passedComments) {
|
||||
return newComments;
|
||||
} else if (replies) {
|
||||
setReplies(newComments);
|
||||
} else {
|
||||
setLComments(newComments);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _comments;
|
||||
};
|
||||
|
||||
const _handleOnReplyPress = (item) => {
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.EDITOR,
|
||||
@ -195,12 +265,25 @@ const CommentsContainer = ({
|
||||
let filteredComments;
|
||||
|
||||
deleteComment(currentAccount, pinCode, _permlink).then(() => {
|
||||
let cachePath = null;
|
||||
const _applyFilter = (item) => {
|
||||
if (item.permlink === _permlink) {
|
||||
cachePath = `${item.parent_author}/${item.parent_permlink}`;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (lcomments.length > 0) {
|
||||
filteredComments = lcomments.filter((item) => item.permlink !== _permlink);
|
||||
filteredComments = lcomments.filter(_applyFilter);
|
||||
setLComments(filteredComments);
|
||||
} else {
|
||||
filteredComments = comments.filter((item) => item.permlink !== _permlink);
|
||||
filteredComments = replies.filter(_applyFilter);
|
||||
setReplies(filteredComments);
|
||||
}
|
||||
setLComments(filteredComments);
|
||||
|
||||
// remove cached entry based on parent
|
||||
dispatch(deleteCommentCacheEntry(cachePath));
|
||||
});
|
||||
};
|
||||
|
||||
@ -246,10 +329,9 @@ const CommentsContainer = ({
|
||||
selectedPermlink={_selectedPermlink || selectedPermlink}
|
||||
author={author}
|
||||
mainAuthor={mainAuthor}
|
||||
isShowMoreButton={isShowMoreButton}
|
||||
commentNumber={commentNumber || 1}
|
||||
commentCount={commentCount}
|
||||
comments={lcomments.length > 0 ? lcomments : comments}
|
||||
comments={lcomments.length > 0 ? lcomments : replies}
|
||||
currentAccountUsername={currentAccount.name}
|
||||
handleOnEditPress={_handleOnEditPress}
|
||||
handleOnReplyPress={_handleOnReplyPress}
|
||||
@ -264,6 +346,8 @@ const CommentsContainer = ({
|
||||
showAllComments={showAllComments}
|
||||
flatListProps={flatListProps}
|
||||
openReplyThread={_openReplyThread}
|
||||
incrementRepliesCount={incrementRepliesCount}
|
||||
fetchedAt={fetchedAt}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -31,11 +31,12 @@ const CommentsView = ({
|
||||
isShowSubComments,
|
||||
mainAuthor,
|
||||
marginLeft,
|
||||
isShowMoreButton,
|
||||
showAllComments,
|
||||
hideManyCommentsButton,
|
||||
flatListProps,
|
||||
openReplyThread,
|
||||
fetchedAt,
|
||||
incrementRepliesCount
|
||||
}) => {
|
||||
const [selectedComment, setSelectedComment] = useState(null);
|
||||
const intl = useIntl();
|
||||
@ -105,13 +106,14 @@ const CommentsView = ({
|
||||
handleOnVotersPress={handleOnVotersPress}
|
||||
isHideImage={isHideImage}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isShowMoreButton={isShowMoreButton || (commentNumber === 1 && get(item, 'children') > 0)}
|
||||
showAllComments={showAllComments}
|
||||
isShowSubComments={isShowSubComments}
|
||||
key={get(item, 'permlink')}
|
||||
marginLeft={marginLeft}
|
||||
handleOnLongPress={() => _openCommentMenu(item)}
|
||||
openReplyThread={()=> _openReplyThread(item)}
|
||||
fetchedAt={fetchedAt}
|
||||
incrementRepliesCount={incrementRepliesCount}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ const CommentsDisplayView = ({
|
||||
permlink,
|
||||
mainAuthor,
|
||||
handleOnVotersPress,
|
||||
fetchedAt,
|
||||
}) => {
|
||||
const [selectedFilter, setSelectedFilter] = useState('trending');
|
||||
const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
|
||||
@ -49,6 +50,7 @@ const CommentsDisplayView = ({
|
||||
permlink={permlink}
|
||||
mainAuthor={mainAuthor}
|
||||
handleOnVotersPress={handleOnVotersPress}
|
||||
fetchedAt={fetchedAt}
|
||||
/>
|
||||
</View>
|
||||
</Fragment>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useRef, useState, useEffect, Fragment } from 'react';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { View, Text, TouchableOpacity, Dimensions } from 'react-native';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import ImageSize from 'react-native-image-size';
|
||||
@ -11,6 +10,7 @@ import { getTimeFromNow } from '../../../utils/time';
|
||||
import { PostHeaderDescription } from '../../postElements';
|
||||
import { IconButton } from '../../iconButton';
|
||||
import ProgressiveImage from '../../progressiveImage';
|
||||
import { OptionsModal } from '../../atoms';
|
||||
|
||||
// Styles
|
||||
import styles from './draftListItemStyles';
|
||||
@ -95,7 +95,7 @@ const DraftListItemView = ({
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={actionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.delete' }),
|
||||
|
@ -82,4 +82,8 @@ export default EStyleSheet.create({
|
||||
color: '$primaryDarkGray',
|
||||
textAlign: 'left',
|
||||
},
|
||||
childrenWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
@ -97,12 +97,17 @@ const DropdownButtonView = ({
|
||||
adjustFrame={(style: any) => adjustDropdownFrame(style) }
|
||||
>
|
||||
{isHasChildIcon && !isLoading ? (
|
||||
<View style={[styles.iconWrapper, childIconWrapperStyle && childIconWrapperStyle]}>
|
||||
<Icon
|
||||
style={[styles.dropdownIcon, iconStyle]}
|
||||
iconType="MaterialIcons"
|
||||
name={!iconName ? 'arrow-drop-down' : iconName}
|
||||
/>
|
||||
<View style={styles.childrenWrapper}>
|
||||
<Text style={[textStyle || styles.buttonText]}>
|
||||
{defaultText}
|
||||
</Text>
|
||||
<View style={[styles.iconWrapper, childIconWrapperStyle && childIconWrapperStyle]}>
|
||||
<Icon
|
||||
style={[styles.dropdownIcon, iconStyle]}
|
||||
iconType="MaterialIcons"
|
||||
name={!iconName ? 'arrow-drop-down' : iconName}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
isHasChildIcon && <ActivityIndicator />
|
||||
|
@ -43,8 +43,6 @@ const TagInput = ({ value, handleTagChanged, intl, isPreviewActive, autoFocus, s
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_lowercase' }))
|
||||
: cats.find((c) => !/^[a-z0-9-#]+$/.test(c))
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_characters' }))
|
||||
: cats.find((c) => !/^[a-z-#]/.test(c))
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_firstchar' }))
|
||||
: cats.find((c) => !/[a-z0-9]$/.test(c))
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_lastchar' }))
|
||||
: setWarning(null);
|
||||
@ -75,8 +73,6 @@ const TagInput = ({ value, handleTagChanged, intl, isPreviewActive, autoFocus, s
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_lowercase' }))
|
||||
: cats.find((c) => !/^[a-z0-9-#]+$/.test(c))
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_characters' }))
|
||||
: cats.find((c) => !/^[a-z-#]/.test(c))
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_firstchar' }))
|
||||
: cats.find((c) => !/[a-z0-9]$/.test(c))
|
||||
? setWarning(intl.formatMessage({ id: 'editor.limited_lastchar' }))
|
||||
: setWarning(null);
|
||||
|
@ -50,6 +50,7 @@ const HorizontalIconList = ({ options, optionsKeys }) => {
|
||||
keyExtractor={(item) => get(item, 'type', Math.random()).toString()}
|
||||
horizontal
|
||||
renderItem={_renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
@ -93,6 +93,8 @@ import { ForegroundNotification } from './foregroundNotification';
|
||||
import { PostHtmlRenderer } from './postHtmlRenderer';
|
||||
import { QuickProfileModal } from './organisms';
|
||||
import QuickReplyModal from './quickReplyModal/quickReplyModalView';
|
||||
import Tooltip from './tooltip/tooltipView';
|
||||
import VideoPlayer from './videoPlayer/videoPlayerView';
|
||||
|
||||
// Basic UI Elements
|
||||
import {
|
||||
@ -234,4 +236,6 @@ export {
|
||||
PostHtmlRenderer,
|
||||
QuickProfileModal,
|
||||
QuickReplyModal,
|
||||
Tooltip,
|
||||
VideoPlayer,
|
||||
};
|
||||
|
@ -13,7 +13,7 @@ export default EStyleSheet.create({
|
||||
},
|
||||
shadowColor: '#5f5f5fbf',
|
||||
shadowOpacity: 0.1,
|
||||
elevation: 3,
|
||||
// elevation: 3,
|
||||
},
|
||||
icon: {
|
||||
alignSelf: 'center',
|
||||
|
23
src/components/markdownEditor/view/formats/applyMediaLink.ts
Normal file
23
src/components/markdownEditor/view/formats/applyMediaLink.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import {replaceBetween } from './utils';
|
||||
|
||||
export default async ({ text, selection, setTextAndSelection, items }) => {
|
||||
const imagePrefix = '!';
|
||||
|
||||
let newText = text;
|
||||
let newSelection = selection;
|
||||
|
||||
items.forEach(item => {
|
||||
if(item.url && item.text){
|
||||
const formatedText = `\n${imagePrefix}[${item.text}](${item.url})\n`
|
||||
newText = replaceBetween(newText, newSelection, formatedText);
|
||||
const newIndex = newText && newText.indexOf(item.url, newSelection.start) + item.url.length + 2;
|
||||
newSelection = {
|
||||
start: newIndex,
|
||||
end: newIndex
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setTextAndSelection({ text: newText, selection: newSelection });
|
||||
};
|
11
src/components/markdownEditor/view/formats/applyUsername.ts
Normal file
11
src/components/markdownEditor/view/formats/applyUsername.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { extractWordAtIndex } from '../../../../utils/editor';
|
||||
import {replaceBetween } from './utils';
|
||||
|
||||
export default async ({ text, selection, setTextAndSelection, username}) => {
|
||||
const _word = extractWordAtIndex(text, selection.start);
|
||||
const _insertAt = text.indexOf(_word, selection.start - _word.length);
|
||||
const _text = replaceBetween(text, {start:_insertAt, end:_insertAt + _word.length}, `@${username} `)
|
||||
const _newPos = _insertAt + username.length + 2;
|
||||
const _selection = { start: _newPos, end: _newPos };
|
||||
setTextAndSelection({ selection: _selection, text: _text });
|
||||
};
|
@ -110,4 +110,31 @@ export default EStyleSheet.create({
|
||||
bottom: 56,
|
||||
},
|
||||
}),
|
||||
searchAccountsContainer: Platform.select({
|
||||
//absolute positioning makes button hide behind keyboard on ios
|
||||
ios: {
|
||||
marginBottom: 12,
|
||||
paddingTop: 8,
|
||||
},
|
||||
//on android the appearing of button was causing momentary glitch with ios variant style
|
||||
android: {
|
||||
position: 'absolute',
|
||||
bottom: 56,
|
||||
},
|
||||
}),
|
||||
userBubble: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 4,
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 4,
|
||||
backgroundColor: '$primaryBlue',
|
||||
borderRadius: 24,
|
||||
},
|
||||
userBubbleText: {
|
||||
fontSize: 16,
|
||||
color: '$white',
|
||||
marginLeft: 6,
|
||||
marginRight: 8,
|
||||
},
|
||||
});
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { renderPostBody } from '@ecency/render-helper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { View as AnimatedView } from 'react-native-animatable';
|
||||
@ -17,7 +16,7 @@ import { Icon } from '../../icon';
|
||||
|
||||
// Utils
|
||||
import Formats from './formats/formats';
|
||||
import applyImageLink from './formats/applyWebLinkFormat';
|
||||
import applyMediaLink from './formats/applyMediaLink';
|
||||
|
||||
// Actions
|
||||
import { toggleAccountsBottomSheet } from '../../../redux/actions/uiAction';
|
||||
@ -37,6 +36,7 @@ import {
|
||||
Modal,
|
||||
SnippetsModal,
|
||||
UploadsGalleryModal,
|
||||
Tooltip,
|
||||
} from '../../index';
|
||||
|
||||
import { ThemeContainer } from '../../../containers';
|
||||
@ -46,6 +46,10 @@ import styles from './markdownEditorStyles';
|
||||
import applySnippet from './formats/applySnippet';
|
||||
import { MainButton } from '../../mainButton';
|
||||
import isAndroidOreo from '../../../utils/isAndroidOreo';
|
||||
import { OptionsModal } from '../../atoms';
|
||||
import { UsernameAutofillBar } from './usernameAutofillBar';
|
||||
import applyUsername from './formats/applyUsername';
|
||||
import { walkthrough } from '../../../redux/constants/walkthroughConstants';
|
||||
|
||||
const MIN_BODY_INPUT_HEIGHT = 300;
|
||||
|
||||
@ -86,11 +90,14 @@ const MarkdownEditorView = ({
|
||||
const galleryRef = useRef(null);
|
||||
const clearRef = useRef(null);
|
||||
const uploadsGalleryModalRef = useRef(null);
|
||||
const tooltipRef = useRef(null);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const isVisibleAccountsBottomSheet = useSelector(
|
||||
(state) => state.ui.isVisibleAccountsBottomSheet,
|
||||
);
|
||||
const draftBtnTooltipState = useSelector((state) => state.walkthrough.walkthroughMap);
|
||||
const draftBtnTooltipRegistered = draftBtnTooltipState.get(walkthrough.EDITOR_DRAFT_BTN);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPreviewActive) {
|
||||
@ -101,6 +108,11 @@ const MarkdownEditorView = ({
|
||||
useEffect(() => {
|
||||
if (onLoadDraftPress) {
|
||||
setShowDraftLoadButton(true);
|
||||
if (!draftBtnTooltipRegistered) {
|
||||
setTimeout(() => {
|
||||
tooltipRef.current?.openTooltip();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}, [onLoadDraftPress]);
|
||||
|
||||
@ -145,12 +157,11 @@ const MarkdownEditorView = ({
|
||||
useEffect(() => {
|
||||
if (uploadedImage && uploadedImage.url) {
|
||||
if (uploadedImage.shouldInsert) {
|
||||
applyImageLink({
|
||||
applyMediaLink({
|
||||
text,
|
||||
selection,
|
||||
setTextAndSelection: _setTextAndSelection,
|
||||
item: { url: uploadedImage.url, text: uploadedImage.hash },
|
||||
isImage: !!uploadedImage,
|
||||
items: [{ url: uploadedImage.url, text: uploadedImage.hash }],
|
||||
});
|
||||
} else {
|
||||
uploadsGalleryModalRef.current.showModal();
|
||||
@ -163,7 +174,7 @@ const MarkdownEditorView = ({
|
||||
}, [draftBody]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocusText && inputRef && inputRef.current) {
|
||||
if (autoFocusText && inputRef && inputRef.current && draftBtnTooltipRegistered) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [autoFocusText]);
|
||||
@ -184,6 +195,15 @@ const MarkdownEditorView = ({
|
||||
dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet));
|
||||
};
|
||||
|
||||
const _onApplyUsername = (username) => {
|
||||
applyUsername({
|
||||
text,
|
||||
selection,
|
||||
setTextAndSelection: _setTextAndSelection,
|
||||
username,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const _changeText = useCallback((input) => {
|
||||
setText(input);
|
||||
@ -254,14 +274,18 @@ const MarkdownEditorView = ({
|
||||
});
|
||||
};
|
||||
|
||||
const _handleOnMediaSelect = (mediaInsert) => {
|
||||
if (mediaInsert && mediaInsert.url) {
|
||||
applyImageLink({
|
||||
const _handleOnMediaSelect = (mediaArray) => {
|
||||
const items = mediaArray.map((mediaInsert) => ({
|
||||
url: mediaInsert.url,
|
||||
text: mediaInsert.hash,
|
||||
}));
|
||||
|
||||
if (items.length) {
|
||||
applyMediaLink({
|
||||
text,
|
||||
selection,
|
||||
setTextAndSelection: _setTextAndSelection,
|
||||
item: { url: mediaInsert.url, text: mediaInsert.hash },
|
||||
isImage: !!mediaInsert,
|
||||
items,
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -287,18 +311,28 @@ const MarkdownEditorView = ({
|
||||
setShowDraftLoadButton(false);
|
||||
onLoadDraftPress();
|
||||
};
|
||||
|
||||
const Wrapper = draftBtnTooltipRegistered ? AnimatedView : View;
|
||||
return (
|
||||
<AnimatedView style={styles.floatingContainer} animation="bounceInRight">
|
||||
<MainButton
|
||||
style={{ width: isLoading ? null : 120 }}
|
||||
onPress={_onPress}
|
||||
iconName="square-edit-outline"
|
||||
iconType="MaterialCommunityIcons"
|
||||
iconColor="white"
|
||||
text="DRAFT"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</AnimatedView>
|
||||
<>
|
||||
<Wrapper style={styles.floatingContainer} animation="bounceInRight">
|
||||
<Tooltip
|
||||
ref={tooltipRef}
|
||||
text={intl.formatMessage({ id: 'walkthrough.load_draft_tooltip' })}
|
||||
walkthroughIndex={walkthrough.EDITOR_DRAFT_BTN}
|
||||
>
|
||||
<MainButton
|
||||
style={{ width: isLoading ? null : 120 }}
|
||||
onPress={_onPress}
|
||||
iconName="square-edit-outline"
|
||||
iconType="MaterialCommunityIcons"
|
||||
iconColor="white"
|
||||
text="DRAFT"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -411,7 +445,7 @@ const MarkdownEditorView = ({
|
||||
<TextInput
|
||||
multiline
|
||||
autoCorrect={true}
|
||||
autoFocus={isReply ? true : false}
|
||||
autoFocus={isReply && draftBtnTooltipRegistered ? true : false}
|
||||
onChangeText={_changeText}
|
||||
onSelectionChange={_handleOnSelectionChange}
|
||||
placeholder={intl.formatMessage({
|
||||
@ -449,8 +483,9 @@ const MarkdownEditorView = ({
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
{isAndroidOreo() ? _renderEditorWithoutScroll() : _renderEditorWithScroll()}
|
||||
|
||||
<UsernameAutofillBar text={text} selection={selection} onApplyUsername={_onApplyUsername} />
|
||||
{_renderFloatingDraftButton()}
|
||||
|
||||
{!isPreviewActive && _renderEditorButtons()}
|
||||
|
||||
<Modal
|
||||
@ -474,7 +509,7 @@ const MarkdownEditorView = ({
|
||||
uploadedImage={uploadedImage}
|
||||
/>
|
||||
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={galleryRef}
|
||||
options={[
|
||||
intl.formatMessage({
|
||||
@ -500,7 +535,7 @@ const MarkdownEditorView = ({
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={clearRef}
|
||||
title={intl.formatMessage({
|
||||
id: 'alert.clear_alert',
|
||||
|
89
src/components/markdownEditor/view/usernameAutofillBar.tsx
Normal file
89
src/components/markdownEditor/view/usernameAutofillBar.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import { View, FlatList, Text, TouchableOpacity } from "react-native"
|
||||
import { UserAvatar } from '../..';
|
||||
import { lookupAccounts } from '../../../providers/hive/dhive';
|
||||
import { extractWordAtIndex } from '../../../utils/editor';
|
||||
import styles from './markdownEditorStyles';
|
||||
import {debounce} from 'lodash';
|
||||
|
||||
interface Props {
|
||||
text:string,
|
||||
selection:{
|
||||
start:number,
|
||||
end:number
|
||||
}
|
||||
onApplyUsername:(username:string)=>void;
|
||||
}
|
||||
|
||||
export const UsernameAutofillBar = ({text, selection, onApplyUsername}:Props) => {
|
||||
|
||||
const [searchedUsers, setSearchedUsers] = useState([])
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (selection.start === selection.end && text) {
|
||||
const word = extractWordAtIndex(text, selection.start);
|
||||
console.log('selection word is: ', word);
|
||||
if (word.startsWith('@') && word.length > 3) {
|
||||
_handleUserSearch(word.substring(1));
|
||||
} else {
|
||||
setSearchedUsers([]);
|
||||
setQuery('')
|
||||
_handleUserSearch.cancel();
|
||||
}
|
||||
}
|
||||
}, [text, selection])
|
||||
|
||||
|
||||
|
||||
const _handleUserSearch = debounce(async (username) => {
|
||||
if(query !== username){
|
||||
let users = [];
|
||||
if (username) {
|
||||
setQuery(username)
|
||||
users = await lookupAccounts(username);
|
||||
console.log('result users for', username, users);
|
||||
}
|
||||
setSearchedUsers(users);
|
||||
}
|
||||
|
||||
}, 200, {leading:true});
|
||||
|
||||
|
||||
|
||||
const _onUserSelect = (username) => {
|
||||
onApplyUsername(username)
|
||||
setSearchedUsers([]);
|
||||
setQuery('')
|
||||
};
|
||||
|
||||
if(!searchedUsers || searchedUsers.length === 0 || query === ''){
|
||||
return null;
|
||||
}
|
||||
|
||||
const _renderItem = ({item}:{item:string}) => {
|
||||
|
||||
const username = item;
|
||||
return (
|
||||
<TouchableOpacity onPress={()=>{_onUserSelect(username)}}>
|
||||
<View style={styles.userBubble}>
|
||||
<UserAvatar username={username}/>
|
||||
<Text style={styles.userBubbleText}>{username}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.searchAccountsContainer}>
|
||||
<FlatList
|
||||
horizontal={true}
|
||||
data={searchedUsers}
|
||||
keyboardShouldPersistTaps="always"
|
||||
showsHorizontalScrollIndicator={false}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(item)=>`searched-user-${item}`}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
@ -4,9 +4,7 @@ export default EStyleSheet.create({
|
||||
popoverDetails: {
|
||||
flexDirection: 'row',
|
||||
width: '$deviceWidth / 2',
|
||||
borderRadius: 20,
|
||||
padding: 16,
|
||||
|
||||
borderRadius: 12,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
arrow: {
|
||||
@ -15,8 +13,9 @@ export default EStyleSheet.create({
|
||||
popoverWrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 8,
|
||||
},
|
||||
overlay: {},
|
||||
popoverText: {
|
||||
|
@ -49,7 +49,6 @@ export default EStyleSheet.create({
|
||||
},
|
||||
autocompleteLineContainer: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 20,
|
||||
zIndex: 999,
|
||||
},
|
||||
autocompleteLabelText: {
|
||||
@ -72,7 +71,8 @@ export default EStyleSheet.create({
|
||||
},
|
||||
autocompleteLabelContainer: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
justifyContent: 'center',
|
||||
color: '$primaryBlack',
|
||||
maxWidth: '$deviceWidth / 2.9',
|
||||
|
@ -3,7 +3,6 @@ import { injectIntl } from 'react-intl';
|
||||
import { Text, View, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import get from 'lodash/get';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import Autocomplete from '@esteemapp/react-native-autocomplete-input';
|
||||
import { Icon, TextInput } from '..';
|
||||
import { hsOptions } from '../../constants/hsOptions';
|
||||
@ -21,6 +20,7 @@ import { Modal } from '../modal';
|
||||
|
||||
// Styles
|
||||
import styles from './postBoostStyles';
|
||||
import { OptionsModal } from '../atoms';
|
||||
|
||||
class BoostPostScreen extends PureComponent {
|
||||
/* Props
|
||||
@ -240,7 +240,7 @@ class BoostPostScreen extends PureComponent {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={this.startActionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.confirm' }),
|
||||
|
@ -2,7 +2,6 @@ import React, { PureComponent, Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withNavigation } from 'react-navigation';
|
||||
import { Share } from 'react-native';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import get from 'lodash/get';
|
||||
|
||||
@ -22,6 +21,7 @@ import { getPostUrl } from '../../../utils/post';
|
||||
|
||||
// Component
|
||||
import PostDropdownView from '../view/postDropdownView';
|
||||
import { OptionsModal } from '../../atoms';
|
||||
|
||||
/*
|
||||
* Props Name Description Value
|
||||
@ -145,17 +145,22 @@ class PostDropdownContainer extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(showActionModal(
|
||||
intl.formatMessage({id:'report.confirm_report_title'}),
|
||||
intl.formatMessage({id:'report.confirm_report_body'}),
|
||||
[{
|
||||
text:intl.formatMessage({id:'alert.cancel'}),
|
||||
onPress:()=>{}
|
||||
},{
|
||||
text:intl.formatMessage({id:'alert.confirm'}),
|
||||
onPress:_onConfirm
|
||||
}]
|
||||
))
|
||||
dispatch(
|
||||
showActionModal({
|
||||
title: intl.formatMessage({ id: 'report.confirm_report_title' }),
|
||||
body: intl.formatMessage({ id: 'report.confirm_report_body' }),
|
||||
buttons: [
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.cancel' }),
|
||||
onPress: () => {},
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.confirm' }),
|
||||
onPress: _onConfirm,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
@ -278,7 +283,7 @@ class PostDropdownContainer extends PureComponent {
|
||||
handleOnDropdownSelect={this._handleOnDropdownSelect}
|
||||
{...this.props}
|
||||
/>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={(o) => (this.ActionSheet = o)}
|
||||
options={['Reblog', intl.formatMessage({ id: 'alert.cancel' })]}
|
||||
title={intl.formatMessage({ id: 'post.reblog_alert' })}
|
||||
|
@ -13,7 +13,7 @@ import { navigate } from '../../../../navigation/service';
|
||||
// Constants
|
||||
import { default as ROUTES } from '../../../../constants/routeNames';
|
||||
|
||||
import { PostHtmlRenderer, TextButton } from '../../..';
|
||||
import { PostHtmlRenderer, TextButton, VideoPlayer } from '../../..';
|
||||
|
||||
// Styles
|
||||
import styles from './commentBodyStyles';
|
||||
@ -21,14 +21,12 @@ import styles from './commentBodyStyles';
|
||||
// Services and Actions
|
||||
import { writeToClipboard } from '../../../../utils/clipboard';
|
||||
import { toastNotification } from '../../../../redux/actions/uiAction';
|
||||
import VideoPlayerSheet from './videoPlayerSheet';
|
||||
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
|
||||
import { useCallback } from 'react';
|
||||
import { OptionsModal } from '../../../atoms';
|
||||
import { useAppDispatch } from '../../../../hooks';
|
||||
import { isCommunity } from '../../../../utils/communityValidation';
|
||||
import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters';
|
||||
import { startsWith } from 'core-js/core/string';
|
||||
|
||||
const WIDTH = Dimensions.get('window').width;
|
||||
|
||||
@ -39,7 +37,7 @@ const CommentBody = ({
|
||||
handleOnLongPress,
|
||||
created,
|
||||
commentDepth,
|
||||
reputation,
|
||||
reputation = 25,
|
||||
isMuted
|
||||
}) => {
|
||||
|
||||
@ -337,6 +335,7 @@ const CommentBody = ({
|
||||
<PostHtmlRenderer
|
||||
contentWidth={_contentWidth}
|
||||
body={body}
|
||||
isComment={true}
|
||||
onElementIsImage={_onElementIsImage}
|
||||
setSelectedImage={_handleSetSelectedImage}
|
||||
setSelectedLink={_handleSetSelectedLink}
|
||||
@ -368,7 +367,12 @@ const CommentBody = ({
|
||||
setVideoUrl(null);
|
||||
}}
|
||||
>
|
||||
<VideoPlayerSheet youtubeVideoId={youtubeVideoId} videoUrl={videoUrl} startTime={videoStartTime} />
|
||||
<VideoPlayer
|
||||
mode={youtubeVideoId ? 'youtube' : 'uri'}
|
||||
youtubeVideoId={youtubeVideoId}
|
||||
uri={videoUrl}
|
||||
startTime={videoStartTime}
|
||||
/>
|
||||
</ActionsSheetView>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -15,11 +15,10 @@ import { toastNotification } from '../../../../redux/actions/uiAction';
|
||||
|
||||
// Constants
|
||||
import { default as ROUTES } from '../../../../constants/routeNames';
|
||||
import VideoPlayerSheet from './videoPlayerSheet';
|
||||
import { OptionsModal } from '../../../atoms';
|
||||
import { isCommunity } from '../../../../utils/communityValidation';
|
||||
import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters';
|
||||
import { PostHtmlRenderer } from '../../..';
|
||||
import { PostHtmlRenderer, VideoPlayer } from '../../..';
|
||||
|
||||
const WIDTH = Dimensions.get('window').width;
|
||||
|
||||
@ -294,9 +293,10 @@ const PostBody = ({ navigation, body, dispatch, onLoadEnd }) => {
|
||||
setVideoUrl(null);
|
||||
}}
|
||||
>
|
||||
<VideoPlayerSheet
|
||||
<VideoPlayer
|
||||
mode={youtubeVideoId ? 'youtube' : 'uri'}
|
||||
youtubeVideoId={youtubeVideoId}
|
||||
videoUrl={videoUrl}
|
||||
uri={videoUrl}
|
||||
startTime={videoStartTime}
|
||||
/>
|
||||
</ActionSheetView>
|
||||
|
@ -1,83 +0,0 @@
|
||||
import React, {useState} from 'react';
|
||||
import { Dimensions } from 'react-native';
|
||||
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
||||
import WebView from 'react-native-webview';
|
||||
import YoutubeIframe, { InitialPlayerParams } from 'react-native-youtube-iframe';
|
||||
|
||||
interface VideoPlayerSheetProps {
|
||||
youtubeVideoId?:string;
|
||||
videoUrl?:string;
|
||||
startTime?:number;
|
||||
}
|
||||
|
||||
const VideoPlayerSheet = ({youtubeVideoId, videoUrl, startTime}: VideoPlayerSheetProps) => {
|
||||
|
||||
const PLAYER_HEIGHT = Dimensions.get('screen').width * (9/16);
|
||||
|
||||
const [shouldPlay, setShouldPlay] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const _onReady = () => {
|
||||
setLoading(false)
|
||||
setShouldPlay(true);
|
||||
}
|
||||
|
||||
const _onChangeState = (event:string) => {
|
||||
setShouldPlay(!(event == 'paused' || event == 'ended'));
|
||||
}
|
||||
|
||||
const _onError = () => {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const initialParams:InitialPlayerParams = {
|
||||
start:startTime
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
||||
{youtubeVideoId &&
|
||||
<YoutubeIframe
|
||||
height={PLAYER_HEIGHT}
|
||||
videoId={youtubeVideoId}
|
||||
initialPlayerParams={initialParams}
|
||||
onReady={_onReady}
|
||||
play={shouldPlay}
|
||||
onChangeState={_onChangeState}
|
||||
onError={_onError}
|
||||
/>
|
||||
|
||||
}{
|
||||
videoUrl &&
|
||||
<View style={{height:PLAYER_HEIGHT}}>
|
||||
<WebView
|
||||
scalesPageToFit={true}
|
||||
bounces={false}
|
||||
javaScriptEnabled={true}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
onLoadEnd={()=>{
|
||||
setLoading(false);
|
||||
}}
|
||||
onLoadStart={()=>{
|
||||
setLoading(true);
|
||||
}}
|
||||
source={{uri:videoUrl}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
}
|
||||
{loading && <ActivityIndicator style={styles.activityIndicator}/>}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoPlayerSheet;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingVertical:16,
|
||||
},
|
||||
activityIndicator: {
|
||||
position:'absolute', alignItems:'center', justifyContent:'center', top:0, bottom:0, left:0, right:0}
|
||||
});
|
@ -1,28 +1,34 @@
|
||||
import React, { memo } from "react";
|
||||
import RenderHTML, { CustomRendererProps, Element, TNode } from "react-native-render-html";
|
||||
import styles from "./postHtmlRendererStyles";
|
||||
import { LinkData, parseLinkData } from "./linkDataParser";
|
||||
import VideoThumb from "./videoThumb";
|
||||
import { AutoHeightImage } from "../autoHeightImage/autoHeightImage";
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import RenderHTML, { CustomRendererProps, Element, TNode } from 'react-native-render-html';
|
||||
import styles from './postHtmlRendererStyles';
|
||||
import { LinkData, parseLinkData } from './linkDataParser';
|
||||
import VideoThumb from './videoThumb';
|
||||
import { AutoHeightImage } from '../autoHeightImage/autoHeightImage';
|
||||
import { useHtmlIframeProps, iframeModel } from '@native-html/iframe-plugin';
|
||||
import WebView from 'react-native-webview';
|
||||
import { VideoPlayer } from '..';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
interface PostHtmlRendererProps {
|
||||
contentWidth:number;
|
||||
body:string;
|
||||
onLoaded?:()=>void;
|
||||
setSelectedImage:(imgUrl:string)=>void;
|
||||
setSelectedLink:(url:string)=>void;
|
||||
onElementIsImage:(imgUrl:string)=>void;
|
||||
handleOnPostPress:(permlink:string, authro:string)=>void;
|
||||
handleOnUserPress:(username:string)=>void;
|
||||
handleTagPress:(tag:string, filter?:string)=>void;
|
||||
handleVideoPress:(videoUrl:string)=>void;
|
||||
handleYoutubePress:(videoId:string, startTime:number)=>void;
|
||||
contentWidth: number;
|
||||
body: string;
|
||||
isComment?: boolean;
|
||||
onLoaded?: () => void;
|
||||
setSelectedImage: (imgUrl: string) => void;
|
||||
setSelectedLink: (url: string) => void;
|
||||
onElementIsImage: (imgUrl: string) => void;
|
||||
handleOnPostPress: (permlink: string, authro: string) => void;
|
||||
handleOnUserPress: (username: string) => void;
|
||||
handleTagPress: (tag: string, filter?: string) => void;
|
||||
handleVideoPress: (videoUrl: string) => void;
|
||||
handleYoutubePress: (videoId: string, startTime: number) => void;
|
||||
}
|
||||
|
||||
export const PostHtmlRenderer = memo(({
|
||||
export const PostHtmlRenderer = memo(
|
||||
({
|
||||
contentWidth,
|
||||
body,
|
||||
isComment,
|
||||
onLoaded,
|
||||
setSelectedImage,
|
||||
setSelectedLink,
|
||||
@ -32,185 +38,177 @@ export const PostHtmlRenderer = memo(({
|
||||
handleTagPress,
|
||||
handleVideoPress,
|
||||
handleYoutubePress,
|
||||
}:PostHtmlRendererProps) => {
|
||||
}: PostHtmlRendererProps) => {
|
||||
//new renderer functions
|
||||
body = body.replace(/<center>/g, '<div class="text-center">').replace(/<\/center>/g, '</div>');
|
||||
|
||||
//new renderer functions
|
||||
body = body.replace(/<center>/g, '<div class="text-center">').replace(/<\/center>/g,'</div>');
|
||||
console.log('Comment body:', body);
|
||||
|
||||
|
||||
console.log("Comment body:", body);
|
||||
|
||||
const _handleOnLinkPress = (data:LinkData) => {
|
||||
|
||||
if(!data){
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
type,
|
||||
href,
|
||||
author,
|
||||
permlink,
|
||||
tag,
|
||||
youtubeId,
|
||||
startTime,
|
||||
filter,
|
||||
videoHref,
|
||||
community
|
||||
} = data;
|
||||
|
||||
try {
|
||||
|
||||
switch (type) {
|
||||
case '_external':
|
||||
case 'markdown-external-link':
|
||||
setSelectedLink(href);
|
||||
break;
|
||||
case 'markdown-author-link':
|
||||
if (handleOnUserPress) {
|
||||
handleOnUserPress(author);
|
||||
}
|
||||
break;
|
||||
case 'markdown-post-link':
|
||||
if (handleOnPostPress) {
|
||||
handleOnPostPress(permlink, author);
|
||||
}
|
||||
break;
|
||||
case 'markdown-tag-link':
|
||||
if(handleTagPress){
|
||||
handleTagPress(tag, filter);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'markdown-video-link':
|
||||
if(handleVideoPress){
|
||||
handleVideoPress(videoHref)
|
||||
}
|
||||
break;
|
||||
case 'markdown-video-link-youtube':
|
||||
if(handleYoutubePress){
|
||||
handleYoutubePress(youtubeId, startTime)
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
//unused cases
|
||||
case 'markdown-witnesses-link':
|
||||
setSelectedLink(href);
|
||||
break;
|
||||
|
||||
case 'markdown-proposal-link':
|
||||
setSelectedLink(href);
|
||||
break;
|
||||
|
||||
case 'markdown-community-link':
|
||||
//tag press also handles community by default
|
||||
if(handleTagPress){
|
||||
handleTagPress(community, filter)
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
const _handleOnLinkPress = (data: LinkData) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
|
||||
const _onElement = (element:Element) => {
|
||||
if(element.tagName === 'img' && element.attribs.src){
|
||||
|
||||
const {
|
||||
type,
|
||||
href,
|
||||
author,
|
||||
permlink,
|
||||
tag,
|
||||
youtubeId,
|
||||
startTime,
|
||||
filter,
|
||||
videoHref,
|
||||
community,
|
||||
} = data;
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case '_external':
|
||||
case 'markdown-external-link':
|
||||
setSelectedLink(href);
|
||||
break;
|
||||
case 'markdown-author-link':
|
||||
if (handleOnUserPress) {
|
||||
handleOnUserPress(author);
|
||||
}
|
||||
break;
|
||||
case 'markdown-post-link':
|
||||
if (handleOnPostPress) {
|
||||
handleOnPostPress(permlink, author);
|
||||
}
|
||||
break;
|
||||
case 'markdown-tag-link':
|
||||
if (handleTagPress) {
|
||||
handleTagPress(tag, filter);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'markdown-video-link':
|
||||
if (handleVideoPress) {
|
||||
handleVideoPress(videoHref);
|
||||
}
|
||||
break;
|
||||
case 'markdown-video-link-youtube':
|
||||
if (handleYoutubePress) {
|
||||
handleYoutubePress(youtubeId, startTime);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
//unused cases
|
||||
case 'markdown-witnesses-link':
|
||||
setSelectedLink(href);
|
||||
break;
|
||||
|
||||
case 'markdown-proposal-link':
|
||||
setSelectedLink(href);
|
||||
break;
|
||||
|
||||
case 'markdown-community-link':
|
||||
//tag press also handles community by default
|
||||
if (handleTagPress) {
|
||||
handleTagPress(community, filter);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const _onElement = (element: Element) => {
|
||||
if (element.tagName === 'img' && element.attribs.src) {
|
||||
const imgUrl = element.attribs.src;
|
||||
console.log("img element detected", imgUrl);
|
||||
onElementIsImage(imgUrl)
|
||||
console.log('img element detected', imgUrl);
|
||||
onElementIsImage(imgUrl);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const _anchorRenderer = ({
|
||||
InternalRenderer,
|
||||
tnode,
|
||||
...props
|
||||
}:CustomRendererProps<TNode>) => {
|
||||
|
||||
const _anchorRenderer = ({ InternalRenderer, tnode, ...props }: CustomRendererProps<TNode>) => {
|
||||
const parsedTnode = parseLinkData(tnode);
|
||||
const _onPress = () => {
|
||||
console.log("Link Pressed:", tnode)
|
||||
console.log('Link Pressed:', tnode);
|
||||
const data = parseLinkData(tnode);
|
||||
_handleOnLinkPress(data);
|
||||
};
|
||||
|
||||
|
||||
//process video link
|
||||
if(tnode.classes?.indexOf('markdown-video-link') >= 0){
|
||||
const imgElement = tnode.children.find((child)=>{
|
||||
return child.classes.indexOf('video-thumbnail') > 0 ? true:false
|
||||
})
|
||||
if(!imgElement){
|
||||
|
||||
//TODO: remove android check when fix for react-native-weview scroll crash is available
|
||||
//ref: https://github.com/react-native-webview/react-native-webview/issues/2364
|
||||
if(isComment || Platform.OS === 'android'){
|
||||
const imgElement = tnode.children.find((child) => {
|
||||
return child.classes.indexOf('video-thumbnail') > 0 ? true : false;
|
||||
});
|
||||
if (!imgElement) {
|
||||
return <VideoThumb contentWidth={contentWidth} onPress={_onPress} />;
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<VideoThumb contentWidth={contentWidth} onPress={_onPress} />
|
||||
)
|
||||
<VideoPlayer
|
||||
mode={parsedTnode.youtubeId ? 'youtube' : 'uri'}
|
||||
contentWidth={contentWidth}
|
||||
uri={parsedTnode.videoHref}
|
||||
youtubeVideoId={parsedTnode.youtubeId}
|
||||
startTime={parsedTnode.startTime}
|
||||
disableAutoplay={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<InternalRenderer
|
||||
tnode={tnode}
|
||||
onPress={_onPress}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return <InternalRenderer tnode={tnode} onPress={_onPress} {...props} />;
|
||||
};
|
||||
|
||||
|
||||
//this method checks if image is a child of table column
|
||||
//and calculates img width accordingly,
|
||||
//returns full width if img is not part of table
|
||||
const getMaxImageWidth = (tnode:TNode)=>{
|
||||
|
||||
const getMaxImageWidth = (tnode: TNode) => {
|
||||
//return full width if not parent exist
|
||||
if(!tnode.parent || tnode.parent.tagName === 'body'){
|
||||
if (!tnode.parent || tnode.parent.tagName === 'body') {
|
||||
return contentWidth;
|
||||
}
|
||||
|
||||
//return divided width based on number td tags
|
||||
if(tnode.parent.tagName === 'td'){
|
||||
const cols = tnode.parent.parent.children.length
|
||||
return contentWidth/cols;
|
||||
if (tnode.parent.tagName === 'td' || tnode.parent.tagName === 'th') {
|
||||
const cols = tnode.parent.parent.children.length;
|
||||
return contentWidth / cols;
|
||||
}
|
||||
|
||||
//check next parent
|
||||
return getMaxImageWidth(tnode.parent);
|
||||
}
|
||||
|
||||
|
||||
const _imageRenderer = ({
|
||||
tnode,
|
||||
}:CustomRendererProps<TNode>) => {
|
||||
|
||||
};
|
||||
|
||||
const _imageRenderer = ({ tnode }: CustomRendererProps<TNode>) => {
|
||||
const imgUrl = tnode.attributes.src;
|
||||
const _onPress = () => {
|
||||
console.log("Image Pressed:", imgUrl)
|
||||
console.log('Image Pressed:', imgUrl);
|
||||
setSelectedImage(imgUrl);
|
||||
};
|
||||
|
||||
|
||||
const isVideoThumb = tnode.classes?.indexOf('video-thumbnail') >= 0;
|
||||
const isAnchored = tnode.parent?.tagName === 'a';
|
||||
|
||||
|
||||
if(isVideoThumb){
|
||||
return <VideoThumb contentWidth={contentWidth} uri={imgUrl}/>;
|
||||
}
|
||||
else {
|
||||
if (isVideoThumb) {
|
||||
return <VideoThumb contentWidth={contentWidth} uri={imgUrl} />;
|
||||
} else {
|
||||
const maxImgWidth = getMaxImageWidth(tnode);
|
||||
return (
|
||||
<AutoHeightImage
|
||||
contentWidth={maxImgWidth}
|
||||
<AutoHeightImage
|
||||
contentWidth={maxImgWidth}
|
||||
imgUrl={imgUrl}
|
||||
isAnchored={isAnchored}
|
||||
onPress={_onPress}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* the para renderer is designd to remove margins from para
|
||||
@ -218,58 +216,93 @@ export const PostHtmlRenderer = memo(({
|
||||
* a weired misalignment of bullet and content
|
||||
* @returns Default Renderer
|
||||
*/
|
||||
const _paraRenderer = ({
|
||||
TDefaultRenderer,
|
||||
...props
|
||||
}:CustomRendererProps<TNode>) => {
|
||||
const _paraRenderer = ({ TDefaultRenderer, ...props }: CustomRendererProps<TNode>) => {
|
||||
props.style = props.tnode.parent.tagName === 'li' ? styles.pLi : styles.p;
|
||||
|
||||
props.style = props.tnode.parent.tagName === 'li'
|
||||
? styles.pLi
|
||||
: styles.p
|
||||
return <TDefaultRenderer {...props} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<TDefaultRenderer
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<RenderHTML
|
||||
source={{ html:body }}
|
||||
contentWidth={contentWidth}
|
||||
baseStyle={{...styles.baseStyle, width:contentWidth}}
|
||||
classesStyles={{
|
||||
phishy:styles.phishy,
|
||||
'text-justify':styles.textJustify,
|
||||
'text-center':styles.textCenter
|
||||
}}
|
||||
tagsStyles={{
|
||||
body:styles.body,
|
||||
a:styles.a,
|
||||
img:styles.img,
|
||||
th:styles.th,
|
||||
tr:{...styles.tr, width:contentWidth}, //center tag causes tr to have 0 width if not exclusivly set, contentWidth help avoid that
|
||||
td:styles.td,
|
||||
blockquote:styles.blockquote,
|
||||
code:styles.code,
|
||||
li:styles.li,
|
||||
p:styles.p,
|
||||
table:styles.table,
|
||||
}}
|
||||
domVisitors={{
|
||||
onElement:_onElement
|
||||
}}
|
||||
renderers={{
|
||||
img:_imageRenderer,
|
||||
a:_anchorRenderer,
|
||||
p:_paraRenderer
|
||||
}}
|
||||
onHTMLLoaded={onLoaded && onLoaded}
|
||||
defaultTextProps={{
|
||||
selectable:true
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}, (next, prev)=>next.body === prev.body)
|
||||
|
||||
// iframe renderer for rendering iframes in body
|
||||
const _iframeRenderer = function IframeRenderer(props) {
|
||||
const iframeProps = useHtmlIframeProps(props);
|
||||
|
||||
//TODO: remove android check logic when fix for react-native-webiew scrollview crash is available
|
||||
//ref: https://github.com/react-native-webview/react-native-webview/issues/2364
|
||||
if(isComment || Platform.OS === 'android'){
|
||||
const _onPress = () => {
|
||||
console.log('iframe thumb Pressed:', iframeProps);
|
||||
if (handleVideoPress) {
|
||||
handleVideoPress(iframeProps.source.uri);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<VideoThumb contentWidth={contentWidth} onPress={_onPress} />
|
||||
)
|
||||
}else{
|
||||
return (
|
||||
|
||||
<VideoPlayer
|
||||
mode='uri'
|
||||
uri={iframeProps.source.uri}
|
||||
contentWidth={contentWidth}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<RenderHTML
|
||||
source={{ html: body }}
|
||||
contentWidth={contentWidth}
|
||||
baseStyle={{ ...styles.baseStyle, width: contentWidth }}
|
||||
classesStyles={{
|
||||
phishy: styles.phishy,
|
||||
'text-justify': styles.textJustify,
|
||||
'text-center': styles.textCenter,
|
||||
}}
|
||||
tagsStyles={{
|
||||
body: styles.body,
|
||||
a: styles.a,
|
||||
img: styles.img,
|
||||
th: styles.th,
|
||||
tr: { ...styles.tr, width: contentWidth }, //center tag causes tr to have 0 width if not exclusivly set, contentWidth help avoid that
|
||||
div:{width:contentWidth},
|
||||
td: styles.td,
|
||||
blockquote: styles.blockquote,
|
||||
code: styles.code,
|
||||
li: styles.li,
|
||||
p: styles.p,
|
||||
table: styles.table,
|
||||
}}
|
||||
domVisitors={{
|
||||
onElement: _onElement,
|
||||
}}
|
||||
renderers={{
|
||||
img: _imageRenderer,
|
||||
a: _anchorRenderer,
|
||||
p: _paraRenderer,
|
||||
iframe: _iframeRenderer,
|
||||
}}
|
||||
onHTMLLoaded={onLoaded && onLoaded}
|
||||
defaultTextProps={{
|
||||
selectable: true,
|
||||
}}
|
||||
customHTMLElementModels={{
|
||||
iframe: iframeModel,
|
||||
}}
|
||||
renderersProps={{
|
||||
iframe: {
|
||||
scalesPageToFit: true,
|
||||
webViewProps: {
|
||||
/* Any prop you want to pass to iframe WebViews */
|
||||
},
|
||||
},
|
||||
}}
|
||||
WebView={WebView}
|
||||
/>
|
||||
);
|
||||
},
|
||||
(next, prev) => next.body === prev.body,
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ImageStyle } from 'react-native';
|
||||
import { ImageStyle, Platform } from 'react-native';
|
||||
import { ViewStyle, TextStyle } from 'react-native';
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
@ -82,7 +82,7 @@ export default EStyleSheet.create({
|
||||
flexWrap:'wrap'
|
||||
} as TextStyle,
|
||||
textJustify:{
|
||||
textAlign:'justify',
|
||||
textAlign: Platform.select({ios:'justify', android:'auto'}), //justify with selectable on android causes ends of text getting clipped,
|
||||
letterSpacing:0
|
||||
} as TextStyle,
|
||||
revealButton: {
|
||||
|
@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useRef, useState, Fragment } from 'react
|
||||
import { View, Text, ScrollView, Dimensions, SafeAreaView, RefreshControl } from 'react-native';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import get from 'lodash/get';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
// Providers
|
||||
import { userActivity } from '../../../providers/ecency/ePoint';
|
||||
@ -20,6 +19,7 @@ import { ParentPost } from '../../parentPost';
|
||||
|
||||
// Styles
|
||||
import styles from './postDisplayStyles';
|
||||
import { OptionsModal } from '../../atoms';
|
||||
|
||||
const HEIGHT = Dimensions.get('window').width;
|
||||
|
||||
@ -247,11 +247,12 @@ const PostDisplayView = ({
|
||||
commentCount={post.children}
|
||||
fetchPost={fetchPost}
|
||||
handleOnVotersPress={handleOnVotersPress}
|
||||
fetchedAt={post.post_fetched_at}
|
||||
/>
|
||||
)}
|
||||
</ScrollView>
|
||||
{post && _getTabBar(true)}
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={actionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.delete' }),
|
||||
|
@ -47,7 +47,6 @@ export default EStyleSheet.create({
|
||||
},
|
||||
autocomplateLineContainer: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 20,
|
||||
zIndex: 999,
|
||||
},
|
||||
autocomplateLabelText: {
|
||||
@ -70,7 +69,8 @@ export default EStyleSheet.create({
|
||||
},
|
||||
autocomplateLabelContainer: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
justifyContent: 'center',
|
||||
color: '$primaryBlack',
|
||||
maxWidth: '$deviceWidth / 2.9',
|
||||
|
@ -3,7 +3,6 @@ import { injectIntl } from 'react-intl';
|
||||
import { Text, View, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import get from 'lodash/get';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import Autocomplete from '@esteemapp/react-native-autocomplete-input';
|
||||
import { ScaleSlider, TextInput } from '..';
|
||||
import { hsOptions } from '../../constants/hsOptions';
|
||||
@ -23,6 +22,7 @@ import { PROMOTE_PRICING, PROMOTE_DAYS } from '../../constants/options/points';
|
||||
|
||||
// Styles
|
||||
import styles from './promoteStyles';
|
||||
import { OptionsModal } from '../atoms';
|
||||
|
||||
const PromoteView = ({
|
||||
intl,
|
||||
@ -214,7 +214,7 @@ const PromoteView = ({
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={startActionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.confirm' }),
|
||||
|
@ -3,18 +3,20 @@ import ActionSheet from 'react-native-actions-sheet';
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
import styles from './quickReplyModalStyles';
|
||||
import { forwardRef } from 'react';
|
||||
import { View, Text, Alert, TouchableOpacity, Keyboard } from 'react-native';
|
||||
import { View, Text, Alert, TouchableOpacity, Keyboard, Platform } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { IconButton, MainButton, SummaryArea, TextButton, TextInput, UserAvatar } from '..';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { generateReplyPermlink } from '../../utils/editor';
|
||||
import { postComment } from '../../providers/hive/dhive';
|
||||
import { toastNotification } from '../../redux/actions/uiAction';
|
||||
import { updateCommentCache } from '../../redux/actions/cacheActions';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { default as ROUTES } from '../../constants/routeNames';
|
||||
import get from 'lodash/get';
|
||||
import { navigate } from '../../navigation/service';
|
||||
import { Portal } from 'react-native-portalize';
|
||||
import { renderPostBody } from '@ecency/render-helper';
|
||||
|
||||
export interface QuickReplyModalProps {}
|
||||
|
||||
@ -132,6 +134,24 @@ const QuickReplyModal = ({}: QuickReplyModalProps, ref) => {
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
//add comment cache entry
|
||||
dispatch(
|
||||
updateCommentCache(
|
||||
`${parentAuthor}/${parentPermlink}`,
|
||||
{
|
||||
author:currentAccount.name,
|
||||
permlink,
|
||||
parent_author:parentAuthor,
|
||||
parent_permlink:parentPermlink,
|
||||
markdownBody: commentValue,
|
||||
},
|
||||
{
|
||||
parentTags: parentTags || ['ecency']
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
clearTimeout(stateTimer);
|
||||
}, 3000);
|
||||
})
|
||||
@ -166,6 +186,8 @@ const QuickReplyModal = ({}: QuickReplyModalProps, ref) => {
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
const _renderSummary = () => (
|
||||
<TouchableOpacity onPress={() => _handleOnSummaryPress()}>
|
||||
<SummaryArea style={styles.summaryStyle} summary={selectedPost.summary} />
|
||||
|
@ -19,6 +19,7 @@ export default EStyleSheet.create({
|
||||
paddingLeft: 16,
|
||||
paddingHorizontal: 14,
|
||||
color: '$primaryDarkGray',
|
||||
flex: 1,
|
||||
},
|
||||
rowTextStyle: {
|
||||
fontSize: 12,
|
||||
@ -43,6 +44,7 @@ export default EStyleSheet.create({
|
||||
flexGrow: 1,
|
||||
height: 'auto',
|
||||
width: 150,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
textStyle: {
|
||||
color: '$primaryBlue',
|
||||
|
@ -51,6 +51,7 @@ class SettingsItemView extends PureComponent {
|
||||
textStyle={styles.dropdownText}
|
||||
options={options}
|
||||
onSelect={(e) => handleOnChange(e, type, actionType)}
|
||||
isHasChildIcon
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
} from 'react-native';
|
||||
import { injectIntl, useIntl } from 'react-intl';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import VersionNumber from 'react-native-version-number';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { getStorageType } from '../../../realm/realm';
|
||||
@ -28,6 +27,7 @@ import { getVotingPower } from '../../../utils/manaBar';
|
||||
|
||||
// Styles
|
||||
import styles from './sideMenuStyles';
|
||||
import { OptionsModal } from '../../atoms';
|
||||
|
||||
// Images
|
||||
const SIDE_MENU_BACKGROUND = require('../../../assets/side_menu_background.png');
|
||||
@ -74,12 +74,13 @@ const SideMenuView = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.id === 'refer') {
|
||||
/* if (item.id === 'refer') {
|
||||
const shareUrl = `https://ecency.com/signup?referral=${currentAccount.username}`;
|
||||
Share.share({
|
||||
url: `https://ecency.com/signup?referral=${currentAccount.username}`,
|
||||
message: shareUrl,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} */
|
||||
|
||||
navigateToRoute(item.route);
|
||||
};
|
||||
@ -165,7 +166,7 @@ const SideMenuView = ({
|
||||
<FlatList data={menuItems} keyExtractor={(item) => item.id} renderItem={_renderItem} />
|
||||
</View>
|
||||
<Text style={styles.versionText}>{`v${appVersion}, ${buildVersion}${storageT}`}</Text>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={ActionSheetRef}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'side_menu.logout' }),
|
||||
|
26
src/components/tooltip/tooltipStyles.ts
Normal file
26
src/components/tooltip/tooltipStyles.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
popoverDetails: {
|
||||
flexDirection: 'row',
|
||||
width: '$deviceWidth / 2',
|
||||
borderRadius: 20,
|
||||
padding: 16,
|
||||
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
arrow: {
|
||||
borderTopColor: '$primaryBackgroundColor',
|
||||
},
|
||||
popoverWrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
overlay: {},
|
||||
popoverText: {
|
||||
color: '$primaryDarkText',
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
73
src/components/tooltip/tooltipView.tsx
Normal file
73
src/components/tooltip/tooltipView.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { View, Text, findNodeHandle, NativeModules } from 'react-native';
|
||||
import { Popover } from 'react-native-modal-popover';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { registerTooltip } from '../../redux/actions/walkthroughActions';
|
||||
import { Walkthrough } from '../../redux/reducers/walkthroughReducer';
|
||||
|
||||
import styles from './tooltipStyles';
|
||||
interface TooltipProps {
|
||||
children?: any;
|
||||
text?: string;
|
||||
walkthroughIndex?: number;
|
||||
}
|
||||
const Tooltip = ({ children, text, walkthroughIndex }: TooltipProps, ref) => {
|
||||
const dispatch = useDispatch();
|
||||
const tooltipState = useSelector((state) => state.walkthrough.walkthroughMap);
|
||||
const tooltipRegistered = tooltipState.get(walkthroughIndex);
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverAnchor, setPopoverAnchor] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
||||
const touchableRef = useRef(null);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
openTooltip() {
|
||||
if (!tooltipRegistered || (tooltipRegistered && !tooltipRegistered.isShown)) {
|
||||
setShowPopover(true);
|
||||
}
|
||||
},
|
||||
closeTooltip() {
|
||||
if (!tooltipRegistered || (tooltipRegistered && !tooltipRegistered.isShown)) {
|
||||
const walkthrough: Walkthrough = {
|
||||
walkthroughIndex: walkthroughIndex,
|
||||
isShown: true,
|
||||
};
|
||||
dispatch(registerTooltip(walkthrough));
|
||||
}
|
||||
setShowPopover(false);
|
||||
},
|
||||
}));
|
||||
|
||||
const _findAnchor = (e) => {
|
||||
if (touchableRef.current) {
|
||||
NativeModules.UIManager.measure(touchableRef.current, (x0, y0, width, height, x, y) => {
|
||||
setPopoverAnchor({ x, y, width, height });
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
ref={(ref) => {
|
||||
touchableRef.current = findNodeHandle(ref);
|
||||
}}
|
||||
onLayout={_findAnchor}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
<Popover
|
||||
backgroundStyle={styles.overlay}
|
||||
contentStyle={styles.popoverDetails}
|
||||
arrowStyle={styles.arrow}
|
||||
visible={showPopover}
|
||||
onClose={() => ref?.current?.closeTooltip()}
|
||||
fromRect={popoverAnchor}
|
||||
supportedOrientations={['portrait', 'landscape']}
|
||||
|
||||
>
|
||||
<Text>{text}</Text>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(Tooltip as any);
|
@ -3,13 +3,17 @@ import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
export default EStyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 20,
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 16,
|
||||
height: 40,
|
||||
marginVertical: 8,
|
||||
},
|
||||
leftPart: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
color: '$primaryBlack',
|
||||
height: 40,
|
||||
},
|
||||
text: {
|
||||
color: '$primaryBlack',
|
||||
@ -18,6 +22,6 @@ export default EStyleSheet.create({
|
||||
},
|
||||
rightPart: {
|
||||
flex: 2,
|
||||
padding: 10,
|
||||
// padding: 10,
|
||||
},
|
||||
});
|
||||
|
@ -7,8 +7,8 @@ import styles from './transferFormItemStyles';
|
||||
* @prop { type } name - Description....
|
||||
*/
|
||||
|
||||
const TransferFormItemView = ({ rightComponent, label }) => (
|
||||
<View style={styles.container}>
|
||||
const TransferFormItemView = ({ rightComponent, label, containerStyle }) => (
|
||||
<View style={[styles.container, containerStyle]}>
|
||||
<View style={styles.leftPart}>{label && <Text style={styles.text}>{label}</Text>}</View>
|
||||
<View style={styles.rightPart}>{rightComponent && rightComponent()}</View>
|
||||
</View>
|
||||
|
@ -2,7 +2,7 @@ import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'rea
|
||||
import { useIntl } from 'react-intl';
|
||||
import {Text, View, FlatList, RefreshControl, TouchableOpacity, Alert, Platform } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { IconButton } from '..';
|
||||
import { CheckBox, MainButton, TextButton } from '..';
|
||||
import { UploadedMedia } from '../../models';
|
||||
import { addImage, deleteImage, getImages } from '../../providers/ecency/ecency';
|
||||
import Modal from '../modal';
|
||||
@ -21,7 +21,7 @@ interface MediaInsertData {
|
||||
|
||||
interface UploadsGalleryModalProps {
|
||||
username:string;
|
||||
handleOnSelect:(data:MediaInsertData)=>void;
|
||||
handleOnSelect:(data:Array<MediaInsertData>)=>void;
|
||||
uploadedImage:MediaInsertData;
|
||||
}
|
||||
|
||||
@ -30,7 +30,8 @@ export const UploadsGalleryModal = forwardRef(({username, handleOnSelect, uploa
|
||||
|
||||
const [mediaUploads, setMediaUploads] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [indices, setIndices] = useState<Map<number, boolean>>(new Map());
|
||||
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@ -44,6 +45,12 @@ export const UploadsGalleryModal = forwardRef(({username, handleOnSelect, uploa
|
||||
_getMediaUploads();
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
if(!showModal){
|
||||
setIndices(new Map());
|
||||
}
|
||||
}, [showModal])
|
||||
|
||||
useEffect(() => {
|
||||
if(uploadedImage){
|
||||
_addUploadedImageToGallery();
|
||||
@ -67,11 +74,14 @@ export const UploadsGalleryModal = forwardRef(({username, handleOnSelect, uploa
|
||||
|
||||
|
||||
// remove image data from user's gallery
|
||||
const _deleteMediaItem = async (id:string) => {
|
||||
const _deleteMedia = async () => {
|
||||
try{
|
||||
setIsLoading(true);
|
||||
await deleteImage(id)
|
||||
for (const index of indices.keys()) {
|
||||
await deleteImage(mediaUploads[index]._id)
|
||||
}
|
||||
await _getMediaUploads();
|
||||
setIndices(new Map());
|
||||
setIsLoading(false);
|
||||
} catch(err){
|
||||
console.warn("failed to remove image from gallery", err)
|
||||
@ -92,45 +102,107 @@ export const UploadsGalleryModal = forwardRef(({username, handleOnSelect, uploa
|
||||
setIsLoading(false);
|
||||
}
|
||||
}catch(err){
|
||||
console.warn("Failed to get snippets")
|
||||
console.warn("Failed to get images")
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//render list item for snippet and handle actions;
|
||||
const _renderItem = ({ item }:{item:UploadedMedia, index:number}) => {
|
||||
|
||||
const _onPress = () => {
|
||||
//inserts media items in post body
|
||||
const _insertMedia = async (selectedIndex?:number) => {
|
||||
const map = selectedIndex ? new Map([[selectedIndex, true]]) : indices;
|
||||
|
||||
const data = {
|
||||
url:item.url,
|
||||
hash:item.url.split('/').pop()
|
||||
}
|
||||
const data = []
|
||||
for (const index of map.keys()) {
|
||||
console.log(index)
|
||||
const item = mediaUploads[index]
|
||||
|
||||
data.push({
|
||||
url:item.url,
|
||||
hash:item.url.split('/').pop()
|
||||
})
|
||||
|
||||
}
|
||||
handleOnSelect(data)
|
||||
setShowModal(false);
|
||||
}
|
||||
|
||||
const _onRemovePress = async () => {
|
||||
const _onConfirm = () => {
|
||||
_deleteMediaItem(item._id)
|
||||
//renders footer with add snipept button and shows new snippet modal
|
||||
const _renderFloatingPanel = () => {
|
||||
|
||||
if(!indices.size){
|
||||
return null
|
||||
}
|
||||
|
||||
const _onRemovePress = async () => {
|
||||
const _onConfirm = () => {
|
||||
_deleteMedia()
|
||||
}
|
||||
Alert.alert(
|
||||
intl.formatMessage({id:'alert.delete'}),
|
||||
intl.formatMessage({id:'alert.remove_alert'}),
|
||||
[{
|
||||
text:intl.formatMessage({id:'alert.cancel'}),
|
||||
style:'cancel'
|
||||
},{
|
||||
text:intl.formatMessage({id:'alert.confirm'}),
|
||||
onPress:_onConfirm
|
||||
}]
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.floatingContainer}>
|
||||
<TextButton
|
||||
style={styles.cancelButton}
|
||||
onPress={_onRemovePress}
|
||||
text={intl.formatMessage({
|
||||
id: 'uploads_modal.btn_delete',
|
||||
})}
|
||||
/>
|
||||
<MainButton
|
||||
style={{ width: 136, marginLeft:12}}
|
||||
onPress={_insertMedia}
|
||||
iconName="plus"
|
||||
iconType="MaterialCommunityIcons"
|
||||
iconColor="white"
|
||||
text={intl.formatMessage({
|
||||
id: 'uploads_modal.btn_insert',
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//render list item for snippet and handle actions;
|
||||
const _renderItem = ({ item, index }:{item:UploadedMedia, index:number}) => {
|
||||
|
||||
const _onCheckPress = () => {
|
||||
//update selection indices
|
||||
if(indices.has(index)){
|
||||
indices.delete(index);
|
||||
}else {
|
||||
indices.set(index, true);
|
||||
}
|
||||
Alert.alert(
|
||||
intl.formatMessage({id:'alert.delete'}),
|
||||
intl.formatMessage({id:'alert.remove_alert'}),
|
||||
[{
|
||||
text:intl.formatMessage({id:'alert.cancel'}),
|
||||
style:'cancel'
|
||||
},{
|
||||
text:intl.formatMessage({id:'alert.confirm'}),
|
||||
onPress:_onConfirm
|
||||
}]
|
||||
)
|
||||
|
||||
|
||||
setIndices(new Map([...indices]));
|
||||
}
|
||||
|
||||
const _onPress = () => {
|
||||
|
||||
if(indices.size){
|
||||
_onCheckPress()
|
||||
}else {
|
||||
_insertMedia(index)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const thumbUrl = proxifyImageSrc(item.url, 600, 500, Platform.OS === 'ios' ? 'match' : 'webp');
|
||||
|
||||
return (
|
||||
@ -139,14 +211,11 @@ export const UploadsGalleryModal = forwardRef(({username, handleOnSelect, uploa
|
||||
source={{uri:thumbUrl}}
|
||||
style={styles.mediaItem}
|
||||
/>
|
||||
<View style={styles.removeItemContainer}>
|
||||
<IconButton
|
||||
iconStyle={styles.itemIcon}
|
||||
style={styles.itemIconWrapper}
|
||||
iconType="MaterialCommunityIcons"
|
||||
name="delete"
|
||||
onPress={_onRemovePress}
|
||||
size={20}
|
||||
<View style={styles.checkContainer}>
|
||||
<CheckBox
|
||||
isChecked={indices.has(index)}
|
||||
clicked={_onCheckPress}
|
||||
style={styles.checkStyle}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@ -176,6 +245,7 @@ export const UploadsGalleryModal = forwardRef(({username, handleOnSelect, uploa
|
||||
keyExtractor={(item) => `item_${item.url}`}
|
||||
renderItem={_renderItem}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
extraData={indices}
|
||||
numColumns={2}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
@ -185,6 +255,7 @@ export const UploadsGalleryModal = forwardRef(({username, handleOnSelect, uploa
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
{_renderFloatingPanel()}
|
||||
</View>
|
||||
)
|
||||
|
||||
|
@ -22,11 +22,17 @@ export default EStyleSheet.create({
|
||||
paddingHorizontal:16
|
||||
},
|
||||
floatingContainer:{
|
||||
flexDirection:'row',
|
||||
position:'absolute',
|
||||
bottom:0,
|
||||
right:20,
|
||||
right:0,
|
||||
left: 0,
|
||||
justifyContent:'flex-end',
|
||||
zIndex:10
|
||||
alignItems:'center',
|
||||
zIndex:10,
|
||||
paddingVertical:8,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor:'$primaryBackgroundColor'
|
||||
} as ViewStyle,
|
||||
|
||||
mediaItem:{
|
||||
@ -95,9 +101,14 @@ export default EStyleSheet.create({
|
||||
|
||||
} as ViewStyle,
|
||||
|
||||
removeItemContainer:{
|
||||
checkContainer:{
|
||||
position:'absolute',
|
||||
top:16,
|
||||
right:16
|
||||
} as ViewStyle
|
||||
})
|
||||
bottom:20,
|
||||
right:20
|
||||
} as ViewStyle,
|
||||
|
||||
checkStyle:{
|
||||
backgroundColor:'$white',
|
||||
} as ViewStyle,
|
||||
|
||||
})
|
||||
|
@ -73,7 +73,8 @@ const UpvoteContainer = (props) => {
|
||||
if (
|
||||
lastCacheUpdate &&
|
||||
lastCacheUpdate.postPath === postPath &&
|
||||
content.post_fetched_at < lastCacheUpdate.updatedAt
|
||||
content.post_fetched_at < lastCacheUpdate.updatedAt &&
|
||||
lastCacheUpdate.type === 'vote'
|
||||
) {
|
||||
_handleCachedVote();
|
||||
}
|
||||
|
5
src/components/videoPlayer/videoPlayerStyles.ts
Normal file
5
src/components/videoPlayer/videoPlayerStyles.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
|
||||
});
|
235
src/components/videoPlayer/videoPlayerView.tsx
Normal file
235
src/components/videoPlayer/videoPlayerView.tsx
Normal file
@ -0,0 +1,235 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Dimensions } from 'react-native';
|
||||
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
||||
import WebView from 'react-native-webview';
|
||||
import YoutubeIframe, { InitialPlayerParams } from 'react-native-youtube-iframe';
|
||||
import Video from 'react-native-video';
|
||||
import MediaControls, { PLAYER_STATES } from 'react-native-media-controls';
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
interface VideoPlayerProps {
|
||||
mode: 'uri' | 'youtube';
|
||||
contentWidth?: number;
|
||||
youtubeVideoId?: string;
|
||||
startTime?: number;
|
||||
uri?: string;
|
||||
//prop for youtube player
|
||||
disableAutoplay?: boolean;
|
||||
}
|
||||
|
||||
const VideoPlayer = ({
|
||||
youtubeVideoId,
|
||||
startTime,
|
||||
uri,
|
||||
contentWidth = Dimensions.get('screen').width,
|
||||
mode,
|
||||
disableAutoplay,
|
||||
}: VideoPlayerProps) => {
|
||||
const PLAYER_HEIGHT = contentWidth * (9 / 16);
|
||||
const checkSrcRegex = /(.*?)\.(mp4|webm|ogg)$/gi;
|
||||
const isExtensionType = mode === 'uri' ? uri.match(checkSrcRegex) : false;
|
||||
|
||||
const videoPlayer = useRef(null);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [paused, setPaused] = useState(true);
|
||||
const [playerState, setPlayerState] = useState(PLAYER_STATES.PAUSED);
|
||||
const [screenType, setScreenType] = useState('contain');
|
||||
|
||||
// react-native-youtube-iframe handlers
|
||||
const [shouldPlay, setShouldPlay] = useState(false);
|
||||
const _onReady = () => {
|
||||
setIsLoading(false);
|
||||
setShouldPlay(disableAutoplay ? false : true);
|
||||
console.log('ready');
|
||||
};
|
||||
|
||||
const _onChangeState = (event: string) => {
|
||||
console.log(event);
|
||||
setShouldPlay(!(event == 'paused' || event == 'ended'));
|
||||
};
|
||||
|
||||
const _onError = () => {
|
||||
console.log('error!');
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const initialParams: InitialPlayerParams = {
|
||||
start: startTime,
|
||||
};
|
||||
|
||||
// react-native-video player handlers
|
||||
const onSeek = (seek) => {
|
||||
videoPlayer.current.seek(seek);
|
||||
};
|
||||
|
||||
const onPaused = (playerState) => {
|
||||
setPaused(!paused);
|
||||
setPlayerState(playerState);
|
||||
};
|
||||
|
||||
const onReplay = () => {
|
||||
setPlayerState(PLAYER_STATES.PLAYING);
|
||||
videoPlayer.current.seek(0);
|
||||
};
|
||||
|
||||
const onProgress = (data) => {
|
||||
if (!isLoading && playerState !== PLAYER_STATES.ENDED) {
|
||||
setCurrentTime(data.currentTime);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoad = (data) => {
|
||||
setDuration(data.duration);
|
||||
videoPlayer.current.seek(0);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const onLoadStart = () => setIsLoading(true);
|
||||
|
||||
const onEnd = () => setPlayerState(PLAYER_STATES.ENDED);
|
||||
|
||||
const onError = () => alert('Error while playing');
|
||||
|
||||
const exitFullScreen = () => {
|
||||
setIsFullScreen(false);
|
||||
};
|
||||
|
||||
const enterFullScreen = () => {
|
||||
setIsFullScreen(true);
|
||||
};
|
||||
|
||||
const onFullScreen = () => {
|
||||
setIsFullScreen(true);
|
||||
if (screenType == 'contain') setScreenType('cover');
|
||||
else setScreenType('contain');
|
||||
};
|
||||
|
||||
const onSeeking = (currentTime) => setCurrentTime(currentTime);
|
||||
|
||||
const _renderVideoplayerWithControls = () => {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Video
|
||||
source={{
|
||||
uri: uri,
|
||||
}}
|
||||
onEnd={onEnd}
|
||||
onLoad={onLoad}
|
||||
onLoadStart={onLoadStart}
|
||||
onProgress={onProgress}
|
||||
onError={onError}
|
||||
paused={paused}
|
||||
ref={videoPlayer}
|
||||
resizeMode={'cover'}
|
||||
fullscreen={isFullScreen}
|
||||
style={styles.mediaPlayer}
|
||||
volume={10}
|
||||
onFullscreenPlayerDidPresent={enterFullScreen}
|
||||
onFullscreenPlayerDidDismiss={exitFullScreen}
|
||||
/>
|
||||
<MediaControls
|
||||
duration={duration}
|
||||
isLoading={isLoading}
|
||||
mainColor={'#3c4449'}
|
||||
onFullScreen={onFullScreen}
|
||||
onPaused={onPaused}
|
||||
onReplay={onReplay}
|
||||
onSeek={onSeek}
|
||||
onSeeking={onSeeking}
|
||||
playerState={playerState}
|
||||
progress={currentTime}
|
||||
isFullScreen={isFullScreen}
|
||||
fadeOutDelay={paused ? Number.MAX_VALUE : 3000}
|
||||
containerStyle={{}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{mode === 'youtube' && youtubeVideoId && (
|
||||
<View style={{ width: contentWidth, height: PLAYER_HEIGHT }}>
|
||||
<YoutubeIframe
|
||||
height={PLAYER_HEIGHT}
|
||||
videoId={youtubeVideoId}
|
||||
initialPlayerParams={initialParams}
|
||||
onReady={_onReady}
|
||||
play={shouldPlay}
|
||||
onChangeState={_onChangeState}
|
||||
onError={_onError}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{mode === 'uri' && uri && (
|
||||
<View style={[styles.playerWrapper, { height: PLAYER_HEIGHT }]}>
|
||||
{isExtensionType ? (
|
||||
_renderVideoplayerWithControls()
|
||||
) : (
|
||||
<WebView
|
||||
scalesPageToFit={true}
|
||||
bounces={false}
|
||||
javaScriptEnabled={true}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
onLoadEnd={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
onLoadStart={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
source={{ uri: uri }}
|
||||
style={[styles.barkBackground, { width: contentWidth, height: PLAYER_HEIGHT }]}
|
||||
startInLoadingState={true}
|
||||
onShouldStartLoadWithRequest={() => true}
|
||||
mediaPlaybackRequiresUserAction={true}
|
||||
allowsInlineMediaPlayback={true}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{isLoading && (
|
||||
<ActivityIndicator size={'large'} color="white" style={styles.activityIndicator} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoPlayer;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingVertical: 16,
|
||||
},
|
||||
activityIndicator: {
|
||||
position: 'absolute',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
top: 25,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
toolbar: {
|
||||
marginTop: 30,
|
||||
backgroundColor: 'white',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
},
|
||||
playerWrapper: {
|
||||
backgroundColor: 'black',
|
||||
},
|
||||
mediaPlayer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
backgroundColor: 'black',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
barkBackground: {
|
||||
backgroundColor: 'black',
|
||||
},
|
||||
});
|
@ -34,7 +34,7 @@ export default EStyleSheet.create({
|
||||
color: '$primaryDarkText',
|
||||
},
|
||||
mainButton: {
|
||||
marginVertical: 8,
|
||||
marginTop: 16,
|
||||
alignSelf: 'center',
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
|
@ -23,6 +23,8 @@ api.interceptors.request.use((request) => {
|
||||
|| request.url === '/auth-api/hs-token-refresh'
|
||||
|| request.url === '/private-api/promoted-entries'
|
||||
|| request.url.startsWith('private-api/leaderboard')
|
||||
|| request.url.startsWith('/private-api/received-vesting/')
|
||||
|| request.url.startsWith('/private-api/referrals/')
|
||||
){
|
||||
return request
|
||||
}
|
||||
|
@ -301,7 +301,6 @@
|
||||
"limited_space": "Use space to separate tags",
|
||||
"limited_lowercase": "Only use lower letters in tag",
|
||||
"limited_characters": "Use only lowercase letters, digits and one dash",
|
||||
"limited_firstchar": "Tag must start with a letter",
|
||||
"limited_lastchar": "Tag must end with letter or number",
|
||||
"setting_schedule": "Scheduling Time",
|
||||
"setting_reward": "Reward",
|
||||
@ -732,5 +731,15 @@
|
||||
},
|
||||
"walkthrough":{
|
||||
"load_draft_tooltip": "Load your lastest draft here"
|
||||
},
|
||||
"refer":{
|
||||
"refer_earn": "Refer & Earn",
|
||||
"refer": "Refer",
|
||||
"rewarded": "Referral points rewarded",
|
||||
"not_rewarded": "Referral reward pending",
|
||||
"delegate_hp": "Delegate HP",
|
||||
"earned": "Earned Points",
|
||||
"pending": "Pending Points",
|
||||
"empty_text": "Refer your loved ones today and earn free points"
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ export default {
|
||||
COMMUNITY: `Community${SCREEN_SUFFIX}`,
|
||||
COMMUNITIES: `Communities${SCREEN_SUFFIX}`,
|
||||
WEB_BROWSER: `WebBrowser${SCREEN_SUFFIX}`,
|
||||
REFER: `Refer${SCREEN_SUFFIX}`,
|
||||
},
|
||||
DRAWER: {
|
||||
MAIN: `Main${DRAWER_SUFFIX}`,
|
||||
|
@ -25,6 +25,12 @@ const authMenuItems = [
|
||||
icon: 'people',
|
||||
id: 'communities',
|
||||
},
|
||||
{
|
||||
name: 'Refer $ Earn',
|
||||
route: ROUTES.SCREENS.REFER,
|
||||
icon: 'share',
|
||||
id: 'refer',
|
||||
},
|
||||
{
|
||||
name: 'Settings',
|
||||
route: ROUTES.SCREENS.SETTINGS,
|
||||
@ -37,12 +43,6 @@ const authMenuItems = [
|
||||
icon: 'power',
|
||||
id: 'logout',
|
||||
},
|
||||
{
|
||||
name: 'Refer $ Earn',
|
||||
route: '',
|
||||
icon: 'share',
|
||||
id: 'refer',
|
||||
},
|
||||
];
|
||||
|
||||
const noAuthMenuItems = [
|
||||
|
@ -439,10 +439,10 @@ class ProfileContainer extends Component {
|
||||
};
|
||||
|
||||
dispatch(
|
||||
showActionModal(
|
||||
intl.formatMessage({ id: 'report.confirm_report_title' }),
|
||||
intl.formatMessage({ id: 'report.confirm_report_body' }),
|
||||
[
|
||||
showActionModal({
|
||||
title: intl.formatMessage({ id: 'report.confirm_report_title' }),
|
||||
body: intl.formatMessage({ id: 'report.confirm_report_body' }),
|
||||
buttons: [
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.cancel' }),
|
||||
onPress: () => {},
|
||||
@ -452,7 +452,7 @@ class ProfileContainer extends Component {
|
||||
onPress: _onConfirm,
|
||||
},
|
||||
],
|
||||
),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -58,7 +58,7 @@ class RedeemContainer extends Component {
|
||||
permlink,
|
||||
...specificParam,
|
||||
});
|
||||
const uriType = redeemType === 'promote' ? 'esteem_promote' : 'esteem_boost';
|
||||
const uriType = redeemType === 'promote' ? 'ecency_promote' : 'ecency_boost';
|
||||
|
||||
const uri = `sign/custom-json?authority=active&required_auths=%5B%22${get(
|
||||
user,
|
||||
|
@ -39,6 +39,7 @@ class TransferContainer extends Component {
|
||||
balance: props.navigation.getParam('balance', ''),
|
||||
tokenAddress: props.navigation.getParam('tokenAddress', ''),
|
||||
transferType: props.navigation.getParam('transferType', ''),
|
||||
referredUsername: props.navigation.getParam('referredUsername'),
|
||||
selectedAccount: props.currentAccount,
|
||||
};
|
||||
}
|
||||
@ -221,14 +222,23 @@ class TransferContainer extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { accounts, navigation, children, hivePerMVests, currentAccount } = this.props;
|
||||
const { balance, fundType, selectedAccount, tokenAddress } = this.state;
|
||||
const {
|
||||
accounts,
|
||||
navigation,
|
||||
children,
|
||||
hivePerMVests,
|
||||
currentAccount,
|
||||
actionModalVisible,
|
||||
dispatch,
|
||||
} = this.props;
|
||||
const { balance, fundType, selectedAccount, tokenAddress, referredUsername } = this.state;
|
||||
|
||||
const transferType = navigation.getParam('transferType', '');
|
||||
|
||||
return (
|
||||
children &&
|
||||
children({
|
||||
dispatch,
|
||||
accounts,
|
||||
balance,
|
||||
tokenAddress,
|
||||
@ -236,6 +246,8 @@ class TransferContainer extends Component {
|
||||
transferType,
|
||||
selectedAccount,
|
||||
hivePerMVests,
|
||||
actionModalVisible,
|
||||
referredUsername,
|
||||
fetchBalance: this.fetchBalance,
|
||||
getAccountsWithUsername: this._getAccountsWithUsername,
|
||||
transferToAccount: this._transferToAccount,
|
||||
@ -253,6 +265,7 @@ const mapStateToProps = (state) => ({
|
||||
currentAccount: state.account.currentAccount,
|
||||
pinCode: state.application.pin,
|
||||
hivePerMVests: state.account.globalProps.hivePerMVests,
|
||||
actionModalVisible: state.ui.actionModalVisible,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(TransferContainer));
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './snippet';
|
||||
export * from './media';
|
||||
export * from './media';
|
||||
export * from './referrals';
|
||||
|
1
src/models/referrals/index.ts
Normal file
1
src/models/referrals/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './referrals';
|
7
src/models/referrals/referrals.ts
Normal file
7
src/models/referrals/referrals.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface Referral {
|
||||
_id: number;
|
||||
referral: string;
|
||||
referredUsername: string;
|
||||
isRewarded: boolean;
|
||||
timestamp: Date;
|
||||
}
|
@ -39,6 +39,7 @@ import {
|
||||
Community,
|
||||
Communities,
|
||||
WebBrowser,
|
||||
ReferScreen,
|
||||
} from '../screens';
|
||||
|
||||
const bottomTabNavigator = createBottomTabNavigator(
|
||||
@ -152,6 +153,7 @@ const stackNavigator = createStackNavigator(
|
||||
[ROUTES.SCREENS.COMMUNITY]: { screen: Community },
|
||||
[ROUTES.SCREENS.COMMUNITIES]: { screen: Communities },
|
||||
[ROUTES.SCREENS.WEB_BROWSER]: { screen: WebBrowser },
|
||||
[ROUTES.SCREENS.REFER]: { screen: ReferScreen },
|
||||
},
|
||||
{
|
||||
headerMode: 'none',
|
||||
|
19
src/providers/ecency/converters.ts
Normal file
19
src/providers/ecency/converters.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Referral } from '../../models';
|
||||
import { ReferralStat } from './ecency.types';
|
||||
|
||||
export const convertReferral = (rawData: any) => {
|
||||
return {
|
||||
_id: rawData.id || 0,
|
||||
referral: rawData.referral || '',
|
||||
referredUsername: rawData.username || '',
|
||||
isRewarded: rawData.rewarded ? true : false,
|
||||
timestamp: new Date(rawData.created) || new Date(),
|
||||
} as Referral;
|
||||
};
|
||||
|
||||
export const convertReferralStat = (rawData: any) => {
|
||||
return {
|
||||
total: rawData.total || 0,
|
||||
rewarded: rawData.rewarded || 0,
|
||||
} as ReferralStat;
|
||||
};
|
@ -6,6 +6,16 @@ import bugsnagInstance from '../../config/bugsnag';
|
||||
import { SERVER_LIST } from '../../constants/options/api';
|
||||
import { parsePost } from '../../utils/postParser';
|
||||
import { extractMetadata, makeJsonMetadata } from '../../utils/editor';
|
||||
import { ReceivedVestingShare, Referral, ReferralStat } from './ecency.types';
|
||||
import { convertReferral, convertReferralStat } from './converters';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ************************************
|
||||
* CURRENCY APIS IMPLEMENTATION
|
||||
* ************************************
|
||||
*/
|
||||
|
||||
export const getCurrencyRate = (currency) =>
|
||||
api
|
||||
@ -27,6 +37,24 @@ export const getCurrencyTokenRate = (currency, token) =>
|
||||
});
|
||||
|
||||
|
||||
export const getReceivedVestingShares = async (username: string):Promise<ReceivedVestingShare[]> => {
|
||||
try{
|
||||
const res = await ecencyApi.get(`/private-api/received-vesting/${username}`);
|
||||
console.log("Vesting Shares User", username, res.data);
|
||||
if(!res.data || !res.data.list){
|
||||
throw new Error("No vesting shares for user")
|
||||
}
|
||||
return res.data.list;
|
||||
} catch (error){
|
||||
bugsnagInstance.notify(error);
|
||||
console.warn(error);
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* returns list of saved drafts on ecency server
|
||||
@ -733,3 +761,45 @@ export const signUp = async (username:string, email:string, referral?:string) =>
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ************************************
|
||||
* REFERRAL API IMPLEMENTATION
|
||||
* ************************************
|
||||
*/
|
||||
|
||||
export const getReferralsList = async (username: string, maxId: number | undefined):Promise<Referral[]> => {
|
||||
try {
|
||||
const res = await ecencyApi.get(`/private-api/referrals/${username}`, {
|
||||
params: {
|
||||
max_id: maxId
|
||||
}
|
||||
});
|
||||
console.log('Referrals List', username, res.data);
|
||||
if (!res.data) {
|
||||
throw new Error('No Referrals for this user!');
|
||||
}
|
||||
const referralsList = res.data.length > 0 ? res.data.map((referralItem: any) => convertReferral(referralItem)) : [];
|
||||
return referralsList;
|
||||
} catch (error) {
|
||||
bugsnagInstance.notify(error);
|
||||
console.warn(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export const getReferralsStats = async (username: string):Promise<ReferralStat> => {
|
||||
try {
|
||||
const res = await ecencyApi.get(`/private-api/referrals/${username}/stats`);
|
||||
console.log('Referrals Stats', username, res.data);
|
||||
if (!res.data) {
|
||||
throw new Error('No Referrals for this user!');
|
||||
}
|
||||
return convertReferralStat(res.data);
|
||||
} catch (error) {
|
||||
bugsnagInstance.notify(error);
|
||||
console.warn(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
18
src/providers/ecency/ecency.types.ts
Normal file
18
src/providers/ecency/ecency.types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface ReceivedVestingShare {
|
||||
delegator:string;
|
||||
delegatee:string;
|
||||
vesting_shares:string;
|
||||
timestamp:string;
|
||||
}
|
||||
export interface Referral {
|
||||
id:number;
|
||||
referral:string;
|
||||
rewarded:boolean;
|
||||
username:string;
|
||||
created:string
|
||||
}
|
||||
|
||||
export interface ReferralStat {
|
||||
total: number;
|
||||
rewarded: number;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import bugsnagInstance from '../../config/bugsnag';
|
||||
import githubApi from '../../config/locales/githubApi';
|
||||
import githubApi from '../../config/githubApi';
|
||||
|
||||
export const fetchLatestAppVersion = async () => {
|
||||
try{
|
||||
|
@ -591,6 +591,8 @@ export const getRepliesByLastUpdate = async (query) => {
|
||||
};
|
||||
|
||||
export const getPost = async (author, permlink, currentUserName = null, isPromoted = false) => {
|
||||
author = author && author.toLowerCase();
|
||||
permlink = permlink && permlink.toLowerCase();
|
||||
try {
|
||||
console.log('Getting post: ', author, permlink);
|
||||
const post = await client.call('bridge', 'get_post', { author, permlink });
|
||||
@ -601,6 +603,8 @@ export const getPost = async (author, permlink, currentUserName = null, isPromot
|
||||
};
|
||||
|
||||
export const isPostAvailable = async (author, permlink) => {
|
||||
author = author && author.toLowerCase();
|
||||
permlink = permlink && permlink.toLowerCase();
|
||||
try {
|
||||
const post = await client.call('bridge', 'get_post', { author, permlink });
|
||||
return get(post, 'post_id', 0) !== 0;
|
||||
@ -610,6 +614,8 @@ export const isPostAvailable = async (author, permlink) => {
|
||||
};
|
||||
|
||||
export const getPurePost = async (author, permlink) => {
|
||||
author = author && author.toLowerCase();
|
||||
permlink = permlink && permlink.toLowerCase();
|
||||
try {
|
||||
return await client.call('bridge', 'get_post', { author, permlink });
|
||||
} catch (error) {
|
||||
@ -1230,6 +1236,59 @@ export const unfollowUser = async (currentAccount, pin, data) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const markHiveNotifications = async (currentAccount, pinHash) => {
|
||||
const digitPinCode = getDigitPinCode(pinHash);
|
||||
const key = getAnyPrivateKey(currentAccount.local, digitPinCode);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const date = now.split('.')[0];
|
||||
|
||||
const params = {
|
||||
id: 'notify',
|
||||
required_auths: [],
|
||||
required_posting_auths: [currentAccount.name],
|
||||
json: JSON.stringify(['setLastRead', { date }]),
|
||||
};
|
||||
const params1 = {
|
||||
id: 'ecency_notify',
|
||||
required_auths: [],
|
||||
required_posting_auths: [currentAccount.name],
|
||||
json: JSON.stringify(['setLastRead', { date }]),
|
||||
};
|
||||
|
||||
const opArray: Operation[] = [
|
||||
['custom_json', params],
|
||||
['custom_json', params1],
|
||||
];
|
||||
|
||||
if (currentAccount.local.authType === AUTH_TYPE.STEEM_CONNECT) {
|
||||
const token = decryptKey(get(currentAccount, 'local.accessToken'), digitPinCode);
|
||||
const api = new hsClient({
|
||||
accessToken: token,
|
||||
});
|
||||
|
||||
return api.broadcast(opArray).then((resp) => resp.result);
|
||||
}
|
||||
|
||||
if (key) {
|
||||
const privateKey = PrivateKey.fromString(key);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
sendHiveOperations(opArray, privateKey)
|
||||
.then((result) => {
|
||||
resolve(result);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(
|
||||
new Error('Check private key permission! Required private posting key or above.'),
|
||||
);
|
||||
};
|
||||
|
||||
export const lookupAccounts = async (username) => {
|
||||
try {
|
||||
const users = await client.database.call('lookup_accounts', [username, 20]);
|
||||
@ -1319,7 +1378,7 @@ export const postComment = (
|
||||
permlink,
|
||||
'',
|
||||
body,
|
||||
makeJsonMetadataReply(parentTags || 'ecency'),
|
||||
makeJsonMetadataReply(parentTags || ['ecency']),
|
||||
null,
|
||||
null,
|
||||
)
|
||||
@ -1562,7 +1621,7 @@ export const transferPoint = (currentAccount, pinCode, data) => {
|
||||
const privateKey = PrivateKey.fromString(key);
|
||||
|
||||
const op = {
|
||||
id: 'esteem_point_transfer',
|
||||
id: 'ecency_point_transfer',
|
||||
json,
|
||||
required_auths: [username],
|
||||
required_posting_auths: [],
|
||||
@ -1585,7 +1644,7 @@ export const promote = (currentAccount, pinCode, duration, permlink, author) =>
|
||||
const user = get(currentAccount, 'name');
|
||||
|
||||
const json = {
|
||||
id: 'esteem_promote',
|
||||
id: 'ecency_promote',
|
||||
json: JSON.stringify({
|
||||
user,
|
||||
author,
|
||||
@ -1614,7 +1673,7 @@ export const boost = (currentAccount, pinCode, point, permlink, author) => {
|
||||
const user = get(currentAccount, 'name');
|
||||
|
||||
const json = {
|
||||
id: 'esteem_boost',
|
||||
id: 'ecency_boost',
|
||||
json: JSON.stringify({
|
||||
user,
|
||||
author,
|
||||
|
@ -1,16 +1,74 @@
|
||||
import { renderPostBody } from '@ecency/render-helper';
|
||||
import { Platform } from 'react-native';
|
||||
import { makeJsonMetadataReply } from '../../utils/editor';
|
||||
import {
|
||||
UPDATE_VOTE_CACHE,
|
||||
PURGE_EXPIRED_CACHE
|
||||
PURGE_EXPIRED_CACHE,
|
||||
UPDATE_COMMENT_CACHE,
|
||||
DELETE_COMMENT_CACHE_ENTRY
|
||||
} from '../constants/constants';
|
||||
import { Vote } from '../reducers/cacheReducer';
|
||||
import { Comment, Vote } from '../reducers/cacheReducer';
|
||||
|
||||
|
||||
|
||||
|
||||
export const updateVoteCache = (postPath:string, vote:Vote) => ({
|
||||
payload:{
|
||||
postPath,
|
||||
vote
|
||||
},
|
||||
type: UPDATE_VOTE_CACHE
|
||||
payload:{
|
||||
postPath,
|
||||
vote
|
||||
},
|
||||
type: UPDATE_VOTE_CACHE
|
||||
})
|
||||
|
||||
|
||||
interface CommentCacheOptions {
|
||||
isUpdate?:boolean;
|
||||
parentTags?:Array<string>;
|
||||
}
|
||||
|
||||
export const updateCommentCache = (commentPath:string, comment:Comment, options:CommentCacheOptions = {isUpdate:false}) => {
|
||||
|
||||
console.log("body received:", comment.markdownBody);
|
||||
const updated = new Date();
|
||||
updated.setSeconds(updated.getSeconds() - 5); //make cache delayed by 5 seconds to avoid same updated stamp in post data
|
||||
const updatedStamp = updated.toISOString().substring(0, 19); //server only return 19 character time string without timezone part
|
||||
|
||||
if(options.isUpdate && !comment.created){
|
||||
throw new Error("For comment update, created prop must be provided from original comment data to update local cache");
|
||||
}
|
||||
|
||||
if(!options.parentTags && !comment.json_metadata){
|
||||
throw new Error("either of json_metadata in comment data or parentTags in options must be provided");
|
||||
}
|
||||
|
||||
comment.created = comment.created || updatedStamp; //created will be set only once for new comment;
|
||||
comment.updated = comment.updated || updatedStamp;
|
||||
comment.expiresAt = comment.expiresAt || updated.getTime() + 6000000;//600000;
|
||||
comment.active_votes = comment.active_votes || [];
|
||||
comment.net_rshares = comment.net_rshares || 0;
|
||||
comment.author_reputation = comment.author_reputation || 25;
|
||||
comment.total_payout = comment.total_payout || 0;
|
||||
comment.json_metadata = comment.json_metadata || makeJsonMetadataReply(options.parentTags)
|
||||
|
||||
comment.body = renderPostBody({
|
||||
author:comment.author,
|
||||
permlink:comment.permlink,
|
||||
last_update:comment.updated,
|
||||
body:comment.markdownBody,
|
||||
}, true, Platform.OS === 'android');
|
||||
|
||||
return ({
|
||||
payload:{
|
||||
commentPath,
|
||||
comment
|
||||
},
|
||||
type: UPDATE_COMMENT_CACHE
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteCommentCacheEntry = (commentPath:string) => ({
|
||||
payload:commentPath,
|
||||
type: DELETE_COMMENT_CACHE_ENTRY
|
||||
})
|
||||
|
||||
export const purgeExpiredCache = () => ({
|
||||
|
@ -23,15 +23,11 @@ export const toastNotification = (payload:string) => ({
|
||||
type: TOAST_NOTIFICATION,
|
||||
});
|
||||
|
||||
export const showActionModal = (title:string, body?:string, buttons?:AlertButton[], headerImage?:any, onClosed?:()=>void) => ({
|
||||
export const showActionModal = (payload:any) => ({
|
||||
payload: {
|
||||
actionModalVisible: new Date().getTime(),
|
||||
actionModalData: {
|
||||
title,
|
||||
body,
|
||||
buttons,
|
||||
headerImage,
|
||||
onClosed,
|
||||
...payload
|
||||
},
|
||||
},
|
||||
type: SHOW_ACTION_MODAL,
|
||||
|
7
src/redux/actions/walkthroughActions.ts
Normal file
7
src/redux/actions/walkthroughActions.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { REGISTER_TOOLTIP } from '../constants/constants';
|
||||
import { Walkthrough } from '../reducers/walkthroughReducer';
|
||||
|
||||
export const registerTooltip = (walkthrough: Walkthrough) => ({
|
||||
payload: walkthrough,
|
||||
type: REGISTER_TOOLTIP,
|
||||
});
|
@ -107,3 +107,8 @@ export const TEMP_BENEFICIARIES_ID = 'temp-beneficiaries';
|
||||
//CACHE
|
||||
export const PURGE_EXPIRED_CACHE = 'PURGE_EXPIRED_CACHE';
|
||||
export const UPDATE_VOTE_CACHE = 'UPDATE_VOTE_CACHE';
|
||||
export const UPDATE_COMMENT_CACHE = 'UPDATE_COMMENT_CACHE';
|
||||
export const DELETE_COMMENT_CACHE_ENTRY = 'DELETE_COMMENT_CACHE_ENTRY';
|
||||
|
||||
// TOOLTIPS
|
||||
export const REGISTER_TOOLTIP = 'REGISTER_TOOLTIP';
|
||||
|
3
src/redux/constants/walkthroughConstants.ts
Normal file
3
src/redux/constants/walkthroughConstants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const walkthrough = {
|
||||
EDITOR_DRAFT_BTN: 1,
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { PURGE_EXPIRED_CACHE, UPDATE_VOTE_CACHE } from "../constants/constants";
|
||||
import { PURGE_EXPIRED_CACHE, UPDATE_VOTE_CACHE, UPDATE_COMMENT_CACHE, DELETE_COMMENT_CACHE_ENTRY } from "../constants/constants";
|
||||
|
||||
export interface Vote {
|
||||
amount:number;
|
||||
@ -8,16 +8,36 @@ export interface Vote {
|
||||
expiresAt:number;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
author:string,
|
||||
permlink:string,
|
||||
parent_author:string,
|
||||
parent_permlink:string,
|
||||
body?:string,
|
||||
markdownBody:string,
|
||||
author_reputation?:number,
|
||||
total_payout?:number,
|
||||
net_rshares?:number,
|
||||
active_votes?:Array<{rshares:number, voter:string}>,
|
||||
json_metadata?:any,
|
||||
created?:string, //handle created and updated separatly
|
||||
updated?:string,
|
||||
expiresAt?:number,
|
||||
}
|
||||
|
||||
interface State {
|
||||
votes:Map<string, Vote>
|
||||
comments:Map<string, Comment> //TODO: handle comment array per post, if parent is same
|
||||
lastUpdate:{
|
||||
postPath:string,
|
||||
updatedAt:number,
|
||||
type:'vote'|'comment',
|
||||
}
|
||||
}
|
||||
|
||||
const initialState:State = {
|
||||
votes:new Map(),
|
||||
comments:new Map(),
|
||||
lastUpdate:null,
|
||||
};
|
||||
|
||||
@ -33,19 +53,49 @@ const initialState:State = {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate:{
|
||||
postPath:payload.postPath,
|
||||
updatedAt: new Date().getTime()
|
||||
updatedAt: new Date().getTime(),
|
||||
type:'vote',
|
||||
}
|
||||
};
|
||||
|
||||
case UPDATE_COMMENT_CACHE:
|
||||
if(!state.comments){
|
||||
state.comments = new Map<string, Comment>();
|
||||
}
|
||||
state.comments.set(payload.commentPath, payload.comment);
|
||||
return {
|
||||
...state, //spread operator in requried here, otherwise persist do not register change
|
||||
lastUpdate:{
|
||||
postPath:payload.commentPath,
|
||||
updatedAt: new Date().getTime(),
|
||||
type:'comment'
|
||||
}
|
||||
};
|
||||
|
||||
case DELETE_COMMENT_CACHE_ENTRY:
|
||||
if(state.comments && state.comments.has(payload)){
|
||||
state.comments.delete(payload);
|
||||
}
|
||||
return { ...state }
|
||||
|
||||
case PURGE_EXPIRED_CACHE:
|
||||
const currentTime = new Date().getTime();
|
||||
|
||||
if(state.votes && state.votes.entries){
|
||||
if(state.votes && state.votes.size){
|
||||
Array.from(state.votes).forEach((entry)=>{
|
||||
if(entry[1].expiresAt < currentTime){
|
||||
state.votes.delete(entry[0]);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(state.comments && state.comments.size){
|
||||
Array.from(state.comments).forEach((entry)=>{
|
||||
if(entry[1].expiresAt < currentTime){
|
||||
state.comments.delete(entry[0]);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...state
|
||||
|
@ -9,6 +9,7 @@ import user from './userReducer';
|
||||
import customTabsReducer from './customTabsReducer';
|
||||
import editorReducer from './editorReducer';
|
||||
import cacheReducer from './cacheReducer';
|
||||
import walkthroughReducer from './walkthroughReducer';
|
||||
|
||||
export default combineReducers({
|
||||
account: accountReducer,
|
||||
@ -21,4 +22,5 @@ export default combineReducers({
|
||||
communities,
|
||||
user,
|
||||
cache: cacheReducer,
|
||||
walkthrough: walkthroughReducer,
|
||||
});
|
||||
|
31
src/redux/reducers/walkthroughReducer.ts
Normal file
31
src/redux/reducers/walkthroughReducer.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { REGISTER_TOOLTIP } from '../constants/constants';
|
||||
|
||||
export interface WalkthroughItem {
|
||||
walkthroughIndex:number,
|
||||
isShown?:boolean,
|
||||
}
|
||||
interface State {
|
||||
walkthroughMap: Map<number, WalkthroughItem>
|
||||
}
|
||||
|
||||
const initialState:State = {
|
||||
walkthroughMap:new Map(),
|
||||
};
|
||||
export default function (state = initialState, action) {
|
||||
console.log('action : ', action);
|
||||
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case REGISTER_TOOLTIP:
|
||||
if(!state.walkthroughMap){
|
||||
state.walkthroughMap = new Map<number, WalkthroughItem>();
|
||||
}
|
||||
state.walkthroughMap.set(payload.walkthroughIndex, payload);
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
@ -7,11 +7,25 @@ import Reactotron from '../../../reactotron-config';
|
||||
import reducer from '../reducers';
|
||||
|
||||
const transformCacheVoteMap = createTransform(
|
||||
(inboundState:any) => ({ ...inboundState, votes : Array.from(inboundState.votes)}),
|
||||
(outboundState) => ({ ...outboundState, votes:new Map(outboundState.votes)}),
|
||||
(inboundState:any) => ({
|
||||
...inboundState,
|
||||
votes : Array.from(inboundState.votes),
|
||||
comments : Array.from(inboundState.comments)
|
||||
}),
|
||||
(outboundState) => ({
|
||||
...outboundState,
|
||||
votes:new Map(outboundState.votes),
|
||||
comments:new Map(outboundState.comments)
|
||||
}),
|
||||
{whitelist:['cache']}
|
||||
);
|
||||
|
||||
const transformWalkthroughMap = createTransform(
|
||||
(inboundState:any) => ({ ...inboundState, walkthroughMap : Array.from(inboundState.walkthroughMap)}),
|
||||
(outboundState) => ({ ...outboundState, walkthroughMap:new Map(outboundState.walkthroughMap)}),
|
||||
{whitelist:['walkthrough']}
|
||||
);
|
||||
|
||||
// Middleware: Redux Persist Config
|
||||
const persistConfig = {
|
||||
// Root
|
||||
@ -21,7 +35,7 @@ const persistConfig = {
|
||||
// Blacklist (Don't Save Specific Reducers)
|
||||
blacklist: ['nav', 'application', 'communities', 'user'],
|
||||
timeout: 0,
|
||||
transforms:[transformCacheVoteMap]
|
||||
transforms:[transformCacheVoteMap,transformWalkthroughMap]
|
||||
};
|
||||
|
||||
// Middleware: Redux Persist Persisted Reducer
|
||||
|
@ -377,10 +377,13 @@ class ApplicationContainer extends Component {
|
||||
|
||||
if (parseVersionNumber(remoteVersion) > parseVersionNumber(VersionNumber.appVersion)) {
|
||||
dispatch(
|
||||
showActionModal(
|
||||
intl.formatMessage({ id: 'alert.update_available_title' }, { version: remoteVersion }),
|
||||
intl.formatMessage({ id: 'alert.update_available_body' }),
|
||||
[
|
||||
showActionModal({
|
||||
title: intl.formatMessage(
|
||||
{ id: 'alert.update_available_title' },
|
||||
{ version: remoteVersion },
|
||||
),
|
||||
body: intl.formatMessage({ id: 'alert.update_available_body' }),
|
||||
buttons: [
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.remind_later' }),
|
||||
onPress: () => {
|
||||
@ -400,8 +403,8 @@ class ApplicationContainer extends Component {
|
||||
},
|
||||
},
|
||||
],
|
||||
require('../../../assets/phone-holding.png'),
|
||||
),
|
||||
headerImage: require('../../../assets/phone-holding.png'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { withNavigation } from 'react-navigation';
|
||||
import get from 'lodash/get';
|
||||
import { connect } from 'react-redux';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { subscribeCommunity, getCommunity, getSubscriptions } from '../../../providers/hive/dhive';
|
||||
import { getCommunity, getSubscriptions } from '../../../providers/hive/dhive';
|
||||
|
||||
import { subscribeCommunity, leaveCommunity } from '../../../redux/actions/communitiesAction';
|
||||
|
||||
import ROUTES from '../../../constants/routeNames';
|
||||
|
||||
@ -11,6 +14,8 @@ const CommunityContainer = ({ children, navigation, currentAccount, pinCode, isL
|
||||
const [data, setData] = useState(null);
|
||||
const [isSubscribed, setIsSubscribed] = useState(false);
|
||||
const tag = get(navigation, 'state.params.tag');
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
getCommunity(tag)
|
||||
@ -39,17 +44,38 @@ const CommunityContainer = ({ children, navigation, currentAccount, pinCode, isL
|
||||
|
||||
const _handleSubscribeButtonPress = () => {
|
||||
const _data = {
|
||||
isSubscribed: !isSubscribed,
|
||||
isSubscribed: isSubscribed,
|
||||
communityId: data.name,
|
||||
};
|
||||
const screen = 'communitiesScreenDiscoverTab';
|
||||
let subscribeAction;
|
||||
let successToastText = '';
|
||||
let failToastText = '';
|
||||
|
||||
subscribeCommunity(currentAccount, pinCode, _data)
|
||||
.then((result) => {
|
||||
setIsSubscribed(!isSubscribed);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
if (!_data.isSubscribed) {
|
||||
subscribeAction = subscribeCommunity;
|
||||
|
||||
successToastText = intl.formatMessage({
|
||||
id: 'alert.success_subscribe',
|
||||
});
|
||||
failToastText = intl.formatMessage({
|
||||
id: 'alert.fail_subscribe',
|
||||
});
|
||||
} else {
|
||||
subscribeAction = leaveCommunity;
|
||||
|
||||
successToastText = intl.formatMessage({
|
||||
id: 'alert.success_leave',
|
||||
});
|
||||
failToastText = intl.formatMessage({
|
||||
id: 'alert.fail_leave',
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(
|
||||
subscribeAction(currentAccount, pinCode, _data, successToastText, failToastText, screen),
|
||||
);
|
||||
setIsSubscribed(!isSubscribed);
|
||||
};
|
||||
|
||||
const _handleNewPostButtonPress = () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, ScrollView } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
// Components
|
||||
@ -60,7 +60,9 @@ const CommunityScreen = ({ navigation }) => {
|
||||
defaultTitle=""
|
||||
>
|
||||
<View style={styles.collapsibleCard}>
|
||||
<Text style={styles.description}>{data.description}</Text>
|
||||
<ScrollView style={styles.descriptionContainer}>
|
||||
<Text style={styles.description}>{data.description}</Text>
|
||||
</ScrollView>
|
||||
<View style={styles.separator} />
|
||||
<Text style={styles.stats}>
|
||||
{`${data.subscribers} ${intl.formatMessage({
|
||||
|
@ -38,6 +38,9 @@ export default EStyleSheet.create({
|
||||
marginTop: 5,
|
||||
color: '$primaryBlack',
|
||||
},
|
||||
descriptionContainer: {
|
||||
maxHeight: 250,
|
||||
},
|
||||
separator: {
|
||||
width: 100,
|
||||
alignSelf: 'center',
|
||||
@ -54,7 +57,10 @@ export default EStyleSheet.create({
|
||||
borderWidth: 1,
|
||||
borderColor: '$primaryBlue',
|
||||
},
|
||||
collapsibleCard: { alignItems: 'center', marginBottom: 20 },
|
||||
collapsibleCard: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
subscribeButtonText: {
|
||||
color: '$primaryBlue',
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import { Alert } from 'react-native';
|
||||
import { Alert, Platform } from 'react-native';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import get from 'lodash/get';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
@ -36,7 +36,6 @@ import {
|
||||
makeJsonMetadata,
|
||||
makeOptions,
|
||||
extractMetadata,
|
||||
makeJsonMetadataReply,
|
||||
makeJsonMetadataForUpdate,
|
||||
createPatch,
|
||||
extractImageUrls,
|
||||
@ -47,6 +46,7 @@ import EditorScreen from '../screen/editorScreen';
|
||||
import bugsnapInstance from '../../../config/bugsnag';
|
||||
import { removeBeneficiaries, setBeneficiaries } from '../../../redux/actions/editorActions';
|
||||
import { TEMP_BENEFICIARIES_ID } from '../../../redux/constants/constants';
|
||||
import { updateCommentCache } from '../../../redux/actions/cacheActions';
|
||||
|
||||
/*
|
||||
* Props Name Description Value
|
||||
@ -737,7 +737,7 @@ class EditorContainer extends Component {
|
||||
};
|
||||
|
||||
_submitReply = async (fields) => {
|
||||
const { currentAccount, pinCode } = this.props;
|
||||
const { currentAccount, pinCode, dispatch } = this.props;
|
||||
const { isPostSending } = this.state;
|
||||
|
||||
if (isPostSending) {
|
||||
@ -769,6 +769,24 @@ class EditorContainer extends Component {
|
||||
.then(() => {
|
||||
AsyncStorage.setItem('temp-reply', '');
|
||||
this._handleSubmitSuccess();
|
||||
|
||||
//create a cache entry
|
||||
dispatch(
|
||||
updateCommentCache(
|
||||
`${parentAuthor}/${parentPermlink}`,
|
||||
{
|
||||
author:currentAccount.name,
|
||||
permlink,
|
||||
parent_author:parentAuthor,
|
||||
parent_permlink:parentPermlink,
|
||||
markdownBody: fields.body,
|
||||
},
|
||||
{
|
||||
parentTags: parentTags || ['ecency']
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
this._handleSubmitFailure(error);
|
||||
@ -777,8 +795,8 @@ class EditorContainer extends Component {
|
||||
};
|
||||
|
||||
_submitEdit = async (fields) => {
|
||||
const { currentAccount, pinCode } = this.props;
|
||||
const { post, isEdit, isPostSending, thumbIndex } = this.state;
|
||||
const { currentAccount, pinCode, dispatch } = this.props;
|
||||
const { post, isEdit, isPostSending, thumbIndex, isReply } = this.state;
|
||||
|
||||
if (isPostSending) {
|
||||
return;
|
||||
@ -814,6 +832,7 @@ class EditorContainer extends Component {
|
||||
} catch (e) {
|
||||
jsonMeta = makeJsonMetadata(meta, tags);
|
||||
}
|
||||
|
||||
await postContent(
|
||||
currentAccount,
|
||||
pinCode,
|
||||
@ -828,8 +847,31 @@ class EditorContainer extends Component {
|
||||
isEdit,
|
||||
)
|
||||
.then(() => {
|
||||
AsyncStorage.setItem('temp-reply', '');
|
||||
this._handleSubmitSuccess();
|
||||
if(isReply){
|
||||
AsyncStorage.setItem('temp-reply', '');
|
||||
dispatch(
|
||||
updateCommentCache(
|
||||
`${parentAuthor}/${parentPermlink}`,
|
||||
{
|
||||
author:currentAccount.name,
|
||||
permlink,
|
||||
parent_author:parentAuthor,
|
||||
parent_permlink:parentPermlink,
|
||||
markdownBody:body,
|
||||
active_votes:post.active_votes,
|
||||
net_rshares:post.net_rshares,
|
||||
author_reputation:post.author_reputation,
|
||||
total_payout:post.total_payout,
|
||||
created:post.created,
|
||||
json_metadata:jsonMeta
|
||||
},
|
||||
{
|
||||
isUpdate:true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this._handleSubmitFailure(error);
|
||||
|
@ -26,6 +26,7 @@ import Register from './register/registerScreen';
|
||||
import TagResult from './tagResult';
|
||||
import { Community } from './community';
|
||||
import Communities from './communities';
|
||||
import ReferScreen from './referScreen/referScreen';
|
||||
|
||||
export {
|
||||
Bookmarks,
|
||||
@ -56,4 +57,5 @@ export {
|
||||
Community,
|
||||
Communities,
|
||||
WebBrowser,
|
||||
ReferScreen,
|
||||
};
|
||||
|
@ -16,6 +16,8 @@ import ROUTES from '../../../constants/routeNames';
|
||||
// Components
|
||||
import NotificationScreen from '../screen/notificationScreen';
|
||||
import { showProfileModal } from '../../../redux/actions/uiAction';
|
||||
import { markHiveNotifications } from '../../../providers/hive/dhive';
|
||||
import bugsnapInstance from '../../../config/bugsnag';
|
||||
|
||||
class NotificationContainer extends Component {
|
||||
constructor(props) {
|
||||
@ -125,7 +127,7 @@ class NotificationContainer extends Component {
|
||||
};
|
||||
|
||||
_readAllNotification = () => {
|
||||
const { dispatch, intl, isConnected } = this.props;
|
||||
const { dispatch, intl, isConnected, currentAccount, pinCode } = this.props;
|
||||
const { notifications } = this.state;
|
||||
|
||||
if (!isConnected) {
|
||||
@ -138,6 +140,13 @@ class NotificationContainer extends Component {
|
||||
.then(() => {
|
||||
const updatedNotifications = notifications.map((item) => ({ ...item, read: 1 }));
|
||||
dispatch(updateUnreadActivityCount(0));
|
||||
markHiveNotifications(currentAccount, pinCode)
|
||||
.then(() => {
|
||||
console.log('Hive notifications marked as Read');
|
||||
})
|
||||
.catch((err) => {
|
||||
bugsnapInstance.notify(err);
|
||||
});
|
||||
this.setState({ notifications: updatedNotifications, isRefreshing: false });
|
||||
})
|
||||
.catch(() => {
|
||||
@ -161,11 +170,11 @@ class NotificationContainer extends Component {
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const { selectedFilter } = this.state;
|
||||
const { username } = this.props;
|
||||
const { currentAccount } = this.props;
|
||||
|
||||
if (
|
||||
(nextProps.activeBottomTab === ROUTES.TABBAR.NOTIFICATION && nextProps.username) ||
|
||||
(nextProps.username !== username && nextProps.username)
|
||||
(nextProps.activeBottomTab === ROUTES.TABBAR.NOTIFICATION && nextProps.currentAccount.name) ||
|
||||
(nextProps.currentAccount.name !== currentAccount.name && nextProps.currentAccount.name)
|
||||
) {
|
||||
this.setState({ endOfNotification: false }, () => this._getActivities(selectedFilter));
|
||||
}
|
||||
@ -194,8 +203,8 @@ class NotificationContainer extends Component {
|
||||
const mapStateToProps = (state) => ({
|
||||
isLoggedIn: state.application.isLoggedIn,
|
||||
isConnected: state.application.isConnected,
|
||||
|
||||
username: state.account.currentAccount.name,
|
||||
pinCode: state.application.pin,
|
||||
currentAccount: state.account.currentAccount,
|
||||
activeBottomTab: state.ui.activeBottomTab,
|
||||
});
|
||||
|
||||
|
@ -2,11 +2,11 @@ import React, { PureComponent, Fragment } from 'react';
|
||||
import { StatusBar } from 'react-native';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import get from 'lodash/get';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
import { ProfileEditContainer } from '../../../containers';
|
||||
|
||||
import { AvatarHeader, ProfileEditForm } from '../../../components';
|
||||
import { OptionsModal } from '../../../components/atoms';
|
||||
|
||||
class ProfileEditScreen extends PureComponent {
|
||||
/* Props
|
||||
@ -81,7 +81,7 @@ class ProfileEditScreen extends PureComponent {
|
||||
handleOnSubmit={handleOnSubmit}
|
||||
/>
|
||||
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={this.galleryRef}
|
||||
options={[
|
||||
intl.formatMessage({
|
||||
|
248
src/screens/referScreen/referScreen.tsx
Normal file
248
src/screens/referScreen/referScreen.tsx
Normal file
@ -0,0 +1,248 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { ActivityIndicator, FlatList, RefreshControl, Share, Text, TouchableOpacity, View } from 'react-native';
|
||||
import {
|
||||
BasicHeader,
|
||||
Icon,
|
||||
ListPlaceHolder,
|
||||
MainButton,
|
||||
PopoverWrapper,
|
||||
UserListItem,
|
||||
} from '../../components';
|
||||
import get from 'lodash/get';
|
||||
// utils
|
||||
import { getReferralsList, getReferralsStats } from '../../providers/ecency/ecency';
|
||||
import { Referral } from '../../models';
|
||||
|
||||
// styles
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
import styles from './referScreenStyles';
|
||||
|
||||
// constants
|
||||
import ROUTES from '../../constants/routeNames';
|
||||
|
||||
// Redux / Services
|
||||
import { showProfileModal } from '../../redux/actions/uiAction';
|
||||
import { navigate } from '../../navigation/service';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const ReferScreen = ({ navigation }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||
const isDarkTheme = useAppSelector((state)=>state.application.isDarkTheme)
|
||||
|
||||
const [referralsList, setReferralsList] = useState<Referral[]>([]);
|
||||
const [earnedPoints, setEarnedPoint] = useState(0);
|
||||
const [pendingPoints, setPendingPoint] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
_getReferralsStats();
|
||||
_getReferralsList();
|
||||
}, []);
|
||||
|
||||
console.log('-----referralsList----- : ', referralsList);
|
||||
|
||||
const _getReferralsList = async (refresh?:boolean) => {
|
||||
if(refresh){
|
||||
setRefreshing(true);
|
||||
}
|
||||
setLoading(true)
|
||||
|
||||
const lastReferralId = refresh || !referralsList.length
|
||||
? null
|
||||
: referralsList[referralsList.length - 1]._id;
|
||||
|
||||
const responseData = await getReferralsList(currentAccount.name, lastReferralId);
|
||||
|
||||
setReferralsList(refresh ? responseData : [...referralsList, ...responseData])
|
||||
setRefreshing(false);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const _getReferralsStats = async () => {
|
||||
setLoading(true);
|
||||
|
||||
const referralStats = await getReferralsStats(currentAccount.name);
|
||||
const earnedPoints = referralStats.rewarded * 100;
|
||||
const unearnedPoints = (referralStats.total - referralStats.rewarded) * 100;
|
||||
setEarnedPoint(earnedPoints)
|
||||
setPendingPoint(unearnedPoints)
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const _handleRefer = () => {
|
||||
const shareUrl = `https://ecency.com/signup?referral=${currentAccount.username}`;
|
||||
Share.share({
|
||||
message: shareUrl,
|
||||
});
|
||||
};
|
||||
|
||||
const _handleDelegateHP = (item: Referral) => {
|
||||
console.log('delegate HP!');
|
||||
navigate({
|
||||
routeName: ROUTES.SCREENS.TRANSFER,
|
||||
params: {
|
||||
transferType: 'delegate',
|
||||
fundType: 'HIVE_POWER',
|
||||
referredUsername: item.referredUsername,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const _handleOnItemPress = (username:string) => {
|
||||
dispatch(showProfileModal(username));
|
||||
};
|
||||
|
||||
const _renderPointsEarned = () => {
|
||||
return (
|
||||
<View style={styles.pointsContainer}>
|
||||
<View style={styles.pointsEarnedRow}>
|
||||
<View style={styles.earnedWrapper}>
|
||||
<Text style={styles.points}>{earnedPoints}</Text>
|
||||
<Text style={styles.earendText}>
|
||||
{intl.formatMessage({
|
||||
id: 'refer.earned',
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.pendingWrapper}>
|
||||
<Text style={styles.points}>{pendingPoints}</Text>
|
||||
<Text style={styles.earendText}>
|
||||
{intl.formatMessage({
|
||||
id: 'refer.pending',
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<MainButton
|
||||
// isLoading={isLoading}
|
||||
// isDisable={isLoading}
|
||||
style={styles.mainButton}
|
||||
height={50}
|
||||
onPress={_handleRefer}
|
||||
>
|
||||
<View style={styles.mainButtonWrapper}>
|
||||
<Text style={styles.unclaimedText}>
|
||||
{intl.formatMessage({
|
||||
id: 'refer.refer',
|
||||
})}
|
||||
</Text>
|
||||
<View style={styles.mainIconWrapper}>
|
||||
<Icon name="share-social" iconType="Ionicons" color="#fff" size={28} />
|
||||
</View>
|
||||
</View>
|
||||
</MainButton>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const _leftItemRenderer = (item: Referral) => {
|
||||
return (
|
||||
<PopoverWrapper
|
||||
text={
|
||||
item.isRewarded
|
||||
? intl.formatMessage({
|
||||
id: 'refer.rewarded',
|
||||
})
|
||||
: intl.formatMessage({
|
||||
id: 'refer.not_rewarded',
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text style={[styles.dollarSign, item.isRewarded ? styles.blueDollarSign : {}]}>$$</Text>
|
||||
</PopoverWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const _rightItemRenderer = (item: Referral) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.rightItemRendererContainer}
|
||||
onPress={() => _handleDelegateHP(item)}
|
||||
>
|
||||
<Text style={styles.rightItemText}>
|
||||
{intl.formatMessage({
|
||||
id: 'refer.delegate_hp',
|
||||
})}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderEmptyView = loading ? (
|
||||
<ListPlaceHolder />
|
||||
) : (
|
||||
<Text style={styles.emptyText}>{intl.formatMessage({ id: 'refer.empty_text' })}</Text>
|
||||
);
|
||||
|
||||
const _renderFooterView = (
|
||||
<View style={{height:72, justifyContent:'center'}}>
|
||||
{loading && <ActivityIndicator color={EStyleSheet.value('$primaryBlue')} />}
|
||||
</View>
|
||||
)
|
||||
|
||||
const _renderReferralListItem = ({ item, index }: { item: Referral; index: number }) => {
|
||||
return (
|
||||
<UserListItem
|
||||
key={get(item, '_id')}
|
||||
index={index}
|
||||
username={item.referredUsername}
|
||||
description={get(item, 'created')}
|
||||
// isClickable
|
||||
isBlackRightColor
|
||||
isLoggedIn
|
||||
leftItemRenderer={() => _leftItemRenderer(item)}
|
||||
rightItemRenderer={() => _rightItemRenderer(item)}
|
||||
handleOnPress={_handleOnItemPress}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const _renderReferralsList = () => {
|
||||
return (
|
||||
<View style={styles.referralsListContainer}>
|
||||
<FlatList
|
||||
data={referralsList}
|
||||
keyExtractor={(item, index) => `item ${index}`}
|
||||
removeClippedSubviews={false}
|
||||
ListEmptyComponent={_renderEmptyView}
|
||||
ListHeaderComponent={_renderPointsEarned}
|
||||
ListFooterComponent={_renderFooterView}
|
||||
renderItem={_renderReferralListItem}
|
||||
contentContainerStyle={styles.listContentContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onEndReachedThreshold={0.3}
|
||||
onEndReached={()=>_getReferralsList()}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={() => _getReferralsList(true)}
|
||||
progressBackgroundColor="#357CE6"
|
||||
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
|
||||
titleColor="#fff"
|
||||
colors={['#fff']}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Fragment>
|
||||
<BasicHeader
|
||||
title={intl.formatMessage({
|
||||
id: 'refer.refer_earn',
|
||||
})}
|
||||
/>
|
||||
<View style={styles.mainContainer}>
|
||||
{_renderReferralsList()}
|
||||
</View>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReferScreen;
|
94
src/screens/referScreen/referScreenStyles.ts
Normal file
94
src/screens/referScreen/referScreenStyles.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
mainContainer:{
|
||||
flex:1,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
pointsContainer: {
|
||||
paddingVertical: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
pointsEarnedRow: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 16,
|
||||
},
|
||||
earnedWrapper: {
|
||||
marginRight: 32,
|
||||
},
|
||||
pendingWrapper: {
|
||||
marginLeft: 32,
|
||||
},
|
||||
points: {
|
||||
color: '$primaryBlue',
|
||||
fontSize: 26,
|
||||
marginTop: 24,
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'center',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
earendText: {
|
||||
color: '$darkIconColor',
|
||||
fontSize: 16,
|
||||
justifyContent: 'center',
|
||||
marginTop: 5,
|
||||
},
|
||||
emptyText:{
|
||||
color: '$primaryDarkText',
|
||||
fontSize: 16,
|
||||
justifyContent: 'center',
|
||||
marginTop: 5,
|
||||
padding: 32,
|
||||
textAlign:'center'
|
||||
},
|
||||
mainButton: {
|
||||
marginTop: 16,
|
||||
alignSelf: 'center',
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
mainButtonWrapper: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
unclaimedText: {
|
||||
color: '$pureWhite',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
alignSelf: 'center',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
mainIconWrapper: {
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 20,
|
||||
},
|
||||
referralsListContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
listContentContainer:{
|
||||
|
||||
},
|
||||
rewardText: {
|
||||
width: 120,
|
||||
},
|
||||
dollarSign: {
|
||||
color: '$primaryDarkGray',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
marginRight: 8,
|
||||
},
|
||||
blueDollarSign: {
|
||||
color: '$primaryBlue',
|
||||
},
|
||||
rightItemRendererContainer:{
|
||||
paddingHorizontal:8,
|
||||
height:40,
|
||||
justifyContent:'center'
|
||||
},
|
||||
rightItemText:{
|
||||
textAlign:'right',
|
||||
color: '$primaryBlue',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
}
|
||||
});
|
@ -22,7 +22,10 @@ const Transfer = ({ navigation }) => (
|
||||
accountType,
|
||||
currentAccountName,
|
||||
hivePerMVests,
|
||||
actionModalVisible,
|
||||
setWithdrawVestingRoute,
|
||||
dispatch,
|
||||
referredUsername,
|
||||
}) => {
|
||||
switch (transferType) {
|
||||
case 'transfer_token':
|
||||
@ -61,6 +64,9 @@ const Transfer = ({ navigation }) => (
|
||||
accountType={accountType}
|
||||
handleOnModalClose={handleOnModalClose}
|
||||
hivePerMVests={hivePerMVests}
|
||||
actionModalVisible={actionModalVisible}
|
||||
dispatch={dispatch}
|
||||
referredUsername={referredUsername}
|
||||
/>
|
||||
);
|
||||
case 'power_down':
|
||||
|
@ -1,17 +1,9 @@
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import { Text, View, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Text, View, ScrollView } from 'react-native';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import get from 'lodash/get';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
|
||||
import { hsOptions } from '../../../constants/hsOptions';
|
||||
import AUTH_TYPE from '../../../constants/authType';
|
||||
import { encryptKey, decryptKey } from '../../../utils/crypto';
|
||||
|
||||
import { BasicHeader, MainButton } from '../../../components';
|
||||
import { connect } from 'react-redux';
|
||||
import { BasicHeader } from '../../../components';
|
||||
|
||||
import styles from './transferStyles';
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, Platform, ScrollView, KeyboardAvoidingView, Alert } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import Slider from '@esteemapp/react-native-slider';
|
||||
import get from 'lodash/get';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { View as AnimatedView } from 'react-native-animatable';
|
||||
import { TouchableOpacity, FlatList } from 'react-native-gesture-handler';
|
||||
|
||||
// Constants
|
||||
import { debounce } from 'lodash';
|
||||
import AUTH_TYPE from '../../../constants/authType';
|
||||
import { hsOptions } from '../../../constants/hsOptions';
|
||||
|
||||
@ -21,46 +23,88 @@ import {
|
||||
Icon,
|
||||
Modal,
|
||||
} from '../../../components';
|
||||
|
||||
import parseToken from '../../../utils/parseToken';
|
||||
import { isEmptyDate } from '../../../utils/time';
|
||||
import { vestsToHp } from '../../../utils/conversions';
|
||||
|
||||
// Styles
|
||||
import styles from './transferStyles';
|
||||
import { OptionsModal } from '../../../components/atoms';
|
||||
// Redux
|
||||
import { showActionModal } from '../../../redux/actions/uiAction';
|
||||
// Utils
|
||||
import { getReceivedVestingShares } from '../../../providers/ecency/ecency';
|
||||
import parseToken from '../../../utils/parseToken';
|
||||
import { isEmptyDate } from '../../../utils/time';
|
||||
import { hpToVests, vestsToHp } from '../../../utils/conversions';
|
||||
import parseAsset from '../../../utils/parseAsset';
|
||||
|
||||
class DelegateScreen extends Component {
|
||||
_handleOnAmountChange = debounce(
|
||||
async (state, amount) => {
|
||||
let _amount = amount.toString();
|
||||
if (_amount.includes(',')) {
|
||||
_amount = amount.replace(',', '.');
|
||||
}
|
||||
|
||||
this._setState(state, _amount);
|
||||
},
|
||||
1000,
|
||||
{ leading: true },
|
||||
);
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
amount: 0,
|
||||
isAmountValid: true,
|
||||
hp: 0.0,
|
||||
isTransfering: false,
|
||||
from: props.currentAccountName,
|
||||
destination: '',
|
||||
steemConnectTransfer: false,
|
||||
usersResult: [],
|
||||
step: 1,
|
||||
delegatedHP: 0,
|
||||
};
|
||||
|
||||
this.startActionSheet = React.createRef();
|
||||
this.destinationTextInput = React.createRef();
|
||||
this.amountTextInput = React.createRef();
|
||||
}
|
||||
|
||||
// Component Life Cycles
|
||||
// Component Lifecycles
|
||||
componentDidMount() {
|
||||
const { referredUsername } = this.props;
|
||||
if (referredUsername) {
|
||||
this.setState({ destination: referredUsername, usersResult: [], step: 2 }, () => {
|
||||
this._fetchReceivedVestingShare();
|
||||
this.destinationTextInput.current?.blur();
|
||||
});
|
||||
} else {
|
||||
this.destinationTextInput.current?.focus();
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.from !== this.state.from) {
|
||||
this._fetchReceivedVestingShare();
|
||||
}
|
||||
}
|
||||
|
||||
// Component Functions
|
||||
_setState = (key, value) => {
|
||||
const { getAccountsWithUsername, balance } = this.props;
|
||||
|
||||
if (key) {
|
||||
switch (key) {
|
||||
case 'destination':
|
||||
getAccountsWithUsername(value).then((res) => {
|
||||
const isValid = res.includes(value);
|
||||
|
||||
this.setState({ usersResult: [...res] });
|
||||
this.setState({ isUsernameValid: isValid });
|
||||
});
|
||||
this.setState({ [key]: value });
|
||||
if (!value) {
|
||||
this.setState({ destination: '', step: 1 });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'amount':
|
||||
if (parseFloat(Number(value)) <= parseFloat(balance)) {
|
||||
if (parseFloat(value) <= parseFloat(balance)) {
|
||||
this.setState({ [key]: value });
|
||||
}
|
||||
break;
|
||||
@ -72,6 +116,29 @@ class DelegateScreen extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
_handleAmountChange = (hp, availableVests) => {
|
||||
if (!hp) {
|
||||
this.setState({ step: 2, hp: 0.0, amount: 0, isAmountValid: false });
|
||||
return;
|
||||
}
|
||||
const parsedValue = parseFloat(hp);
|
||||
const { hivePerMVests } = this.props;
|
||||
const vestsForHp = hpToVests(hp, hivePerMVests);
|
||||
const totalHP = vestsToHp(availableVests, hivePerMVests).toFixed(3);
|
||||
if (Number.isNaN(parsedValue)) {
|
||||
this.setState({ amount: 0, hp: 0.0, step: 2, isAmountValid: false });
|
||||
} else if (parsedValue >= totalHP) {
|
||||
this.setState({
|
||||
amount: hpToVests(totalHP, hivePerMVests),
|
||||
hp: totalHP,
|
||||
step: 2,
|
||||
isAmountValid: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({ amount: vestsForHp, hp: parsedValue, step: 2, isAmountValid: true });
|
||||
}
|
||||
};
|
||||
|
||||
_handleTransferAction = () => {
|
||||
const { transferToAccount, accountType } = this.props;
|
||||
const { from, destination, amount } = this.state;
|
||||
@ -85,22 +152,111 @@ class DelegateScreen extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
_handleOnAmountChange = (state, amount) => {
|
||||
let _amount = amount.toString();
|
||||
if (_amount.includes(',')) {
|
||||
_amount = amount.replace(',', '.');
|
||||
_fetchReceivedVestingShare = async () => {
|
||||
try {
|
||||
const { hivePerMVests } = this.props;
|
||||
const delegateeUser = this.state.destination;
|
||||
const vestingShares = await getReceivedVestingShares(delegateeUser);
|
||||
if (vestingShares && vestingShares.length) {
|
||||
const curShare = vestingShares.find((item) => item.delegator === this.state.from);
|
||||
if (curShare) {
|
||||
const vest_shares = parseAsset(curShare.vesting_shares);
|
||||
this.setState({
|
||||
delegatedHP: vestsToHp(vest_shares.amount, hivePerMVests).toFixed(3),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
delegatedHP: 0,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
delegatedHP: 0,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
this._setState(state, _amount);
|
||||
};
|
||||
|
||||
_handleOnDropdownChange = (value) => {
|
||||
const { fetchBalance } = this.props;
|
||||
|
||||
const { fetchBalance, intl } = this.props;
|
||||
const { destination } = this.state;
|
||||
if (value === destination) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({ id: 'transfer.username_alert' }),
|
||||
intl.formatMessage({ id: 'transfer.username_alert_detail' }),
|
||||
);
|
||||
this.setState({ step: 1, destination: '' });
|
||||
return;
|
||||
}
|
||||
fetchBalance(value);
|
||||
this.setState({ from: value, amount: 0 });
|
||||
};
|
||||
|
||||
_handleSliderValueChange = (value, availableVestingShares) => {
|
||||
const { hivePerMVests } = this.props;
|
||||
if (value === availableVestingShares) {
|
||||
this.setState({ isAmountValid: false });
|
||||
}
|
||||
if (value !== availableVestingShares) {
|
||||
this.setState({
|
||||
step: 2,
|
||||
isAmountValid: true,
|
||||
amount: value,
|
||||
hp: vestsToHp(value, hivePerMVests).toFixed(3),
|
||||
});
|
||||
} else {
|
||||
this.setState({ amount: value, hp: vestsToHp(value, hivePerMVests).toFixed(3), step: 2 });
|
||||
}
|
||||
};
|
||||
|
||||
_handleNext = () => {
|
||||
const { step, hp, amount, destination, from, delegatedHP } = this.state;
|
||||
const { dispatch, intl } = this.props;
|
||||
if (step === 1) {
|
||||
// this.setState({ step: 2 });
|
||||
} else {
|
||||
// this.amountTextInput.current.blur();
|
||||
let body =
|
||||
intl.formatMessage(
|
||||
{ id: 'transfer.confirm_summary' },
|
||||
{
|
||||
hp: hp,
|
||||
vests: amount.toFixed(3),
|
||||
delegatee: from,
|
||||
delegator: destination,
|
||||
},
|
||||
) +
|
||||
(delegatedHP
|
||||
? `\n${intl.formatMessage(
|
||||
{ id: 'transfer.confirm_summary_para' },
|
||||
{
|
||||
prev: delegatedHP,
|
||||
},
|
||||
)}`
|
||||
: '');
|
||||
|
||||
dispatch(
|
||||
showActionModal({
|
||||
title: intl.formatMessage({ id: 'transfer.confirm' }),
|
||||
body,
|
||||
buttons: [
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.cancel' }),
|
||||
onPress: () => console.log('Cancel'),
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: 'alert.confirm' }),
|
||||
onPress: () => this._handleTransferAction(),
|
||||
},
|
||||
],
|
||||
headerContent: this._renderToFromAvatars(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
// Note: dropdown for user account selection. can be used in later implementaion
|
||||
_renderDropdown = (accounts, currentAccountName) => (
|
||||
<DropdownButton
|
||||
dropdownButtonStyle={styles.dropdownButtonStyle}
|
||||
@ -115,22 +271,128 @@ class DelegateScreen extends Component {
|
||||
/>
|
||||
);
|
||||
|
||||
_renderInput = (placeholder, state, keyboardType, isTextArea) => (
|
||||
<TextInput
|
||||
style={[isTextArea ? styles.textarea : styles.input]}
|
||||
onChangeText={(amount) => this._handleOnAmountChange(state, amount)}
|
||||
value={this.state[state]}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor="#c1c5c7"
|
||||
autoCapitalize="none"
|
||||
multiline={isTextArea}
|
||||
numberOfLines={isTextArea ? 4 : 1}
|
||||
keyboardType={keyboardType}
|
||||
_renderUsersDropdownItem = ({ item }) => {
|
||||
const username = item;
|
||||
const { from } = this.state;
|
||||
const { intl } = this.props;
|
||||
|
||||
const _onItemPress = () => {
|
||||
if (username === from) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({ id: 'transfer.username_alert' }),
|
||||
intl.formatMessage({ id: 'transfer.username_alert_detail' }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ destination: username, usersResult: [], step: 2 }, () => {
|
||||
//since method uses destination from state it sould be called
|
||||
//after state has been updated successfully
|
||||
this._fetchReceivedVestingShare();
|
||||
});
|
||||
|
||||
this.destinationTextInput.current?.blur();
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={_onItemPress} style={styles.usersDropItemRow}>
|
||||
<UserAvatar username={username} noAction />
|
||||
<Text style={styles.usersDropItemRowText}>{username}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
_renderUsersDropdown = () => (
|
||||
<FlatList
|
||||
data={this.state.usersResult}
|
||||
keyboardShouldPersistTaps="always"
|
||||
renderItem={this._renderUsersDropdownItem}
|
||||
keyExtractor={(item) => `searched-user-${item}`}
|
||||
style={styles.usersDropdown}
|
||||
ListFooterComponent={<View />}
|
||||
ListFooterComponentStyle={{ height: 20 }} //this fixes the last item visibility bug
|
||||
/>
|
||||
);
|
||||
|
||||
_renderInput = (placeholder, state, keyboardType, availableVestingShares, isTextArea) => {
|
||||
const { isAmountValid } = this.state;
|
||||
switch (state) {
|
||||
case 'from':
|
||||
return (
|
||||
<TextInput
|
||||
style={[styles.input]}
|
||||
value={this.state[state]}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor="#c1c5c7"
|
||||
autoCapitalize="none"
|
||||
multiline={isTextArea}
|
||||
numberOfLines={isTextArea ? 4 : 1}
|
||||
keyboardType={keyboardType}
|
||||
editable={false}
|
||||
/>
|
||||
);
|
||||
case 'destination':
|
||||
return (
|
||||
<View style={styles.transferToContainer}>
|
||||
<TextInput
|
||||
style={[styles.input]}
|
||||
onChangeText={(value) => {
|
||||
this.setState({ destination: value, step: 1 });
|
||||
this._handleOnAmountChange(state, value);
|
||||
}}
|
||||
value={this.state[state]}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor="#c1c5c7"
|
||||
autoCapitalize="none"
|
||||
multiline={isTextArea}
|
||||
numberOfLines={isTextArea ? 4 : 1}
|
||||
keyboardType={keyboardType}
|
||||
innerRef={this.destinationTextInput}
|
||||
/>
|
||||
|
||||
<View style={styles.usersDropdownContainer}>
|
||||
{this.state.destination !== '' &&
|
||||
this.state.usersResult.length !== 0 &&
|
||||
this._renderUsersDropdown()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
case 'amount':
|
||||
return (
|
||||
<TextInput
|
||||
style={[styles.amountInput, !isAmountValid && styles.error]}
|
||||
onChangeText={(amount) => {
|
||||
this._handleAmountChange(amount, availableVestingShares);
|
||||
}}
|
||||
value={this.state.hp}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor="#c1c5c7"
|
||||
autoCapitalize="none"
|
||||
multiline={isTextArea}
|
||||
numberOfLines={isTextArea ? 4 : 1}
|
||||
keyboardType={keyboardType}
|
||||
innerRef={this.amountTextInput}
|
||||
blurOnSubmit={false}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
null;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
_renderInformationText = (text) => <Text style={styles.amountText}>{text}</Text>;
|
||||
|
||||
_renderToFromAvatars = () => {
|
||||
const { destination, from } = this.state;
|
||||
return (
|
||||
<View style={styles.toFromAvatarsContainer}>
|
||||
<UserAvatar username={from} size="xl" style={styles.userAvatar} noAction />
|
||||
<Icon style={styles.icon} name="arrow-forward" iconType="MaterialIcons" />
|
||||
<UserAvatar username={destination} size="xl" style={styles.userAvatar} noAction />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
@ -139,9 +401,20 @@ class DelegateScreen extends Component {
|
||||
selectedAccount,
|
||||
handleOnModalClose,
|
||||
hivePerMVests,
|
||||
actionModalVisible,
|
||||
accountType,
|
||||
} = this.props;
|
||||
const { amount, isTransfering, from, destination, steemConnectTransfer } = this.state;
|
||||
const {
|
||||
amount,
|
||||
isTransfering,
|
||||
from,
|
||||
destination,
|
||||
steemConnectTransfer,
|
||||
step,
|
||||
delegatedHP,
|
||||
hp,
|
||||
isAmountValid,
|
||||
} = this.state;
|
||||
let availableVestingShares = 0;
|
||||
if (!isEmptyDate(get(selectedAccount, 'next_vesting_withdrawal'))) {
|
||||
// powering down
|
||||
@ -161,68 +434,127 @@ class DelegateScreen extends Component {
|
||||
const path = `sign/delegate-vesting-shares?delegator=${from}&delegatee=${destination}&vesting_shares=${encodeURIComponent(
|
||||
fixedAmount,
|
||||
)}`;
|
||||
const totalHP = vestsToHp(availableVestingShares, hivePerMVests);
|
||||
const _renderSlider = () => (
|
||||
<View style={styles.sliderBox}>
|
||||
<View style={styles.emptyBox} />
|
||||
<View style={styles.sliderContainer}>
|
||||
<Slider
|
||||
style={styles.slider}
|
||||
trackStyle={styles.track}
|
||||
thumbStyle={styles.thumb}
|
||||
minimumTrackTintColor="#357ce6"
|
||||
thumbTintColor="#007ee5"
|
||||
maximumValue={availableVestingShares}
|
||||
value={amount}
|
||||
onValueChange={(value) => this._handleSliderValueChange(value, availableVestingShares)}
|
||||
/>
|
||||
<View style={styles.sliderAmountContainer}>
|
||||
<Text style={styles.amountText}>{`MAX ${totalHP.toFixed(3)} HP`}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
const spCalculated = vestsToHp(amount, hivePerMVests);
|
||||
const _renderStepOne = () => (
|
||||
<View style={styles.stepOneContainer}>
|
||||
<Text style={styles.sectionHeading}>
|
||||
{intl.formatMessage({ id: 'transfer.account_detail_head' })}
|
||||
</Text>
|
||||
<Text style={styles.sectionSubheading}>
|
||||
{intl.formatMessage({ id: 'transfer.account_detail_subhead' })}
|
||||
</Text>
|
||||
<TransferFormItem
|
||||
containerStyle={{ marginTop: 32 }}
|
||||
label={intl.formatMessage({ id: 'transfer.from' })}
|
||||
rightComponent={() =>
|
||||
this._renderInput(intl.formatMessage({ id: 'transfer.from' }), 'from', 'default')
|
||||
}
|
||||
/>
|
||||
<TransferFormItem
|
||||
label={intl.formatMessage({ id: 'transfer.to' })}
|
||||
rightComponent={() =>
|
||||
this._renderInput(
|
||||
intl.formatMessage({ id: 'transfer.to_placeholder' }),
|
||||
'destination',
|
||||
'default',
|
||||
)
|
||||
}
|
||||
containerStyle={styles.elevate}
|
||||
/>
|
||||
{this._renderToFromAvatars()}
|
||||
</View>
|
||||
);
|
||||
|
||||
const _renderStepTwo = () => (
|
||||
<AnimatedView animation="bounceInRight" delay={500} useNativeDriver>
|
||||
<View style={styles.stepTwoContainer}>
|
||||
<Text style={styles.sectionHeading}>
|
||||
{intl.formatMessage({ id: 'transfer.delegat_detail_head' })}
|
||||
</Text>
|
||||
<Text style={styles.sectionSubheading}>
|
||||
{intl.formatMessage({ id: 'transfer.delegat_detail_subhead' })}
|
||||
</Text>
|
||||
<View style={styles.alreadyDelegateRow}>
|
||||
<Text style={styles.sectionSubheading}>
|
||||
{`${intl.formatMessage({ id: 'transfer.already_delegated' })} @${destination}`}
|
||||
</Text>
|
||||
<Text style={styles.sectionSubheading}>{`${delegatedHP} HP`}</Text>
|
||||
</View>
|
||||
<TransferFormItem
|
||||
label={intl.formatMessage({ id: 'transfer.new_amount' })}
|
||||
rightComponent={() =>
|
||||
this._renderInput(
|
||||
intl.formatMessage({ id: 'transfer.amount' }),
|
||||
'amount',
|
||||
'decimal-pad',
|
||||
availableVestingShares,
|
||||
null,
|
||||
null,
|
||||
200,
|
||||
)
|
||||
}
|
||||
containerStyle={styles.paddBottom}
|
||||
/>
|
||||
{_renderSlider()}
|
||||
</View>
|
||||
</AnimatedView>
|
||||
);
|
||||
const _renderMainBtn = () => (
|
||||
<View style={styles.stepThreeContainer}>
|
||||
<MainButton
|
||||
style={styles.button}
|
||||
onPress={this._handleNext}
|
||||
isLoading={isTransfering}
|
||||
isDisable={!isAmountValid || step === 1}
|
||||
>
|
||||
<Text style={styles.buttonText}>
|
||||
{step === 2
|
||||
? intl.formatMessage({ id: 'transfer.review' })
|
||||
: intl.formatMessage({ id: 'transfer.next' })}
|
||||
</Text>
|
||||
</MainButton>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<BasicHeader title={intl.formatMessage({ id: 'transfer.delegate' })} />
|
||||
<View style={styles.container}>
|
||||
<View style={styles.topContent}>
|
||||
<UserAvatar username={from} size="xl" style={styles.userAvatar} noAction />
|
||||
<Icon style={styles.icon} name="arrow-forward" iconType="MaterialIcons" />
|
||||
<UserAvatar username={destination} size="xl" style={styles.userAvatar} noAction />
|
||||
</View>
|
||||
<View style={styles.middleContent}>
|
||||
<TransferFormItem
|
||||
label={intl.formatMessage({ id: 'transfer.from' })}
|
||||
rightComponent={() => this._renderDropdown(accounts, currentAccountName)}
|
||||
/>
|
||||
<TransferFormItem
|
||||
label={intl.formatMessage({ id: 'transfer.to' })}
|
||||
rightComponent={() =>
|
||||
this._renderInput(
|
||||
intl.formatMessage({ id: 'transfer.to_placeholder' }),
|
||||
'destination',
|
||||
'default',
|
||||
)
|
||||
}
|
||||
/>
|
||||
<TransferFormItem
|
||||
label={intl.formatMessage({ id: 'transfer.amount' })}
|
||||
rightComponent={() => this._renderInformationText(`${amount.toFixed(6)} VESTS`)}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.fillSpace}
|
||||
keyboardShouldPersistTaps
|
||||
>
|
||||
<ScrollView keyboardShouldPersistTaps contentContainerStyle={styles.grow}>
|
||||
<View style={styles.container}>
|
||||
{step >= 1 && _renderStepOne()}
|
||||
{step >= 2 && _renderStepTwo()}
|
||||
|
||||
<TransferFormItem
|
||||
rightComponent={() => this._renderInformationText(`${spCalculated.toFixed(3)} HP`)}
|
||||
/>
|
||||
<Slider
|
||||
style={styles.slider}
|
||||
trackStyle={styles.track}
|
||||
thumbStyle={styles.thumb}
|
||||
minimumTrackTintColor="#357ce6"
|
||||
thumbTintColor="#007ee5"
|
||||
maximumValue={availableVestingShares}
|
||||
value={amount}
|
||||
onValueChange={(value) => {
|
||||
this.setState({ amount: value });
|
||||
}}
|
||||
/>
|
||||
<Text style={styles.informationText}>
|
||||
{intl.formatMessage({ id: 'transfer.amount_information' })}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.bottomContent}>
|
||||
<MainButton
|
||||
style={styles.button}
|
||||
onPress={() => this.startActionSheet.current.show()}
|
||||
isLoading={isTransfering}
|
||||
>
|
||||
<Text style={styles.buttonText}>{intl.formatMessage({ id: 'transfer.next' })}</Text>
|
||||
</MainButton>
|
||||
</View>
|
||||
</View>
|
||||
<ActionSheet
|
||||
{_renderMainBtn()}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
<OptionsModal
|
||||
ref={this.startActionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.confirm' }),
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* eslint-disable react/no-unused-state */
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import { Text, View, ScrollView, Alert } from 'react-native';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import Slider from '@esteemapp/react-native-slider';
|
||||
import get from 'lodash/get';
|
||||
@ -28,6 +27,7 @@ import { vestsToHp } from '../../../utils/conversions';
|
||||
import { isEmptyDate } from '../../../utils/time';
|
||||
|
||||
import styles from './transferStyles';
|
||||
import { OptionsModal } from '../../../components/atoms';
|
||||
/* Props
|
||||
* ------------------------------------------------
|
||||
* @prop { type } name - Description....
|
||||
@ -327,7 +327,7 @@ class PowerDownView extends Component {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={this.startActionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.confirm' }),
|
||||
@ -338,7 +338,7 @@ class PowerDownView extends Component {
|
||||
destructiveButtonIndex={0}
|
||||
onPress={(index) => (index === 0 ? this._handleTransferAction() : null)}
|
||||
/>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={this.stopActionSheet}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.confirm' }),
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { Fragment, Component, useState, useRef, useEffect } from 'react';
|
||||
import React, { Fragment, useState, useRef } from 'react';
|
||||
import { Text, View, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import get from 'lodash/get';
|
||||
|
||||
@ -20,6 +19,7 @@ import {
|
||||
} from '../../../components';
|
||||
|
||||
import styles from './transferStyles';
|
||||
import { OptionsModal } from '../../../components/atoms';
|
||||
|
||||
const TransferView = ({
|
||||
currentAccountName,
|
||||
@ -156,7 +156,7 @@ const TransferView = ({
|
||||
path = `sign/custom-json?authority=active&required_auths=%5B%22${get(
|
||||
selectedAccount,
|
||||
'name',
|
||||
)}%22%5D&required_posting_auths=%5B%5D&id=esteem_point_transfer&json=${encodeURIComponent(
|
||||
)}%22%5D&required_posting_auths=%5B%5D&id=ecency_point_transfer&json=${encodeURIComponent(
|
||||
json,
|
||||
)}`;
|
||||
} else if (transferType === 'transfer_to_savings') {
|
||||
@ -194,7 +194,7 @@ const TransferView = ({
|
||||
<BasicHeader title={intl.formatMessage({ id: `transfer.${transferType}` })} />
|
||||
<View style={styles.container}>
|
||||
<ScrollView>
|
||||
<View style={styles.topContent}>
|
||||
<View style={[styles.toFromAvatarsContainer, { marginBottom: 16 }]}>
|
||||
<UserAvatar username={from} size="xl" style={styles.userAvatar} noAction />
|
||||
<Icon style={styles.icon} name="arrow-forward" iconType="MaterialIcons" />
|
||||
<UserAvatar username={destination} size="xl" style={styles.userAvatar} noAction />
|
||||
@ -275,7 +275,7 @@ const TransferView = ({
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<ActionSheet
|
||||
<OptionsModal
|
||||
ref={confirm}
|
||||
options={[
|
||||
intl.formatMessage({ id: 'alert.confirm' }),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user