[NEW] Request review (#1627)

This commit is contained in:
Djorkaeff Alexandre 2020-02-03 15:28:18 -03:00 committed by GitHub
parent b6e48e3763
commit 7dffa14b77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 3 deletions

View File

@ -103,7 +103,7 @@ Readme will guide you on how to config.
| Custom Fields on Signup | ✅ | | Custom Fields on Signup | ✅ |
| Report message | ✅ | | Report message | ✅ |
| Theming | ✅ | | Theming | ✅ |
| Settings -> Review the App | | | Settings -> Review the App | |
| Settings -> Default Browser | ❌ | | Settings -> Default Browser | ❌ |
| Admin panel | ✅ | | Admin panel | ✅ |
| Reply message from notification | ✅ | | Reply message from notification | ✅ |

View File

@ -1,3 +1,8 @@
export const PLAY_MARKET_LINK = 'https://play.google.com/store/apps/details?id=chat.rocket.reactnative'; import { getBundleId, isIOS } from '../utils/deviceInfo';
export const APP_STORE_LINK = 'https://itunes.apple.com/app/rocket-chat-experimental/id1272915472?ls=1&mt=8';
const APP_STORE_ID = '1272915472';
export const PLAY_MARKET_LINK = `market://details?id=${ getBundleId }`;
export const APP_STORE_LINK = `itms-apps://itunes.apple.com/app/id${ APP_STORE_ID }`;
export const LICENSE_LINK = 'https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/LICENSE'; export const LICENSE_LINK = 'https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/LICENSE';
export const STORE_REVIEW_LINK = isIOS ? `${ APP_STORE_LINK }?action=write-review` : PLAY_MARKET_LINK;

View File

@ -42,6 +42,7 @@ import {
MENTIONS_TRACKING_TYPE_USERS MENTIONS_TRACKING_TYPE_USERS
} from './constants'; } from './constants';
import CommandsPreview from './CommandsPreview'; import CommandsPreview from './CommandsPreview';
import { Review } from '../../utils/review';
const imagePickerConfig = { const imagePickerConfig = {
cropping: true, cropping: true,
@ -506,6 +507,7 @@ class MessageBox extends Component {
}; };
try { try {
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user); await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
Review.pushPositiveEvent();
} catch (e) { } catch (e) {
log(e); log(e);
} }

View File

