feat: new announcements and dialog

This commit is contained in:
ammarahm-ed
2021-11-23 15:06:35 +05:00
parent ff33619865
commit c6bd4d4f13
10 changed files with 533 additions and 334 deletions

View File

@@ -0,0 +1,70 @@
import React from 'react';
import {FlatList, View} from 'react-native';
import {useTracked} from '../../provider';
import {useMessageStore, useSelectionStore} from '../../provider/stores';
import {Button} from '../Button';
import {allowedOnPlatform, renderItem} from './functions';
export const Announcement = ({color}) => {
const [state] = useTracked();
const colors = state.colors;
const announcements = useMessageStore(state => state.announcements);
const remove = useMessageStore(state => state.remove);
let announcement = announcements.length > 0 ? announcements[0] : null;
const selectionMode = useSelectionStore(state => state.selectionMode);
return !announcement || selectionMode ? null : (
<View
style={{
backgroundColor: colors.bg,
width: '100%',
paddingHorizontal: 12
}}>
<View
style={{
paddingVertical: 12,
width: '100%',
borderRadius: 10,
overflow: 'hidden'
}}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<View />
<Button
fontSize={12}
type="errorShade"
icon="close"
height={null}
onPress={() => {
remove(announcement.id);
}}
iconSize={20}
style={{
borderRadius: 100,
paddingHorizontal: 0,
backgroundColor: 'transparent'
}}
/>
</View>
<View>
<FlatList
style={{
width: '100%'
}}
data={announcement?.body.filter(item =>
allowedOnPlatform(item.platform)
)}
renderItem={({item, index}) =>
renderItem({item: item, index: index, color: colors[color]})
}
/>
</View>
</View>
</View>
);
};

View File

