add support to show promo

This commit is contained in:
ammarahm-ed
2021-04-11 10:34:01 +05:00
parent 777d605dac
commit 84d2b72fab
13 changed files with 245 additions and 44 deletions

View File

@@ -322,6 +322,8 @@ export const AppRootEvents = React.memo(
await PremiumService.setPremiumStatus(); await PremiumService.setPremiumStatus();
setLoginMessage(dispatch); setLoginMessage(dispatch);
} }
} catch (e) { } catch (e) {
let user = await db.user.getUser(); let user = await db.user.getUser();
if (user && !user.isEmailConfirmed) { if (user && !user.isEmailConfirmed) {
@@ -336,6 +338,9 @@ export const AppRootEvents = React.memo(
if (login) { if (login) {
eSendEvent(eCloseProgressDialog); eSendEvent(eCloseProgressDialog);
} }
let announcement = await db.announcement();
} }
}; };

View File

@@ -185,8 +185,8 @@ export class DialogManager extends Component {
eUnSubscribeEvent(eClosePremiumDialog, this.hidePremiumDialog); eUnSubscribeEvent(eClosePremiumDialog, this.hidePremiumDialog);
} }
showPremiumDialog = () => { showPremiumDialog = (prompoInfo) => {
this.premiumDialog.open(); this.premiumDialog.open(prompoInfo);
}; };
hidePremiumDialog = () => { hidePremiumDialog = () => {

View File

@@ -7,14 +7,17 @@ class PremiumDialog extends React.Component {
super(props); super(props);
this.state = { this.state = {
visible: false, visible: false,
promo:null
}; };
this.actionSheetRef = createRef(); this.actionSheetRef = createRef();
} }
open() { open(promoInfo) {
console.log(promoInfo)
this.setState( this.setState(
{ {
visible: true, visible: true,
promo:promoInfo
}, },
() => { () => {
this.actionSheetRef.current?.setModalVisible(true); this.actionSheetRef.current?.setModalVisible(true);
@@ -35,7 +38,7 @@ class PremiumDialog extends React.Component {
render() { render() {
return !this.state.visible ? null : ( return !this.state.visible ? null : (
<ActionSheetWrapper onClose={this.onClose} fwdRef={this.actionSheetRef}> <ActionSheetWrapper onClose={this.onClose} fwdRef={this.actionSheetRef}>
<PremiumComponent close={this.close} /> <PremiumComponent promo={this.state.promo} close={this.close} />
</ActionSheetWrapper> </ActionSheetWrapper>
); );
} }

View File

@@ -10,6 +10,7 @@ import {eCloseResultDialog, eOpenResultDialog} from '../../utils/Events';
import {ph, SIZE} from '../../utils/SizeUtils'; import {ph, SIZE} from '../../utils/SizeUtils';
import {Button} from '../Button'; import {Button} from '../Button';
import BaseDialog from '../Dialog/base-dialog'; import BaseDialog from '../Dialog/base-dialog';
import {SvgToPngView} from '../ListPlaceholders';
import Seperator from '../Seperator'; import Seperator from '../Seperator';
import Heading from '../Typography/Heading'; import Heading from '../Typography/Heading';
import Paragraph from '../Typography/Paragraph'; import Paragraph from '../Typography/Paragraph';
@@ -33,7 +34,7 @@ const ResultDialog = () => {
}; };
}, []); }, []);
const open = (data) => { const open = data => {
setDialogData(data); setDialogData(data);
setVisible(true); setVisible(true);
}; };
@@ -56,7 +57,13 @@ const ResultDialog = () => {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}}> }}>
<SvgXml xml={WELCOME_SVG(colors.accent)} width={170} height={170} /> <SvgToPngView
src={WELCOME_SVG(colors.accent)}
color={colors.accent}
img="welcome"
width={170}
height={170}
/>
<Heading <Heading
size={SIZE.lg} size={SIZE.lg}

View File

@@ -0,0 +1,83 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {useTracked} from '../../provider';
import {eSendEvent} from '../../services/EventManager';
import {eOpenPremiumDialog} from '../../utils/Events';
import {openLinkInBrowser} from '../../utils/functions';
import {SIZE} from '../../utils/SizeUtils';
import useAnnouncement from '../../utils/useAnnouncement';
import {Button} from '../Button';
import Seperator from '../Seperator';
import Heading from '../Typography/Heading';
import Paragraph from '../Typography/Paragraph';
export const Announcement = ({data, color}) => {
const [state] = useTracked();
const {selectionMode} = state;
const [announcement, remove] = useAnnouncement();
return !announcement || selectionMode ? null : (
<View
style={{
backgroundColor: color,
width: '100%',
}}>
<View
style={{
paddingHorizontal: 12,
paddingVertical: 12,
width: '100%',
}}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}>
{announcement?.title && (
<Heading
style={{
width: '90%',
}}
size={SIZE.lg}
color="white">
{announcement.title}
</Heading>
)}
<Icon onPress={remove} name="close" size={SIZE.xl} color="white" />
</View>
{announcement?.description && (
<Paragraph color="white">{announcement.description}</Paragraph>
)}
<Seperator />
{announcement?.cta && (
<Button
type="inverted"
title={announcement.cta.text}
fontSize={SIZE.md}
onPress={async () => {
if (announcement.cta.type === 'link') {
try {
await openLinkInBrowser(
announcement.cta.action,
state.colors,
);
} catch (e) {}
} else if (announcement.cta.type === 'promo') {
eSendEvent(eOpenPremiumDialog, {
promoCode: announcement.cta.action,
text: announcement.cta.text,
});
}
}}
width="100%"
/>
)}
</View>
</View>
);
};

View File

@@ -12,6 +12,8 @@ export const Card = ({data, color}) => {
const [state] = useTracked(); const [state] = useTracked();
const {selectionMode, messageBoardState} = state; const {selectionMode, messageBoardState} = state;
return !messageBoardState.visible || selectionMode ? null : ( return !messageBoardState.visible || selectionMode ? null : (
<TouchableOpacity <TouchableOpacity
activeOpacity={0.8} activeOpacity={0.8}
@@ -24,7 +26,7 @@ export const Card = ({data, color}) => {
position: DDS.isLargeTablet() ? 'relative' : 'absolute', position: DDS.isLargeTablet() ? 'relative' : 'absolute',
right: 0, right: 0,
top: 0, top: 0,
zIndex: 999, zIndex: 100,
backgroundColor: messageBoardState.type === 'error' ? 'red' : color, backgroundColor: messageBoardState.type === 'error' ? 'red' : color,
width: '100%', width: '100%',
}}> }}>

View File

@@ -1,14 +1,15 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import {View} from 'react-native';
import { useTracked } from '../../provider'; import {useTracked} from '../../provider';
import { DDS } from '../../services/DeviceDetection'; import {DDS} from '../../services/DeviceDetection';
import { COLORS_NOTE } from '../../utils/Colors'; import {COLORS_NOTE} from '../../utils/Colors';
import { hexToRGBA } from '../../utils/ColorUtils'; import {hexToRGBA} from '../../utils/ColorUtils';
import { normalize, SIZE } from '../../utils/SizeUtils'; import {normalize, SIZE} from '../../utils/SizeUtils';
import { Button } from '../Button'; import {Button} from '../Button';
import { Placeholder } from '../ListPlaceholders'; import {Placeholder} from '../ListPlaceholders';
import Heading from '../Typography/Heading'; import Heading from '../Typography/Heading';
import { Card } from './card'; import {Announcement} from './announcement';
import {Card} from './card';
export const Header = ({ export const Header = ({
type, type,
@@ -103,7 +104,7 @@ export const Header = ({
fontSize={SIZE.sm} fontSize={SIZE.sm}
onPress={onPress} onPress={onPress}
/> />
): null} ) : null}
</View> </View>
</View> </View>
</View> </View>

View File

@@ -10,11 +10,13 @@ import Navigation from '../../services/Navigation';
import SettingsService from '../../services/SettingsService'; import SettingsService from '../../services/SettingsService';
import Sync from '../../services/Sync'; import Sync from '../../services/Sync';
import {dHeight} from '../../utils'; import {dHeight} from '../../utils';
import {COLORS_NOTE} from '../../utils/Colors';
import {eScrollEvent} from '../../utils/Events'; import {eScrollEvent} from '../../utils/Events';
import {sleep} from '../../utils/TimeUtils'; import {sleep} from '../../utils/TimeUtils';
import {NotebookWrapper} from '../NotebookItem/wrapper'; import {NotebookWrapper} from '../NotebookItem/wrapper';
import {NoteWrapper} from '../NoteItem/wrapper'; import {NoteWrapper} from '../NoteItem/wrapper';
import TagItem from '../TagItem'; import TagItem from '../TagItem';
import {Announcement} from './announcement';
import {Empty} from './empty'; import {Empty} from './empty';
import {Footer} from './footer'; import {Footer} from './footer';
import {Header} from './header'; import {Header} from './header';
@@ -69,9 +71,14 @@ const SimpleList = ({
), ),
); );
setLoading(false); setLoading(false);
setTimeout(() => { setTimeout(
setLoaded(true) () => {
},Navigation.getCurrentScreen() === SettingsService.get().homepage ? 1000 : 150); setLoaded(true);
},
Navigation.getCurrentScreen() === SettingsService.get().homepage
? 1000
: 150,
);
} }
}, [listData, deviceMode, loading]); }, [listData, deviceMode, loading]);
@@ -82,14 +89,14 @@ const SimpleList = ({
} }
}; };
const _onScroll = (event) => { const _onScroll = event => {
if (!event) return; if (!event) return;
let y = event.nativeEvent.contentOffset.y; let y = event.nativeEvent.contentOffset.y;
eSendEvent(eScrollEvent, y); eSendEvent(eScrollEvent, y);
}; };
const _layoutProvider = new LayoutProvider( const _layoutProvider = new LayoutProvider(
(index) => { index => {
return dataProvider.getDataForIndex(index).type; return dataProvider.getDataForIndex(index).type;
}, },
(type, dim) => { (type, dim) => {
@@ -230,6 +237,9 @@ const SimpleList = ({
}; };
return ( return (
<> <>
<Announcement
color={COLORS_NOTE[headerProps.heading?.toLowerCase()] || colors.accent}
/>
{loaded && !loading ? null : ( {loaded && !loading ? null : (
<> <>
<View <View
@@ -240,6 +250,11 @@ const SimpleList = ({
backgroundColor: colors.bg, backgroundColor: colors.bg,
zIndex: 999, zIndex: 999,
}}> }}>
<Announcement
color={
COLORS_NOTE[headerProps.heading?.toLowerCase()] || colors.accent
}
/>
<Header <Header
title={headerProps.heading} title={headerProps.heading}
paragraph={headerProps.paragraph} paragraph={headerProps.paragraph}

View File

@@ -25,6 +25,8 @@ import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {DDS} from '../../services/DeviceDetection'; import {DDS} from '../../services/DeviceDetection';
import {openLinkInBrowser} from '../../utils/functions'; import {openLinkInBrowser} from '../../utils/functions';
import {Modal} from 'react-native'; import {Modal} from 'react-native';
import {SafeAreaView} from 'react-native';
import {SvgToPngView} from '../ListPlaceholders';
const features = [ const features = [
{ {
@@ -39,6 +41,7 @@ const features = [
'Your data is encrypted on your device. No one but you can read your notes.', 'Your data is encrypted on your device. No one but you can read your notes.',
icon: PRIVACY_SVG, icon: PRIVACY_SVG,
link: 'https://notesnook.com', link: 'https://notesnook.com',
img:"privacy"
}, },
{ {
icon: SYNC_SVG, icon: SYNC_SVG,
@@ -46,6 +49,7 @@ const features = [
description: description:
'Everything is automatically synced to all your devices in a safe and secure way. Notesnook is available on all major platforms.', 'Everything is automatically synced to all your devices in a safe and secure way. Notesnook is available on all major platforms.',
link: 'https://notesnook.com', link: 'https://notesnook.com',
img:'sync'
}, },
{ {
icon: ORGANIZE_SVG, icon: ORGANIZE_SVG,
@@ -53,6 +57,7 @@ const features = [
description: description:
'Add your notes in notebooks and topics or simply assign tags or colors to find them easily.', 'Add your notes in notebooks and topics or simply assign tags or colors to find them easily.',
link: 'https://notesnook.com', link: 'https://notesnook.com',
img:"sync"
}, },
{ {
icon: COMMUNITY_SVG, icon: COMMUNITY_SVG,
@@ -60,6 +65,7 @@ const features = [
description: description:
'We are not ghosts, chat with us and share your experience. Give suggestions, report issues and meet other people using Notesnook', 'We are not ghosts, chat with us and share your experience. Give suggestions, report issues and meet other people using Notesnook',
link: 'https://discord.gg/zQBK97EE22', link: 'https://discord.gg/zQBK97EE22',
img:'community'
}, },
]; ];
let currentIndex = 0; let currentIndex = 0;
@@ -75,7 +81,7 @@ const SplashScreen = () => {
useEffect(() => { useEffect(() => {
Storage.read('introCompleted').then(async r => { Storage.read('introCompleted').then(async r => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (!r) { if (r) {
setVisible(true); setVisible(true);
timing(opacity, { timing(opacity, {
toValue: 1, toValue: 1,
@@ -94,7 +100,7 @@ const SplashScreen = () => {
return ( return (
visible && ( visible && (
<Modal animationType="slide" statusBarTranslucent visible> <Modal animationType="slide" statusBarTranslucent visible>
<Animated.View <SafeAreaView
style={{ style={{
width: '100%', width: '100%',
height: '100%', height: '100%',
@@ -119,8 +125,7 @@ const SplashScreen = () => {
onSnapToItem={i => { onSnapToItem={i => {
currentIndex = i; currentIndex = i;
}} }}
activeAnimationType="timing" maxToRenderPerBatch={10}
shouldOptimizeUpdates
renderItem={({item, index}) => ( renderItem={({item, index}) => (
<View <View
style={{ style={{
@@ -155,12 +160,14 @@ const SplashScreen = () => {
size={170} size={170}
/> />
) : ( ) : (
<SvgXml <SvgToPngView
xml={ src={
item.icon item.icon
? item.icon(colors.accent) ? item.icon(colors.accent)
: NOTE_SVG(colors.accent) : NOTE_SVG(colors.accent)
} }
img={item.img}
color={colors.accent}
width={250} width={250}
height={250} height={250}
/> />
@@ -249,7 +256,7 @@ const SplashScreen = () => {
/> />
</View> </View>
</Animated.View> </Animated.View>
</Animated.View> </SafeAreaView>
</Modal> </Modal>
) )
); );

View File

@@ -20,7 +20,7 @@ export const UpdateDialog = () => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [version, setVersion] = useState(null); const [version, setVersion] = useState(null);
const open = (version) => { const open = version => {
setVersion(version); setVersion(version);
setVisible(true); setVisible(true);
}; };
@@ -31,10 +31,10 @@ export const UpdateDialog = () => {
return () => { return () => {
eUnSubscribeEvent('updateDialog', open); eUnSubscribeEvent('updateDialog', open);
}; };
},[]); }, []);
const format = (ver) => { const format = ver => {
if (!ver) return "" if (!ver) return '';
let parts = ver.toString().split(''); let parts = ver.toString().split('');
return `v${parts[0]}.${parts[1]}.${parts[2]}${ return `v${parts[0]}.${parts[1]}.${parts[2]}${
parts[3] === '0' ? '' : parts[3] parts[3] === '0' ? '' : parts[3]
@@ -45,7 +45,7 @@ export const UpdateDialog = () => {
visible && ( visible && (
<BaseDialog <BaseDialog
onRequestClose={() => { onRequestClose={() => {
setVisible(false); version.severity !== 2 && setVisible(false);
}} }}
visible={true}> visible={true}>
<DialogContainer> <DialogContainer>
@@ -114,9 +114,7 @@ export const UpdateDialog = () => {
let url_android = let url_android =
'https://play.google.com/store/apps/details?id=com.streetwriters.notesnook'; 'https://play.google.com/store/apps/details?id=com.streetwriters.notesnook';
let url_ios = 'itms-apps://itunes.apple.com/app/id1544027013'; let url_ios = 'itms-apps://itunes.apple.com/app/id1544027013';
setVisible(false); version.severity !== 2 && setVisible(false);
await Linking.openURL( await Linking.openURL(
Platform.OS === 'android' ? url_android : url_ios, Platform.OS === 'android' ? url_android : url_ios,
); );

View File

@@ -14,15 +14,18 @@ import {itemSkus} from '../utils';
let premiumStatus = 0; let premiumStatus = 0;
let products = []; let products = [];
let user = null
function getUser() {
return user;
}
async function setPremiumStatus() { async function setPremiumStatus() {
try { try {
let user = await db.user.getUser(); user = await db.user.getUser();
if (!user) { if (!user) {
premiumStatus = null; premiumStatus = null;
updateEvent({type: Actions.PREMIUM, state: get()}); updateEvent({type: Actions.PREMIUM, state: get()});
} else { } else {
premiumStatus = user.subscription.type; premiumStatus = user.subscription.type;
updateEvent({type: Actions.PREMIUM, state: get()}); updateEvent({type: Actions.PREMIUM, state: get()});
@@ -172,5 +175,6 @@ export default {
get, get,
onUserStatusCheck, onUserStatusCheck,
showVerifyEmailDialog, showVerifyEmailDialog,
getProducts getProducts,
getUser
}; };

View File

@@ -26,7 +26,7 @@ export async function setSetting(settings, name, value) {
export const scrollRef = createRef(); export const scrollRef = createRef();
export const AndroidModule = NativeModules.NNativeModule; export const AndroidModule = NativeModules.NNativeModule;
export const getElevation = (elevation) => { export const getElevation = elevation => {
return { return {
elevation, elevation,
shadowColor: 'black', shadowColor: 'black',
@@ -154,8 +154,9 @@ export const SUBSCRIPTION_STATUS = {
BASIC: 0, BASIC: 0,
TRIAL: 1, TRIAL: 1,
BETA: 2, BETA: 2,
TRIAL_EXPIRED: 3, PREMIUM: 5,
BETA_EXPIRED: 4, PREMIUM_EXPIRED: 6,
PREMIUM_CANCELLED: 7,
}; };
export const SUBSCRIPTION_STATUS_STRINGS = { export const SUBSCRIPTION_STATUS_STRINGS = {

View File

@@ -0,0 +1,75 @@
import {useCallback, useEffect, useState} from 'react';
import {Platform} from 'react-native';
import {SUBSCRIPTION_STATUS} from '.';
import PremiumService from '../services/PremiumService';
import { db } from './DB';
import Storage from './storage';
var CACHED_ANNOUNCEMENT;
export default function useAnnouncement() {
const [announcement, setAnnouncement] = useState();
useEffect(() => {
(async function () {
try {
CACHED_ANNOUNCEMENT = CACHED_ANNOUNCEMENT || (await db.announcement());
if (
!CACHED_ANNOUNCEMENT ||
await Storage.read('removedAnnouncement') === CACHED_ANNOUNCEMENT.id ||
!shouldShowAnnouncement(CACHED_ANNOUNCEMENT)
)
return;
setAnnouncement(CACHED_ANNOUNCEMENT);
} catch(e) {
setAnnouncement()
}
})();
}, []);
const remove = useCallback(async () => {
await Storage.write('removedAnnouncement', CACHED_ANNOUNCEMENT.id);
setAnnouncement();
}, [announcement]);
return [announcement, remove];
}
const allowedPlatforms = ['all', 'mobile', Platform.OS];
function shouldShowAnnouncement(announcement) {
let show = allowedPlatforms.indexOf(announcement.platform) > -1;
console.log(show)
if (!show) return;
const subStatus = PremiumService.getUser()?.subscription?.type;
switch (announcement.userType) {
case 'pro':
show = isUserPremium();
break;
case 'trial':
show = subStatus === SUBSCRIPTION_STATUS.TRIAL;
break;
case 'trialExpired':
show = subStatus === SUBSCRIPTION_STATUS.BASIC;
break;
case 'loggedOut':
show = !PremiumService.getUser();
break;
case 'unverified':
show = !PremiumService.getUser()?.isEmailVerified;
break;
case 'proExpired':
show =
subStatus === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED ||
subStatus === SUBSCRIPTION_STATUS.PREMIUM_CANCELED;
break;
case 'any':
default:
break;
}
return show;
}