...
 
......@@ -22,7 +22,34 @@
"externalStorage":"Minds needs access to your external storage so you can upload awesome pictures.",
"writeExternalStorage":"Minds needs access to your external storage so you can download awesome pictures.",
"sms":"Minds needs access to read sms to auto-detect the validation code.",
"camera":"Minds needs access to your external camera so you can upload awesome pictures."
"camera":"Minds needs access to your external camera so you can upload awesome pictures.",
"notAllowed":{
"appoint_admin":"You are not allowed to appoint an admin",
"appoint_moderator":"You are not allowed to appoint a moderator",
"approve_subscriber":"You are not allowed to approve a subscriber",
"create_channel":"You are not allowed to create a channel",
"create_comment":"You are not allowed to create a comment",
"create_group":"You are not allowed to create a group",
"create_post":"You are not allowed to create a post",
"delete_channel":"You are not allowed to delete this channel",
"delete_comment":"You are not allowed to delete this comment",
"delete_group":"You are not allowed to delete this group",
"delete_post":"You are not allowed to delete this post",
"edit_channel":"You are not allowed to edit this channel",
"edit_comment":"You are not allowed to edit this comment",
"edit_group":"You are not allowed to edit this group",
"edit_post":"You are not allowed to edit this post",
"invite":"You are not allowed to invite",
"join":"You are not allowed to join",
"gathering":"You are not allowed to join to this gathering",
"message":"You are not allowed to message this user",
"subscribe":"You are not allowed to subscribe",
"tag":"You are not allowed to tag this content",
"remind":"You are not allowed to remind this content",
"wire":"You are not allowed to wire this user",
"view":"You are not allowed to view this content",
"vote":"You are not allowed to vote this content"
}
},
"auth":{
"login":"LOGIN",
......@@ -746,5 +773,6 @@
"offline":"Offline",
"cantReachServer":"Can't reach the server",
"showingStored":"Showing stored data",
"actions":"Actions"
"actions":"Actions",
"notAllowed":"You are not allowed"
}
......@@ -27,7 +27,7 @@ export default class BlogCard extends PureComponent {
* Navigate to blog
*/
navToBlog = () => {
if (!this.props.navigation || !this.props.entity.can(FLAG_VIEW)) return;
if (!this.props.navigation || !this.props.entity.can(FLAG_VIEW, true)) return;
return this.props.navigation.push('BlogView', { blog: this.props.entity });
}
......
......@@ -6,7 +6,6 @@ import {
StyleSheet,
FlatList,
Text,
Image,
View,
Alert,
SafeAreaView,
......@@ -35,15 +34,15 @@ import logService from '../common/services/log.service';
import { GOOGLE_PLAY_STORE } from '../config/Config';
import i18n from '../common/services/i18n.service';
import FeedList from '../common/components/FeedList';
import featuresService from '../common/services/features.service';
import channelsService from '../common/services/channels.service';
import { FLAG_VIEW } from '../common/Permissions';
/**
* Channel Screen
*/
export default
@inject('channel')
@observer
export default class ChannelScreen extends Component {
class ChannelScreen extends Component {
state = {
guid: null
......@@ -109,7 +108,11 @@ export default class ChannelScreen extends Component {
try {
const channel = await store.load(isModel ? channelOrGuid : undefined);
if (channel) {
// check permissions
if (!this.checkCanView(channel)) return;
this.props.channel.addVisited(channel);
}
} catch (err) {
......@@ -126,6 +129,18 @@ export default class ChannelScreen extends Component {
store.feedStore.refresh();
}
/**
* Check if the current user can view this channel
* @param {UserModel} channel
*/
checkCanView(channel) {
if (!channel.can(FLAG_VIEW, true)) {
this.props.navigation.goBack();
return false;
}
return true;
}
//TODO: make a reverse map so we can cache usernames
async loadByUsername(username) {
try {
......@@ -133,6 +148,10 @@ export default class ChannelScreen extends Component {
// get store by name and load channel
const store = await this.props.channel.storeByName(username);
this.setState({ guid: store.channel.guid });
// check permissions
if (!this.checkCanView(store.channel)) return;
// load feed now
store.feedStore.loadFeed();
......
......@@ -5,6 +5,7 @@ import {
toJS,
} from 'mobx';
import _ from 'lodash';
import { Alert } from 'react-native';
import sessionService from './services/session.service';
import { vote } from './services/votes.service';
......@@ -12,8 +13,8 @@ import { toggleExplicit } from '../newsfeed/NewsfeedService';
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';
import i18n from './services/i18n.service';
/**
* Base model
......@@ -294,15 +295,28 @@ export default class BaseModel {
}
/**
* Check if the user can perform an action with the entity
* @param {string} permission
* Check if the current user can perform an action with the entity
* @param {string} action
* @param {boolean} showAlert Show an alert message if the action is not allowed
* @returns {boolean}
*/
can(permission) {
can(action, showAlert = false) {
let allowed = true;
if (!this.permissions || !this.permissions.permissions) {
return false;
allowed = false;
} else {
allowed = this.permissions.permissions.some(item => item === action);
}
if (showAlert && !allowed) {
Alert.alert(
i18n.t('sorry'),
i18n.t(`permissions.notAllowed.${action}`, {defaultValue: i18n.t('notAllowed')})
);
}
return this.permissions.permissions.some(item => item === permission);
return allowed;
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import {
} from 'mobx';
import entitiesService from '../services/entities.service';
import logService from '../services/log.service';
/**
* Single Entity Store
......@@ -30,6 +31,7 @@ class SingleEntityStore {
this.setEntity(entity);
} catch (err) {
this.setErrorLoading(true);
logService.exception(err);
}
}
......
......@@ -510,7 +510,7 @@ export default class DiscoveryScreen extends Component {
* Navigate to feed screen
* @param {string} urn
*/
navigateToFeed = (urn) => {
navigateToFeed = ({urn}) => {
const index = this.props.discovery.listStore.feedsService.feed.findIndex(e => e.urn === urn);
this.props.discovery.feedStore.setFeed(this.props.discovery.listStore.feedsService.feed.slice(index));
......@@ -533,7 +533,7 @@ export default class DiscoveryScreen extends Component {
<DiscoveryTile
entity={row.item}
size={this.state.itemHeight}
onPress={() => this.navigateToFeed(row.item.urn)}
onPress={this.navigateToFeed}
/>
</ErrorBoundary>
);
......@@ -582,12 +582,12 @@ export default class DiscoveryScreen extends Component {
const item = row.item;
return (
<ErrorBoundary containerStyle={CS.hairLineBottom}>
<GroupsListItem group={row.item} onPress={() => this.navigateToGroup(row.item)}/>
<GroupsListItem group={row.item} onPress={this.navigateToGroup}/>
</ErrorBoundary>
)
}
navigateToGroup(group) {
navigateToGroup = (group) => {
this.props.navigation.push('GroupView', { group: group })
}
}
......
......@@ -3,7 +3,6 @@ import React, {
} from 'react';
import {
Platform,
Text,
View,
TouchableOpacity,
......@@ -12,24 +11,17 @@ import {
import FastImage from 'react-native-fast-image';
import {
MINDS_CDN_URI
} from '../config/Config';
import {
observer
} from 'mobx-react/native'
import Placeholder from 'rn-placeholder';
import ExplicitImage from '../common/components/explicit/ExplicitImage';
import ExplicitOverlay from '../common/components/explicit/ExplicitOverlay';
import { CommonStyle as CS } from '../styles/Common';
import i18n from '../common/services/i18n.service';
const isAndroid = Platform.OS === 'android';
export default
@observer
export default class DiscoveryTile extends Component {
class DiscoveryTile extends Component {
state = {
error: false,
......@@ -52,16 +44,12 @@ export default class DiscoveryTile extends Component {
}
/**
* Navigate to view
* On press
*/
_navToView = () => {
if (this.props.navigation) {
this.props.navigation.push('Activity', {
entity: this.props.entity,
scrollToBottom: false
});
_onPress = () => {
if (this.props.onPress) {
this.props.onPress(this.props.entity);
}
if (this.props.onPress) this.props.onPress();
}
errorRender = (err) => {
......@@ -112,7 +100,7 @@ export default class DiscoveryTile extends Component {
null;
return (
<TouchableOpacity onPress={this._navToView} style={[ this.state.style, styles.tile ]}>
<TouchableOpacity onPress={this._onPress} style={[ this.state.style, styles.tile ]}>
<View style={ [CS.flexContainer, CS.backgroundGreyed] }>
<FastImage
source={ url }
......
......@@ -28,7 +28,7 @@ import colors from '../styles/Colors'
import { ComponentsStyle } from '../styles/Components';
import { CommonStyle } from '../styles/Common';
import i18n from '../common/services/i18n.service';
import { FLAG_SUBSCRIBE } from '../common/Permissions';
import { FLAG_SUBSCRIBE, FLAG_VIEW } from '../common/Permissions';
@inject('user')
@observer
......@@ -40,6 +40,9 @@ export default class DiscoveryUser extends Component {
_navToChannel = () => {
Keyboard.dismiss();
if (this.props.navigation) {
if (!this.props.entity.item.can(FLAG_VIEW, true)) {
return;
}
this.props.navigation.push('Channel', { entity: this.props.entity.item });
}
}
......
......@@ -16,10 +16,13 @@ import { ListItem, Avatar } from 'react-native-elements';
import Button from '../common/components/Button';
import colors from '../styles/Colors';
import i18n from '../common/services/i18n.service';
import { CommonStyle as CS } from '../styles/Common';
import { FLAG_JOIN } from '../common/Permissions';
export default
@inject('groupView')
@observer
export default class GroupsListItem extends Component {
class GroupsListItem extends Component {
/**
* Render
*/
......@@ -27,7 +30,7 @@ export default class GroupsListItem extends Component {
const button = this.getButton();
return (
<ListItem
containerStyle={{ borderBottomWidth: 0 }}
containerStyle={CS.noBorderBottom}
title={this.props.group.name}
keyExtractor={item => item.rowKey}
avatar={
......@@ -39,13 +42,22 @@ export default class GroupsListItem extends Component {
/>
}
subtitle={i18n.t('groups.listMembersCount', {count: this.props.group['members:count']})}
onPress={this.props.onPress}
onPress={this._onPress}
hideChevron={!button}
rightIcon={button}
/>
);
}
/**
* On press
*/
_onPress = () => {
if (this.props.onPress) {
this.props.onPress(this.props.group)
}
}
/**
* Get button
*/
......@@ -59,6 +71,7 @@ export default class GroupsListItem extends Component {
* Join the group
*/
join = () => {
if (!this.props.group.can(FLAG_JOIN, true)) return;
this.props.groupView.setGroup(this.props.group);
this.props.groupView.join(this.props.group.guid);
}
......
......@@ -7,8 +7,6 @@ import {
import { observer } from 'mobx-react/native'
import FastImage from 'react-native-fast-image';
import NewsfeedStore from "./NewsfeedStore";
import { getSingle } from './NewsfeedService';
import { CommonStyle as CS } from '../styles/Common';
import CommentList from '../comments/CommentList';
import Activity from '../newsfeed/activity/Activity';
......@@ -17,15 +15,16 @@ import { ComponentsStyle } from '../styles/Components';
import SingleEntityStore from '../common/stores/SingleEntityStore';
import CenteredLoading from '../common/components/CenteredLoading';
import commentsStoreProvider from '../comments/CommentsStoreProvider';
import logService from '../common/services/log.service';
import i18n from '../common/services/i18n.service';
import OffsetFeedListStore from '../common/stores/OffsetFeedListStore';
import { FLAG_VIEW } from '../common/Permissions';
/**
* Activity screen
*/
export default
@observer
export default class ActivityScreen extends Component {
class ActivityScreen extends Component {
static navigationOptions = ({ navigation }) => {
return {
......@@ -42,15 +41,26 @@ export default class ActivityScreen extends Component {
constructor(props) {
super(props);
const params = props.navigation.state.params;
this.comments = commentsStoreProvider.get();
this.loadEntity();
}
async loadEntity() {
const params = this.props.navigation.state.params;
if (params.entity && (params.entity.guid || params.entity.entity_guid)) {
const urn = 'urn:entity:' + (params.entity.guid || params.entity.entity_guid);
this.entityStore.loadEntity(urn, ActivityModel.checkOrCreate(params.entity), true);
const entity = ActivityModel.checkOrCreate(params.entity);
if (!entity.can(FLAG_VIEW, true)) {
this.props.navigation.goBack();
return;
}
this.entityStore.loadEntity(urn, entity, true);
// change metadata source
if (params.entity._list && params.entity._list.metadataService) {
......@@ -58,7 +68,12 @@ export default class ActivityScreen extends Component {
}
} else {
const urn = 'urn:entity:' + params.guid;
this.entityStore.loadEntity(urn);
await this.entityStore.loadEntity(urn);
if (!this.entityStore.entity.can(FLAG_VIEW, true)) {
this.props.navigation.goBack();
return;
}
}
if (params.entity && params.entity._list) {
......
......@@ -37,7 +37,6 @@ export default class RemindAction extends PureComponent {
<TouchableOpacityCustom
style={[CS.flexContainer, CS.centered, this.props.vertical === true ? CS.columnAlignCenter : CS.rowJustifyCenter]}
onPress={this.remind}
disabled={!canRemind}
{...testID('Remind activity button')}
>
<Icon style={[color, CS.marginRight]} name='repeat' size={this.props.size} />
......@@ -50,6 +49,9 @@ export default class RemindAction extends PureComponent {
* Open remind
*/
remind = () => {
// check permission and show alert
if (!this.props.entity.can(FLAG_REMIND, true)) return;
const { state } = this.props.navigation
this.props.navigation.push('Capture', {isRemind: true, entity: this.props.entity, parentKey: state.key});
}
......
......@@ -66,7 +66,6 @@ class ThumbUpAction extends Component {
<TouchableOpacityCustom
style={[CS.flexContainer, CS.centered, this.props.orientation == 'column' ? CS.columnAlignCenter : CS.rowJustifyCenter ]}
onPress={this.toggleThumb}
disabled={!canVote}
{...testID(`Thumb ${this.direction} activity button`)}
>
<Icon style={[color, CS.marginRight]} name={this.iconName} size={this.props.size} />
......@@ -83,6 +82,9 @@ class ThumbUpAction extends Component {
* Toggle thumb
*/
toggleThumb = async () => {
if (!this.props.entity.can(FLAG_VOTE, true)) return;
try {
await this.props.entity.toggleVote(this.direction);
} catch (err) {
......