@@ -1,28 +1,86 @@
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 { SettingsBackupAndRestore } from '../../views/Settings';
import { Button } from '../Button';
import { getStyle } from './functions';
import { allowedOnPlatform, getStyle } from './functions';
export const Cta = ({actions, style = {}}) => {
export const Cta = ({actions, style = {}, color}) => {
const [state] = useTracked();
const colors = state.colors;
let buttons =
actions.filter(item =>
allowedOnPlatform(item.platforms)
) || [];
const onPress = async item => {
eSendEvent(eCloseAnnouncementDialog);
await sleep(500);
if (item.type === 'link') {
try {
await openLinkInBrowser(item.data, colors);
} catch (e) {}
} else if (item.type === 'promo') {
eSendEvent(eOpenPremiumDialog, {
promoCode: item.data,
text: item.title
});
} else if (item.type === 'backup') {
presentSheet({
title: 'Backup & restore',
paragraph: 'Please enable automatic backups to keep your data safe',
noProgress: true,
noIcon: true,
component: <SettingsBackupAndRestore isSheet={true} />
});
}
};
return (
<View
style={{
paddingHorizontal: 12,
paddingTop: 12,
backgroundColor: colors.bg,
...getStyle(style)
}}>
{actions.map((item, index) => (
{buttons.length > 0 &&
buttons.slice(0, 1).map(item => (
<Button
type={index === 0 ? 'accent' : 'grayBg'}
title={item.text}
width="100%"
onPress={() => {}}
key={item.title}
title={item.title}
fontSize={SIZE.md}
buttonType={{
color: color ? color : colors.accent,
text: colors.light,
selected: color ? color : colors.accent,
opacity: 1
}}
onPress={() => onPress(item)}
width={'100%'}
style={{
marginBottom: 10
marginBottom: 5
}}
/>
))}
{buttons.length > 1 &&
buttons.slice(1, 2).map((item, index) => (
<Button
key={item.title}
title={item.title}
fontSize={SIZE.xs + 1}
type="gray"
onPress={() => onPress(item)}
width={null}
height={20}
style={{
minWidth: '50%'
}}
textStyle={{
textDecorationLine: 'underline'
}}
/>
))}

View File

@@ -1,3 +1,19 @@
import React from 'react';
import {View} from 'react-native';
import {allowedPlatforms} from '../../provider/stores';
import {ProFeatures} from '../ResultDialog/pro-features';
import {Body} from './body';
import {Cta} from './cta';
import {Description} from './description';
import {List} from './list';
import {Photo} from './photo';
import {SubHeading} from './subheading';
import {Title} from './title';
export function allowedOnPlatform(platforms) {
return platforms.some(platform => allowedPlatforms.indexOf(platform) > -1);
}
export const margins = {
0: 0,
1: 12,
@@ -12,3 +28,34 @@ export const getStyle = style => {
textAlign: style.textAlign || 'left'
};
};
const Features = () => {
return (
<View
style={{
paddingHorizontal: 12,
alignItems: 'center',
width: '100%'
}}>
<ProFeatures />
</View>
);
};
const renderItems = {
title: Title,
description: Description,
body: Body,
cta: Cta,
image: Photo,
list: List,
subheading: SubHeading,
features: Features,
callToActions: Cta
};
export const renderItem = ({item, index, color}) => {
const Item = renderItems[item.type];
return <Item {...item} index={index} color={color} />;
};

View File

@@ -1,112 +1,21 @@
import React, {useEffect, useState} from 'react';
import {FlatList, View} from 'react-native';
import {useTracked} from '../../provider';
import {useMessageStore} from '../../provider/stores';
import {eSubscribeEvent, eUnSubscribeEvent} from '../../services/EventManager';
import {
eCloseAnnouncementDialog,
eOpenAnnouncementDialog
} from '../../utils/Events';
import BaseDialog from '../Dialog/base-dialog';
import {Body} from './body';
import {Description} from './description';
import {Photo} from './photo';
import {SubHeading} from './subheading';
import {List} from './list';
import {Title} from './title';
import {Cta} from './cta';
import {allowedPlatforms} from '../../provider/stores';
import {ProFeatures} from '../ResultDialog/pro-features';
import {renderItem} from "./functions";
const announcement_dialog_info = {
body: [
{
type: 'image',
src: 'https://media.istockphoto.com/vectors/flash-sale-promotional-labels-templates-set-special-offer-text-design-vector-id1195558677?s=170667a',
caption: 'an image of a bear',
style: {}
},
{
type: 'title',
text: "Don't miss out on this one!",
style: {
textAlign: 'center',
marginTop: 1
}
},
{
type: 'description',
text: "It's 50% off on Notesnook Pro today. Grab the offer now before it's too late.",
style: {
textAlign: 'center',
marginBottom: 1
}
},
{
type: 'features'
}
// {
// type: 'subheading',
// text: 'New image tool'
// },
// {
// type: 'body',
// text: 'But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer.'
// },
// {
// type: 'list',
// items: [
// {
// text: 'Lorem ipsum dolor sit amet'
// },
// {
// text: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium'
// }
// ]
// }
],
callToActions: [
{
text: 'Get 50% Off for One Year',
action: 'none',
platforms: ['mobile']
}
]
};
const Features = () => {
return (
<View
style={{
paddingHorizontal: 12,
alignItems:'center',
width:'100%'
}}>
<ProFeatures />
</View>
);
};
const renderItems = {
title: Title,
description: Description,
body: Body,
cta: Cta,
image: Photo,
list: List,
subheading: SubHeading,
features: Features
};
const renderItem = ({item, index}) => {
const Item = renderItems[item.type];
return <Item {...item} index={index} />;
};
export const Announcement = () => {
export const AnnouncementDialog = () => {
const [state] = useTracked();
const colors = state.colors;
const [visible, setVisible] = useState(true);
const [visible, setVisible] = useState(false);
const [info, setInfo] = useState(null);
const remove = useMessageStore(state => state.remove);
useEffect(() => {
eSubscribeEvent(eOpenAnnouncementDialog, open);
@@ -117,11 +26,15 @@ export const Announcement = () => {
};
}, [visible]);
const open = () => {
const open = data => {
setInfo(data);
console.log(info);
setVisible(true);
};
const close = () => {
//remove(info.id);
setInfo(null);
setVisible(false);
};
@@ -130,6 +43,7 @@ export const Announcement = () => {
animated={false}
centered={false}
bottom={true}
onRequestClose={close}
visible={visible}>
<View
style={{
@@ -141,17 +55,10 @@ export const Announcement = () => {
style={{
width: '100%'
}}
data={announcement_dialog_info.body}
data={info?.body}
renderItem={renderItem}
/>
<Cta
actions={announcement_dialog_info.callToActions.filter(item =>
item.platforms.some(
platform => allowedPlatforms.indexOf(platform) > -1
)
)}
/>
<View
style={{
height: 15

View File

@@ -25,7 +25,7 @@ import { ActionSheetComponent } from '../ActionSheetComponent';
import ActionSheetWrapper from '../ActionSheetComponent/ActionSheetWrapper';
import { AddNotebookDialog } from '../AddNotebookDialog';
import { AddTopicDialog } from '../AddTopicDialog';
import { Announcement } from '../Announcements';
import { AnnouncementDialog } from '../Announcements';
import { AttachmentDialog } from '../AttachmentDialog';
import { Dialog } from '../Dialog';
import ExportDialog from '../ExportDialog';
@@ -302,7 +302,7 @@ export class DialogManager extends Component {
<TagsDialog />
<AttachmentDialog />
<Expiring/>
<Announcement/>
<AnnouncementDialog/>
</>
);
}

View File

@@ -0,0 +1,287 @@
import React from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
const Circle = ({size, color, position}) => {
let style = {
wrapper: {
flexDirection: 'row',
...position
},
circle: {
width: size,
height: size,
borderRadius: size / 2,
backgroundColor: color
}
};
return (
<View style={style.wrapper}>
<View style={style.circle} />
</View>
);
};
const Donut = ({size, color, position}) => {
let style = {
wrapper: {
flexDirection: 'row',
...position
},
donut: {
width: size,
height: size,
borderRadius: size / 2,
borderWidth: size / 4,
borderColor: color
}
};
return (
<View style={style.wrapper}>
<View style={style.donut} />
</View>
);
};
const Triangle = ({size, color, position}) => {
let style = {
wrapper: {
flexDirection: 'row',
...position
},
triangle: {
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: size / 2,
borderRightWidth: size / 2,
borderBottomWidth: size,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: color,
transform: [{rotate: '180deg'}]
}
};
return (
<View style={style.wrapper}>
<View style={style.triangle} />
</View>
);
};
const DiamondNarrow = ({size, color, position}) => {
let style = {
wrapper: {
flexDirection: 'row',
...position
},
diamondNarrow: {},
diamondNarrowTop: {
width: 0,
height: 0,
borderTopWidth: 0,
borderTopColor: 'transparent',
borderLeftColor: 'transparent',
borderLeftWidth: size / 2,
borderRightColor: 'transparent',
borderRightWidth: size / 2,
borderBottomColor: color,
borderBottomWidth: size / 1.42
},
diamondNarrowBottom: {
width: 0,
height: 0,
borderTopWidth: size / 1.42,
borderTopColor: color,
borderLeftColor: 'transparent',
borderLeftWidth: size / 2,
borderRightColor: 'transparent',
borderRightWidth: size / 2,
borderBottomColor: 'transparent',
borderBottomWidth: 0
}
};
return (
<View style={style.wrapper}>
<View style={style.diamondNarrow}>
<View style={style.diamondNarrowTop} />
<View style={style.diamondNarrowBottom} />
</View>
</View>
);
};
const CutDiamond = ({size, color, position}) => {
let style = {
wrapper: {
flexDirection: 'row',
...position
},
cutDiamond: {},
cutDiamondTop: {
width: size,
height: 0,
borderTopWidth: 0,
borderTopColor: 'transparent',
borderLeftColor: 'transparent',
borderLeftWidth: size / 4,
borderRightColor: 'transparent',
borderRightWidth: size / 4,
borderBottomColor: color,
borderBottomWidth: size / 4
},
cutDiamondBottom: {
width: 0,
height: 0,
borderTopWidth: size / 1.42,
borderTopColor: color,
borderLeftColor: 'transparent',
borderLeftWidth: size / 2,
borderRightColor: 'transparent',
borderRightWidth: size / 2,
borderBottomColor: 'transparent',
borderBottomWidth: 0
}
};
return (
<View style={style.wrapper}>
<View style={style.cutDiamond}>
<View style={style.cutDiamondTop} />
<View style={style.cutDiamondBottom} />
</View>
</View>
);
};
const Shapes = ({
primaryColor,
secondaryColor,
height,
figures,
borderRadius,
style
}) => {
const config = {
primaryColor: primaryColor || '#416DF8',
secondaryColor: secondaryColor || '#2F53D5',
height: Dimensions.get('window').height / (height || 3.5),
sizefigure: 100,
figures: figures || [
{name: 'circle', position: 'center', size: 60},
{name: 'donut', position: 'flex-start', axis: 'top', size: 80},
{name: 'circle', position: 'center', axis: 'right', size: 100}
],
borderRadius: borderRadius !== undefined ? borderRadius : 30
};
const arrFigures = [];
const buildFigures = () => {
config.figures.forEach((e, i) => {
let position = {
alignItems: e.position
};
const sizefigure = e.size || config.sizefigure;
switch (e.axis) {
case 'left':
position.left = -sizefigure / 2;
break;
case 'right':
position.right = -sizefigure / 2;
break;
case 'top':
position.top = -sizefigure / 2;
break;
case 'bottom':
position.bottom = -sizefigure / 2;
break;
default:
break;
}
if (e.name === 'circle') {
arrFigures.push(
<Circle
key={i}
size={sizefigure}
color={config.secondaryColor}
position={position}
/>
);
}
if (e.name === 'donut') {
arrFigures.push(
<Donut
key={i}
size={sizefigure}
color={config.secondaryColor}
position={position}
/>
);
}
if (e.name === 'triangle') {
arrFigures.push(
<Triangle
key={i}
size={sizefigure}
color={config.secondaryColor}
position={position}
/>
);
}
if (e.name === 'diamondNarrow') {
arrFigures.push(
<DiamondNarrow
key={i}
size={sizefigure}
color={config.secondaryColor}
position={position}
/>
);
}
if (e.name === 'cutDiamond') {
arrFigures.push(
<CutDiamond
key={i}
size={sizefigure}
color={config.secondaryColor}
position={position}
/>
);
}
});
return arrFigures;
};
return (
<View
style={{
...styles.wrapper,
backgroundColor: config.primaryColor,
height: config.height,
borderBottomLeftRadius: config.borderRadius,
borderBottomRightRadius: config.borderRadius,
...style
}}>
<>{buildFigures()}</>
</View>
);
};
const styles = StyleSheet.create({
wrapper: {
position: 'absolute',
height: '100%',
left: 0,
right: 0,
flexDirection: 'row',
justifyContent: 'space-between'
}
});
export { Shapes };

View File

@@ -1,195 +0,0 @@
import React from 'react';
import { View } from 'react-native';
import { useTracked } from '../../provider';
import {
allowedPlatforms,
useMessageStore,
useSelectionStore
} from '../../provider/stores';
import { eSendEvent, presentSheet } from '../../services/EventManager';
import { hexToRGBA, RGB_Linear_Shade } from '../../utils/ColorUtils';
import { eOpenPremiumDialog } from '../../utils/Events';
import { openLinkInBrowser } from '../../utils/functions';
import { SIZE } from '../../utils/SizeUtils';
import { SettingsBackupAndRestore } from '../../views/Settings';
import { Button } from '../Button';
import Seperator from '../Seperator';
import Heading from '../Typography/Heading';
import Paragraph from '../Typography/Paragraph';
export const Announcement = ({color}) => {
const [state] = useTracked();
const colors = state.colors;
const announcements = useMessageStore(state => state.announcements);
const remove = useMessageStore(state => state.remove);
let announcement = announcements.length > 0 ? announcements[0] : null;
const selectionMode = useSelectionStore(state => state.selectionMode);
return !announcement || selectionMode ? null : (
<View
style={{
backgroundColor: colors.bg,
width: '100%'
}}>
<View
style={{
paddingHorizontal: 12,
paddingVertical: 12,
width: '100%'
}}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<View
style={{
flexDirection: 'row',
alignItems: 'center'
}}>
<View
style={{
backgroundColor: colors.accent,
borderRadius: 100,
width: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
marginRight: 2.5
}}>
<Paragraph color={colors.light} size={SIZE.xs}>
{announcements.length}
</Paragraph>
</View>
<Button
title={'Announcement'}
fontSize={12}
height={null}
icon="bullhorn"
style={{
backgroundColor: color
? RGB_Linear_Shade(
colors.night ? 0.04 : -0.04,
hexToRGBA(color, 0.12)
)
: colors.shade
}}
buttonType={{text: color || colors.accent}}
style={{
paddingVertical: 4
}}
/>
</View>
<Button
title="Dismiss"
fontSize={12}
type="error"
height={null}
onPress={() => {
remove(announcement.id);
}}
style={{
paddingVertical: 4
}}
/>
</View>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
}}>
{announcement?.title && (
<Heading
style={{
width: '100%'
}}
size={SIZE.lg}
color={colors.heading}>
{announcement.title}
</Heading>
)}
</View>
{announcement?.description && (
<Paragraph color={colors.pri}>{announcement.description}</Paragraph>
)}
<Seperator />
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
flexWrap: 'wrap'
}}>
{announcement?.callToActions &&
announcement.callToActions.map((item, index) =>
item.platforms.some(
platform => allowedPlatforms.indexOf(platform) > -1
) ? (
<>
<Button
key={item.title}
title={item.title}
fontSize={SIZE.md}
buttonType={{
color:
index === 0
? color
? color
: colors.accent
: color
? RGB_Linear_Shade(
colors.night ? 0.04 : -0.04,
hexToRGBA(color, 0.12)
)
: colors.shade,
text:
index !== 0
? color
? color
: colors.accent
: colors.light,
selected: color
? color
: index === 0
? colors.accent
: colors.shade,
opacity: 1
}}
onPress={async () => {
if (item.type === 'link') {
try {
await openLinkInBrowser(item.data, state.colors);
} catch (e) {}
} else if (item.type === 'promo') {
eSendEvent(eOpenPremiumDialog, {
promoCode: item.data,
text: item.title
});
} else if (item.type === 'backup') {
presentSheet({
title: 'Backup & restore',
paragraph:
'Please enable automatic backups to keep your data safe',
noProgress: true,
noIcon: true,
component: <SettingsBackupAndRestore isSheet={true} />
});
}
}}
width={'100%'}
style={{
marginBottom: 10
}}
/>
</>
) : null
)}
</View>
</View>
</View>
);
};

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import {View} from 'react-native';
import {useTracked} from '../../provider';
import {useMessageStore} from '../../provider/stores';
@@ -8,8 +8,10 @@ import {normalize, SIZE} from '../../utils/SizeUtils';
import {Button} from '../Button';
import {Placeholder} from '../ListPlaceholders';
import Heading from '../Typography/Heading';
import {Announcement} from './announcement';
import {Announcement} from '../Announcements/announcement';
import {Card} from './card';
import { eSendEvent } from '../../services/EventManager';
import { eOpenAnnouncementDialog } from '../../utils/Events';
export const Header = React.memo(
({
@@ -28,8 +30,14 @@ export const Header = React.memo(
const [state] = useTracked();
const {colors} = state;
const announcements = useMessageStore(state => state.announcements);
const dialogs = useMessageStore(state => state.dialogs);
return announcements.length > 0 && !noAnnouncement ? (
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
@@ -43,7 +51,6 @@ export const Header = React.memo(
{messageCard && (
<Card color={COLORS_NOTE[color?.toLowerCase()] || colors.accent} />
)}
</View>
) : (
<View

View File

@@ -149,10 +149,25 @@ export type Action = {
title: string
data: string
}
export type Style = {
marginTop?: number,
marginBottom?: number,
textAlign?: "center" | "left" | "right"
}
export type BodyItem = {
type: "image" | "title" | "description" | "body" | "list" | "features" | "poll" | "subheading" | "shapes"
src?: string
caption?: string
text?: string
style?: Style,
items?: Array<{
text?: string
}>
}
export type Announcement = {
title: string
description: string
type: "dialog" | "inline"
body: BodyItem[]
id: string
callToActions: Action[]
timestamp: number
@@ -166,6 +181,7 @@ export interface MessageStore extends State {
setMessage: (message: Message) => void
announcements: Announcement[],
setAnnouncement: () => Promise<void>
dialogs: Announcement[]
remove: (id: string) => void
}

View File

@@ -332,6 +332,7 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
}
set({ announcements: copy });
},
dialogs:[],
setAnnouncement: async function () {
let announcements: Announcement[] = [];
try {
@@ -342,14 +343,15 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
} catch (e) {
set({ announcements: [] })
} finally {
set({ announcements: await getFiltered(announcements) })
let all = await getFiltered(announcements);
set({ announcements: all.filter(a => a.type === "inline"),dialogs:all.filter(a => a.type === "dialog") })
}
}
}));
const getFiltered = async (announcements) => {
const getFiltered = async (announcements:Announcement[]) => {
if (!announcements) return [];
let filtered = [];
let filtered:Announcement[] = [];
for (var announcement of announcements) {
if (await shouldShowAnnouncement(announcement)) {
filtered.push(announcement);