@ -332,6 +332,13 @@ export default {
Reset_password: 'Reset password', Reset_password: 'Reset password',
resetting_password: 'resetting password', resetting_password: 'resetting password',
RESET: 'RESET', RESET: 'RESET',
Review_app_title: 'Are you enjoying this app?',
Review_app_desc: 'Give us 5 stars on {{store}}',
Review_app_yes: 'Sure!',
Review_app_no: 'No',
Review_app_later: 'Maybe later',
Review_app_unable_store: 'Unable to open {{store}}',
Review_this_app: 'Review this app',
Roles: 'Roles', Roles: 'Roles',
Room_actions: 'Room actions', Room_actions: 'Room actions',
Room_changed_announcement: 'Room announcement changed to: {{announcement}} by {{userBy}}', Room_changed_announcement: 'Room announcement changed to: {{announcement}} by {{userBy}}',

View File

@ -302,6 +302,13 @@ export default {
Reset_password: 'Resetar senha', Reset_password: 'Resetar senha',
resetting_password: 'redefinindo senha', resetting_password: 'redefinindo senha',
RESET: 'RESETAR', RESET: 'RESETAR',
Review_app_title: 'Você está gostando do app?',
Review_app_desc: 'Nos dê 5 estrelas na {{store}}',
Review_app_yes: 'Claro!',
Review_app_no: 'Não',
Review_app_later: 'Talvez depois',
Review_app_unable_store: 'Não foi possível abrir {{store}}',
Review_this_app: 'Avaliar esse app',
Roles: 'Papéis', Roles: 'Papéis',
Room_actions: 'Ações', Room_actions: 'Ações',
Room_changed_announcement: 'O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}', Room_changed_announcement: 'O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}',

96
app/utils/review.js Normal file
View File

@ -0,0 +1,96 @@
import { Alert, Linking, AsyncStorage } from 'react-native';
import { isIOS } from './deviceInfo';
import I18n from '../i18n';
import { showErrorAlert } from './info';
import { STORE_REVIEW_LINK } from '../constants/links';
const store = isIOS ? 'App Store' : 'Play Store';
const reviewKey = 'reviewKey';
const reviewDelay = 2000;
const numberOfDays = 7;
const numberOfPositiveEvent = 5;
const daysBetween = (date1, date2) => {
const one_day = 1000 * 60 * 60 * 24;
const date1_ms = date1.getTime();
const date2_ms = date2.getTime();
const difference_ms = date2_ms - date1_ms;
return Math.round(difference_ms / one_day);
};
const onCancelPress = () => {
try {
const data = JSON.stringify({ doneReview: true });
return AsyncStorage.setItem(reviewKey, data);
} catch (e) {
// do nothing
}
};
export const onReviewPress = async() => {
await onCancelPress();
try {
const supported = await Linking.canOpenURL(STORE_REVIEW_LINK);
if (supported) {
Linking.openURL(STORE_REVIEW_LINK);
}
} catch (e) {
showErrorAlert(I18n.t('Review_app_unable_store', { store }));
}
};
const onAskMeLaterPress = () => {
try {
const data = JSON.stringify({ lastReview: new Date().getTime() });
return AsyncStorage.setItem(reviewKey, data);
} catch (e) {
// do nothing
}
};
const onReviewButton = { text: I18n.t('Review_app_yes'), onPress: onReviewPress };
const onAskMeLaterButton = { text: I18n.t('Review_app_later'), onPress: onAskMeLaterPress };
const onCancelButton = { text: I18n.t('Review_app_no'), onPress: onCancelPress };
const askReview = () => Alert.alert(
I18n.t('Review_app_title'),
I18n.t('Review_app_desc', { store }),
isIOS
? [onReviewButton, onAskMeLaterButton, onCancelButton]
: [onAskMeLaterButton, onCancelButton, onReviewButton],
{
cancelable: true,
onDismiss: onAskMeLaterPress
}
);
const tryReview = async() => {
const data = await AsyncStorage.getItem(reviewKey) || '{}';
const reviewData = JSON.parse(data);
const { lastReview = 0, doneReview = false } = reviewData;
const lastReviewDate = new Date(lastReview);
// if ask me later was pressed earlier, we can ask for review only after {{numberOfDays}} days
// if there's no review and it wasn't dismissed by the user
if (daysBetween(lastReviewDate, new Date()) >= numberOfDays && !doneReview) {
setTimeout(askReview, reviewDelay);
}
};
class ReviewApp {
positiveEventCount = 0;
pushPositiveEvent = () => {
if (this.positiveEventCount >= numberOfPositiveEvent) {
return;
}
this.positiveEventCount += 1;
if (this.positiveEventCount === numberOfPositiveEvent) {
tryReview();
}
}
}
export const Review = new ReviewApp();

View File

@ -22,6 +22,7 @@ import StatusBar from '../containers/StatusBar';
import { SWITCH_TRACK_COLOR, themes } from '../constants/colors'; import { SWITCH_TRACK_COLOR, themes } from '../constants/colors';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { themedHeader } from '../utils/navigation'; import { themedHeader } from '../utils/navigation';
import { Review } from '../utils/review';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -201,6 +202,8 @@ class CreateChannelView extends React.Component {
create({ create({
name: channelName, users, type, readOnly, broadcast name: channelName, users, type, readOnly, broadcast
}); });
Review.pushPositiveEvent();
} }
removeUser = (user) => { removeUser = (user) => {

View File

@ -47,6 +47,7 @@ import {
handleCommandReplyLatest handleCommandReplyLatest
} from '../../commands'; } from '../../commands';
import ModalNavigation from '../../lib/ModalNavigation'; import ModalNavigation from '../../lib/ModalNavigation';
import { Review } from '../../utils/review';
const stateAttrsUpdate = [ const stateAttrsUpdate = [
'joined', 'joined',
@ -472,6 +473,7 @@ class RoomView extends React.Component {
try { try {
await RocketChat.setReaction(shortname, messageId); await RocketChat.setReaction(shortname, messageId);
this.onReactionClose(); this.onReactionClose();
Review.pushPositiveEvent();
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -556,6 +558,7 @@ class RoomView extends React.Component {
this.list.current.update(); this.list.current.update();
} }
this.setLastOpen(null); this.setLastOpen(null);
Review.pushPositiveEvent();
}); });
}; };

View File

@ -33,6 +33,7 @@ import { withSplit } from '../../split';
import Navigation from '../../lib/Navigation'; import Navigation from '../../lib/Navigation';
import { LISTENER } from '../../containers/Toast'; import { LISTENER } from '../../containers/Toast';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import { onReviewPress } from '../../utils/review';
const SectionSeparator = React.memo(({ theme }) => ( const SectionSeparator = React.memo(({ theme }) => (
<View <View
@ -255,6 +256,15 @@ class SettingsView extends React.Component {
theme={theme} theme={theme}
/> />
<Separator theme={theme} /> <Separator theme={theme} />
<ListItem
title={I18n.t('Review_this_app')}
showActionIndicator
onPress={onReviewPress}
testID='settings-view-review-app'
right={this.renderDisclosure}
theme={theme}
/>
<Separator theme={theme} />
<ListItem <ListItem
title={I18n.t('Share_this_app')} title={I18n.t('Share_this_app')}
showActionIndicator showActionIndicator