mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
add support to show promo
This commit is contained in:
@@ -322,6 +322,8 @@ export const AppRootEvents = React.memo(
|
||||
await PremiumService.setPremiumStatus();
|
||||
setLoginMessage(dispatch);
|
||||
}
|
||||
|
||||
|
||||
} catch (e) {
|
||||
let user = await db.user.getUser();
|
||||
if (user && !user.isEmailConfirmed) {
|
||||
@@ -336,6 +338,9 @@ export const AppRootEvents = React.memo(
|
||||
if (login) {
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
}
|
||||
let announcement = await db.announcement();
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -185,8 +185,8 @@ export class DialogManager extends Component {
|
||||
eUnSubscribeEvent(eClosePremiumDialog, this.hidePremiumDialog);
|
||||
}
|
||||
|
||||
showPremiumDialog = () => {
|
||||
this.premiumDialog.open();
|
||||
showPremiumDialog = (prompoInfo) => {
|
||||
this.premiumDialog.open(prompoInfo);
|
||||
};
|
||||
|
||||
hidePremiumDialog = () => {
|
||||
|
||||
@@ -7,14 +7,17 @@ class PremiumDialog extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
promo:null
|
||||
};
|
||||
this.actionSheetRef = createRef();
|
||||
}
|
||||
|
||||
open() {
|
||||
open(promoInfo) {
|
||||
console.log(promoInfo)
|
||||
this.setState(
|
||||
{
|
||||
visible: true,
|
||||
promo:promoInfo
|
||||
},
|
||||
() => {
|
||||
this.actionSheetRef.current?.setModalVisible(true);
|
||||
@@ -35,7 +38,7 @@ class PremiumDialog extends React.Component {
|
||||
render() {
|
||||
return !this.state.visible ? null : (
|
||||
<ActionSheetWrapper onClose={this.onClose} fwdRef={this.actionSheetRef}>
|
||||
<PremiumComponent close={this.close} />
|
||||
<PremiumComponent promo={this.state.promo} close={this.close} />
|
||||
</ActionSheetWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {eCloseResultDialog, eOpenResultDialog} from '../../utils/Events';
|
||||
import {ph, SIZE} from '../../utils/SizeUtils';
|
||||
import {Button} from '../Button';
|
||||
import BaseDialog from '../Dialog/base-dialog';
|
||||
import {SvgToPngView} from '../ListPlaceholders';
|
||||
import Seperator from '../Seperator';
|
||||
import Heading from '../Typography/Heading';
|
||||
import Paragraph from '../Typography/Paragraph';
|
||||
@@ -33,7 +34,7 @@ const ResultDialog = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = (data) => {
|
||||
const open = data => {
|
||||
setDialogData(data);
|
||||
setVisible(true);
|
||||
};
|
||||
@@ -56,7 +57,13 @@ const ResultDialog = () => {
|
||||
justifyContent: '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
|
||||
size={SIZE.lg}
|
||||
|
||||
83
apps/mobile/src/components/SimpleList/announcement.js
Normal file
83
apps/mobile/src/components/SimpleList/announcement.js
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,8 @@ export const Card = ({data, color}) => {
|
||||
const [state] = useTracked();
|
||||
const {selectionMode, messageBoardState} = state;
|
||||
|
||||
|
||||
|
||||
return !messageBoardState.visible || selectionMode ? null : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
@@ -24,7 +26,7 @@ export const Card = ({data, color}) => {
|
||||
position: DDS.isLargeTablet() ? 'relative' : 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
zIndex: 999,
|
||||
zIndex: 100,
|
||||
backgroundColor: messageBoardState.type === 'error' ? 'red' : color,
|
||||
width: '100%',
|
||||
}}>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useTracked } from '../../provider';
|
||||
import { DDS } from '../../services/DeviceDetection';
|
||||
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 {View} from 'react-native';
|
||||
import {useTracked} from '../../provider';
|
||||
import {DDS} from '../../services/DeviceDetection';
|
||||
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 Heading from '../Typography/Heading';
|
||||
import { Card } from './card';
|
||||
import {Announcement} from './announcement';
|
||||
import {Card} from './card';
|
||||
|
||||
export const Header = ({
|
||||
type,
|
||||
@@ -103,7 +104,7 @@ export const Header = ({
|
||||
fontSize={SIZE.sm}
|
||||
onPress={onPress}
|
||||
/>
|
||||
): null}
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -10,11 +10,13 @@ import Navigation from '../../services/Navigation';
|
||||
import SettingsService from '../../services/SettingsService';
|
||||
import Sync from '../../services/Sync';
|
||||
import {dHeight} from '../../utils';
|
||||
import {COLORS_NOTE} from '../../utils/Colors';
|
||||
import {eScrollEvent} from '../../utils/Events';
|
||||
import {sleep} from '../../utils/TimeUtils';
|
||||
import {NotebookWrapper} from '../NotebookItem/wrapper';
|
||||
import {NoteWrapper} from '../NoteItem/wrapper';
|
||||
import TagItem from '../TagItem';
|
||||
import {Announcement} from './announcement';
|
||||
import {Empty} from './empty';
|
||||
import {Footer} from './footer';
|
||||
import {Header} from './header';
|
||||
@@ -69,9 +71,14 @@ const SimpleList = ({
|
||||
),
|
||||
);
|
||||
setLoading(false);
|
||||
setTimeout(() => {
|
||||
setLoaded(true)
|
||||
},Navigation.getCurrentScreen() === SettingsService.get().homepage ? 1000 : 150);
|
||||
setTimeout(
|
||||
() => {
|
||||
setLoaded(true);
|
||||
},
|
||||
Navigation.getCurrentScreen() === SettingsService.get().homepage
|
||||
? 1000
|
||||
: 150,
|
||||
);
|
||||
}
|
||||
}, [listData, deviceMode, loading]);
|
||||
|
||||
@@ -82,14 +89,14 @@ const SimpleList = ({
|
||||
}
|
||||
};
|
||||
|
||||
const _onScroll = (event) => {
|
||||
const _onScroll = event => {
|
||||
if (!event) return;
|
||||
let y = event.nativeEvent.contentOffset.y;
|
||||
eSendEvent(eScrollEvent, y);
|
||||
};
|
||||
|
||||
const _layoutProvider = new LayoutProvider(
|
||||
(index) => {
|
||||
index => {
|
||||
return dataProvider.getDataForIndex(index).type;
|
||||
},
|
||||
(type, dim) => {
|
||||
@@ -230,6 +237,9 @@ const SimpleList = ({
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Announcement
|
||||
color={COLORS_NOTE[headerProps.heading?.toLowerCase()] || colors.accent}
|
||||
/>
|
||||
{loaded && !loading ? null : (
|
||||
<>
|
||||
<View
|
||||
@@ -240,6 +250,11 @@ const SimpleList = ({
|
||||
backgroundColor: colors.bg,
|
||||
zIndex: 999,
|
||||
}}>
|
||||
<Announcement
|
||||
color={
|
||||
COLORS_NOTE[headerProps.heading?.toLowerCase()] || colors.accent
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
title={headerProps.heading}
|
||||
paragraph={headerProps.paragraph}
|
||||
|
||||
@@ -25,6 +25,8 @@ import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {DDS} from '../../services/DeviceDetection';
|
||||
import {openLinkInBrowser} from '../../utils/functions';
|
||||
import {Modal} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native';
|
||||
import {SvgToPngView} from '../ListPlaceholders';
|
||||
|
||||
const features = [
|
||||
{
|
||||
@@ -39,6 +41,7 @@ const features = [
|
||||
'Your data is encrypted on your device. No one but you can read your notes.',
|
||||
icon: PRIVACY_SVG,
|
||||
link: 'https://notesnook.com',
|
||||
img:"privacy"
|
||||
},
|
||||
{
|
||||
icon: SYNC_SVG,
|
||||
@@ -46,6 +49,7 @@ const features = [
|
||||
description:
|
||||
'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',
|
||||
img:'sync'
|
||||
},
|
||||
{
|
||||
icon: ORGANIZE_SVG,
|
||||
@@ -53,6 +57,7 @@ const features = [
|
||||
description:
|
||||
'Add your notes in notebooks and topics or simply assign tags or colors to find them easily.',
|
||||
link: 'https://notesnook.com',
|
||||
img:"sync"
|
||||
},
|
||||
{
|
||||
icon: COMMUNITY_SVG,
|
||||
@@ -60,6 +65,7 @@ const features = [
|
||||
description:
|
||||
'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',
|
||||
img:'community'
|
||||
},
|
||||
];
|
||||
let currentIndex = 0;
|
||||
@@ -75,7 +81,7 @@ const SplashScreen = () => {
|
||||
useEffect(() => {
|
||||
Storage.read('introCompleted').then(async r => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!r) {
|
||||
if (r) {
|
||||
setVisible(true);
|
||||
timing(opacity, {
|
||||
toValue: 1,
|
||||
@@ -94,7 +100,7 @@ const SplashScreen = () => {
|
||||
return (
|
||||
visible && (
|
||||
<Modal animationType="slide" statusBarTranslucent visible>
|
||||
<Animated.View
|
||||
<SafeAreaView
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@@ -119,8 +125,7 @@ const SplashScreen = () => {
|
||||
onSnapToItem={i => {
|
||||
currentIndex = i;
|
||||
}}
|
||||
activeAnimationType="timing"
|
||||
shouldOptimizeUpdates
|
||||
maxToRenderPerBatch={10}
|
||||
renderItem={({item, index}) => (
|
||||
<View
|
||||
style={{
|
||||
@@ -155,12 +160,14 @@ const SplashScreen = () => {
|
||||
size={170}
|
||||
/>
|
||||
) : (
|
||||
<SvgXml
|
||||
xml={
|
||||
<SvgToPngView
|
||||
src={
|
||||
item.icon
|
||||
? item.icon(colors.accent)
|
||||
: NOTE_SVG(colors.accent)
|
||||
}
|
||||
img={item.img}
|
||||
color={colors.accent}
|
||||
width={250}
|
||||
height={250}
|
||||
/>
|
||||
@@ -249,7 +256,7 @@ const SplashScreen = () => {
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const UpdateDialog = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [version, setVersion] = useState(null);
|
||||
|
||||
const open = (version) => {
|
||||
const open = version => {
|
||||
setVersion(version);
|
||||
setVisible(true);
|
||||
};
|
||||
@@ -31,10 +31,10 @@ export const UpdateDialog = () => {
|
||||
return () => {
|
||||
eUnSubscribeEvent('updateDialog', open);
|
||||
};
|
||||
},[]);
|
||||
}, []);
|
||||
|
||||
const format = (ver) => {
|
||||
if (!ver) return ""
|
||||
const format = ver => {
|
||||
if (!ver) return '';
|
||||
let parts = ver.toString().split('');
|
||||
return `v${parts[0]}.${parts[1]}.${parts[2]}${
|
||||
parts[3] === '0' ? '' : parts[3]
|
||||
@@ -45,7 +45,7 @@ export const UpdateDialog = () => {
|
||||
visible && (
|
||||
<BaseDialog
|
||||
onRequestClose={() => {
|
||||
setVisible(false);
|
||||
version.severity !== 2 && setVisible(false);
|
||||
}}
|
||||
visible={true}>
|
||||
<DialogContainer>
|
||||
@@ -114,9 +114,7 @@ export const UpdateDialog = () => {
|
||||
let url_android =
|
||||
'https://play.google.com/store/apps/details?id=com.streetwriters.notesnook';
|
||||
let url_ios = 'itms-apps://itunes.apple.com/app/id1544027013';
|
||||
setVisible(false);
|
||||
|
||||
|
||||
version.severity !== 2 && setVisible(false);
|
||||
await Linking.openURL(
|
||||
Platform.OS === 'android' ? url_android : url_ios,
|
||||
);
|
||||
|
||||
@@ -14,15 +14,18 @@ import {itemSkus} from '../utils';
|
||||
|
||||
let premiumStatus = 0;
|
||||
let products = [];
|
||||
let user = null
|
||||
|
||||
function getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
async function setPremiumStatus() {
|
||||
try {
|
||||
let user = await db.user.getUser();
|
||||
user = await db.user.getUser();
|
||||
if (!user) {
|
||||
premiumStatus = null;
|
||||
updateEvent({type: Actions.PREMIUM, state: get()});
|
||||
|
||||
|
||||
} else {
|
||||
premiumStatus = user.subscription.type;
|
||||
updateEvent({type: Actions.PREMIUM, state: get()});
|
||||
@@ -172,5 +175,6 @@ export default {
|
||||
get,
|
||||
onUserStatusCheck,
|
||||
showVerifyEmailDialog,
|
||||
getProducts
|
||||
getProducts,
|
||||
getUser
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function setSetting(settings, name, value) {
|
||||
export const scrollRef = createRef();
|
||||
export const AndroidModule = NativeModules.NNativeModule;
|
||||
|
||||
export const getElevation = (elevation) => {
|
||||
export const getElevation = elevation => {
|
||||
return {
|
||||
elevation,
|
||||
shadowColor: 'black',
|
||||
@@ -154,8 +154,9 @@ export const SUBSCRIPTION_STATUS = {
|
||||
BASIC: 0,
|
||||
TRIAL: 1,
|
||||
BETA: 2,
|
||||
TRIAL_EXPIRED: 3,
|
||||
BETA_EXPIRED: 4,
|
||||
PREMIUM: 5,
|
||||
PREMIUM_EXPIRED: 6,
|
||||
PREMIUM_CANCELLED: 7,
|
||||
};
|
||||
|
||||
export const SUBSCRIPTION_STATUS_STRINGS = {
|
||||
|
||||
75
apps/mobile/src/utils/useAnnouncement.js
Normal file
75
apps/mobile/src/utils/useAnnouncement.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user