...
 
Commits (16)
......@@ -18,6 +18,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......@@ -74,6 +75,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......@@ -146,6 +148,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......@@ -246,6 +249,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......@@ -313,6 +317,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......@@ -376,6 +381,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......@@ -445,6 +451,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......@@ -507,6 +514,7 @@ exports[`Activity component renders correctly 1`] = `
entity={
ActivityModel {
"__list": null,
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
"comments:count": undefined,
......
......@@ -17,7 +17,7 @@ exports[`Comment action component renders correctly 1`] = `
>
<Icon
color="rgb(96, 125, 139)"
name="chat-bubble"
name="speaker-notes-off"
raised={false}
reverse={false}
reverseColor="white"
......
......@@ -18,6 +18,7 @@ exports[`blog view screen component should renders correctly 1`] = `
BlogModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"boost_rejection_reason": -1,
"categories": Array [],
"category": "education",
......@@ -44,6 +45,7 @@ exports[`blog view screen component should renders correctly 1`] = `
"ownerObj": UserModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"banned": "no",
"blocked": false,
"boostProPlus": false,
......@@ -204,6 +206,7 @@ exports[`blog view screen component should renders correctly 1`] = `
BlogModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"boost_rejection_reason": -1,
"categories": Array [],
"category": "education",
......@@ -230,6 +233,7 @@ exports[`blog view screen component should renders correctly 1`] = `
"ownerObj": UserModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"banned": "no",
"blocked": false,
"boostProPlus": false,
......@@ -372,6 +376,7 @@ exports[`blog view screen component should renders correctly 1`] = `
BlogModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"boost_rejection_reason": -1,
"categories": Array [],
"category": "education",
......@@ -398,6 +403,7 @@ exports[`blog view screen component should renders correctly 1`] = `
"ownerObj": UserModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"banned": "no",
"blocked": false,
"boostProPlus": false,
......@@ -520,6 +526,7 @@ exports[`blog view screen component should renders correctly 1`] = `
BlogModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"boost_rejection_reason": -1,
"categories": Array [],
"category": "education",
......@@ -546,6 +553,7 @@ exports[`blog view screen component should renders correctly 1`] = `
"ownerObj": UserModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"banned": "no",
"blocked": false,
"boostProPlus": false,
......@@ -664,6 +672,7 @@ exports[`blog view screen component should renders correctly 1`] = `
BlogModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"boost_rejection_reason": -1,
"categories": Array [],
"category": "education",
......@@ -690,6 +699,7 @@ exports[`blog view screen component should renders correctly 1`] = `
"ownerObj": UserModel {
"__list": null,
"access_id": "2",
"allow_comments": true,
"banned": "no",
"blocked": false,
"boostProPlus": false,
......
......@@ -234,7 +234,9 @@
"removeOwner":"Remove as Owner",
"makeModerator":"Make Moderator",
"removeModerator":"Remove as Moderator",
"listMembersCount":"Members {{count}}"
"listMembersCount":"Members {{count}}",
"disableConversations":"Disable conversations",
"enableConversations":"Enable conversations"
},
"keychain": {
"unlockMessage": "Unlock {{keychain}} keychain",
......@@ -725,6 +727,8 @@
"wantToUpdate":"Do you want to update the app?",
"updateAvailable":"Update available",
"rememberTomorrow":"Remind me later",
"enableComments": "Enable comments",
"disableComments": "Disable comments",
"noInternet":"No Internet Connection",
"offline":"Offline",
"cantReachServer":"Can't reach the server",
......
......@@ -217,7 +217,9 @@
"banConfirm": "Estás seguro? Quieres suspender a este usuario?",
"errorLoading": "Error cargando grupos",
"confirmKick": "Estás seguro? Quieres expulsar a este usuario?",
"listMembersCount": "Miembros {{count}}"
"listMembersCount": "Miembros",
"disableConversations":"Inhabilitar conversaciones",
"enableConversations":"Habilitar conversaciones"
},
"keychain": {
"unlockMessage": "Desbloquear {{keychain}} keychain",
......@@ -746,6 +748,8 @@
"wantToUpdate": "Quieres actualizar la aplicación? ",
"updateAvailable": "Actualización disponible",
"rememberTomorrow": "Recuerdame más tarde",
"enableComments": "Habilitar comentarios",
"disableComments": "Inhabilitar comentarios",
"noInternet": "Sin conexión a internet",
"offline": "Fuera de línea",
"cantReachServer": "No puedo conectarme al servidor",
......
......@@ -21,6 +21,8 @@ import {
Alert,
} from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import { Header } from 'react-navigation';
import { inject, observer } from 'mobx-react/native';
......@@ -44,6 +46,7 @@ import CommentList from '../comments/CommentList';
import CenteredLoading from '../common/components/CenteredLoading';
import logService from '../common/services/log.service';
import i18n from '../common/services/i18n.service';
import featuresService from '../common/services/features.service';
/**
* Blog View Screen
......@@ -142,10 +145,18 @@ export default class BlogsViewScreen extends Component {
</View>
)
const image = blog.getBannerSource();
const actionSheet = this.getActionSheet();
const optMenu = featuresService.has('allow-comments-toggle') ?
(<View style={styles.rightToolbar}>
<Icon name="more-vert" onPress={() => this.showActionSheet()} size={26} style={styles.icon}/>
{actionSheet}
</View>) : (null);
return (
<View style={styles.screen}>
<FastImage source={image} resizeMode={FastImage.resizeMode.cover} style={styles.image} />
<Text style={styles.title}>{blog.title}</Text>
{optMenu}
<View style={styles.ownerBlockContainer}>
<OwnerBlock entity={blog} navigation={this.props.navigation} rightToolbar={actions}>
<Text style={styles.timestamp}>{formatDate(blog.time_created)}</Text>
......@@ -170,6 +181,51 @@ export default class BlogsViewScreen extends Component {
)
}
getActionSheet() {
let options = [ i18n.t('cancel') ];
options.push(this.props.blogsView.blog.allow_comments ? i18n.t('disableComments') : i18n.t('enableComments'));
return (
<ActionSheet
ref={o => this.ActionSheet = o}
options={options}
onPress={ (i) => { this.handleActionSheetSelection(options[i]) }}
cancelButtonIndex={0}
/>
)
}
async showActionSheet() {
this.ActionSheet.show();
}
async handleActionSheetSelection(option) {
switch(option) {
case i18n.t('disableComments'):
case i18n.t('enableComments'):
try {
await this.props.blogsView.blog.toggleAllowComments();
} catch (err) {
console.error(err);
this.showError();
}
}
}
/**
* Show an error message
*/
showError() {
Alert.alert(
i18n.t('sorry'),
i18n.t('errorMessage') + '\n' + i18n.t('activity.tryAgain'),
[
{text: i18n.t('ok'), onPress: () => {}},
],
{ cancelable: false }
);
}
/**
* Render
*/
......@@ -314,4 +370,12 @@ const styles = StyleSheet.create({
color: '#888',
fontSize: 10,
},
rightToolbar: {
alignSelf: 'flex-end',
bottom: 35,
right: 10
},
icon: {
color: '#888',
},
});
......@@ -313,7 +313,7 @@ export default class CommentList extends React.Component<Props, State> {
* Render poster
*/
renderPoster() {
if (this.state.hideInput) return null;
if (this.state.hideInput || (!this.props.entity.allow_comments && this.props.entity.type !== "group")) return null;
const attachment = this.props.store.attachment;
......
......@@ -112,4 +112,15 @@ export function updateComment(guid, description) {
return api.post(`api/v1/comments/update/${guid}`, {
description: description
});
}
/**
* Enable/Disable comments
* @param {string} guid
* @param {boolean} state
*/
export function toggleAllowComments(guid, state) {
return api.post(`api/v2/permissions/comments/${guid}`,{
allowed: state
});
}
\ No newline at end of file
......@@ -14,11 +14,18 @@ import logService from './services/log.service';
import channelService from '../channel/ChannelService';
import { revokeBoost, acceptBoost, rejectBoost } from '../boost/BoostService';
import { toggleAllowComments as toggleAllow } from '../comments/CommentsService';
/**
* Base model
*/
export default class BaseModel {
/**
* Enable/Disable comments
*/
@observable allow_comments = true;
/**
* List reference (if the entity belongs to one)
* @var {OffsetListStore}
......@@ -261,4 +268,10 @@ export default class BaseModel {
throw err;
}
}
@action
async toggleAllowComments() {
const data = await toggleAllow(this.guid, !this.allow_comments);
this.allow_comments = !this.allow_comments;
}
}
\ No newline at end of file
import { observable, decorate } from 'mobx';
import { observable, decorate, action } from 'mobx';
import BaseModel from '../common/BaseModel';
import groupsService from './GroupsService';
/**
* Group model
*/
export default class GroupModel extends BaseModel {
@observable conversationDisabled = false;
@action
async toggleConversationDisabled() {
await groupsService.toggleConversationDisabled(this.guid, !this.conversationDisabled);
this.conversationDisabled = !this.conversationDisabled;
}
}
/**
......
......@@ -366,7 +366,7 @@ class GroupViewStore {
*/
@action
setGroup(group) {
this.group = group;
this.group = GroupModel.checkOrCreate(group);
this.setGuid(group.guid);
}
......
......@@ -201,6 +201,10 @@ class GroupsService {
revokeModerator(group_guid, user_guid) {
return api.delete(`api/v1/groups/management/${group_guid}/${user_guid}/moderator`);
}
toggleConversationDisabled(group_guid, state) {
return api.post(`api/v1/groups/group/${group_guid}`, { conversationDisabled: state });
}
}
export default new GroupsService();
......@@ -8,6 +8,7 @@ import {
View,
TouchableHighlight,
ActivityIndicator,
StyleSheet
} from 'react-native';
import {
......@@ -17,6 +18,7 @@ import {debounce} from 'lodash';
import FastImage from 'react-native-fast-image';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ActionSheet from 'react-native-actionsheet';
import { MINDS_CDN_URI } from '../../config/Config';
import abbrev from '../../common/helpers/abbrev';
......@@ -28,6 +30,7 @@ import SearchView from '../../common/components/SearchView';
import gathering from '../../common/services/gathering.service';
import colors from '../../styles/Colors';
import i18n from '../../common/services/i18n.service';
import featuresService from '../../common/services/features.service';
/**
* Group Header
......@@ -129,14 +132,17 @@ export default class GroupHeader extends Component {
*/
renderToolbar() {
const group = this.props.store.group;
const conversation = { text: i18n.t('conversation').toUpperCase(), icon: 'ios-chatboxes', iconType: 'ion', value: 'conversation' };
const typeOptions = [
{ text: i18n.t('feed').toUpperCase(), icon: 'list', value: 'feed' },
{ text: i18n.t('description').toUpperCase(), icon: 'short-text', value: 'desc' },
{ text: i18n.t('members').toUpperCase(), badge: abbrev(group['members:count'], 0), value: 'members' },
{ text: i18n.t('conversation').toUpperCase(), icon: 'ios-chatboxes', iconType: 'ion', value: 'conversation' },
{ text: i18n.t('members').toUpperCase(), badge: abbrev(group['members:count'], 0), value: 'members' }
]
if (!featuresService.has('allow-disabling-groups-conversations') || group.conversationDisabled !== 1) {
typeOptions.push(conversation);
}
const searchBar = this.props.store.tab == 'members' ?
<SearchView
containerStyle={[CommonStyle.flexContainer, CommonStyle.hairLineBottom]}
......@@ -174,6 +180,8 @@ export default class GroupHeader extends Component {
this.props.store.loadMembers();
break;
case 'conversation':
if (group.conversationDisabled) return;
this.props.groupsBar.markAsRead(group, 'conversation');
break;
default:
......@@ -194,6 +202,53 @@ export default class GroupHeader extends Component {
}
}
getActionSheet() {
let options = [ i18n.t('cancel') ];
options.push(this.props.store.group.conversationDisabled ? i18n.t('groups.enableConversations') : i18n.t('groups.disableConversations'));
return (
<View style={stylesheet.rightToolbar}>
<Icon name="more-vert" onPress={() => this.showActionSheet()} size={26} style={stylesheet.icon}/>
<ActionSheet
ref={o => this.ActionSheet = o}
options={options}
onPress={ (i) => { this.handleActionSheetSelection(options[i]) }}
cancelButtonIndex={0}
/>
</View>
)
}
async showActionSheet() {
this.ActionSheet.show();
}
async handleActionSheetSelection(option) {
switch(option) {
case i18n.t('groups.disableConversations'):
case i18n.t('groups.enableConversations'):
try{
await this.props.store.group.toggleConversationDisabled();
} catch (err) {
console.error(err);
this.showError();
}
}
}
/**
* Show an error message
*/
showError() {
Alert.alert(
i18n.t('sorry'),
i18n.t('errorMessage') + '\n' + i18n.t('activity.tryAgain'),
[
{text: i18n.t('ok'), onPress: () => {}},
],
{ cancelable: false }
);
}
/**
* Render Header
*/
......@@ -203,10 +258,11 @@ export default class GroupHeader extends Component {
const styles = this.props.styles;
const avatar = { uri: this.getAvatar() };
const iurl = { uri: this.getBannerFromGroup() };
const actionSheet = group['is:owner'] ? this.getActionSheet() : (null);
return (
<View >
<FastImage source={iurl} style={styles.banner} resizeMode={FastImage.resizeMode.cover} />
{actionSheet}
<View style={styles.headertextcontainer}>
<View style={styles.avatarContainer}>
<View style={[CommonStyle.rowJustifyStart, CommonStyle.flexContainer]}>
......@@ -229,3 +285,14 @@ export default class GroupHeader extends Component {
)
}
}
const stylesheet = StyleSheet.create({
rightToolbar: {
alignSelf: 'flex-end',
bottom: 126,
right: 10
},
icon: {
color: '#888',
}
})
......@@ -26,6 +26,7 @@ import ActionSheet from 'react-native-actionsheet';
import { MINDS_URI } from '../../config/Config';
import testID from '../../common/helpers/testID';
import i18n from '../../common/services/i18n.service';
import featuresService from '../../common/services/features.service';
/**
* Activity Actions
......@@ -83,6 +84,10 @@ export default class ActivityActions extends Component {
}
}
if (featuresService.has('allow-comments-toggle')) {
options.push( this.props.entity.allow_comments ? i18n.t('disableComments') : i18n.t('enableComments'));
}
} else {
if (this.props.user.isAdmin()) {
......@@ -116,7 +121,6 @@ export default class ActivityActions extends Component {
options.push( i18n.t('unfollow') );
}
return options;
}
......@@ -136,7 +140,7 @@ export default class ActivityActions extends Component {
{ cancelable: false }
);
if (this.props.navigation.state.routeName == 'Activity'){
if (this.props.navigation.state.routeName == 'Activity') {
this.props.navigation.goBack();
}
} catch (err) {
......@@ -227,6 +231,15 @@ export default class ActivityActions extends Component {
case i18n.t('report'):
this.props.navigation.navigate('Report', { entity: this.props.entity });
break;
case i18n.t('enableComments'):
case i18n.t('disableComments'):
try {
await this.props.entity.toggleAllowComments();
} catch (err) {
console.error(err);
this.showError();
}
break;
}
}
......
......@@ -33,9 +33,11 @@ export default class CommentsAction extends Component {
* Render
*/
render() {
const icon = this.props.entity.allow_comments ? 'chat-bubble' : 'speaker-notes-off';
return (
<TouchableOpacityCustom style={[CommonStyle.flexContainer, CommonStyle.rowJustifyCenter]} onPress={this.openComments}>
<Icon color={this.props.entity['comments:count'] > 0 ? 'rgb(70, 144, 214)' : 'rgb(96, 125, 139)'} name='chat-bubble' size={this.props.size} />
<Icon color={this.props.entity['comments:count'] > 0 ? 'rgb(70, 144, 214)' : 'rgb(96, 125, 139)'} name={icon} size={this.props.size} />
<Counter size={this.props.size * 0.75} count={this.props.entity['comments:count']} />
</TouchableOpacityCustom>
);
......@@ -45,8 +47,9 @@ export default class CommentsAction extends Component {
* Open comments screen
*/
openComments = () => {
const cantOpen = !this.props.entity.allow_comments && this.props.entity['comments:count'] == 0;
// TODO: fix
if (this.props.navigation.state.routeName == 'Activity' ){
if (this.props.navigation.state.routeName == 'Activity' || cantOpen) {
return;
}
this.props.navigation.push('Activity', {
......