Merge branch 'development' of https://github.com/ecency/ecency-mobile into sa/getting-started-tooltip

This commit is contained in:
Sadaqat Ali 2022-02-01 10:42:42 +05:00
commit d4f9b1c688
35 changed files with 609 additions and 363 deletions

View File

@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch
versionName "3.0.25" versionName "3.0.26"
resValue "string", "build_config_package", "app.esteem.mobile.android" resValue "string", "build_config_package", "app.esteem.mobile.android"
multiDexEnabled true multiDexEnabled true
// react-native-image-crop-picker // react-native-image-crop-picker

View File

@ -15,11 +15,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.25</string> <string>3.0.26</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2804</string> <string>2805</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true /> <true />
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>

View File

@ -15,10 +15,10 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>BNDL</string> <string>BNDL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.25</string> <string>3.0.26</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2804</string> <string>2805</string>
</dict> </dict>
</plist> </plist>

View File

@ -1129,7 +1129,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_IDENTITY = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2804; CURRENT_PROJECT_VERSION = 2805;
DEAD_CODE_STRIPPING = NO; DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = 75B6RXTKGT; DEVELOPMENT_TEAM = 75B6RXTKGT;
EXCLUDED_ARCHS = ""; EXCLUDED_ARCHS = "";
@ -1208,7 +1208,7 @@
CODE_SIGN_ENTITLEMENTS = Ecency/Ecency.entitlements; CODE_SIGN_ENTITLEMENTS = Ecency/Ecency.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2804; CURRENT_PROJECT_VERSION = 2805;
DEAD_CODE_STRIPPING = NO; DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = 75B6RXTKGT; DEVELOPMENT_TEAM = 75B6RXTKGT;
EXCLUDED_ARCHS = ""; EXCLUDED_ARCHS = "";

View File

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.25</string> <string>3.0.26</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>

View File

@ -15,10 +15,10 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>BNDL</string> <string>BNDL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.25</string> <string>3.0.26</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2804</string> <string>2805</string>
</dict> </dict>
</plist> </plist>

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.25</string> <string>3.0.26</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2804</string> <string>2805</string>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionAttributes</key> <key>NSExtensionAttributes</key>

View File

