[NEW] Request review (#1627)
This commit is contained in:
parent
b6e48e3763
commit
7dffa14b77
|
@ -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 | ✅ |
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}}',
|
||||||
|
|
|
@ -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}}',
|
||||||
|
|
|
@ -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();
|
|
@ -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) => {
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue