...
 
Commits (5)
......@@ -745,5 +745,6 @@
"noInternet":"No Internet Connection",
"offline":"Offline",
"cantReachServer":"Can't reach the server",
"showingStored":"Showing stored data"
"showingStored":"Showing stored data",
"actions":"Actions"
}
......@@ -38,7 +38,7 @@
"i18n-js": "^3.2.2",
"jwt-simple": "^0.5.5",
"lodash": "^4.17.11",
"mobx": "^5.9.4",
"mobx": "^5.14.0",
"mobx-react": "^5.4.4",
"mobx-utils": "^5.4.1",
"moment": "^2.20.1",
......
......@@ -3,48 +3,38 @@ import React, {
} from 'react';
import {
Text,
Image,
View,
ActivityIndicator,
StyleSheet
ActivityIndicator
} from 'react-native';
import {
observer,
inject
} from 'mobx-react/native'
import Icon from 'react-native-vector-icons/Ionicons';
import channelService from './ChannelService';
import i18n from '../common/services/i18n.service';
import ActionSheet from 'react-native-actionsheet';
import WireAction from '../newsfeed/activity/actions/WireAction';
import featuresService from '../common/services/features.service';
import sessionService from '../common/services/session.service';
import Button from '../common/components/Button';
import withPreventDoubleTap from '../common/components/PreventDoubleTap';
import { CommonStyle as CS } from '../styles/Common';
import { FLAG_SUBSCRIBE, FLAG_MESSAGE, FLAG_EDIT_CHANNEL, FLAG_WIRE } from '../common/Permissions';
const ButtonCustom = withPreventDoubleTap(Button);
/**
* Channel Actions
*/
const title = 'Actions';
export default
@observer
export default class ChannelActions extends Component {
constructor(props) {
super(props)
this.state = {
selected: '',
scheduledCount: '',
}
class ChannelActions extends Component {
this.handleSelection = this.handleSelection.bind(this);
state = {
scheduledCount: '',
}
componentWillMount() {
componentDidMount() {
this.getScheduledCount();
}
......@@ -52,14 +42,14 @@ export default class ChannelActions extends Component {
this.ActionSheet.show();
}
handleSelection(i) {
this.makeAction(i);
handleSelection = (index) => {
this.executeAction(index);
}
getOptions() {
let options = [ i18n.t('cancel') ];
if(this.props.store.channel.subscribed){
if (this.props.store.channel.subscribed){
options.push( i18n.t('channel.unsubscribe') );
}
......@@ -75,7 +65,7 @@ export default class ChannelActions extends Component {
}
makeAction(option) {
executeAction(option) {
let options = this.getOptions();
let selected = options[option];
switch (selected) {
......@@ -130,82 +120,73 @@ export default class ChannelActions extends Component {
return featuresService.has('post-scheduler') && !this.state.edit;
}
/**
* Get Action Button, Message or Subscribe
*/
getActionButton() {
if (!this.props.store.loaded && sessionService.guid !== this.props.store.channel.guid )
return null;
if (sessionService.guid === this.props.store.channel.guid) {
const viewScheduledButton = this.shouldRenderScheduledButton() ? (
<ButtonCustom
onPress={this.onViewScheduledAction}
accessibilityLabel={i18n.t('channel.viewScheduled')}
text={`${i18n.t('channel.viewScheduled').toUpperCase()}: ${this.state.scheduledCount}`}
loading={this.state.saving}
inverted={this.props.store.feedStore.endpoint == this.props.store.feedStore.scheduledEndpoint ? true : undefined}
/> ) : null ;
return (
<View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
{ viewScheduledButton }
<ButtonCustom
onPress={this.onEditAction}
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={this.props.editing ? i18n.t('channel.saveChanges') : i18n.t('channel.editChannel')}
text={this.props.editing ? i18n.t('save').toUpperCase() : i18n.t('edit').toUpperCase()}
loading={this.props.saving}
/>
</View>
);
} else if (!!this.props.store.channel.subscribed) {
return (
<ButtonCustom
onPress={ this.navToConversation }
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={i18n.t('channel.sendMessage')}
text={i18n.t('channel.message')}
/>
);
} else if (sessionService.guid !== this.props.store.channel.guid) {
return (
<ButtonCustom
onPress={this.toggleSubscription}
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={i18n.t('channel.subscribeMessage')}
text={i18n.t('channel.subscribe').toUpperCase()}
/>
);
} else if (this.props.store.isUploading) {
return (
<ActivityIndicator size="small" />
)
}
}
/**
* Render Header
*/
render() {
const channel = this.props.store.channel;
const showWire = !channel.blocked && !channel.isOwner() && featuresService.has('crypto');
const isOwner = channel.isOwner();
const showWire = !channel.blocked && !isOwner && featuresService.has('crypto') && channel.can(FLAG_WIRE);
const showScheduled = featuresService.has('post-scheduler') && !this.state.edit && isOwner;
const showSubscribe = !isOwner && !channel.subscribed && channel.can(FLAG_SUBSCRIBE);
const showMessage = !isOwner && channel.can(FLAG_MESSAGE);
if (this.props.store.isUploading) {
return (
<ActivityIndicator size="small" />
)
}
return (
<View style={[CS.rowJustifyEnd, CS.marginTop2x]}>
{this.getActionButton()}
{!!showWire &&
{ showScheduled &&
<ButtonCustom
onPress={this.onViewScheduledAction}
accessibilityLabel={i18n.t('channel.viewScheduled')}
text={`${i18n.t('channel.viewScheduled')}: ${this.state.scheduledCount}`}
loading={this.state.saving}
inverted={this.props.store.feedStore.endpoint == this.props.store.feedStore.scheduledEndpoint ? true : undefined}
/>
}
{ showSubscribe &&
<ButtonCustom
onPress={this.toggleSubscription}
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={i18n.t('channel.subscribeMessage')}
text={i18n.t('channel.subscribe')}
/>
}
{ showMessage &&
<ButtonCustom
onPress={ this.navToConversation }
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={i18n.t('channel.sendMessage')}
text={i18n.t('channel.message')}
/>
}
{ channel.can(FLAG_EDIT_CHANNEL) &&
<ButtonCustom
onPress={this.onEditAction}
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={this.props.editing ? i18n.t('channel.saveChanges') : i18n.t('channel.editChannel')}
text={this.props.editing ? i18n.t('save') : i18n.t('edit')}
loading={this.props.saving}
/>
}
{ showWire &&
<ButtonCustom
onPress={ this.openWire }
accessibilityLabel="Wire Button"
containerStyle={[CS.rowJustifyCenter]}
textStyle={[CS.marginLeft, CS.marginRight]}
icon="ios-flash"
text="Wire"
onPress={ this.openWire }
accessibilityLabel="Wire Button"
containerStyle={[CS.rowJustifyCenter]}
textStyle={[CS.marginLeft, CS.marginRight]}
icon="ios-flash"
text="Wire"
>
<Icon name='ios-flash' size={18} style={[CS.marginLeft, CS.colorPrimary]} />
</ButtonCustom>
}
{!channel.isOwner() &&
{ !isOwner &&
<ButtonCustom
onPress={ this.showActionSheet }
accessibilityLabel={i18n.t('more')}
......@@ -217,7 +198,7 @@ export default class ChannelActions extends Component {
}
<ActionSheet
ref={o => this.ActionSheet = o}
title={title}
title={i18n.t('actions')}
options={this.getOptions()}
onPress={this.handleSelection}
cancelButtonIndex={0}
......@@ -226,18 +207,3 @@ export default class ChannelActions extends Component {
)
}
}
const styles = StyleSheet.create({
wrapper: {
backgroundColor: '#FFF',
paddingLeft: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
height: 40,
},
icon: {
paddingLeft: 10,
color: '#888888',
},
});
......@@ -57,23 +57,6 @@ class ChannelService {
return result;
}
async getFeedFromService(guid, type, opts = { limit: 12 }) {
const limit = opts.limit || 12;
// const { entities, next } = await feedService.get({
// endpoint: `api/v2/feeds/container/${guid}/${type}`,
// timebased: true,
// limit,
// offset: opts.offset || 0,
// syncPageSize: limit * 20,
// });
return {
entities: entities || [],
offset: entities && entities.length ? next : '',
}
}
async getFeed(guid, opts = { limit: 12 }) {
const tag = `channel:feed:${guid}`;
// abort previous call
......
......@@ -55,7 +55,7 @@ export default class UserModel extends BaseModel {
this.subscribed = value;
try {
const metadata = this.getClientMetadata();
await ChannelService.toggleSubscription(this.guid, value, metadata)
await ChannelService.toggleSubscription(this.guid, value, metadata);
} catch (err) {
runInAction(() => {
this.subscribed = !value;
......@@ -68,7 +68,6 @@ export default class UserModel extends BaseModel {
* current user is owner of the channel
*/
isOwner() {
console.log(sessionService.getUser().guid , this.guid, sessionService.getUser().guid === this.guid)
return sessionService.getUser().guid === this.guid;
}
......
......@@ -2,24 +2,19 @@ import React, {
Component
} from 'react';
import { Icon } from 'react-native-elements'
import { observer, inject } from 'mobx-react/native';
import { observer } from 'mobx-react/native';
import entities from 'entities';
import {
Text,
Alert,
StyleSheet,
ScrollView,
TouchableOpacity,
Clipboard,
View,
Dimensions
} from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import CommentEditor from './CommentEditor';
import { CommonStyle } from '../styles/Common';
import formatDate from '../common/helpers/date';
......@@ -30,32 +25,29 @@ import MediaView from '../common/components/MediaView';
import Tags from '../common/components/Tags';
import i18n from '../common/services/i18n.service';
import {
MINDS_CDN_URI
} from '../config/Config';
import CommentList from './CommentList';
import DoubleTap from '../common/components/DoubleTap';
import ExplicitOverlay from '../common/components/explicit/ExplicitOverlay';
import colors from '../styles/Colors';
import FastImage from 'react-native-fast-image';
import CommentActionSheet from './CommentActionSheet';
const DoubleTapText = DoubleTap(Text);
/**
* Comment Component
*/
@inject('user')
export default
@observer
export default class Comment extends Component {
class Comment extends Component {
state = {
editing: false,
}
constructor(props) {
super(props)
this.state = {
avatarSrc: { uri: 'https://d3ae0shxev0cb7.cloudfront.net/' + 'icon/' + this.props.comment.ownerObj.guid },
options: this.getOptions(),
editing: false,
}
super(props);
this.actionSheetRef = React.createRef();
}
onInputFocus = (comment, offset) => {
......@@ -79,8 +71,8 @@ export default class Comment extends Component {
<View style={styles.actionsContainer}>
<Text style={styles.timestamp}>{formatDate(comment.time_created, 'friendly')}</Text>
<View style={[CommonStyle.flexContainer, CommonStyle.rowJustifyStart]}>
<ThumbUpAction entity={comment} me={this.props.user.me} size={16}/>
<ThumbDownAction entity={comment} me={this.props.user.me} size={16} />
<ThumbUpAction entity={comment} size={16}/>
<ThumbDownAction entity={comment} size={16} />
{canReply && <ReplyAction entity={comment} size={16} toggleExpand={this.toggleExpand}/>}
</View>
</View>
......@@ -142,16 +134,19 @@ export default class Comment extends Component {
/>
</View>
<ActionSheet
ref={o => this.ActionSheet = o}
options={this.getOptions()}
onPress={this.handleSelection}
cancelButtonIndex={0}
<CommentActionSheet
comment={this.props.comment}
onSelection={this.onSelection}
ref={this.actionSheetRef}
/>
</View>
);
}
showActions = () => {
this.actionSheetRef.current && this.actionSheetRef.current.showActions();
}
/**
* Toggle expand
*/
......@@ -166,50 +161,6 @@ export default class Comment extends Component {
this.setState({editing: value});
}
/**
* Show actions
*/
showActions = () => {
this.ActionSheet.show();
}
/**
* Get actionsheet options
*/
getOptions = () => {
let actions = [i18n.t('cancel')];
if (this.props.user.me.guid == this.props.comment.owner_guid) {
actions.push( i18n.t('edit') );
actions.push( i18n.t('delete') );
if (!this.props.comment.mature) {
actions.push( i18n.t('setExplicit') );
} else {
actions.push( i18n.t('removeExplicit') );
}
} else {
if (this.props.user.isAdmin()) {
actions.push( i18n.t('delete') );
if (!this.props.comment.mature) {
actions.push( i18n.t('setExplicit') );
} else {
actions.push( i18n.t('removeExplicit') )
}
} else if (this.props.user.me.guid == this.props.entity.owner_guid) {
actions.push( i18n.t('delete') );
}
actions.push( i18n.t('report') );
actions.push( i18n.t('copy') );
}
if (this.props.comment.parent_guid_l2 == 0) {
actions.push( i18n.t('reply') );
}
return actions;
}
/**
* Navigate To channel
*/
......@@ -223,8 +174,7 @@ export default class Comment extends Component {
/**
* Handle action on comment
*/
handleSelection = (i) => {
const action = this.state.options[i];
onSelection = (action) => {
switch (action) {
case i18n.t('edit'):
......
import React, {
Component
} from 'react';
import ActionSheet from 'react-native-actionsheet';
import i18n from '../common/services/i18n.service';
import { FLAG_EDIT_COMMENT, FLAG_DELETE_COMMENT, FLAG_CREATE_COMMENT } from '../common/Permissions';
/**
* Comment Component
*/
export default class CommentActionSheet extends Component {
state = {
options: [],
}
constructor(props) {
super(props);
this.ref = React.createRef();
}
/**
* Show actions
*/
showActions = () => {
this.setState({
options: this.getOptions()
}, () => {
this.ref.current && this.ref.current.show();
});
}
handleSelection = (i) => {
const action = this.state.options[i];
this.props.onSelection && this.props.onSelection(action);
}
/**
* Get actionsheet options
*/
getOptions = () => {
let actions = [i18n.t('cancel')];
const comment = this.props.comment;
if (comment.can(FLAG_EDIT_COMMENT)) {
actions.push( i18n.t('edit') );
if (!comment.mature) {
actions.push( i18n.t('setExplicit') );
} else {
actions.push( i18n.t('removeExplicit') );
}
}
if (comment.can(FLAG_DELETE_COMMENT)) {
actions.push( i18n.t('delete') );
}
if (comment.parent_guid_l2 == 0 && comment.can(FLAG_CREATE_COMMENT)) {
actions.push( i18n.t('reply') );
}
if (!comment.isOwner()) {
actions.push( i18n.t('report') );
}
actions.push( i18n.t('copy') );
return actions;
}
/**
* Render
*/
render() {
return (
<ActionSheet
ref={this.ref}
options={this.state.options}
onPress={this.handleSelection}
cancelButtonIndex={0}
/>
)
}
}
\ No newline at end of file
This diff is collapsed.
......@@ -3,134 +3,124 @@ import React, {
} from 'react';
import {
Text,
Image,
View,
ActivityIndicator,
Button,
StyleSheet,
Modal,
Alert,
Text,
} from 'react-native';
import {
observer,
inject
} from 'mobx-react/native'
import translationService from '../../common/services/translation.service';
import shareService from '../../share/ShareService';
import { isFollowing } from '../NewsfeedService';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ActionSheet from 'react-native-actionsheet';
import { ActionSheetCustom as ActionSheet } from 'react-native-actionsheet'
import { MINDS_URI } from '../../config/Config';
import testID from '../../common/helpers/testID';
import { isFollowing } from '../NewsfeedService';
import { CommonStyle as CS } from '../../styles/Common';
import shareService from '../../share/ShareService';
import i18n from '../../common/services/i18n.service';
import featuresService from '../../common/services/features.service';
import translationService from '../../common/services/translation.service';
import { FLAG_EDIT_POST, FLAG_DELETE_POST } from '../../common/Permissions';
/**
* Activity Actions
* Activity Actions Component
*/
const title = 'Actions';
@inject("user")
@inject("newsfeed")
@observer
export default class ActivityActions extends Component {
export default class ActivityActionSheet extends Component {
constructor(props) {
super(props)
this.state = {
selected: '',
reportModalVisible: false,
userBlocked: false
}
this.handleSelection = this.handleSelection.bind(this);
state = {
options: [],
userBlocked: false
}
/**
* Show menu
*/
async showActionSheet() {
if (this.props.entity['is:following'] === undefined) {
this.props.entity['is:following'] = await isFollowing(this.props.entity.guid);
}
this.setState({
options: this.getOptions()
this.setState({options: this.getOptions()}, () => {
this.ActionSheet.show();
});
this.ActionSheet.show();
}
handleSelection(i) {
this.makeAction(this.state.options[i]);
/**
* Handle selection by index
* @param {number} index
*/
handleSelection = (index) => {
if (!this.state.options[index]) return;
this.executeAction(this.state.options[index]);
}
/**
* Get the options array based on the permissions
*/
getOptions() {
let options = [ i18n.t('cancel') ];
if (this.props.entity.isOwner()) {
options.push( i18n.t('edit') );
const entity = this.props.entity;
options.push( i18n.t('delete') );
// if can edit
if (entity.can(FLAG_EDIT_POST)) {
options.push( i18n.t('edit') );
if (!this.props.entity.mature) {
if (!entity.mature) {
options.push( i18n.t('setExplicit') );
} else {
options.push( i18n.t('removeExplicit') );
}
if (!this.props.entity.dontPin) {
if (!this.props.entity.pinned) {
if (!entity.dontPin) {
if (!entity.pinned) {
options.push( i18n.t('pin') );
} else {
options.push( i18n.t('unpin') );
}
}
if (featuresService.has('allow-comments-toggle')) {
options.push( this.props.entity.allow_comments ? i18n.t('disableComments') : i18n.t('enableComments'));
options.push( entity.allow_comments ? i18n.t('disableComments') : i18n.t('enableComments'));
}
}
} else {
if (this.props.user.isAdmin()) {
options.push( i18n.t('delete') );
if (translationService.isTranslatable(entity)) {
options.push( i18n.t('translate.translate') );
}
if (!this.props.entity.mature) {
options.push( i18n.t('setExplicit') );
} else {
options.push( i18n.t('removeExplicit') );
}
}
// if is not the owner
if (!entity.isOwner()) {
options.push( i18n.t('report') );
if (this.state && this.state.userBlocked) {
options.push( i18n.t('channel.unblock') );
} else {
options.push( i18n.t('channel.block') );
}
if (translationService.isTranslatable(this.props.entity)) {
options.push( i18n.t('translate.translate') );
}
options.push( i18n.t('report') );
}
options.push( i18n.t('share') );
if (!this.props.entity['is:following']) {
if (!entity['is:following']) {
options.push( i18n.t('follow') );
} else {
options.push( i18n.t('unfollow') );
}
return options;
// if can delete
if (entity.can(FLAG_DELETE_POST)) {
options.push(<Text style={[CS.colorDanger, CS.fontXL]}>{i18n.t('delete')}</Text>);
}
return options;
}
/**
* Delete an entity
*/
async deleteEntity() {
try {
await this.props.entity.deleteEntity();
this.reloadOptions();
Alert.alert(
i18n.t('success'),
i18n.t('newsfeed.successRemoving'),
......@@ -162,7 +152,11 @@ export default class ActivityActions extends Component {
);
}
async makeAction(option) {
/**
* Execute an action
* @param {string} option
*/
async executeAction(option) {
switch (option) {
case i18n.t('translate.translate'):
if (this.props.onTranslate) this.props.onTranslate();
......@@ -185,7 +179,7 @@ export default class ActivityActions extends Component {
case i18n.t('removeExplicit'):
try {
await this.props.entity.toggleExplicit();
this.reloadOptions();
// this.reloadOptions();
} catch (err) {
this.showError();
}
......@@ -195,7 +189,6 @@ export default class ActivityActions extends Component {
await this.props.entity.blockOwner();
this.setState({
userBlocked: true,
options: this.getOptions(),
});
} catch (err) {
this.showError();
......@@ -206,7 +199,6 @@ export default class ActivityActions extends Component {
await this.props.entity.unblockOwner();
this.setState({
userBlocked: false,
options: this.getOptions(),
});
} catch (err) {
this.showError();
......@@ -216,7 +208,7 @@ export default class ActivityActions extends Component {
case i18n.t('unfollow'):
try {
await this.props.entity.toggleFollow();
this.reloadOptions();
// this.reloadOptions();
} catch (err) {
this.showError();
}
......@@ -236,73 +228,33 @@ export default class ActivityActions extends Component {
try {
await this.props.entity.toggleAllowComments();
} catch (err) {
console.error(err);
this.showError();
}
break;
}
}
reloadOptions() {
this.setState({
options: this.getOptions()
});
}
/**
* Close report modal
*/
closeReport = () => {
this.setState({ reportModalVisible: false });
}
/**
* Render Header
*/
render() {
return (
<View style={styles.wrapper}>
<View style={[CS.flexContainer, CS.centered]}>
<Icon
name="more-vert"
onPress={() => this.showActionSheet()}
size={26}
style={styles.icon}
style={CS.colorDarkGreyed}
{...testID('Activity Menu button')}
/>
/>
<ActionSheet
ref={o => this.ActionSheet = o}
title={title}
options={this.getOptions()}
title={i18n.t('actions')}
options={this.state.options}
onPress={this.handleSelection}
cancelButtonIndex={0}
/>
</View>
)
}
}
const styles = StyleSheet.create({
wrapper: {
flex:1,
alignSelf: 'center'
},
icon: {
color: '#888',
},
iconclose: {
flex:1,
},
modal: {
flex: 1,
paddingTop: 4,
},
modalContainer: {
alignItems: 'center',
backgroundColor: '#ede3f2',
},
modalHeader: {
padding: 5
}
});
\ No newline at end of file
}
\ No newline at end of file
......@@ -6304,10 +6304,10 @@ mobx-utils@^5.4.1:
resolved "https://registry.yarnpkg.com/mobx-utils/-/mobx-utils-5.4.1.tgz#18ff5f9723b27e1ff50ae0b362938a4792eb077a"
integrity sha512-u5BSvFiSx1ZkzBz/7V96RB7xWn7oOFwczI+AVi1v8TV8HbT8rXLco1Aqb87tEA8Jsc2e5uvTwHdYoiv81qspaA==
mobx@^5.9.4:
version "5.9.4"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.9.4.tgz#1dee92aba33f67b7baeeb679e3bd376a12e55812"
integrity sha512-L9JjTX2rtQUAhCIgnHokfntNOsF14uioT9LqStf6Mya+16j56ZBe21E8Y9V59tfr2aH2kLQPD10qtCJXBuTAxw==
mobx@^5.14.0:
version "5.14.0"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.14.0.tgz#357c1023aca2851df357fa0cb9a6eaaab3a57793"
integrity sha512-GhDSZV9rGlCeVBpFPVYaYSn7UjgkD3158Njailp4IJhKqbT5iiEtiRyr76b7gPj3wpUSl+NHREQqBXzc1I8jpQ==
mock-fs@^4.1.0:
version "4.7.0"
......@@ -7558,6 +7558,7 @@ react-is@^16.8.1, react-is@^16.8.6:
react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-native-actionsheet@^2.3.0:
version "2.4.2"
......