Merge remote-tracking branch 'upstream/development' into nt/navigation

# Conflicts:
#	src/screens/register/registerScreen.js
This commit is contained in:
Nouman Tahir 2022-09-08 11:59:46 +05:00
commit 6580f79153
33 changed files with 761 additions and 486 deletions

View File

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

View File

@ -10,6 +10,16 @@ public class SplashActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, MainActivity.class);
//workaround for getInitialNotification and onNotificationOpenedApp returning null always
//TOOD: use react-native-bootsplash instead of react-native-splash-screen as recommended by firebase
//firebase issue ref: https://github.com/invertase/react-native-firebase/issues/3469
//ecency project card ref: https://github.com/orgs/ecency/projects/2#card-85455956
Bundle extras = getIntent().getExtras();
if (extras != null) {
intent.putExtras(extras);
}
startActivity(intent);
finish();
}

View File

@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.33</string>
<string>3.0.34</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2812</string>
<string>2813</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>NSAppTransportSecurity</key>

View File

@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.33</string>
<string>3.0.34</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2812</string>
<string>2813</string>
</dict>
</plist>

View File

@ -1132,7 +1132,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2812;
CURRENT_PROJECT_VERSION = 2813;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = 75B6RXTKGT;
EXCLUDED_ARCHS = "";
@ -1211,7 +1211,7 @@
CODE_SIGN_ENTITLEMENTS = Ecency/Ecency.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2812;
CURRENT_PROJECT_VERSION = 2813;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = 75B6RXTKGT;
EXCLUDED_ARCHS = "";

View File

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.33</string>
<string>3.0.34</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -62,6 +62,8 @@
</dict>
<key>NSCameraUsageDescription</key>
<string>To access your photos, Ecency needs your permission to help you share your photos.</string>
<key>NSFaceIDUsageDescription</key>
<string>Ecency requires FaceID access to allow you quick and secure access.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>To get accurate location for sharing</string>
<key>NSLocationWhenInUseUsageDescription</key>
@ -74,8 +76,6 @@
<string>Photo Library Access for allowing user to download and upload photos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo Usage Access for allowing user to upload photos</string>
<key>NSFaceIDUsageDescription</key>
<string>Ecency requires FaceID access to allow you quick and secure access.</string>
<key>UIAppFonts</key>
<array>
<string>Entypo.ttf</string>

View File

@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.33</string>
<string>3.0.34</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2812</string>
<string>2813</string>
</dict>
</plist>

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>3.0.33</string>
<string>3.0.34</string>
<key>CFBundleVersion</key>
<string>2812</string>
<string>2813</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@ -1,6 +1,6 @@
{
"name": "ecency",
"version": "3.0.33",
"version": "3.0.34",
"displayName": "Ecency",
"private": true,
"rnpm": {

View File

@ -45,14 +45,15 @@ const CommentView = ({
incrementRepliesCount
}) => {
const intl = useIntl();
const actionSheet = useRef(null);
const dispatch = useDispatch();
const actionSheet = useRef(null);
const repliesContainerRef = useRef<AnimatedView>(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 [_isShowSubComments, setIsShowSubComments] = useState(false);
const [isPressedShowButton, setIsPressedShowButton] = useState(false);
const [activeVotes, setActiveVotes] = useState([]);
const [cacheVoteIcrement, setCacheVoteIcrement] = useState(0);
@ -61,6 +62,16 @@ const CommentView = ({
const [replies, setReplies] = useState(comment.replies);
useEffect(()=>{
if(isShowSubComments){
setTimeout(()=>{
if(repliesContainerRef.current){
setIsShowSubComments(true);
repliesContainerRef.current.slideInRight(300);
}
},150)
}
},[])
useEffect(() => {
if (comment) {
@ -97,8 +108,20 @@ const CommentView = ({
const _showSubCommentsToggle = (force) => {
if (((replies && replies.length > 0) || force)) {
setIsShowSubComments(!_isShowSubComments);
if (repliesContainerRef.current) {
if (_isShowSubComments) {
repliesContainerRef.current.slideOutRight(300).then(()=>{
setIsShowSubComments(false);
});
} else {
setIsShowSubComments(true);
repliesContainerRef.current.slideInRight(300);
}
}
setIsPressedShowButton(true);
} else if (openReplyThread) {
openReplyThread();
}
@ -144,8 +167,11 @@ const CommentView = ({
)
const _renderReplies = () => {
return (
<AnimatedView animation="zoomIn" duration={300}>
<AnimatedView ref={repliesContainerRef}>
{_isShowSubComments &&
<Comments
isShowComments={isShowComments}
commentNumber={commentNumber + 1}
@ -163,7 +189,7 @@ const CommentView = ({
fetchedAt={fetchedAt}
incrementRepliesCount={_incrementRepliesCount}
handleOnReplyPress={_handleOnReplyPress}
/>
/>}
</AnimatedView>
)
@ -316,7 +342,7 @@ const CommentView = ({
secondaryContentComponent={_renderComment()}
/>
{_isShowSubComments && commentNumber > 0 && _renderReplies()}
{commentNumber > 0 && _renderReplies()}
</View>
</Fragment>
);

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect } from 'react';
import { Platform } from 'react-native';
import { withNavigation } from '@react-navigation/compat';
import { connect } from 'react-redux';
@ -19,7 +19,8 @@ import ROUTES from '../../../constants/routeNames';
// Component
import CommentsView from '../view/commentsView';
import { useAppSelector } from '../../../hooks';
import { deleteCommentCacheEntry } from '../../../redux/actions/cacheActions';
import { updateCommentCache } from '../../../redux/actions/cacheActions';
import { CommentCacheStatus } from '../../../redux/reducers/cacheReducer';
const CommentsContainer = ({
author,
@ -66,7 +67,11 @@ const CommentsContainer = ({
}, [commentCount, selectedFilter]);
useEffect(() => {
setPropComments(comments);
let _comments = comments;
if (_comments) {
_comments = _handleCachedComment(comments);
}
setPropComments(_comments);
}, [comments]);
useEffect(() => {
@ -190,12 +195,15 @@ const CommentsContainer = ({
var ignoreCache = false;
var replaceAtIndex = -1;
var removeAtIndex = -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 if (cachedComment.status === CommentCacheStatus.DELETED) {
removeAtIndex = index;
} else {
//comment is present in list but data is old
replaceAtIndex = index;
@ -203,10 +211,18 @@ const CommentsContainer = ({
}
});
//means deleted comment is not being retuend in fresh data, cache needs to be ignored
if (cachedComment.status === CommentCacheStatus.DELETED && removeAtIndex < 0) {
ignoreCache = true;
}
//manipulate comments with cached data
if (!ignoreCache) {
let newComments = [];
if (replaceAtIndex >= 0) {
if (removeAtIndex >= 0) {
newComments = _comments;
newComments.splice(removeAtIndex, 1);
} else if (replaceAtIndex >= 0) {
_comments[replaceAtIndex] = cachedComment;
newComments = [..._comments];
} else {
@ -266,10 +282,11 @@ const CommentsContainer = ({
let filteredComments;
deleteComment(currentAccount, pinCode, _permlink).then(() => {
let cachePath = null;
let deletedItem = null;
const _applyFilter = (item) => {
if (item.permlink === _permlink) {
cachePath = `${item.parent_author}/${item.parent_permlink}`;
deletedItem = item;
return false;
}
return true;
@ -284,7 +301,12 @@ const CommentsContainer = ({
}
// remove cached entry based on parent
dispatch(deleteCommentCacheEntry(cachePath));
if (deletedItem) {
const cachePath = `${deletedItem.parent_author}/${deletedItem.parent_permlink}`;
deletedItem.status = CommentCacheStatus.DELETED;
delete deletedItem.updated;
dispatch(updateCommentCache(cachePath, deletedItem, { isUpdate: true }));
}
});
};

View File

@ -18,4 +18,11 @@ export default EStyleSheet.create({
color: '$white',
fontSize: 10,
},
emptyText: {
color: '$primaryDarkGray',
fontSize: 16,
justifyContent: 'center',
marginTop: 5,
padding: 32,
},
});

View File

@ -1,5 +1,5 @@
import React, { useState, Fragment, useRef } from 'react';
import { FlatList } from 'react-native';
import { FlatList, Text } from 'react-native';
import get from 'lodash/get';
import { useIntl } from 'react-intl';
@ -124,6 +124,20 @@ const CommentsView = ({
marginTop: 8,
} : null
const _renderEmptyContent = () => {
if(commentNumber > 1){
return;
}
const _onPress = () => {
handleOnReplyPress()
}
return (
<Text onPress={_onPress} style={styles.emptyText}>
{intl.formatMessage({ id: "comments.no_comments" })}
</Text>
)
}
return (
<Fragment>
@ -133,6 +147,7 @@ const CommentsView = ({
data={comments}
renderItem={_renderItem}
keyExtractor={(item) => get(item, 'permlink')}
ListEmptyComponent={_renderEmptyContent()}
{...flatListProps}
/>
<OptionsModal

View File

@ -0,0 +1,27 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
padding: 16,
height: 40,
flexDirection: 'row',
alignItems: 'center',
marginVertical: 16,
},
inputContainer: {
marginLeft: 12,
justifyContent: 'center',
height: 36,
borderRadius: 12,
flex: 1,
backgroundColor: '$primaryLightBackground',
borderWidth: EStyleSheet.hairlineWidth,
borderColor: '$primaryDarkGray',
},
inputPlaceholder: {
color: '$primaryDarkGray',
fontSize: 16,
marginTop: 5,
paddingHorizontal: 16,
},
});

View File

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

View File

@ -1,29 +1,43 @@
import React, { useState, Fragment } from 'react';
import React, { useState, Fragment, useImperativeHandle, forwardRef, useRef } from 'react';
import { View } from 'react-native';
import { injectIntl } from 'react-intl';
import { injectIntl, useIntl } from 'react-intl';
// Components
import { FilterBar } from '../../filterBar';
import { Comments } from '../../comments';
import COMMENT_FILTER, { VALUE } from '../../../constants/options/comment';
import { WriteCommentButton } from './writeCommentButton';
// Styles
import styles from './commentDisplayStyles';
const CommentsDisplayView = ({
const CommentsDisplayView = forwardRef(
(
{
author,
commentCount,
fetchPost,
intl,
permlink,
mainAuthor,
handleOnVotersPress,
handleOnReplyPress,
fetchedAt,
}) => {
},
ref,
) => {
const intl = useIntl();
const writeCommentRef = useRef(null);
const [selectedFilter, setSelectedFilter] = useState('trending');
const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
useImperativeHandle(ref, () => ({
bounceCommentButton: () => {
console.log('bouncing comment button');
if (writeCommentRef.current) {
writeCommentRef.current.bounce();
}
},
}));
const _handleOnDropdownSelect = (option, index) => {
setSelectedFilter(option);
setSelectedOptionIndex(index);
@ -31,8 +45,8 @@ const CommentsDisplayView = ({
return (
<Fragment>
{commentCount > 0 && (
<Fragment>
<WriteCommentButton ref={writeCommentRef} onPress={handleOnReplyPress} />
<FilterBar
dropdownIconName="arrow-drop-down"
options={VALUE.map((val) => intl.formatMessage({ id: `comment_filter.${val}` }))}
@ -56,9 +70,9 @@ const CommentsDisplayView = ({
/>
</View>
</Fragment>
)}
</Fragment>
);
};
},
);
export default injectIntl(CommentsDisplayView);
export default CommentsDisplayView;

View File

@ -0,0 +1,56 @@
import { View, Text } from 'react-native'
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
import UserAvatar from '../../userAvatar';
import { View as AnimatedView } from 'react-native-animatable';
import { TouchableOpacity } from 'react-native-gesture-handler';
import styles from './WriteCommentButtonStyles';
import { useAppSelector } from '../../../hooks';
import showLoginAlert from '../../../utils/showLoginAlert';
import { useIntl } from 'react-intl';
interface WriteCommentButton {
onPress: () => void;
}
export const WriteCommentButton = forwardRef(({ onPress }, ref) => {
const intl = useIntl();
const animatedContainer = useRef<AnimatedView>();
const isLoggedIn = useAppSelector(state => state.application.isLoggedIn);
useImperativeHandle(ref, () => ({
bounce: () => {
console.log("bouncing")
if (animatedContainer.current) {
animatedContainer.current.swing(1000);
}
},
}));
const _onPress = () => {
if (!isLoggedIn) {
showLoginAlert({ intl })
return;
}
if (onPress) {
onPress();
}
}
return (
<AnimatedView ref={animatedContainer}>
<TouchableOpacity onPress={_onPress}>
<View style={styles.container}>
<UserAvatar username="demo.com" />
<View style={styles.inputContainer}>
<Text style={styles.inputPlaceholder}>
{intl.formatMessage({id:'quick_reply.placeholder'})}
</Text>
</View>
</View>
</TouchableOpacity>
</AnimatedView>
)
})

View File

@ -1,6 +1,7 @@
import { get, isEmpty, some } from 'lodash';
import { get } from 'lodash';
import React, { useEffect, useRef, useState } from 'react';
import { Animated, Text, TouchableOpacity, View } from 'react-native';
import { Text, TouchableOpacity, View } from 'react-native';
import { View as AnimatedView } from 'react-native-animatable';
import { useDispatch } from 'react-redux';
import { IconButton } from '..';
import { toastNotification } from '../../redux/actions/uiAction';
@ -35,10 +36,11 @@ interface Props {
const ForegroundNotification = ({ remoteMessage }: Props) => {
let hideTimeout = null;
const dispatch = useDispatch();
const intl = useIntl();
const hideTimeoutRef = useRef<any>(null);
const containerRef = useRef<AnimatedView>(null);
const [duration] = useState(5000);
const [activeId, setActiveId] = useState('');
const [isVisible, setIsVisible] = useState(false);
@ -47,9 +49,6 @@ const ForegroundNotification = ({remoteMessage}:Props) => {
const [body, setBody] = useState('');
const animatedValue = useRef(new Animated.Value(-CONTAINER_HEIGHT)).current;
useEffect(() => {
if (remoteMessage) {
@ -75,39 +74,29 @@ const ForegroundNotification = ({remoteMessage}:Props) => {
}
return () => {
if(hideTimeout){
clearTimeout(hideTimeout);
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
}
}, [remoteMessage]);
const show = () => {
// Will change fadeAnim value to 1 in 5 seconds
Animated.timing(animatedValue, {
toValue: 0,
duration: 350
}).start();
setIsVisible(true);
hideTimeout = setTimeout(()=>{
setIsVisible(true)
hideTimeoutRef.current = setTimeout(() => {
hide();
}, duration)
};
const hide = () => {
if(hideTimeout || isVisible){
// Will change fadeAnim value to 0 in 3 seconds
Animated.timing(animatedValue, {
toValue: -CONTAINER_HEIGHT,
duration: 200
}).start(()=>{
dispatch(toastNotification(''))
});
const hide = async () => {
if (containerRef.current) {
await containerRef.current.fadeOutUp(300);
setIsVisible(false);
clearTimeout(hideTimeout);
if(hideTimeoutRef.current){
clearTimeout(hideTimeoutRef.current);
}
}
};
@ -118,7 +107,7 @@ const ForegroundNotification = ({remoteMessage}:Props) => {
get(data, 'permlink1', '') + get(data, 'permlink2', '') + get(data, 'permlink3', '');
let params = {
author: get(remoteMessage, 'source', ''),
author: get(data, 'source', ''),
permlink: fullPermlink,
};
let key = fullPermlink
@ -129,17 +118,19 @@ const ForegroundNotification = ({remoteMessage}:Props) => {
params,
key,
});
hide();
}
return (
<Animated.View
style={{
...styles.container,
transform: [{ translateY: animatedValue }],
}}
>
isVisible &&
<AnimatedView
ref={containerRef}
style={styles.container}
animation='slideInDown'
duration={500}>
<View style={styles.contentContainer}>
<TouchableOpacity onPress={_onPress} style={{ flexShrink: 1 }}>
@ -153,8 +144,6 @@ const ForegroundNotification = ({remoteMessage}:Props) => {
</View>
</TouchableOpacity>
<IconButton
name='close'
color="white"
@ -162,8 +151,7 @@ const ForegroundNotification = ({remoteMessage}:Props) => {
onPress={hide}
/>
</View>
</Animated.View>
</AnimatedView>
)
}

View File

@ -16,7 +16,7 @@ export default EStyleSheet.create({
ios:getStatusBarHeight() + 12,
android:8,
}),
backgroundColor: '$primaryDarkText',
backgroundColor: '$darkGrayBackground',
shadowColor: '#5f5f5fbf',
shadowOpacity: 0.3,
shadowOffset: {

View File

@ -26,6 +26,8 @@ interface Props extends TextInputProps {
inputStyle:TextStyle;
isValid:boolean;
onChange?:(value:string)=>void;
onFocus?:()=>void;
onBlur?:()=>void;
}
const FormInputView = ({
@ -44,6 +46,7 @@ const FormInputView = ({
isValid,
value,
onBlur,
onFocus,
...props
}:Props) => {
const [_value, setValue] = useState(value || '');
@ -62,6 +65,9 @@ const FormInputView = ({
const _handleOnFocus = () => {
setInputBorderColor('#357ce6');
if(onFocus){
onFocus();
}
};
const _handleOnBlur = () => {

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { View as AnimatedView } from 'react-native-animatable'
import { Portal } from 'react-native-portalize';
import styles from '../children/inputSupportModal.styles';
@ -12,32 +12,56 @@ export interface InputSupportModalProps {
export const InputSupportModal = ({ children, visible, onClose }: InputSupportModalProps, ref) => {
const container = useRef<AnimatedView>(null);
const innerContainer = useRef<AnimatedView>(null);
return (
const [showModal, setShowModal] = useState(visible);
useEffect(() => {
if (visible) {
setShowModal(true);
}
else if (!visible && container.current && innerContainer.current) {
innerContainer.current.slideOutDown(1000)
setTimeout(async () => {
await container.current?.fadeOut(200)
setShowModal(false);
}, 300)
}
}, [visible])
return showModal && (
<Portal>
{
visible && (
<AnimatedView
style={styles.container}
ref={container}
animation='fadeIn'
duration={300}
animation='fadeInUp'>
<>
<View style={styles.container} onTouchEnd={onClose} />
style={styles.container} >
<AnimatedView
ref={innerContainer}
style={{ flex: 1 }}
animation='slideInUp'
duration={300}>
<View
style={{ flex: 1 }}
onTouchEnd={onClose} />
{
Platform.select({
ios: (
<KeyboardAvoidingView behavior="padding" style={{backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>
<KeyboardAvoidingView behavior="padding" style={{}}>
{children}
</KeyboardAvoidingView>
),
android: <View>{children}</View>,
})
}
</>
</AnimatedView>
)
}
</AnimatedView>
</Portal>
);
};

View File

@ -1,4 +1,3 @@
import { Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import getWindowDimensions from '../../../utils/getWindowDimensions';
@ -14,7 +13,8 @@ export default EStyleSheet.create({
backgroundColor: '$primaryBackgroundColor',
},
headerLine: {
bottom: 10,
marginTop: -4,
marginBottom: 4,
},
title: {
fontSize: 24,

View File

@ -1,10 +1,9 @@
import React, { useCallback, useEffect, useRef, useState, Fragment } from 'react';
import { View, Text, ScrollView, SafeAreaView, RefreshControl } from 'react-native';
import { View, Text, ScrollView, RefreshControl } from 'react-native';
import { injectIntl } from 'react-intl';
import get from 'lodash/get';
// Providers
import { useSelector } from 'react-redux';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { userActivity } from '../../../providers/ecency/ePoint';
@ -22,7 +21,6 @@ import { ParentPost } from '../../parentPost';
// Styles
import styles from './postDisplayStyles';
import { OptionsModal } from '../../atoms';
import { QuickReplyModal } from '../..';
import getWindowDimensions from '../../../utils/getWindowDimensions';
import { useAppDispatch } from '../../../hooks';
import { showReplyModal } from '../../../redux/actions/uiAction';
@ -37,7 +35,6 @@ const PostDisplayView = ({
isNewPost,
fetchPost,
handleOnEditPress,
handleOnReplyPress,
handleOnVotersPress,
handleOnReblogsPress,
post,
@ -53,6 +50,11 @@ const PostDisplayView = ({
const dispatch = useAppDispatch();
const insets = useSafeAreaInsets();
const commentsRef = useRef<CommentsDisplay>();
const scrollRef = useRef<ScrollView>();
const commentsReached = useRef<boolean>(false);
const [postHeight, setPostHeight] = useState(0);
const [scrollHeight, setScrollHeight] = useState(0);
const [cacheVoteIcrement, setCacheVoteIcrement] = useState(0);
@ -87,16 +89,30 @@ const PostDisplayView = ({
const _handleOnScroll = (event) => {
const { y } = event.nativeEvent.contentOffset;
console.log("scroll height", y)
setScrollHeight(HEIGHT + y);
const _commentButtonBounceOffset = y + (HEIGHT/1.7);
if(!commentsReached.current && commentsRef.current && _commentButtonBounceOffset > postHeight ){
commentsRef.current.bounceCommentButton();
commentsReached.current = true;
}
};
const _handleOnPostLayout = (event) => {
const { height } = event.nativeEvent.layout;
console.log('post height', height)
setPostHeight(height);
};
const _scrollToComments = () => {
if(scrollRef.current){
const pos = postHeight;
scrollRef.current.scrollTo({y:pos})
}
}
const _handleOnReblogsPress = () => {
if (reblogs.length > 0 && handleOnReblogsPress) {
handleOnReblogsPress();
@ -144,7 +160,7 @@ const PostDisplayView = ({
isClickable
text={get(post, 'children', 0)}
textMarginLeft={20}
onPress={() => _showQuickReplyModal(post)}
onPress={() => _scrollToComments()}
// onPress={() => handleOnReplyPress && handleOnReplyPress()}
/>
)}
@ -211,9 +227,9 @@ const PostDisplayView = ({
};
// show quick reply modal
const _showQuickReplyModal = (post) => {
const _showQuickReplyModal = (_post = post) => {
if (isLoggedIn) {
dispatch(showReplyModal(post));
dispatch(showReplyModal(_post));
} else {
console.log('Not LoggedIn');
}
@ -222,6 +238,7 @@ const PostDisplayView = ({
return (
<View style={styles.container}>
<ScrollView
ref={scrollRef}
style={styles.scroll}
contentContainerStyle={[styles.scrollContent]}
onScroll={(event) => _handleOnScroll(event)}
@ -262,7 +279,9 @@ const PostDisplayView = ({
)}
</View>
{post && !postBodyLoading && (isGetComment || isLoadedComments) && (
<CommentsDisplay
ref={commentsRef}
author={author || post.author}
mainAuthor={author || post.author}
permlink={post.permlink}

View File

@ -724,7 +724,8 @@
"title": "Comments",
"reveal_comment": "Reveal comment",
"read_more": "Read more comments",
"more_replies": "replies"
"more_replies": "replies",
"no_comments":"Be the first to respond..."
},
"search_result": {
"others": "Others",
@ -817,7 +818,7 @@
"year":"years"
},
"quick_reply":{
"placeholder":"Add a comment",
"placeholder":"Add a comment...",
"comment": "Comment",
"reply": "REPLY",
"close":"CLOSE"

View File

@ -252,7 +252,7 @@
"feedback_fail": "Ocurrió un error al intentar abrir el cliente del correo electrónico",
"server_fail": "Servidor no disponible",
"show_imgs": "Mostrar imágenes",
"delete_account": "Delete Account"
"delete_account": "Eliminar cuenta"
},
"voters": {
"voters_info": "Información de votantes",
@ -550,9 +550,9 @@
"confirm_report_body": "¿Estás seguro de que quieres reportar?"
},
"delete": {
"confirm_delete_title": "Confirm Delete",
"confirm_delete_body": "Are you sure you want to delete account? It will erase all of your data",
"request_sent": "Your request for deletion is sent"
"confirm_delete_title": "Confirmar eliminación",
"confirm_delete_body": "¿Estás seguro de que deseas eliminar la cuenta? Se borrarán todos tus datos",
"request_sent": "Su solicitud de eliminación está enviada"
},
"favorites": {
"title": "Favoritos",

View File

@ -252,7 +252,7 @@
"feedback_fail": "Sähköpostipalvelin alhaalla",
"server_fail": "Ei yhteyttä palvelimeen",
"show_imgs": "Näytä kuvat",
"delete_account": "Delete Account"
"delete_account": "Poista tili"
},
"voters": {
"voters_info": "Äänestystiedot",
@ -269,8 +269,8 @@
"login": "KIRJAUDU SISÄÄN",
"steemconnect_description": "Jos et halua säilyttää kryptattua salasanaa laitteessasi, voit käyttää Hivesigneriä.",
"steemconnect_fee_description": "tietoa",
"not_loggedin_alert": "Not LoggedIn",
"not_loggedin_alert_desc": "Please login first"
"not_loggedin_alert": "Ei Kirjautunut",
"not_loggedin_alert_desc": "Kirjaudu ensin sisään"
},
"register": {
"button": "Luo tili",
@ -550,9 +550,9 @@
"confirm_report_body": "Haluatko varmasti tehdä ilmiannon?"
},
"delete": {
"confirm_delete_title": "Confirm Delete",
"confirm_delete_body": "Are you sure you want to delete account? It will erase all of your data",
"request_sent": "Your request for deletion is sent"
"confirm_delete_title": "Vahvista poistaminen",
"confirm_delete_body": "Oletko varma, että haluat poistaa tilin? Se poistaa kaikki tietosi",
"request_sent": "Pyyntösi poistamisesta on lähetetty"
},
"favorites": {
"title": "Suosikit",
@ -592,7 +592,7 @@
"pin-community": "Kiinnitä yhteisölle",
"unpin-community": "Poista kiinnitys yhteisöstä",
"edit-history": "Muokkaushistoria",
"mute": "Mute / Block"
"mute": "Mykistä / Estä"
},
"deep_link": {
"no_existing_user": "Käyttäjää ei ole",

View File

@ -10,9 +10,9 @@ import {
DELETE_DRAFT_CACHE_ENTRY,
UPDATE_SUBSCRIBED_COMMUNITY_CACHE,
DELETE_SUBSCRIBED_COMMUNITY_CACHE,
CLEAR_SUBSCRIBED_COMMUNITIES_CACHE,
CLEAR_SUBSCRIBED_COMMUNITIES_CACHE
} from '../constants/constants';
import { Comment, Draft, SubscribedCommunity, Vote } from '../reducers/cacheReducer';
import { Comment, CommentCacheStatus, Draft, SubscribedCommunity, Vote } from '../reducers/cacheReducer';
@ -46,6 +46,8 @@ export const updateCommentCache = (commentPath: string, comment: Comment, option
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;
@ -55,6 +57,7 @@ export const updateCommentCache = (commentPath: string, comment: Comment, option
comment.total_payout = comment.total_payout || 0;
comment.json_metadata = comment.json_metadata || makeJsonMetadataReply(options.parentTags)
comment.isDeletable = comment.isDeletable || true;
comment.status = comment.status || CommentCacheStatus.PENDING;
comment.body = renderPostBody({
author: comment.author,

View File

@ -1,4 +1,10 @@
import { PURGE_EXPIRED_CACHE, UPDATE_VOTE_CACHE, UPDATE_COMMENT_CACHE, DELETE_COMMENT_CACHE_ENTRY, DELETE_DRAFT_CACHE_ENTRY, UPDATE_DRAFT_CACHE, UPDATE_SUBSCRIBED_COMMUNITY_CACHE, DELETE_SUBSCRIBED_COMMUNITY_CACHE, CLEAR_SUBSCRIBED_COMMUNITIES_CACHE, } from "../constants/constants";
import { PURGE_EXPIRED_CACHE, UPDATE_VOTE_CACHE, UPDATE_COMMENT_CACHE, DELETE_COMMENT_CACHE_ENTRY, DELETE_DRAFT_CACHE_ENTRY, UPDATE_DRAFT_CACHE, UPDATE_SUBSCRIBED_COMMUNITY_CACHE, DELETE_SUBSCRIBED_COMMUNITY_CACHE, CLEAR_SUBSCRIBED_COMMUNITIES_CACHE, UPDATE_COMMENT_CACHE_ENTRY_STATUS, } from "../constants/constants";
export enum CommentCacheStatus {
PENDING = 'PENDING',
POSTPONED = 'PUBLISHED',
DELETED = 'DELETED',
}
export interface Vote {
amount: number;
@ -24,6 +30,7 @@ export interface Comment {
created?: string, //handle created and updated separatly
updated?: string,
expiresAt?: number,
status: CommentCacheStatus
}
export interface Draft {

View File

@ -18,7 +18,6 @@ import SplashScreen from 'react-native-splash-screen'
// Constants
import AUTH_TYPE from '../../../constants/authType';
import ROUTES from '../../../constants/routeNames';
import postUrlParser from '../../../utils/postUrlParser';
// Services
import {
@ -34,7 +33,7 @@ import {
setLastUpdateCheck,
getTheme,
} from '../../../realm/realm';
import { getUser, getPost, getDigitPinCode, getMutes } from '../../../providers/hive/dhive';
import { getUser, getDigitPinCode, getMutes } from '../../../providers/hive/dhive';
import { getPointsSummary } from '../../../providers/ecency/ePoint';
import {
migrateToMasterKeyWithAccessToken,
@ -62,7 +61,6 @@ import {
login,
logoutDone,
setConnectivityStatus,
setAnalyticsStatus,
setPinCode as savePinCode,
isRenderRequired,
logout,
@ -85,21 +83,12 @@ import lightTheme from '../../../themes/lightTheme';
import persistAccountGenerator from '../../../utils/persistAccountGenerator';
import parseVersionNumber from '../../../utils/parseVersionNumber';
import { setMomentLocale } from '../../../utils/time';
import parseAuthUrl from '../../../utils/parseAuthUrl';
import { purgeExpiredCache } from '../../../redux/actions/cacheActions';
import { fetchSubscribedCommunities } from '../../../redux/actions/communitiesAction';
import MigrationHelpers from '../../../utils/migrationHelpers';
import { deepLinkParser } from '../../../utils/deepLinkParser';
import bugsnapInstance from '../../../config/bugsnag';
// Workaround
let previousAppState = 'background';
export const setPreviousAppState = () => {
previousAppState = AppState.currentState;
const appStateTimeout = setTimeout(() => {
previousAppState = AppState.currentState;
clearTimeout(appStateTimeout);
}, 500);
};
let firebaseOnNotificationOpenedAppListener = null;
let firebaseOnMessageListener = null;
@ -131,7 +120,6 @@ class ApplicationContainer extends Component {
});
AppState.addEventListener('change', this._handleAppStateChange);
setPreviousAppState();
this.removeAppearanceListener = Appearance.addChangeListener(this._appearanceChangeListener);
@ -338,7 +326,7 @@ class ApplicationContainer extends Component {
if (appState.match(/active|forground/) && nextAppState === 'inactive') {
this._startPinCodeTimer();
}
setPreviousAppState();
this.setState({
appState: nextAppState,
});
@ -375,7 +363,7 @@ class ApplicationContainer extends Component {
let key = null;
let routeName = null;
if (previousAppState !== 'active' && !!notification) {
if (!!notification) {
const push = get(notification, 'data');
const type = get(push, 'type', '');
const fullPermlink =
@ -477,11 +465,11 @@ class ApplicationContainer extends Component {
firebaseOnMessageListener = messaging().onMessage((remoteMessage) => {
console.log('Notification Received: foreground', remoteMessage);
// this._showNotificationToast(remoteMessage);
this.setState({
foregroundNotificationData: remoteMessage,
});
this._pushNavigate(remoteMessage);
});
firebaseOnNotificationOpenedAppListener = messaging().onNotificationOpenedApp(
@ -673,25 +661,39 @@ class ApplicationContainer extends Component {
//update notification settings and update push token for each signed accoutn useing access tokens
_registerDeviceForNotifications = (settings?: any) => {
const { otherAccounts, notificationDetails, isNotificationsEnabled } = this.props;
const { currentAccount, otherAccounts, notificationDetails, isNotificationsEnabled } = this.props;
const isEnabled = settings ? !!settings.notification : isNotificationsEnabled;
settings = settings || notificationDetails;
//updateing fcm token with settings;
otherAccounts.forEach((account) => {
//since there can be more than one accounts, process access tokens separate
const _enabledNotificationForAccount = (account) => {
const encAccessToken = account?.local?.accessToken;
//decrypt access token
let accessToken = null;
if (encAccessToken) {
//NOTE: default pin decryption works also for custom pin as other account
//keys are not yet being affected by changed pin, which I think we should dig more
accessToken = decryptKey(encAccessToken, Config.DEFAULT_PIN);
accessToken = decryptKey(account.name, Config.DEFAULT_PIN);
}
this._enableNotification(account.name, isEnabled, settings, accessToken);
}
//updateing fcm token with settings;
otherAccounts.forEach((account) => {
//since there can be more than one accounts, process access tokens separate
if (account?.local?.accessToken) {
_enabledNotificationForAccount(account)
} else {
console.warn("access token not present, reporting to bugsnag")
bugsnapInstance.notify(new Error(`Reporting missing access token in other accounts section: account:${account.name} with local data ${JSON.stringify(account?.local)}`))
//fallback to current account access token to register atleast logged in account
if (currentAccount.name === account.name) {
_enabledNotificationForAccount(currentAccount)
}
}
});
};

View File

@ -1,8 +1,16 @@
import React, { useState } from 'react';
import { View, StatusBar, Platform, Image, Text, SafeAreaView } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import React, { useState, useEffect } from 'react';
import {
View,
StatusBar,
Platform,
Image,
Text,
SafeAreaView,
Keyboard,
KeyboardAvoidingView,
} from 'react-native';
import { useIntl } from 'react-intl';
import * as Animatable from 'react-native-animatable';
import RegisterContainer from './registerContainer';
// Internal Components
@ -16,6 +24,7 @@ import styles from './registerStyles';
import ESTEEM_LOGO from '../../assets/like_new.png';
import ESTEEM_SMALL_LOGO from '../../assets/ecency_logo_transparent.png';
import getWindowDimensions from '../../utils/getWindowDimensions';
const RegisterScreen = ({ navigation, route }) => {
const intl = useIntl();
@ -27,6 +36,19 @@ const RegisterScreen = ({ navigation, route }) => {
const [refUsername, setRefUsername] = useState(route.params?.referredUser ?? '');
const [isRefUsernameValid, setIsRefUsernameValid] = useState(true);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
setKeyboardIsOpen(true);
});
const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
setKeyboardIsOpen(false);
});
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
const _handleEmailChange = (value) => {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
setIsEmailValid(re.test(value));
@ -74,7 +96,11 @@ const RegisterScreen = ({ navigation, route }) => {
/>
</View>
</View>
{!keyboardIsOpen && (
<Animatable.View
animation={keyboardIsOpen ? hideAnimation : showAnimation}
delay={0}
duration={300}
>
<View style={styles.header}>
<View style={styles.titleText}>
<Text style={styles.title}>{intl.formatMessage({ id: 'register.title' })}</Text>
@ -84,15 +110,13 @@ const RegisterScreen = ({ navigation, route }) => {
</View>
<Image style={styles.mascot} source={ESTEEM_LOGO} />
</View>
)}
<View style={styles.body}>
<KeyboardAwareScrollView
onKeyboardWillShow={() => setKeyboardIsOpen(true)}
onKeyboardWillHide={() => setKeyboardIsOpen(false)}
enableAutoAutomaticScroll={Platform.OS === 'ios'}
contentContainerStyle={styles.formWrapper}
enableOnAndroid={true}
</Animatable.View>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.formWrapper}
keyboardShouldPersistTaps
>
<View style={styles.body}>
<FormInput
rightIconName="at"
leftIconName="close"
@ -107,6 +131,7 @@ const RegisterScreen = ({ navigation, route }) => {
isFirstImage
value={username}
inputStyle={styles.input}
onFocus={() => setKeyboardIsOpen(true)}
/>
<FormInput
rightIconName="mail"
@ -120,6 +145,7 @@ const RegisterScreen = ({ navigation, route }) => {
type="emailAddress"
value={email}
inputStyle={styles.input}
onFocus={() => setKeyboardIsOpen(true)}
/>
<FormInput
rightIconName="person"
@ -134,14 +160,14 @@ const RegisterScreen = ({ navigation, route }) => {
isFirstImage
value={refUsername}
inputStyle={styles.input}
onFocus={() => setKeyboardIsOpen(true)}
/>
<InformationArea
description={intl.formatMessage({ id: 'register.form_description' })}
iconName="ios-information-circle-outline"
link="https://ecency.com/terms-of-service"
/>
</KeyboardAwareScrollView>
</View>
<View style={styles.footerButtons}>
<TextButton
style={styles.cancelButton}
@ -166,11 +192,34 @@ const RegisterScreen = ({ navigation, route }) => {
style={styles.mainButton}
/>
</View>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
)}
</RegisterContainer>
);
};
const { height } = getWindowDimensions();
const bodyHeight = height / 5;
const showAnimation = {
from: {
opacity: 0,
height: 0,
},
to: {
opacity: 1,
height: bodyHeight,
},
};
const hideAnimation = {
from: {
opacity: 1,
height: bodyHeight,
},
to: {
opacity: 0,
height: 0,
},
};
export default RegisterScreen;

View File

@ -1,5 +1,4 @@
import EStyleSheet from 'react-native-extended-stylesheet';
import autoMergeLevel1 from 'redux-persist/es/stateReconciler/autoMergeLevel1';
export default EStyleSheet.create({
container: {
@ -12,12 +11,9 @@ export default EStyleSheet.create({
},
footerButtons: {
flexDirection: 'row',
justifyContent: 'center',
justifyContent: 'flex-end',
alignItems: 'center',
alignSelf: 'flex-end',
marginRight: 10,
bottom: 24,
right: 24,
height: 80,
},
cancelButton: {
marginRight: 10,
@ -25,6 +21,7 @@ export default EStyleSheet.create({
formWrapper: {
marginHorizontal: 30,
marginVertical: 10,
flex: 1,
},
input: {
color: '$primaryDarkText',

View File

@ -1,5 +1,9 @@
import { Platform } from "react-native"
export default () => {
return Platform.OS === 'android' && Platform.Version === 26
return Platform.OS === 'android'
&& (
Platform.Version === 26
|| Platform.Version === 27
)
}

View File

@ -1,7 +1,8 @@
import { Alert } from 'react-native';
import ROUTES from '../../src/constants/routeNames';
import { navigate } from '../navigation/service';
const showLoginAlert = ({ navigation, intl }) => {
const showLoginAlert = ({ intl }) => {
return Alert.alert(
intl.formatMessage({ id: 'login.not_loggedin_alert' }),
intl.formatMessage({ id: 'login.not_loggedin_alert_desc' }),
@ -14,7 +15,7 @@ const showLoginAlert = ({ navigation, intl }) => {
{
text: intl.formatMessage({ id: 'login.login' }),
onPress: () => {
navigation.navigate(ROUTES.SCREENS.LOGIN);
navigate({routeName:ROUTES.SCREENS.LOGIN});
},
},
],