mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
feat: new announcements and dialog
This commit is contained in:
70
apps/mobile/src/components/Announcements/announcement.js
Normal file
70
apps/mobile/src/components/Announcements/announcement.js
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
287
apps/mobile/src/components/Shapes/index.js
Normal file
287
apps/mobile/src/components/Shapes/index.js
Normal 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 };
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user