fix ux for announcements for logged out users

This commit is contained in:
ammarahm-ed
2021-11-27 11:20:04 +05:00
parent 5586ec5129
commit e9a90a40c1
10 changed files with 205 additions and 152 deletions

View File

@@ -1,22 +1,21 @@
import React from 'react';
import { View } from 'react-native';
import { useTracked } from '../../provider';
import { eSendEvent, presentSheet } from '../../services/EventManager';
import { eCloseAnnouncementDialog, eOpenPremiumDialog } from '../../utils/Events';
import { openLinkInBrowser } from '../../utils/functions';
import { SIZE } from '../../utils/SizeUtils';
import { sleep } from '../../utils/TimeUtils';
import {View} from 'react-native';
import {useTracked} from '../../provider';
import {eSendEvent, presentSheet} from '../../services/EventManager';
import {eCloseAnnouncementDialog} from '../../utils/Events';
import {openLinkInBrowser} from '../../utils/functions';
import {SIZE} from '../../utils/SizeUtils';
import {sleep} from '../../utils/TimeUtils';
import SettingsBackupAndRestore from '../../views/Settings/backup-restore';
import { Button } from '../Button';
import { allowedOnPlatform, getStyle } from './functions';
import {Button} from '../Button';
import GeneralSheet from '../GeneralSheet';
import {PricingPlans} from '../Premium/pricing-plans';
import {allowedOnPlatform, getStyle} from './functions';
export const Cta = ({actions, style = {}, color,inline}) => {
export const Cta = ({actions, style = {}, color, inline}) => {
const [state] = useTracked();
const colors = state.colors;
let buttons =
actions.filter(item =>
allowedOnPlatform(item.platforms)
) || [];
let buttons = actions.filter(item => allowedOnPlatform(item.platforms)) || [];
const onPress = async item => {
if (!inline) {
@@ -28,9 +27,18 @@ export const Cta = ({actions, style = {}, color,inline}) => {
await openLinkInBrowser(item.data, colors);
} catch (e) {}
} else if (item.type === 'promo') {
eSendEvent(eOpenPremiumDialog, {
promoCode: item.data,
text: item.title
presentSheet({
component: (
<PricingPlans
marginTop={1}
promo={{
promoCode: item.data,
text: item.title
}}
/>
),
noIcon: true,
noProgress: true
});
} else if (item.type === 'backup') {
presentSheet({
@@ -48,6 +56,7 @@ export const Cta = ({actions, style = {}, color,inline}) => {
paddingHorizontal: 12,
...getStyle(style)
}}>
<GeneralSheet context="premium_cta" />
{buttons.length > 0 &&
buttons.slice(0, 1).map(item => (
<Button

View File

@@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react';
import {FlatList, View} from 'react-native';
import {useTracked} from '../../provider';
import {useMessageStore} from '../../provider/stores';
import {DDS} from '../../services/DeviceDetection';
import {eSubscribeEvent, eUnSubscribeEvent} from '../../services/EventManager';
import {
eCloseAnnouncementDialog,
@@ -33,7 +34,7 @@ export const AnnouncementDialog = () => {
const close = () => {
if (visible) {
remove(info.id);
remove(info.id);
setInfo(null);
setVisible(false);
}
@@ -48,17 +49,18 @@ export const AnnouncementDialog = () => {
visible={visible}>
<View
style={{
width: '100%',
width: DDS.isTab ? 600 : '100%',
backgroundColor: colors.bg,
maxHeight: '100%'
maxHeight: DDS.isTab ? '90%' : '100%',
borderRadius: DDS.isTab ? 10 : 0,
overflow: 'hidden',
marginBottom: DDS.isTab ? 20 : 0
}}>
<FlatList
style={{
width: '100%'
}}
data={info?.body.filter(item =>
allowedOnPlatform(item.platforms)
)}
data={info?.body.filter(item => allowedOnPlatform(item.platforms))}
renderItem={renderItem}
/>

View File

@@ -5,6 +5,7 @@ import AnimatedProgress from 'react-native-reanimated-progress-bar';
import {useTracked} from '../../provider';
import {
useFavoriteStore,
useMessageStore,
useNoteStore,
useSettingStore,
useUserStore
@@ -23,7 +24,11 @@ import PremiumService from '../../services/PremiumService';
import {editing} from '../../utils';
import {COLOR_SCHEME_DARK} from '../../utils/Colors';
import {db} from '../../utils/database';
import {eOpenLoginDialog, eOpenRateDialog} from '../../utils/Events';
import {
eOpenAnnouncementDialog,
eOpenLoginDialog,
eOpenRateDialog
} from '../../utils/Events';
import {MMKV} from '../../utils/mmkv';
import {tabBarRef} from '../../utils/Refs';
import {SIZE} from '../../utils/SizeUtils';
@@ -51,6 +56,7 @@ const AppLoader = ({onLoad}) => {
const verifyUser = useUserStore(state => state.verifyUser);
const setVerifyUser = useUserStore(state => state.setVerifyUser);
const deviceMode = useSettingStore(state => state.deviceMode);
const isIntroCompleted = useSettingStore(state => state.isIntroCompleted);
const pwdInput = useRef();
const load = async value => {
@@ -86,6 +92,7 @@ const AppLoader = ({onLoad}) => {
eSendEvent(eOpenLoginDialog, 4);
return;
}
let settingsStore = useSettingStore.getState();
if (await Backup.checkBackupRequired(settingsStore.settings.reminder)) {
await Backup.checkAndRun();
@@ -93,7 +100,15 @@ const AppLoader = ({onLoad}) => {
}
if (await checkForRateAppRequest()) return;
if (await checkNeedsBackup()) return;
PremiumService.getRemainingTrialDaysStatus();
if (await PremiumService.getRemainingTrialDaysStatus()) return;
await useMessageStore.getState().setAnnouncement();
if (isIntroCompleted) {
let dialogs = useMessageStore.getState().dialogs;
if (dialogs.length > 0) {
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
}
}
})();
}
}, [_loading]);

View File

@@ -7,9 +7,8 @@ import {
StyleSheet,
TouchableOpacity
} from 'react-native';
import {useTracked} from '../../provider';
import useIsFloatingKeyboard from '../../utils/use-is-floating-keyboard';
import {BouncingView} from '../ActionSheetComponent/BouncingView';
import { BouncingView } from '../ActionSheetComponent/BouncingView';
const BaseDialog = ({
visible,

View File

@@ -1,15 +1,14 @@
import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import {useTracked} from '../../provider';
import {DDS} from '../../services/DeviceDetection';
import {eSubscribeEvent, eUnSubscribeEvent} from '../../services/EventManager';
import {getElevation} from '../../utils';
import {eCloseSimpleDialog, eOpenSimpleDialog} from '../../utils/Events';
import {ph, pv} from '../../utils/SizeUtils';
import {sleep} from '../../utils/TimeUtils';
import React, { useEffect, useRef, useState } from 'react';
import { View } from 'react-native';
import { useTracked } from '../../provider';
import { DDS } from '../../services/DeviceDetection';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/EventManager';
import { getElevation } from '../../utils';
import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/Events';
import { sleep } from '../../utils/TimeUtils';
import Input from '../Input';
import Seperator from '../Seperator';
import {Toast} from '../Toast';
import { Toast } from '../Toast';
import BaseDialog from './base-dialog';
import DialogButtons from './dialog-buttons';
import DialogHeader from './dialog-header';

View File

@@ -1,24 +1,24 @@
import React, {useState} from 'react';
import {Image, ScrollView, View} from 'react-native';
import {LAUNCH_ROCKET} from '../../assets/images/assets';
import {useTracked} from '../../provider';
import {useUserStore} from '../../provider/stores';
import {DDS} from '../../services/DeviceDetection';
import {eSendEvent, presentSheet} from '../../services/EventManager';
import {getElevation} from '../../utils';
import {eOpenLoginDialog} from '../../utils/Events';
import {SIZE} from '../../utils/SizeUtils';
import {ActionIcon} from '../ActionIcon';
import {Button} from '../Button';
import React, { useState } from 'react';
import { ScrollView, View } from 'react-native';
import { LAUNCH_ROCKET } from '../../assets/images/assets';
import { useTracked } from '../../provider';
import { useUserStore } from '../../provider/stores';
import { DDS } from '../../services/DeviceDetection';
import { eSendEvent, presentSheet } from '../../services/EventManager';
import { getElevation } from '../../utils';
import { eOpenLoginDialog } from '../../utils/Events';
import { SIZE } from '../../utils/SizeUtils';
import { ActionIcon } from '../ActionIcon';
import { Button } from '../Button';
import GeneralSheet from '../GeneralSheet';
import {SvgToPngView} from '../ListPlaceholders';
import { SvgToPngView } from '../ListPlaceholders';
import Seperator from '../Seperator';
import {Toast} from '../Toast';
import { Toast } from '../Toast';
import Heading from '../Typography/Heading';
import Paragraph from '../Typography/Paragraph';
import {features} from './features';
import {Group} from './group';
import {PricingPlans} from './pricing-plans';
import { features } from './features';
import { Group } from './group';
import { PricingPlans } from './pricing-plans';
export const Component = ({close, promo, getRef}) => {
const [state, dispatch] = useTracked();
@@ -84,6 +84,8 @@ export const Component = ({close, promo, getRef}) => {
style={{
paddingHorizontal: DDS.isTab ? DDS.width / 5 : 0
}}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onScroll={onScroll}>
<View
style={{

View File

@@ -1,5 +1,5 @@
import React, {useEffect, useState} from 'react';
import {ActivityIndicator, Platform, View} from 'react-native';
import {ActivityIndicator, Platform, Text, View} from 'react-native';
import * as RNIap from 'react-native-iap';
import {useTracked} from '../../provider';
import {useUserStore} from '../../provider/stores';
@@ -13,6 +13,7 @@ import {db} from '../../utils/database';
import {
eClosePremiumDialog,
eCloseProgressDialog,
eCloseSimpleDialog,
eOpenLoginDialog
} from '../../utils/Events';
import {openLinkInBrowser} from '../../utils/functions';
@@ -79,28 +80,41 @@ export const PricingPlans = ({
}
};
const getPromo = async productId => {
let products = PremiumService.getProducts();
let product = products.find(p => p.productId === productId);
if (!product) return;
let isMonthly = product.productId.indexOf('.mo') > -1;
let cycleText = isMonthly
? promoCyclesMonthly[
product.introductoryPriceCyclesAndroid ||
product.introductoryPriceNumberOfPeriodsIOS
]
: promoCyclesYearly[
product.introductoryPriceCyclesAndroid ||
product.introductoryPriceNumberOfPeriodsIOS
];
const getPromo = async code => {
try {
let productId;
if (code.startsWith('com.streetwriters.notesnook')) {
productId = code;
} else {
productId = await db.offers.getCode(code.split(':')[0], Platform.OS);
}
setProduct({
type: 'promo',
offerType: isMonthly ? 'monthly' : 'yearly',
data: product,
cycleText: cycleText,
info: 'Pay monthly, cancel anytime'
});
let products = await PremiumService.getProducts();
let product = products.find(p => p.productId === productId);
if (!product) return false;
let isMonthly = product.productId.indexOf('.mo') > -1;
let cycleText = isMonthly
? promoCyclesMonthly[
product.introductoryPriceCyclesAndroid ||
product.introductoryPriceNumberOfPeriodsIOS
]
: promoCyclesYearly[
product.introductoryPriceCyclesAndroid ||
product.introductoryPriceNumberOfPeriodsIOS
];
setProduct({
type: 'promo',
offerType: isMonthly ? 'monthly' : 'yearly',
data: product,
cycleText: cycleText,
info: 'Pay monthly, cancel anytime'
});
return true;
} catch (e) {
console.log('PROMOCODE ERROR:', code, e);
return false;
}
};
useEffect(() => {
@@ -247,23 +261,19 @@ export const PricingPlans = ({
positivePress: async value => {
if (!value) return;
console.log(value);
eSendEvent(eCloseSimpleDialog);
setBuying(true);
try {
let productId = await db.offers.getCode(value, Platform.OS);
if (productId) {
getPromo(productId);
ToastEvent.show({
heading: 'Discount applied!',
type: 'success',
context: 'local'
});
} else {
ToastEvent.show({
heading: 'Promo code invalid or expired',
type: 'error',
context: 'local'
});
}
if (!(await getPromo(value)))
throw new Error('Error applying promo code');
ToastEvent.show({
heading: 'Discount applied!',
type: 'success',
context: 'local'
});
setBuying(false);
} catch (e) {
setBuying(false);
ToastEvent.show({
heading: 'Promo code invalid or expired',
message: e.message,
@@ -282,32 +292,63 @@ export const PricingPlans = ({
) : (
<View>
{!user ? (
<Button
onPress={() => {
eSendEvent(eClosePremiumDialog);
setTimeout(() => {
eSendEvent(eOpenLoginDialog, 1);
}, 400);
}}
title={'Try free for 14 days'}
type="accent"
style={{
paddingHorizontal: 24,
marginTop: 20,
marginBottom: 10
}}
/>
<>
<Button
onPress={() => {
eSendEvent(eClosePremiumDialog);
eSendEvent(eCloseProgressDialog);
setTimeout(() => {
eSendEvent(eOpenLoginDialog, 1);
}, 400);
}}
title={'Try free for 14 days'}
type="accent"
style={{
paddingHorizontal: 24,
marginTop: 20,
marginBottom: 10
}}
/>
{promo &&
!promo.promoCode.startsWith('com.streetwriters.notesnook') ? (
<Paragraph
size={SIZE.md}
textBreakStrategy="balanced"
style={{
alignSelf: 'center',
justifyContent: 'center',
textAlign: 'center'
}}>
Use promo code{' '}
<Text
style={{
fontFamily: 'OpenSans-SemiBold'
}}>
{promo.promoCode}
</Text>{' '}
at checkout
</Paragraph>
) : null}
</>
) : (
<>
<PricingItem
product={product}
<Button
onPress={() => buySubscription(product.data)}
height={40}
width="50%"
type="accent"
title="Subscribe now"
/>
<Button
onPress={() => {
setProduct(null);
}}
style={{
marginTop: 5
}}
height={30}
fontSize={13}
type="errorShade"
title="Cancel promo code"
/>

View File

@@ -1,17 +1,15 @@
import React, { useEffect } from 'react';
import {View} from 'react-native';
import {useTracked} from '../../provider';
import {useMessageStore} from '../../provider/stores';
import {COLORS_NOTE} from '../../utils/Colors';
import {hexToRGBA} from '../../utils/ColorUtils';
import {normalize, SIZE} from '../../utils/SizeUtils';
import {Button} from '../Button';
import {Placeholder} from '../ListPlaceholders';
import React from 'react';
import { View } from 'react-native';
import { useTracked } from '../../provider';
import { useMessageStore } from '../../provider/stores';
import { COLORS_NOTE } from '../../utils/Colors';
import { hexToRGBA } from '../../utils/ColorUtils';
import { normalize, SIZE } from '../../utils/SizeUtils';
import { Announcement } from '../Announcements/announcement';
import { Button } from '../Button';
import { Placeholder } from '../ListPlaceholders';
import Heading from '../Typography/Heading';
import {Announcement} from '../Announcements/announcement';
import {Card} from './card';
import { eSendEvent } from '../../services/EventManager';
import { eOpenAnnouncementDialog } from '../../utils/Events';
import { Card } from './card';
export const Header = React.memo(
({
@@ -30,13 +28,7 @@ export const Header = React.memo(
const [state] = useTracked();
const {colors} = state;
const announcements = useMessageStore(state => state.announcements);
const dialogs = useMessageStore(state => state.dialogs);
useEffect(() => {
if (dialogs.length > 0) {
eSendEvent(eOpenAnnouncementDialog,dialogs[0]);
}
},[dialogs])
return announcements.length !== 0 && !noAnnouncement ? (
<Announcement color={color || colors.accent} />
) : type === 'search' ? null : !shouldShow ? (

View File

@@ -364,9 +364,11 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
announcements = [];
}
} catch (e) {
console.log("ERROR",e);
set({announcements: []});
} finally {
let all = await getFiltered(announcements);
console.log("all", all)
set({
announcements: all.filter(a => a.type === 'inline'),
dialogs: all.filter(a => a.type === 'dialog')
@@ -415,9 +417,7 @@ async function shouldShowAnnouncement(announcement) {
let show = announcement.platforms.some(
platform => allowedPlatforms.indexOf(platform) > -1
);
if (!show) return false;
const subStatus = PremiumService.getUser()?.subscription?.type;
show = announcement.userTypes.some(userType => {
switch (userType) {
@@ -428,22 +428,15 @@ async function shouldShowAnnouncement(announcement) {
case 'trialExpired':
return subStatus === SUBSCRIPTION_STATUS.BASIC;
case 'loggedOut':
show = !PremiumService.getUser();
break;
return !PremiumService.getUser();
case 'verified':
show = PremiumService.getUser()?.isEmailVerified;
break;
return PremiumService.getUser()?.isEmailVerified;
case 'loggedIn':
show = !!PremiumService.getUser();
break;
return !!PremiumService.getUser();
case 'unverified':
show = !PremiumService.getUser()?.isEmailVerified;
break;
return !PremiumService.getUser()?.isEmailVerified;
case 'proExpired':
show =
subStatus === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED ||
subStatus === SUBSCRIPTION_STATUS.PREMIUM_CANCELED;
break;
return subStatus === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED || subStatus === SUBSCRIPTION_STATUS.PREMIUM_CANCELED;
case 'any':
default:
return true;

View File

@@ -1,20 +1,20 @@
import {CHECK_IDS} from 'notes-core/common';
import { CHECK_IDS } from 'notes-core/common';
import React from 'react';
import * as RNIap from 'react-native-iap';
import DialogHeader from '../components/Dialog/dialog-header';
import {CompactFeatures} from '../components/Premium/compact-features';
import {PricingPlans} from '../components/Premium/pricing-plans';
import { CompactFeatures } from '../components/Premium/compact-features';
import { PricingPlans } from '../components/Premium/pricing-plans';
import Seperator from '../components/Seperator';
import {useMessageStore, useUserStore} from '../provider/stores';
import {itemSkus, SUBSCRIPTION_STATUS} from '../utils';
import {db} from '../utils/database';
import { useUserStore } from '../provider/stores';
import { itemSkus, SUBSCRIPTION_STATUS } from '../utils';
import { db } from '../utils/database';
import {
eOpenPremiumDialog,
eOpenTrialEndingDialog,
eShowGetPremium
} from '../utils/Events';
import {MMKV} from '../utils/mmkv';
import {eSendEvent, presentSheet, ToastEvent} from './EventManager';
import { MMKV } from '../utils/mmkv';
import { eSendEvent, presentSheet, ToastEvent } from './EventManager';
let premiumStatus = 0;
let products = [];
@@ -39,7 +39,6 @@ async function setPremiumStatus() {
} catch (e) {
premiumStatus = 0;
} finally {
useMessageStore.getState().setAnnouncement();
if (get()) {
await subscriptions.clear();
}
@@ -283,7 +282,7 @@ const subscriptions = {
async function getRemainingTrialDaysStatus() {
let user = await db.user.getUser();
if (!user) return;
if (!user) return false;
let premium = user.subscription.type !== SUBSCRIPTION_STATUS.BASIC;
let isTrial = user.subscription.type === SUBSCRIPTION_STATUS.TRIAL;
let total = user.subscription.expiry - user.subscription.start;
@@ -299,7 +298,7 @@ async function getRemainingTrialDaysStatus() {
extend: false
});
MMKV.setItem('lastTrialDialogShownAt', 'ending');
return;
return true;
}
if (!premium && lastTrialDialogShownAt !== 'expired') {
@@ -309,7 +308,9 @@ async function getRemainingTrialDaysStatus() {
extend: false
});
MMKV.setItem('lastTrialDialogShownAt', 'expired');
return true;
}
return false;
}
const features_list = [