@ -1,6 +1,6 @@
{ {
"name": "ecency", "name": "ecency",
"version": "3.0.25", "version": "3.0.26",
"displayName": "Ecency", "displayName": "Ecency",
"private": true, "private": true,
"rnpm": { "rnpm": {
@ -18,7 +18,7 @@
"test": "node node_modules/jest/bin/jest.js --watch", "test": "node node_modules/jest/bin/jest.js --watch",
"lint": "eslint src/", "lint": "eslint src/",
"lint:fix": "eslint src/ --fix", "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", "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", "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", "bump-patch": "npm version patch --no-git-tag-version",
@ -35,6 +35,7 @@
"@esteemapp/react-native-multi-slider": "^1.1.0", "@esteemapp/react-native-multi-slider": "^1.1.0",
"@esteemapp/react-native-slider": "^0.12.0", "@esteemapp/react-native-slider": "^0.12.0",
"@hiveio/dhive": "^1.0.1", "@hiveio/dhive": "^1.0.1",
"@native-html/iframe-plugin": "^2.6.1",
"@react-native-community/async-storage": "^1.11.0", "@react-native-community/async-storage": "^1.11.0",
"@react-native-community/cameraroll": "^1.3.0", "@react-native-community/cameraroll": "^1.3.0",
"@react-native-community/cli-platform-ios": "^4.10.1", "@react-native-community/cli-platform-ios": "^4.10.1",
@ -100,8 +101,8 @@
"react-native-linear-gradient": "^2.4.2", "react-native-linear-gradient": "^2.4.2",
"react-native-matomo-sdk": "feruzm/react-native-matomo-sdk", "react-native-matomo-sdk": "feruzm/react-native-matomo-sdk",
"react-native-modal": "^11.5.6", "react-native-modal": "^11.5.6",
"react-native-modal-dropdown": "^1.0.1",
"react-native-modal-popover": "^2.1.0", "react-native-modal-popover": "^2.1.0",
"react-native-modal-dropdown": "^1.0.2",
"react-native-modal-translucent": "^5.0.0", "react-native-modal-translucent": "^5.0.0",
"react-native-navigation-bar-color": "^1.0.0", "react-native-navigation-bar-color": "^1.0.0",
"react-native-os": "^1.0.1", "react-native-os": "^1.0.1",

View File

@ -1,7 +1,6 @@
import React, { useState, Fragment, useRef } from 'react'; import React, { useState, Fragment, useRef } from 'react';
import { View, Text, ActivityIndicator, SafeAreaView } from 'react-native'; import { View, Text, ActivityIndicator, SafeAreaView } from 'react-native';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import ActionSheet from 'react-native-actionsheet';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import moment from 'moment'; import moment from 'moment';
@ -15,6 +14,7 @@ import { DateTimePicker } from '../../dateTimePicker';
// Constants // Constants
// Styles // Styles
import styles from './basicHeaderStyles'; import styles from './basicHeaderStyles';
import { OptionsModal } from '../../atoms';
const BasicHeaderView = ({ const BasicHeaderView = ({
disabled, disabled,
@ -324,7 +324,7 @@ const BasicHeaderView = ({
/> />
</SafeAreaView> </SafeAreaView>
</Modal> </Modal>
<ActionSheet <OptionsModal
ref={settingMenuRef} ref={settingMenuRef}
options={[ options={[
intl.formatMessage({ id: 'editor.setting_schedule' }), intl.formatMessage({ id: 'editor.setting_schedule' }),
@ -337,7 +337,7 @@ const BasicHeaderView = ({
title={intl.formatMessage({ id: 'editor.options' })} title={intl.formatMessage({ id: 'editor.options' })}
onPress={_handleSettingMenuSelect} onPress={_handleSettingMenuSelect}
/> />
<ActionSheet <OptionsModal
ref={rewardMenuRef} ref={rewardMenuRef}
options={[ options={[
intl.formatMessage({ id: 'editor.reward_default' }), intl.formatMessage({ id: 'editor.reward_default' }),

View File

@ -1,6 +1,5 @@
import React, { Fragment, useState, useRef, useEffect } from 'react'; import React, { Fragment, useState, useRef, useEffect } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import get from 'lodash/get'; import get from 'lodash/get';
import { View as AnimatedView } from 'react-native-animatable'; import { View as AnimatedView } from 'react-native-animatable';
@ -18,6 +17,7 @@ import { TextWithIcon } from '../../basicUIElements';
// Styles // Styles
import styles from './commentStyles'; import styles from './commentStyles';
import { useAppSelector } from '../../../hooks'; import { useAppSelector } from '../../../hooks';
import { OptionsModal } from '../../atoms';
const CommentView = ({ const CommentView = ({
avatarSize, avatarSize,
@ -203,7 +203,7 @@ const CommentView = ({
onPress={() => actionSheet.current.show()} onPress={() => actionSheet.current.show()}
iconType="MaterialIcons" iconType="MaterialIcons"
/> />
<ActionSheet <OptionsModal
ref={actionSheet} ref={actionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.delete' }), intl.formatMessage({ id: 'alert.delete' }),

View File

@ -1,5 +1,4 @@
import React, { useRef, useState, useEffect, Fragment } from 'react'; import React, { useRef, useState, useEffect, Fragment } from 'react';
import ActionSheet from 'react-native-actionsheet';
import { View, Text, TouchableOpacity, Dimensions } from 'react-native'; import { View, Text, TouchableOpacity, Dimensions } from 'react-native';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import ImageSize from 'react-native-image-size'; import ImageSize from 'react-native-image-size';
@ -11,6 +10,7 @@ import { getTimeFromNow } from '../../../utils/time';
import { PostHeaderDescription } from '../../postElements'; import { PostHeaderDescription } from '../../postElements';
import { IconButton } from '../../iconButton'; import { IconButton } from '../../iconButton';
import ProgressiveImage from '../../progressiveImage'; import ProgressiveImage from '../../progressiveImage';
import { OptionsModal } from '../../atoms';
// Styles // Styles
import styles from './draftListItemStyles'; import styles from './draftListItemStyles';
@ -95,7 +95,7 @@ const DraftListItemView = ({
</View> </View>
</View> </View>
<ActionSheet <OptionsModal
ref={actionSheet} ref={actionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.delete' }), intl.formatMessage({ id: 'alert.delete' }),

View File

@ -94,6 +94,7 @@ import { PostHtmlRenderer } from './postHtmlRenderer';
import { QuickProfileModal } from './organisms'; import { QuickProfileModal } from './organisms';
import QuickReplyModal from './quickReplyModal/quickReplyModalView'; import QuickReplyModal from './quickReplyModal/quickReplyModalView';
import Tooltip from './tooltip/tooltipView'; import Tooltip from './tooltip/tooltipView';
import VideoPlayer from './videoPlayer/videoPlayerView';
// Basic UI Elements // Basic UI Elements
import { import {
@ -236,4 +237,5 @@ export {
QuickProfileModal, QuickProfileModal,
QuickReplyModal, QuickReplyModal,
Tooltip, Tooltip,
VideoPlayer,
}; };

View 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 });
};

View File

@ -110,4 +110,31 @@ export default EStyleSheet.create({
bottom: 56, 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,
},
}); });

View File

@ -8,7 +8,6 @@ import {
ScrollView, ScrollView,
TouchableOpacity, TouchableOpacity,
} from 'react-native'; } from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import { renderPostBody } from '@ecency/render-helper'; import { renderPostBody } from '@ecency/render-helper';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { View as AnimatedView } from 'react-native-animatable'; import { View as AnimatedView } from 'react-native-animatable';
@ -47,6 +46,9 @@ import styles from './markdownEditorStyles';
import applySnippet from './formats/applySnippet'; import applySnippet from './formats/applySnippet';
import { MainButton } from '../../mainButton'; import { MainButton } from '../../mainButton';
import isAndroidOreo from '../../../utils/isAndroidOreo'; import isAndroidOreo from '../../../utils/isAndroidOreo';
import { OptionsModal } from '../../atoms';
import { UsernameAutofillBar } from './usernameAutofillBar';
import applyUsername from './formats/applyUsername';
const MIN_BODY_INPUT_HEIGHT = 300; const MIN_BODY_INPUT_HEIGHT = 300;
@ -186,6 +188,15 @@ const MarkdownEditorView = ({
dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet)); dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet));
}; };
const _onApplyUsername = (username) => {
applyUsername({
text,
selection,
setTextAndSelection: _setTextAndSelection,
username,
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const _changeText = useCallback((input) => { const _changeText = useCallback((input) => {
setText(input); setText(input);
@ -459,8 +470,9 @@ const MarkdownEditorView = ({
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
> >
{isAndroidOreo() ? _renderEditorWithoutScroll() : _renderEditorWithScroll()} {isAndroidOreo() ? _renderEditorWithoutScroll() : _renderEditorWithScroll()}
<UsernameAutofillBar text={text} selection={selection} onApplyUsername={_onApplyUsername} />
{_renderFloatingDraftButton()} {_renderFloatingDraftButton()}
{!isPreviewActive && _renderEditorButtons()} {!isPreviewActive && _renderEditorButtons()}
<Modal <Modal
@ -484,7 +496,7 @@ const MarkdownEditorView = ({
uploadedImage={uploadedImage} uploadedImage={uploadedImage}
/> />
<ActionSheet <OptionsModal
ref={galleryRef} ref={galleryRef}
options={[ options={[
intl.formatMessage({ intl.formatMessage({
@ -510,7 +522,7 @@ const MarkdownEditorView = ({
} }
}} }}
/> />
<ActionSheet <OptionsModal
ref={clearRef} ref={clearRef}
title={intl.formatMessage({ title={intl.formatMessage({
id: 'alert.clear_alert', id: 'alert.clear_alert',

View 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>
)
}

View File

@ -3,7 +3,6 @@ import { injectIntl } from 'react-intl';
import { Text, View, ScrollView, TouchableOpacity, Alert } from 'react-native'; import { Text, View, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { WebView } from 'react-native-webview'; import { WebView } from 'react-native-webview';
import get from 'lodash/get'; import get from 'lodash/get';
import ActionSheet from 'react-native-actionsheet';
import Autocomplete from '@esteemapp/react-native-autocomplete-input'; import Autocomplete from '@esteemapp/react-native-autocomplete-input';
import { Icon, TextInput } from '..'; import { Icon, TextInput } from '..';
import { hsOptions } from '../../constants/hsOptions'; import { hsOptions } from '../../constants/hsOptions';
@ -21,6 +20,7 @@ import { Modal } from '../modal';
// Styles // Styles
import styles from './postBoostStyles'; import styles from './postBoostStyles';
import { OptionsModal } from '../atoms';
class BoostPostScreen extends PureComponent { class BoostPostScreen extends PureComponent {
/* Props /* Props
@ -240,7 +240,7 @@ class BoostPostScreen extends PureComponent {
</View> </View>
</ScrollView> </ScrollView>
</View> </View>
<ActionSheet <OptionsModal
ref={this.startActionSheet} ref={this.startActionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.confirm' }), intl.formatMessage({ id: 'alert.confirm' }),

View File

@ -2,7 +2,6 @@ import React, { PureComponent, Fragment } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withNavigation } from 'react-navigation'; import { withNavigation } from 'react-navigation';
import { Share } from 'react-native'; import { Share } from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import get from 'lodash/get'; import get from 'lodash/get';
@ -22,6 +21,7 @@ import { getPostUrl } from '../../../utils/post';
// Component // Component
import PostDropdownView from '../view/postDropdownView'; import PostDropdownView from '../view/postDropdownView';
import { OptionsModal } from '../../atoms';
/* /*
* Props Name Description Value * Props Name Description Value
@ -278,7 +278,7 @@ class PostDropdownContainer extends PureComponent {
handleOnDropdownSelect={this._handleOnDropdownSelect} handleOnDropdownSelect={this._handleOnDropdownSelect}
{...this.props} {...this.props}
/> />
<ActionSheet <OptionsModal
ref={(o) => (this.ActionSheet = o)} ref={(o) => (this.ActionSheet = o)}
options={['Reblog', intl.formatMessage({ id: 'alert.cancel' })]} options={['Reblog', intl.formatMessage({ id: 'alert.cancel' })]}
title={intl.formatMessage({ id: 'post.reblog_alert' })} title={intl.formatMessage({ id: 'post.reblog_alert' })}

View File

@ -13,7 +13,7 @@ import { navigate } from '../../../../navigation/service';
// Constants // Constants
import { default as ROUTES } from '../../../../constants/routeNames'; import { default as ROUTES } from '../../../../constants/routeNames';
import { PostHtmlRenderer, TextButton } from '../../..'; import { PostHtmlRenderer, TextButton, VideoPlayer } from '../../..';
// Styles // Styles
import styles from './commentBodyStyles'; import styles from './commentBodyStyles';
@ -21,14 +21,12 @@ import styles from './commentBodyStyles';
// Services and Actions // Services and Actions
import { writeToClipboard } from '../../../../utils/clipboard'; import { writeToClipboard } from '../../../../utils/clipboard';
import { toastNotification } from '../../../../redux/actions/uiAction'; import { toastNotification } from '../../../../redux/actions/uiAction';
import VideoPlayerSheet from './videoPlayerSheet';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler'; import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { OptionsModal } from '../../../atoms'; import { OptionsModal } from '../../../atoms';
import { useAppDispatch } from '../../../../hooks'; import { useAppDispatch } from '../../../../hooks';
import { isCommunity } from '../../../../utils/communityValidation'; import { isCommunity } from '../../../../utils/communityValidation';
import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters'; import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters';
import { startsWith } from 'core-js/core/string';
const WIDTH = Dimensions.get('window').width; const WIDTH = Dimensions.get('window').width;
@ -337,6 +335,7 @@ const CommentBody = ({
<PostHtmlRenderer <PostHtmlRenderer
contentWidth={_contentWidth} contentWidth={_contentWidth}
body={body} body={body}
isComment={true}
onElementIsImage={_onElementIsImage} onElementIsImage={_onElementIsImage}
setSelectedImage={_handleSetSelectedImage} setSelectedImage={_handleSetSelectedImage}
setSelectedLink={_handleSetSelectedLink} setSelectedLink={_handleSetSelectedLink}
@ -368,7 +367,12 @@ const CommentBody = ({
setVideoUrl(null); setVideoUrl(null);
}} }}
> >
<VideoPlayerSheet youtubeVideoId={youtubeVideoId} videoUrl={videoUrl} startTime={videoStartTime} /> <VideoPlayer
mode={youtubeVideoId ? 'youtube' : 'url'}
youtubeVideoId={youtubeVideoId}
videoUrl={videoUrl}
startTime={videoStartTime}
/>
</ActionsSheetView> </ActionsSheetView>
</Fragment> </Fragment>
); );

View File

@ -15,11 +15,10 @@ import { toastNotification } from '../../../../redux/actions/uiAction';
// Constants // Constants
import { default as ROUTES } from '../../../../constants/routeNames'; import { default as ROUTES } from '../../../../constants/routeNames';
import VideoPlayerSheet from './videoPlayerSheet';
import { OptionsModal } from '../../../atoms'; import { OptionsModal } from '../../../atoms';
import { isCommunity } from '../../../../utils/communityValidation'; import { isCommunity } from '../../../../utils/communityValidation';
import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters'; import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters';
import { PostHtmlRenderer } from '../../..'; import { PostHtmlRenderer, VideoPlayer } from '../../..';
const WIDTH = Dimensions.get('window').width; const WIDTH = Dimensions.get('window').width;
@ -294,7 +293,8 @@ const PostBody = ({ navigation, body, dispatch, onLoadEnd }) => {
setVideoUrl(null); setVideoUrl(null);
}} }}
> >
<VideoPlayerSheet <VideoPlayer
mode={youtubeVideoId ? 'youtube' : 'url'}
youtubeVideoId={youtubeVideoId} youtubeVideoId={youtubeVideoId}
videoUrl={videoUrl} videoUrl={videoUrl}
startTime={videoStartTime} startTime={videoStartTime}

View File

@ -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}
});

View File

@ -1,28 +1,33 @@
import React, { memo } from "react"; import React, { memo } from 'react';
import RenderHTML, { CustomRendererProps, Element, TNode } from "react-native-render-html"; import RenderHTML, { CustomRendererProps, Element, TNode } from 'react-native-render-html';
import styles from "./postHtmlRendererStyles"; import styles from './postHtmlRendererStyles';
import { LinkData, parseLinkData } from "./linkDataParser"; import { LinkData, parseLinkData } from './linkDataParser';
import VideoThumb from "./videoThumb"; import VideoThumb from './videoThumb';
import { AutoHeightImage } from "../autoHeightImage/autoHeightImage"; import { AutoHeightImage } from '../autoHeightImage/autoHeightImage';
import { useHtmlIframeProps, iframeModel } from '@native-html/iframe-plugin';
import WebView from 'react-native-webview';
import { VideoPlayer } from '..';
interface PostHtmlRendererProps { interface PostHtmlRendererProps {
contentWidth:number; contentWidth: number;
body:string; body: string;
onLoaded?:()=>void; isComment?: boolean;
setSelectedImage:(imgUrl:string)=>void; onLoaded?: () => void;
setSelectedLink:(url:string)=>void; setSelectedImage: (imgUrl: string) => void;
onElementIsImage:(imgUrl:string)=>void; setSelectedLink: (url: string) => void;
handleOnPostPress:(permlink:string, authro:string)=>void; onElementIsImage: (imgUrl: string) => void;
handleOnUserPress:(username:string)=>void; handleOnPostPress: (permlink: string, authro: string) => void;
handleTagPress:(tag:string, filter?:string)=>void; handleOnUserPress: (username: string) => void;
handleVideoPress:(videoUrl:string)=>void; handleTagPress: (tag: string, filter?: string) => void;
handleYoutubePress:(videoId:string, startTime:number)=>void; handleVideoPress: (videoUrl: string) => void;
handleYoutubePress: (videoId: string, startTime: number) => void;
} }
export const PostHtmlRenderer = memo(({ export const PostHtmlRenderer = memo(
({
contentWidth, contentWidth,
body, body,
isComment,
onLoaded, onLoaded,
setSelectedImage, setSelectedImage,
setSelectedLink, setSelectedLink,
@ -32,185 +37,174 @@ export const PostHtmlRenderer = memo(({
handleTagPress, handleTagPress,
handleVideoPress, handleVideoPress,
handleYoutubePress, handleYoutubePress,
}:PostHtmlRendererProps) => { }: PostHtmlRendererProps) => {
//new renderer functions
body = body.replace(/<center>/g, '<div class="text-center">').replace(/<\/center>/g, '</div>');
//new renderer functions console.log('Comment body:', body);
body = body.replace(/<center>/g, '<div class="text-center">').replace(/<\/center>/g,'</div>');
const _handleOnLinkPress = (data: LinkData) => {
console.log("Comment body:", body); if (!data) {
return;
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;
} }
} catch (error) {}
}; const {
type,
href,
const _onElement = (element:Element) => { author,
if(element.tagName === 'img' && element.attribs.src){ 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; const imgUrl = element.attribs.src;
console.log("img element detected", imgUrl); console.log('img element detected', imgUrl);
onElementIsImage(imgUrl) onElementIsImage(imgUrl);
} }
}; };
const _anchorRenderer = ({ InternalRenderer, tnode, ...props }: CustomRendererProps<TNode>) => {
const _anchorRenderer = ({ const parsedTnode = parseLinkData(tnode);
InternalRenderer,
tnode,
...props
}:CustomRendererProps<TNode>) => {
const _onPress = () => { const _onPress = () => {
console.log("Link Pressed:", tnode) console.log('Link Pressed:', tnode);
const data = parseLinkData(tnode); const data = parseLinkData(tnode);
_handleOnLinkPress(data); _handleOnLinkPress(data);
}; };
//process video link
if(tnode.classes?.indexOf('markdown-video-link') >= 0){ if(tnode.classes?.indexOf('markdown-video-link') >= 0){
const imgElement = tnode.children.find((child)=>{ if(isComment){
return child.classes.indexOf('video-thumbnail') > 0 ? true:false const imgElement = tnode.children.find((child) => {
}) return child.classes.indexOf('video-thumbnail') > 0 ? true : false;
if(!imgElement){ });
if (!imgElement) {
return <VideoThumb contentWidth={contentWidth} onPress={_onPress} />;
}
} else {
return ( return (
<VideoThumb contentWidth={contentWidth} onPress={_onPress} /> <VideoPlayer
) mode={parsedTnode.youtubeId ? 'youtube' : 'url'}
contentWidth={contentWidth}
videoUrl={parsedTnode.videoHref}
youtubeVideoId={parsedTnode.youtubeId}
startTime={parsedTnode.startTime}
disableAutoplay={true}
/>
);
} }
} }
return ( return <InternalRenderer tnode={tnode} onPress={_onPress} {...props} />;
<InternalRenderer };
tnode={tnode}
onPress={_onPress}
{...props}
/>
);
}
//this method checks if image is a child of table column //this method checks if image is a child of table column
//and calculates img width accordingly, //and calculates img width accordingly,
//returns full width if img is not part of table //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 //return full width if not parent exist
if(!tnode.parent || tnode.parent.tagName === 'body'){ if (!tnode.parent || tnode.parent.tagName === 'body') {
return contentWidth; return contentWidth;
} }
//return divided width based on number td tags //return divided width based on number td tags
if(tnode.parent.tagName === 'td'){ if (tnode.parent.tagName === 'td') {
const cols = tnode.parent.parent.children.length const cols = tnode.parent.parent.children.length;
return contentWidth/cols; return contentWidth / cols;
} }
//check next parent //check next parent
return getMaxImageWidth(tnode.parent); return getMaxImageWidth(tnode.parent);
} };
const _imageRenderer = ({ tnode }: CustomRendererProps<TNode>) => {
const _imageRenderer = ({
tnode,
}:CustomRendererProps<TNode>) => {
const imgUrl = tnode.attributes.src; const imgUrl = tnode.attributes.src;
const _onPress = () => { const _onPress = () => {
console.log("Image Pressed:", imgUrl) console.log('Image Pressed:', imgUrl);
setSelectedImage(imgUrl); setSelectedImage(imgUrl);
}; };
const isVideoThumb = tnode.classes?.indexOf('video-thumbnail') >= 0; const isVideoThumb = tnode.classes?.indexOf('video-thumbnail') >= 0;
const isAnchored = tnode.parent?.tagName === 'a'; const isAnchored = tnode.parent?.tagName === 'a';
if(isVideoThumb){ if (isVideoThumb) {
return <VideoThumb contentWidth={contentWidth} uri={imgUrl}/>; return <VideoThumb contentWidth={contentWidth} uri={imgUrl} />;
} } else {
else {
const maxImgWidth = getMaxImageWidth(tnode); const maxImgWidth = getMaxImageWidth(tnode);
return ( return (
<AutoHeightImage <AutoHeightImage
contentWidth={maxImgWidth} contentWidth={maxImgWidth}
imgUrl={imgUrl} imgUrl={imgUrl}
isAnchored={isAnchored} isAnchored={isAnchored}
onPress={_onPress} onPress={_onPress}
/> />
) );
} }
};
}
/** /**
* the para renderer is designd to remove margins from para * the para renderer is designd to remove margins from para
@ -218,58 +212,90 @@ export const PostHtmlRenderer = memo(({
* a weired misalignment of bullet and content * a weired misalignment of bullet and content
* @returns Default Renderer * @returns Default Renderer
*/ */
const _paraRenderer = ({ const _paraRenderer = ({ TDefaultRenderer, ...props }: CustomRendererProps<TNode>) => {
TDefaultRenderer, props.style = props.tnode.parent.tagName === 'li' ? styles.pLi : styles.p;
...props
}:CustomRendererProps<TNode>) => {
props.style = props.tnode.parent.tagName === 'li' return <TDefaultRenderer {...props} />;
? styles.pLi };
: styles.p
return (
<TDefaultRenderer // iframe renderer for rendering iframes in body
{...props} const _iframeRenderer = function IframeRenderer(props) {
/> const iframeProps = useHtmlIframeProps(props);
) const checkSrcRegex = /(.*?)\.(mp4|webm|ogg)$/gi;
} const isVideoFormat = iframeProps.source.uri.match(checkSrcRegex);
//this hack help avoid autoplaying fullscreened iframe videos;
return ( const src = isVideoFormat ?
<RenderHTML {
source={{ html:body }} html: `
contentWidth={contentWidth} <video width="100%" height="auto" controls>
baseStyle={{...styles.baseStyle, width:contentWidth}} <source src="${iframeProps.source.uri}" type="video/mp4">
classesStyles={{ </video>`,
phishy:styles.phishy, }:{
'text-justify':styles.textJustify, uri: iframeProps.source.uri,
'text-center':styles.textCenter };
}}
tagsStyles={{ return (
body:styles.body, <VideoPlayer
a:styles.a, mode='source'
img:styles.img, source={src}
th:styles.th, contentWidth={contentWidth}
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, return (
p:styles.p, <RenderHTML
table:styles.table, source={{ html: body }}
}} contentWidth={contentWidth}
domVisitors={{ baseStyle={{ ...styles.baseStyle, width: contentWidth }}
onElement:_onElement classesStyles={{
}} phishy: styles.phishy,
renderers={{ 'text-justify': styles.textJustify,
img:_imageRenderer, 'text-center': styles.textCenter,
a:_anchorRenderer, }}
p:_paraRenderer tagsStyles={{
}} body: styles.body,
onHTMLLoaded={onLoaded && onLoaded} a: styles.a,
defaultTextProps={{ img: styles.img,
selectable:true 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,
}, (next, prev)=>next.body === prev.body) 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,
);

View File

@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useRef, useState, Fragment } from 'react
import { View, Text, ScrollView, Dimensions, SafeAreaView, RefreshControl } from 'react-native'; import { View, Text, ScrollView, Dimensions, SafeAreaView, RefreshControl } from 'react-native';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import get from 'lodash/get'; import get from 'lodash/get';
import ActionSheet from 'react-native-actionsheet';
// Providers // Providers
import { userActivity } from '../../../providers/ecency/ePoint'; import { userActivity } from '../../../providers/ecency/ePoint';
@ -20,6 +19,7 @@ import { ParentPost } from '../../parentPost';
// Styles // Styles
import styles from './postDisplayStyles'; import styles from './postDisplayStyles';
import { OptionsModal } from '../../atoms';
const HEIGHT = Dimensions.get('window').width; const HEIGHT = Dimensions.get('window').width;
@ -251,7 +251,7 @@ const PostDisplayView = ({
)} )}
</ScrollView> </ScrollView>
{post && _getTabBar(true)} {post && _getTabBar(true)}
<ActionSheet <OptionsModal
ref={actionSheet} ref={actionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.delete' }), intl.formatMessage({ id: 'alert.delete' }),

View File

@ -3,7 +3,6 @@ import { injectIntl } from 'react-intl';
import { Text, View, ScrollView, TouchableOpacity, Alert } from 'react-native'; import { Text, View, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { WebView } from 'react-native-webview'; import { WebView } from 'react-native-webview';
import get from 'lodash/get'; import get from 'lodash/get';
import ActionSheet from 'react-native-actionsheet';
import Autocomplete from '@esteemapp/react-native-autocomplete-input'; import Autocomplete from '@esteemapp/react-native-autocomplete-input';
import { ScaleSlider, TextInput } from '..'; import { ScaleSlider, TextInput } from '..';
import { hsOptions } from '../../constants/hsOptions'; import { hsOptions } from '../../constants/hsOptions';
@ -23,6 +22,7 @@ import { PROMOTE_PRICING, PROMOTE_DAYS } from '../../constants/options/points';
// Styles // Styles
import styles from './promoteStyles'; import styles from './promoteStyles';
import { OptionsModal } from '../atoms';
const PromoteView = ({ const PromoteView = ({
intl, intl,
@ -214,7 +214,7 @@ const PromoteView = ({
</View> </View>
</ScrollView> </ScrollView>
</View> </View>
<ActionSheet <OptionsModal
ref={startActionSheet} ref={startActionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.confirm' }), intl.formatMessage({ id: 'alert.confirm' }),

View File

@ -10,7 +10,6 @@ import {
} from 'react-native'; } from 'react-native';
import { injectIntl, useIntl } from 'react-intl'; import { injectIntl, useIntl } from 'react-intl';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import ActionSheet from 'react-native-actionsheet';
import VersionNumber from 'react-native-version-number'; import VersionNumber from 'react-native-version-number';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { getStorageType } from '../../../realm/realm'; import { getStorageType } from '../../../realm/realm';
@ -28,6 +27,7 @@ import { getVotingPower } from '../../../utils/manaBar';
// Styles // Styles
import styles from './sideMenuStyles'; import styles from './sideMenuStyles';
import { OptionsModal } from '../../atoms';
// Images // Images
const SIDE_MENU_BACKGROUND = require('../../../assets/side_menu_background.png'); const SIDE_MENU_BACKGROUND = require('../../../assets/side_menu_background.png');
@ -75,8 +75,9 @@ const SideMenuView = ({
} }
if (item.id === 'refer') { if (item.id === 'refer') {
const shareUrl = `https://ecency.com/signup?referral=${currentAccount.username}`;
Share.share({ Share.share({
url: `https://ecency.com/signup?referral=${currentAccount.username}`, message: shareUrl,
}); });
return; return;
} }
@ -165,7 +166,7 @@ const SideMenuView = ({
<FlatList data={menuItems} keyExtractor={(item) => item.id} renderItem={_renderItem} /> <FlatList data={menuItems} keyExtractor={(item) => item.id} renderItem={_renderItem} />
</View> </View>
<Text style={styles.versionText}>{`v${appVersion}, ${buildVersion}${storageT}`}</Text> <Text style={styles.versionText}>{`v${appVersion}, ${buildVersion}${storageT}`}</Text>
<ActionSheet <OptionsModal
ref={ActionSheetRef} ref={ActionSheetRef}
options={[ options={[
intl.formatMessage({ id: 'side_menu.logout' }), intl.formatMessage({ id: 'side_menu.logout' }),

View File

@ -0,0 +1,5 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
});

View File

@ -0,0 +1,112 @@
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';
import { WebViewSource } from 'react-native-webview/lib/WebViewTypes';
interface VideoPlayerProps {
mode: 'source'|'youtube'|'url';
contentWidth?: number;
youtubeVideoId?: string;
videoUrl?: string;
startTime?: number;
source?: WebViewSource;
//prop for youtube player
disableAutoplay?:boolean;
}
const VideoPlayer = ({
youtubeVideoId,
videoUrl,
startTime,
source,
contentWidth = Dimensions.get('screen').width,
mode,
disableAutoplay
}: VideoPlayerProps) => {
const PLAYER_HEIGHT = contentWidth * (9 / 16);
const [shouldPlay, setShouldPlay] = useState(false);
const [loading, setLoading] = useState(true);
const _onReady = () => {
setLoading(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!');
setLoading(false);
};
const initialParams: InitialPlayerParams = {
start: startTime,
};
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 === 'source' && source) || (mode === 'url' && videoUrl)) && (
<View style={{ height: PLAYER_HEIGHT }}>
<WebView
scalesPageToFit={true}
bounces={false}
javaScriptEnabled={true}
automaticallyAdjustContentInsets={false}
onLoadEnd={() => {
setLoading(false);
}}
onLoadStart={() => {
setLoading(true);
}}
source={source || { uri: videoUrl }}
style={{ width: contentWidth, height: PLAYER_HEIGHT}}
startInLoadingState={true}
onShouldStartLoadWithRequest={() => true}
mediaPlaybackRequiresUserAction={true}
allowsInlineMediaPlayback={true}
/>
</View>
)}
{loading && <ActivityIndicator style={styles.activityIndicator} />}
</View>
);
};
export default VideoPlayer;
const styles = StyleSheet.create({
container: {
paddingVertical: 16,
},
activityIndicator: {
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
top: 0,
bottom: 0,
left: 0,
right: 0,
},
});

View File

@ -2,11 +2,11 @@ import React, { PureComponent, Fragment } from 'react';
import { StatusBar } from 'react-native'; import { StatusBar } from 'react-native';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import get from 'lodash/get'; import get from 'lodash/get';
import ActionSheet from 'react-native-actionsheet';
import { ProfileEditContainer } from '../../../containers'; import { ProfileEditContainer } from '../../../containers';
import { AvatarHeader, ProfileEditForm } from '../../../components'; import { AvatarHeader, ProfileEditForm } from '../../../components';
import { OptionsModal } from '../../../components/atoms';
class ProfileEditScreen extends PureComponent { class ProfileEditScreen extends PureComponent {
/* Props /* Props
@ -81,7 +81,7 @@ class ProfileEditScreen extends PureComponent {
handleOnSubmit={handleOnSubmit} handleOnSubmit={handleOnSubmit}
/> />
<ActionSheet <OptionsModal
ref={this.galleryRef} ref={this.galleryRef}
options={[ options={[
intl.formatMessage({ intl.formatMessage({

View File

@ -1,17 +1,9 @@
import React, { Fragment, Component } from 'react'; import React, { Fragment } from 'react';
import { Text, View, ScrollView, TouchableOpacity } from 'react-native'; import { Text, View, ScrollView } from 'react-native';
import { WebView } from 'react-native-webview';
import ActionSheet from 'react-native-actionsheet';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import get from 'lodash/get';
import QRCode from 'react-native-qrcode-svg'; import QRCode from 'react-native-qrcode-svg';
import { connect, useDispatch } from 'react-redux'; import { connect } from 'react-redux';
import { BasicHeader } from '../../../components';
import { hsOptions } from '../../../constants/hsOptions';
import AUTH_TYPE from '../../../constants/authType';
import { encryptKey, decryptKey } from '../../../utils/crypto';
import { BasicHeader, MainButton } from '../../../components';
import styles from './transferStyles'; import styles from './transferStyles';

View File

@ -4,7 +4,6 @@ import { WebView } from 'react-native-webview';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import Slider from '@esteemapp/react-native-slider'; import Slider from '@esteemapp/react-native-slider';
import get from 'lodash/get'; import get from 'lodash/get';
import ActionSheet from 'react-native-actionsheet';
// Constants // Constants
import AUTH_TYPE from '../../../constants/authType'; import AUTH_TYPE from '../../../constants/authType';
@ -28,6 +27,7 @@ import { vestsToHp } from '../../../utils/conversions';
// Styles // Styles
import styles from './transferStyles'; import styles from './transferStyles';
import { OptionsModal } from '../../../components/atoms';
class DelegateScreen extends Component { class DelegateScreen extends Component {
constructor(props) { constructor(props) {
@ -222,7 +222,7 @@ class DelegateScreen extends Component {
</MainButton> </MainButton>
</View> </View>
</View> </View>
<ActionSheet <OptionsModal
ref={this.startActionSheet} ref={this.startActionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.confirm' }), intl.formatMessage({ id: 'alert.confirm' }),

View File

@ -1,7 +1,6 @@
/* eslint-disable react/no-unused-state */ /* eslint-disable react/no-unused-state */
import React, { Fragment, Component } from 'react'; import React, { Fragment, Component } from 'react';
import { Text, View, ScrollView, Alert } from 'react-native'; import { Text, View, ScrollView, Alert } from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import Slider from '@esteemapp/react-native-slider'; import Slider from '@esteemapp/react-native-slider';
import get from 'lodash/get'; import get from 'lodash/get';
@ -28,6 +27,7 @@ import { vestsToHp } from '../../../utils/conversions';
import { isEmptyDate } from '../../../utils/time'; import { isEmptyDate } from '../../../utils/time';
import styles from './transferStyles'; import styles from './transferStyles';
import { OptionsModal } from '../../../components/atoms';
/* Props /* Props
* ------------------------------------------------ * ------------------------------------------------
* @prop { type } name - Description.... * @prop { type } name - Description....
@ -327,7 +327,7 @@ class PowerDownView extends Component {
</View> </View>
</ScrollView> </ScrollView>
</View> </View>
<ActionSheet <OptionsModal
ref={this.startActionSheet} ref={this.startActionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.confirm' }), intl.formatMessage({ id: 'alert.confirm' }),
@ -338,7 +338,7 @@ class PowerDownView extends Component {
destructiveButtonIndex={0} destructiveButtonIndex={0}
onPress={(index) => (index === 0 ? this._handleTransferAction() : null)} onPress={(index) => (index === 0 ? this._handleTransferAction() : null)}
/> />
<ActionSheet <OptionsModal
ref={this.stopActionSheet} ref={this.stopActionSheet}
options={[ options={[
intl.formatMessage({ id: 'alert.confirm' }), intl.formatMessage({ id: 'alert.confirm' }),

View File

@ -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 { Text, View, ScrollView, TouchableOpacity } from 'react-native';
import { WebView } from 'react-native-webview'; import { WebView } from 'react-native-webview';
import ActionSheet from 'react-native-actionsheet';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import get from 'lodash/get'; import get from 'lodash/get';
@ -20,6 +19,7 @@ import {
} from '../../../components'; } from '../../../components';
import styles from './transferStyles'; import styles from './transferStyles';
import { OptionsModal } from '../../../components/atoms';
const TransferView = ({ const TransferView = ({
currentAccountName, currentAccountName,
@ -275,7 +275,7 @@ const TransferView = ({
</View> </View>
</ScrollView> </ScrollView>
</View> </View>
<ActionSheet <OptionsModal
ref={confirm} ref={confirm}
options={[ options={[
intl.formatMessage({ id: 'alert.confirm' }), intl.formatMessage({ id: 'alert.confirm' }),

View File

@ -1,7 +1,6 @@
import React, { Fragment, Component } from 'react'; import React, { Fragment, Component } from 'react';
import { Text, View, ScrollView, TouchableOpacity } from 'react-native'; import { Text, View, ScrollView, TouchableOpacity } from 'react-native';
import { WebView } from 'react-native-webview'; import { WebView } from 'react-native-webview';
import ActionSheet from 'react-native-actionsheet';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import get from 'lodash/get'; import get from 'lodash/get';
@ -20,6 +19,7 @@ import {
} from '../../../components'; } from '../../../components';
import styles from './transferStyles'; import styles from './transferStyles';
import { OptionsModal } from '../../../components/atoms';
/* Props /* Props
* ------------------------------------------------ * ------------------------------------------------
@ -260,7 +260,7 @@ class TransferTokenView extends Component {
</View> </View>
</ScrollView> </ScrollView>
</View> </View>
<ActionSheet <OptionsModal
ref={(o) => (this.ActionSheet = o)} ref={(o) => (this.ActionSheet = o)}
options={[ options={[
intl.formatMessage({ id: 'alert.confirm' }), intl.formatMessage({ id: 'alert.confirm' }),

View File

@ -44,6 +44,28 @@ export const generatePermlink = (title, random = false) => {
return perm; return perm;
}; };
export const extractWordAtIndex = (text:string, index:number) => {
const END_REGEX = /[\s,]/
let word = '';
for(let i = index; i >= 0 && (!END_REGEX.test(text[i]) || i === index); i--){
if(text[i]){
word += text[i];
}
}
word = word.split('').reverse().join('');
if(!END_REGEX.test(text[index])){
for(let i = index + 1; i < text.length && !END_REGEX.test(text[i]); i++){
if(text[i]){
word += text[i];
}
}
}
return word;
}
export const generateReplyPermlink = (toAuthor) => { export const generateReplyPermlink = (toAuthor) => {
if (!toAuthor) { if (!toAuthor) {
return ''; return '';

View File

@ -1118,6 +1118,11 @@
resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz#2dc8c57044de0340eb53a7ba602e59abf80dc799" resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz#2dc8c57044de0340eb53a7ba602e59abf80dc799"
integrity sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ== integrity sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==
"@formidable-webview/webshell@^2.6.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@formidable-webview/webshell/-/webshell-2.6.0.tgz#64704c0b513206e71b23118b3c9d096f0d545005"
integrity sha512-FwQQDajg1xs7W3CUiUNJMvdjgLjKLDGzs0XPzoVg0Dunhold1Jg7w5pihUdvVugFlNtkSpXMA+du9QDHE8lmpg==
"@hapi/address@2.x.x": "@hapi/address@2.x.x":
version "2.1.4" version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@ -1412,6 +1417,21 @@
css-to-react-native "^3.0.0" css-to-react-native "^3.0.0"
csstype "^3.0.8" csstype "^3.0.8"
"@native-html/iframe-plugin@^2.6.1":
version "2.6.1"
resolved "https://registry.yarnpkg.com/@native-html/iframe-plugin/-/iframe-plugin-2.6.1.tgz#5b9c36d9d500f82f0bcf654bc005922df4211158"
integrity sha512-PM2vFNT44n/UkCm9+OUn+cNSKgiMjaw7c7/2JnnztHDLVMtPIf52K/86miWNpQpxFoy1ouoLVOvfjFRhoPXjag==
dependencies:
"@formidable-webview/webshell" "^2.6.0"
"@native-html/plugins-core" "1.3.0"
"@types/prop-types" "^15.7.4"
prop-types "^15.7.2"
"@native-html/plugins-core@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@native-html/plugins-core/-/plugins-core-1.3.0.tgz#f1f24622097551930d9dab0214c4929d00f7446e"
integrity sha512-vce35gqGJKa2oPDZVa2sKjucFFVK+3g8quLayeXiJtj5LzuS8TWGrBFTS5O4ToUtE02AJkCDOLwAguCzK2HWdQ==
"@native-html/transient-render-engine@^9.2.2": "@native-html/transient-render-engine@^9.2.2":
version "9.2.2" version "9.2.2"
resolved "https://registry.yarnpkg.com/@native-html/transient-render-engine/-/transient-render-engine-9.2.2.tgz#00691518926ea47709185c3a25a786472c99a1f0" resolved "https://registry.yarnpkg.com/@native-html/transient-render-engine/-/transient-render-engine-9.2.2.tgz#00691518926ea47709185c3a25a786472c99a1f0"
@ -1784,6 +1804,11 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/prop-types@^15.7.4":
version "15.7.4"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
"@types/ramda@^0.27.40": "@types/ramda@^0.27.40":
version "0.27.44" version "0.27.44"
resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.44.tgz#ba2283d67fcff366f7e68bd5124a0466e467967f" resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.44.tgz#ba2283d67fcff366f7e68bd5124a0466e467967f"
@ -8599,10 +8624,10 @@ react-native-matomo-sdk@feruzm/react-native-matomo-sdk:
version "0.4.1" version "0.4.1"
resolved "https://codeload.github.com/feruzm/react-native-matomo-sdk/tar.gz/392b1cfca771b28005821ef909ffb9a2082156d9" resolved "https://codeload.github.com/feruzm/react-native-matomo-sdk/tar.gz/392b1cfca771b28005821ef909ffb9a2082156d9"
react-native-modal-dropdown@^1.0.1: react-native-modal-dropdown@^1.0.2:
version "1.0.1" version "1.0.2"
resolved "https://registry.yarnpkg.com/react-native-modal-dropdown/-/react-native-modal-dropdown-1.0.1.tgz#24a9396dcb5dadf92feea1deae1089bcef054b20" resolved "https://registry.yarnpkg.com/react-native-modal-dropdown/-/react-native-modal-dropdown-1.0.2.tgz#3e1efae5a5eacc42f44ac96468ea2f1b5bb0d759"
integrity sha512-7QAuKvdsIMvz5N2FnRGrZKb+JR3bYFFelONdmfwp1UW3acOyrhF32H06aMD09x2PdBz3VaR5+3iApt9FYClktQ== integrity sha512-X13RbwoQdaOb9Ffi7fjRVwh9OXauXIJhum0uXGzYZLMyoCQ5yt5cxf27P/YZbdh80Fb7bdPx6vTA4PTpUK2WDQ==
dependencies: dependencies:
prop-types "^15.6.0" prop-types "^15.6.0"