refactor intro and auth flow

This commit is contained in:
Ammar Ahmed
2022-07-05 14:33:48 +05:00
parent 07628f070d
commit add9473157
27 changed files with 935 additions and 630 deletions

View File

@@ -5,6 +5,10 @@
<!-- Customize your theme here. -->
<item name="android:windowDisablePreview">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
</style>
<style name="Share.Window" parent="Theme.AppCompat">

View File

@@ -0,0 +1,93 @@
import React, { useEffect, useRef, useState } from 'react';
import { Platform, StatusBar } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { useThemeStore } from '../../stores/use-theme-store';
import { eCloseLoginDialog, eOpenLoginDialog } from '../../utils/events';
import { sleep } from '../../utils/time';
import BaseDialog from '../dialog/base-dialog';
import { Toast } from '../toast';
import { IconButton } from '../ui/icon-button';
import { initialAuthMode } from './common';
import { Login } from './login';
import { Signup } from './signup';
export const AuthMode = {
login: 0,
signup: 1,
welcomeSignup: 2,
trialSignup: 3
};
const AuthModal = () => {
const colors = useThemeStore(state => state.colors);
const [visible, setVisible] = useState(false);
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
const actionSheetRef = useRef();
const insets = useSafeAreaInsets();
useEffect(() => {
eSubscribeEvent(eOpenLoginDialog, open);
eSubscribeEvent(eCloseLoginDialog, close);
return () => {
eUnSubscribeEvent(eOpenLoginDialog, open);
eUnSubscribeEvent(eCloseLoginDialog, close);
};
}, []);
async function open(mode) {
setCurrentAuthMode(mode ? mode : AuthMode.login);
initialAuthMode.current = -1;
setVisible(true);
await sleep(10);
actionSheetRef.current?.show();
}
const close = () => {
actionSheetRef.current?.hide();
setCurrentAuthMode(AuthMode.login);
setVisible(false);
};
return !visible ? null : (
<BaseDialog
overlayOpacity={0}
statusBarTranslucent={false}
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
visible={true}
onClose={close}
useSafeArea={false}
bounce={false}
background={colors.bg}
transparent={false}
>
{currentAuthMode !== AuthMode.login ? (
<Signup
changeMode={mode => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={currentAuthMode === AuthMode.welcomeSignup}
/>
) : (
<Login changeMode={mode => setCurrentAuthMode(mode)} />
)}
<IconButton
name="arrow-left"
onPress={() => {
eSendEvent(eCloseLoginDialog);
}}
color={colors.pri}
customStyle={{
position: 'absolute',
zIndex: 999,
left: 12,
top: Platform.OS === 'ios' ? 12 + insets.top : 12
}}
/>
<Toast context="local" />
</BaseDialog>
);
};
export default AuthModal;

View File

@@ -0,0 +1,33 @@
import { createRef } from 'react';
import { eSendEvent } from '../../services/event-manager';
import Navigation from '../../services/navigation';
import SettingsService from '../../services/settings';
import { eCloseLoginDialog } from '../../utils/events';
import { tabBarRef } from '../../utils/global-refs';
export const initialAuthMode = createRef(0);
export function hideAuth() {
if (initialAuthMode.current === -1) {
eSendEvent(eCloseLoginDialog);
return;
}
if (initialAuthMode.current === 2) {
Navigation.replace(
{
name: 'Notes'
},
{
menu: true
}
);
} else {
Navigation.goBack();
}
tabBarRef.current?.unlock();
if (!SettingsService.get().introCompleted) {
SettingsService.set({
introCompleted: true
});
}
}

View File

@@ -1,13 +1,11 @@
import React, { useEffect, useRef, useState } from 'react';
import { Platform } from 'react-native';
import React, { useState } from 'react';
import { View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { useThemeStore } from '../../stores/use-theme-store';
import { eCloseLoginDialog, eOpenLoginDialog } from '../../utils/events';
import { sleep } from '../../utils/time';
import BaseDialog from '../dialog/base-dialog';
import { tabBarRef } from '../../utils/global-refs';
import { useNavigationFocus } from '../../utils/hooks/use-navigation-focus';
import { Toast } from '../toast';
import { IconButton } from '../ui/icon-button';
import { initialAuthMode } from './common';
import { Login } from './login';
import { Signup } from './signup';
@@ -18,75 +16,48 @@ export const AuthMode = {
trialSignup: 3
};
const Auth = () => {
const Auth = ({ navigation, route }) => {
const colors = useThemeStore(state => state.colors);
const [visible, setVisible] = useState(false);
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
const actionSheetRef = useRef();
const [currentAuthMode, setCurrentAuthMode] = useState(route?.params?.mode || AuthMode.login);
const insets = useSafeAreaInsets();
initialAuthMode.current = route?.params.mode || AuthMode.login;
useNavigationFocus(navigation, {
onFocus: () => {
tabBarRef?.current.lock();
initialAuthMode.current = route?.params.mode || AuthMode.login;
}
});
useEffect(() => {
eSubscribeEvent(eOpenLoginDialog, open);
eSubscribeEvent(eCloseLoginDialog, close);
return () => {
eUnSubscribeEvent(eOpenLoginDialog, open);
eUnSubscribeEvent(eCloseLoginDialog, close);
};
}, []);
async function open(mode) {
setCurrentAuthMode(mode ? mode : AuthMode.login);
setVisible(true);
await sleep(10);
actionSheetRef.current?.show();
}
const close = () => {
actionSheetRef.current?.hide();
setCurrentAuthMode(AuthMode.login);
setVisible(false);
};
return !visible ? null : (
<BaseDialog
overlayOpacity={0}
statusBarTranslucent={false}
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
visible={true}
onClose={close}
useSafeArea={false}
bounce={false}
background={colors.bg}
transparent={false}
>
return (
<View style={{ flex: 1 }}>
{currentAuthMode !== AuthMode.login ? (
<Signup
changeMode={mode => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={currentAuthMode === AuthMode.welcomeSignup}
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/>
) : (
<Login changeMode={mode => setCurrentAuthMode(mode)} />
<Login welcome={initialAuthMode.current} changeMode={mode => setCurrentAuthMode(mode)} />
)}
{currentAuthMode === AuthMode.welcomeSignup ? null : (
{/* {initialAuthMode.current === AuthMode.welcomeSignup ? null : (
<IconButton
name="arrow-left"
onPress={() => {
eSendEvent(eCloseLoginDialog);
hideAuth();
}}
color={colors.pri}
customStyle={{
position: 'absolute',
zIndex: 999,
left: 12,
top: Platform.OS === 'ios' ? 12 + insets.top : 12
top: Platform.OS === 'ios' ? 12 + insets.top : insets.top
}}
/>
)}
)} */}
<Toast context="local" />
</BaseDialog>
</View>
);
};

View File

@@ -26,7 +26,10 @@ import Paragraph from '../ui/typography/paragraph';
import { SVG } from './background';
import { ForgotPassword } from './forgot-password';
import TwoFactorVerification from './two-factor';
export const Login = ({ changeMode }) => {
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
import Navigation from '../../services/navigation';
import { hideAuth } from './common';
export const Login = ({ changeMode, welcome }) => {
const colors = useThemeStore(state => state.colors);
const email = useRef();
const emailInputRef = useRef();
@@ -87,8 +90,8 @@ export const Login = ({ changeMode }) => {
type: 'success',
context: 'global'
});
eSendEvent(eCloseLoginDialog);
await SettingsService.set({
hideAuth();
SettingsService.set({
sessionExpired: false,
userEmailConfirmed: user.isEmailConfirmed
});
@@ -131,10 +134,11 @@ export const Login = ({ changeMode }) => {
return (
<>
<ForgotPassword />
<SheetProvider context="two_factor_verify" />
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
<View
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
@@ -149,9 +153,7 @@ export const Login = ({ changeMode }) => {
overflow: 'hidden'
}}
>
<BouncingView initialScale={1.05} duration={5000}>
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
</BouncingView>
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
</View>
<View
style={{
@@ -250,13 +252,12 @@ export const Login = ({ changeMode }) => {
<View
style={{
// position: 'absolute',
marginTop: 50,
marginTop: 25,
alignSelf: 'center'
}}
>
<Button
style={{
marginTop: 10,
width: 250,
borderRadius: 100
}}
@@ -266,9 +267,24 @@ export const Login = ({ changeMode }) => {
type="accent"
title={loading ? null : 'Login to your account'}
/>
{loading || !welcome ? null : (
<Button
style={{
marginTop: 10,
width: 250,
borderRadius: 100
}}
onPress={() => {
hideAuth();
}}
type="grayBg"
title="Skip for now"
/>
)}
</View>
</View>
</View>
</Animated.View>
</>
);
};

View File

@@ -1,12 +1,14 @@
import React, { useRef, useState } from 'react';
import { Dimensions, Platform, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Dimensions, View } from 'react-native';
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
import { DDS } from '../../services/device-detection';
import { eSendEvent, ToastEvent } from '../../services/event-manager';
import { clearMessage, setEmailVerifyMessage } from '../../services/message';
import Navigation from '../../services/navigation';
import PremiumService from '../../services/premium';
import { useUserStore } from '../../stores/use-user-store';
import SettingsService from '../../services/settings';
import { useThemeStore } from '../../stores/use-theme-store';
import { useUserStore } from '../../stores/use-user-store';
import umami from '../../utils/analytics';
import { db } from '../../utils/database';
import { eCloseLoginDialog } from '../../utils/events';
@@ -15,13 +17,13 @@ import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import BaseDialog from '../dialog/base-dialog';
import { Button } from '../ui/button';
import { IconButton } from '../ui/icon-button';
import Input from '../ui/input';
import { SvgView } from '../ui/svg';
import { BouncingView } from '../ui/transitions/bouncing-view';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { SVG } from './background';
import { hideAuth } from './common';
export const Signup = ({ changeMode, welcome, trial }) => {
const colors = useThemeStore(state => state.colors);
@@ -33,9 +35,6 @@ export const Signup = ({ changeMode, welcome, trial }) => {
const confirmPassword = useRef();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const insets = useSafeAreaInsets();
const setUser = useUserStore(state => state.setUser);
const setLastSynced = useUserStore(state => state.setLastSynced);
@@ -64,7 +63,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
setLastSynced(await db.lastSynced());
clearMessage();
setEmailVerifyMessage();
eSendEvent(eCloseLoginDialog);
hideAuth();
umami.pageView('/account-created', '/welcome/signup');
await sleep(300);
if (trial) {
@@ -86,7 +85,9 @@ export const Signup = ({ changeMode, welcome, trial }) => {
return (
<>
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
<View
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
@@ -101,9 +102,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
overflow: 'hidden'
}}
>
<BouncingView initialScale={1.05}>
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
</BouncingView>
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
</View>
<View
@@ -205,48 +204,14 @@ export const Signup = ({ changeMode, welcome, trial }) => {
marginBottom={5}
onSubmit={signup}
/>
<Paragraph size={SIZE.xs} color={colors.icon}>
By signing up, you agree to our{' '}
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser('https://notesnook.com/tos', colors)
.catch(e => {})
.then(r => {});
}}
style={{
textDecorationLine: 'underline'
}}
color={colors.accent}
>
terms of service{' '}
</Paragraph>
and{' '}
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser('https://notesnook.com/privacy', colors)
.catch(e => {})
.then(r => {});
}}
style={{
textDecorationLine: 'underline'
}}
color={colors.accent}
>
privacy policy.
</Paragraph>
</Paragraph>
<View
style={{
marginTop: 50,
marginTop: 25,
alignSelf: 'center'
}}
>
<Button
style={{
marginTop: 10,
width: 250,
borderRadius: 100
}}
@@ -264,15 +229,54 @@ export const Signup = ({ changeMode, welcome, trial }) => {
borderRadius: 100
}}
onPress={() => {
eSendEvent(eCloseLoginDialog);
hideAuth();
}}
type="grayBg"
title="Skip for now"
/>
)}
</View>
<Paragraph
style={{
textAlign: 'center',
position: 'absolute',
bottom: 0,
alignSelf: 'center',
marginBottom: 20
}}
size={SIZE.xs}
color={colors.icon}
>
By signing up, you agree to our{' '}
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser('https://notesnook.com/tos', colors);
}}
style={{
textDecorationLine: 'underline'
}}
color={colors.accent}
>
terms of service{' '}
</Paragraph>
and{' '}
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser('https://notesnook.com/privacy', colors);
}}
style={{
textDecorationLine: 'underline'
}}
color={colors.accent}
>
privacy policy.
</Paragraph>
</Paragraph>
</View>
</View>
</Animated.View>
</>
);
};

View File

@@ -1,10 +1,12 @@
import React from 'react';
import { KeyboardAvoidingView, Platform, SafeAreaView } from 'react-native';
import { useSettingStore } from '../../stores/use-setting-store';
import useIsFloatingKeyboard from '../../utils/hooks/use-is-floating-keyboard';
import { Header } from '../header';
import SelectionHeader from '../selection-header';
export const Container = ({ children }) => {
const floating = useIsFloatingKeyboard();
const introCompleted = useSettingStore(state => state.settings.introCompleted);
return (
<KeyboardAvoidingView
behavior="padding"
@@ -19,8 +21,13 @@ export const Container = ({ children }) => {
overflow: 'hidden'
}}
>
<SelectionHeader />
<Header title="Header" screen="Header" />
{!introCompleted ? null : (
<>
<SelectionHeader />
<Header title="Header" screen="Header" />
</>
)}
{children}
</SafeAreaView>
</KeyboardAvoidingView>

View File

@@ -3,6 +3,7 @@ import { useThemeStore } from '../../stores/use-theme-store';
import { AnnouncementDialog } from '../announcements';
import { AttachmentDialog } from '../attachments';
import Auth from '../auth';
import AuthModal from '../auth/auth-modal';
import { SessionExpired } from '../auth/session-expired';
import { Dialog } from '../dialog';
import { AddTopicDialog } from '../dialogs/add-topic';
@@ -32,7 +33,7 @@ const DialogProvider = React.memo(
<AddTopicDialog colors={colors} />
<AddNotebookSheet colors={colors} />
<PremiumDialog colors={colors} />
<Auth colors={colors} />
<AuthModal colors={colors} />
<MergeConflicts />
<ExportNotesSheet />
<RecoveryKeySheet colors={colors} />

View File

@@ -5,6 +5,7 @@ import Navigation from '../../services/navigation';
import useNavigationStore from '../../stores/use-navigation-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { tabBarRef } from '../../utils/global-refs';
import { IconButton } from '../ui/icon-button';
export const LeftMenus = () => {
@@ -19,6 +20,12 @@ export const LeftMenus = () => {
return;
}
Navigation.goBack();
if (
useNavigationStore.getState().currentScreen.name === 'Signup' ||
useNavigationStore.getState().currentScreen.name === 'Login'
) {
tabBarRef.current.unlock();
}
};
return isTablet ? null : (

View File

@@ -20,7 +20,10 @@ export const RightMenus = () => {
return (
<View style={styles.rightBtnContainer}>
{!currentScreen.startsWith('Settings') ? (
{!currentScreen.startsWith('Settings') &&
currentScreen !== 'Auth' &&
currentScreen !== 'Signup' &&
currentScreen !== 'Login' ? (
<IconButton
onPress={async () => {
SearchService.prepareSearch();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -46,11 +46,7 @@ const Launcher = React.memo(
const deviceMode = useSettingStore(state => state.deviceMode);
const passwordInputRef = useRef();
const password = useRef();
const introCompleted = SettingsService.get().introCompleted;
const [requireIntro, setRequireIntro] = useState({
updated: introCompleted,
value: !introCompleted
});
const introCompleted = false; //SettingsService.get().introCompleted;
const dbInitCompleted = useRef(false);
const loadNotes = async () => {
@@ -62,13 +58,17 @@ const Launcher = React.memo(
db.notes.init().then(() => {
Walkthrough.init();
initialize();
setImmediate(() => setLoading(false));
setImmediate(() => {
setLoading(false);
setImmediate(() => doAppLoadActions());
});
});
});
};
const init = async () => {
if (!dbInitCompleted.current) {
await RNBootSplash.hide();
await db.init();
dbInitCompleted.current = true;
}
@@ -78,24 +78,9 @@ const Launcher = React.memo(
}
};
const hideSplashScreen = async () => {
if (requireIntro.value) await sleep(500);
await RNBootSplash.hide({ fade: true });
};
useEffect(() => {
console.log('hide splash', requireIntro.updated);
if (requireIntro.updated) {
hideSplashScreen();
return;
}
let introCompleted = SettingsService.get().introCompleted;
console.log(requireIntro);
setRequireIntro({
updated: true,
value: !introCompleted
});
}, [requireIntro, verifyUser]);
// useEffect(() => {
// hideSplashScreen();
// }, [verifyUser]);
useEffect(() => {
if (!loading) {
@@ -129,7 +114,7 @@ const Launcher = React.memo(
}
}
if (!requireIntro?.value) {
if (introCompleted) {
useMessageStore.subscribe(state => {
let dialogs = state.dialogs;
if (dialogs.length > 0) {
@@ -141,17 +126,17 @@ const Launcher = React.memo(
const checkAppUpdateAvailable = async () => {
return;
try {
const version = await checkVersion();
if (!version.needsUpdate) return false;
presentSheet({
component: ref => <Update version={version} fwdRef={ref} />
});
// try {
// const version = await checkVersion();
// if (!version.needsUpdate) return false;
// presentSheet({
// component: ref => <Update version={version} fwdRef={ref} />
// });
return true;
} catch (e) {
return false;
}
// return true;
// } catch (e) {
// return false;
// }
};
const restoreEditorState = async () => {
@@ -354,8 +339,6 @@ const Launcher = React.memo(
</View>
</View>
</View>
) : requireIntro.value && !loading ? (
<Intro />
) : null;
},
() => true

File diff suppressed because one or more lines are too long

View File

@@ -26,6 +26,7 @@ export const SideMenu = React.memo(
const insets = useSafeAreaInsets();
const subscriptionType = useUserStore(state => state.user?.subscription?.type);
const loading = useNoteStore(state => state.loading);
const introCompleted = useSettingStore(state => state.settings.introCompleted);
const noTextMode = false;
const BottomItemsList = [
@@ -72,7 +73,7 @@ export const SideMenu = React.memo(
[]
);
return !loading ? (
return !loading && introCompleted ? (
<View
style={{
height: '100%',

View File

@@ -14,6 +14,8 @@ import Paragraph from '../ui/typography/paragraph';
import { TimeSince } from '../ui/time-since';
import useSyncProgress from '../../utils/hooks/use-sync-progress';
import * as Progress from 'react-native-progress';
import Navigation from '../../services/navigation';
import { tabBarRef } from '../../utils/global-refs';
export const UserStatus = () => {
const colors = useThemeStore(state => state.colors);
@@ -45,7 +47,16 @@ export const UserStatus = () => {
if (user) {
await Sync.run();
} else {
eSendEvent(eOpenLoginDialog);
tabBarRef.current?.closeDrawer();
Navigation.navigate(
{
name: 'Login'
},
{
mode: 0,
canGoBack: true
}
);
}
}}
type="gray"

View File

@@ -1,11 +1,23 @@
import React from 'react';
import DelayLayout from '../components/delay-layout';
import DialogProvider from '../components/dialog-provider';
import { Header } from '../components/header';
import Intro from '../components/intro';
import { Toast } from '../components/toast';
import SettingsService from '../services/settings';
import { useNoteStore } from '../stores/use-notes-store';
import { TabsHolder } from './tabs-holder';
export const ApplicationHolder = React.memo(
() => {
return (
const loading = useNoteStore(state => state.loading);
const introCompleted = SettingsService.get().introCompleted;
return loading && introCompleted ? (
<>
<Header />
<DelayLayout wait={loading} />
</>
) : (
<>
<TabsHolder />
<Toast />

View File

@@ -1,8 +1,11 @@
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import * as React from 'react';
import Auth from '../components/auth';
import { Login } from '../components/auth/login';
import { Signup } from '../components/auth/signup';
import Container from '../components/container';
import Intro from '../components/intro';
import Favorites from '../screens/favorites';
import Home from '../screens/home';
import Notebook from '../screens/notebook';
@@ -13,6 +16,7 @@ import { TaggedNotes } from '../screens/notes/tagged';
import { TopicNotes } from '../screens/notes/topic-notes';
import { Search } from '../screens/search';
import Settings from '../screens/settings';
import AppLock from '../screens/settings/app-lock';
import Tags from '../screens/tags';
import Trash from '../screens/trash';
import { eSendEvent } from '../services/event-manager';
@@ -24,10 +28,50 @@ import { history } from '../utils';
import { rootNavigatorRef } from '../utils/global-refs';
import { hideAllTooltips } from '../utils/hooks/use-tooltip';
const NativeStack = createNativeStackNavigator();
const IntroStack = createNativeStackNavigator();
/**
* Intro Stack:
*
* Welcome Page
* Select Privacy Mode Page
* Login/Signup Page
*
*/
const IntroStackNavigator = () => {
const colors = useThemeStore(state => state.colors);
const introCompleted = SettingsService.get().introCompleted;
return (
<IntroStack.Navigator
screenOptions={{
headerShown: false,
lazy: false,
animation: 'none',
contentStyle: {
backgroundColor: colors.bg
}
}}
initialRouteName={'Intro'}
>
<NativeStack.Screen name="Intro" component={Intro} />
<NativeStack.Screen name="AppLock" component={AppLock} />
<NativeStack.Screen
name="Auth"
initialParams={{
mode: 0
}}
component={Auth}
/>
</IntroStack.Navigator>
);
};
const Tabs = React.memo(
() => {
const colors = useThemeStore(state => state.colors);
const homepage = SettingsService.get().homepage;
const showWelcome = !SettingsService.get().introCompleted;
React.useEffect(() => {
setTimeout(() => {
useNavigationStore.getState().update({ name: homepage });
@@ -37,7 +81,7 @@ const Tabs = React.memo(
return (
<NativeStack.Navigator
tabBar={() => null}
initialRouteName={homepage}
initialRouteName={showWelcome ? 'Welcome' : homepage}
backBehavior="history"
screenOptions={{
headerShown: false,
@@ -48,6 +92,21 @@ const Tabs = React.memo(
}
}}
>
<NativeStack.Screen
name="Signup"
initialParams={{
mode: 1
}}
component={Auth}
/>
<NativeStack.Screen
name="Login"
initialParams={{
mode: 0
}}
component={Auth}
/>
<NativeStack.Screen name="Welcome" component={IntroStackNavigator} />
<NativeStack.Screen name="Notes" component={Home} />
<NativeStack.Screen name="Notebooks" component={Notebooks} />
<NativeStack.Screen options={{ lazy: true }} name="Favorites" component={Favorites} />

View File

@@ -188,7 +188,6 @@ const pick = async options => {
}
return;
}
if (options?.type.startsWith('image') || options?.type === 'camera') {
if (options.type === 'image') {
gallery(options);

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef } from 'react';
import { BackHandler, InteractionManager, NativeEventSubscription } from 'react-native';
import { WebViewMessageEvent } from 'react-native-webview';
import { AuthMode } from '../../../components/auth';
import { Properties } from '../../../components/properties';
import { DDS } from '../../../services/device-detection';
import {
@@ -95,21 +96,18 @@ const showActionsheet = async (editor: useEditorType) => {
const currentNote = editor?.note?.current;
if (currentNote?.id) {
let note = db.notes?.note(currentNote.id)?.data as NoteType;
if (!note) {
ToastEvent.show({
heading: 'Start writing to create a new note',
type: 'success',
context: 'global'
});
return;
}
if (editorState().isFocused || editorState().isFocused) {
editorState().isFocused = true;
}
Properties.present(note, ['Dark Mode']);
} else {
ToastEvent.show({
heading: 'Start writing to create a new note',
type: 'success',
context: 'global'
});
}
};
@@ -222,103 +220,100 @@ export const useEditorEvents = (
};
}, []);
const onMessage = useCallback(
(event: WebViewMessageEvent) => {
const data = event.nativeEvent.data;
let editorMessage = JSON.parse(data) as EditorMessage;
const onMessage = (event: WebViewMessageEvent) => {
const data = event.nativeEvent.data;
let editorMessage = JSON.parse(data) as EditorMessage;
logger.info('editor', editorMessage.type);
if (
editorMessage.sessionId !== editor.sessionId &&
editorMessage.type !== EditorEvents.status
) {
logger.error(
'editor',
'invalid session',
editorMessage.type,
editor.sessionId,
editorMessage.sessionId
);
logger.info('editor', editorMessage.type);
if (
editorMessage.sessionId !== editor.sessionId &&
editorMessage.type !== EditorEvents.status
) {
logger.error(
'editor',
'invalid session',
editorMessage.type,
editor.sessionId,
editorMessage.sessionId
);
return;
}
switch (editorMessage.type) {
case EventTypes.logger:
logger.info('[WEBVIEW LOG]', editorMessage.value);
break;
case EventTypes.content:
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value
});
break;
case EventTypes.selection:
break;
case EventTypes.title:
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value
});
break;
case EventTypes.newtag:
return;
}
switch (editorMessage.type) {
case EventTypes.logger:
logger.info('[WEBVIEW LOG]', editorMessage.value);
break;
case EventTypes.content:
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value
});
break;
case EventTypes.selection:
break;
case EventTypes.title:
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value
});
break;
case EventTypes.newtag:
if (!editor.note.current) return;
eSendEvent(eOpenTagsDialog, editor.note.current);
break;
case EventTypes.tag:
if (editorMessage.value) {
if (!editor.note.current) return;
eSendEvent(eOpenTagsDialog, editor.note.current);
break;
case EventTypes.tag:
if (editorMessage.value) {
if (!editor.note.current) return;
db.notes
//@ts-ignore
?.note(editor.note.current?.id)
.untag(editorMessage.value)
.then(async () => {
useTagStore.getState().setTags();
await editor.commands.setTags(editor.note.current);
Navigation.queueRoutesForUpdate(
'ColoredNotes',
'Notes',
'TaggedNotes',
'TopicNotes',
'Tags'
);
});
}
break;
case EventTypes.filepicker:
picker.pick({ type: editorMessage.value });
break;
case EventTypes.download:
console.log('download attachment request', editorMessage.value);
filesystem.downloadAttachment(editorMessage.value?.hash, true);
break;
case EventTypes.pro:
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;
}
umami.pageView('/pro-screen', '/editor');
eSendEvent(eOpenPremiumDialog);
break;
case EventTypes.monograph:
publishNote(editor);
break;
case EventTypes.properties:
showActionsheet(editor);
break;
case EventTypes.back:
onBackPress();
break;
default:
console.log(
'unhandled event recieved from editor: ',
editorMessage.type,
editorMessage.value
);
break;
}
eSendEvent(editorMessage.type, editorMessage);
},
[editor.sessionId, editor.saveContent]
);
db.notes
//@ts-ignore
?.note(editor.note.current?.id)
.untag(editorMessage.value)
.then(async () => {
useTagStore.getState().setTags();
await editor.commands.setTags(editor.note.current);
Navigation.queueRoutesForUpdate(
'ColoredNotes',
'Notes',
'TaggedNotes',
'TopicNotes',
'Tags'
);
});
}
break;
case EventTypes.filepicker:
picker.pick({ type: editorMessage.value });
break;
case EventTypes.download:
console.log('download attachment request', editorMessage.value);
filesystem.downloadAttachment(editorMessage.value?.hash, true);
break;
case EventTypes.pro:
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;
}
umami.pageView('/pro-screen', '/editor');
eSendEvent(eOpenPremiumDialog);
break;
case EventTypes.monograph:
publishNote(editor);
break;
case EventTypes.properties:
showActionsheet(editor);
break;
case EventTypes.back:
onBackPress();
break;
default:
console.log(
'unhandled event recieved from editor: ',
editorMessage.type,
editorMessage.value
);
break;
}
eSendEvent(editorMessage.type, editorMessage);
};
return onMessage;
};

View File

@@ -26,6 +26,7 @@ export const EditorWrapper = ({ width }) => {
const loading = useNoteStore(state => state.loading);
const insets = useSafeAreaInsets();
const floating = useIsFloatingKeyboard();
const introCompleted = useSettingStore(state => state.settings.introCompleted);
const onAppStateChanged = async state => {
if (editorState().movedAway) return;
@@ -54,7 +55,7 @@ export const EditorWrapper = ({ width }) => {
borderLeftColor: DDS.isTab ? colors.nav : 'transparent'
}}
>
{loading ? null : (
{loading || !introCompleted ? null : (
<SafeAreaView
style={{
flex: 1

View File

@@ -103,8 +103,7 @@ export const AccentColorPicker = ({ settings = true, wrap = true }) => {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'center',
alignContent: 'flex-start'
width: '100%'
}}
>
{[

View File

@@ -1,27 +1,29 @@
import React, { useState } from 'react';
import { LayoutAnimation, Platform, View } from 'react-native';
import { Dimensions, LayoutAnimation, Platform, View } from 'react-native';
import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import BiometricService from '../../services/biometrics';
import { PressableButton } from '../../components/ui/pressable';
import Seperator from '../../components/ui/seperator';
import Heading from '../../components/ui/typography/heading';
import Paragraph from '../../components/ui/typography/paragraph';
import { useThemeStore } from '../../stores/use-theme-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { presentSheet } from '../../services/event-manager';
import SettingsService from '../../services/settings';
import { SIZE } from '../../utils/size';
import { db } from '../../utils/database';
import { SVG_Z } from '../../components/intro';
import { WelcomeNotice } from '../../components/intro/welcome';
import { Button } from '../../components/ui/button';
import { PressableButton } from '../../components/ui/pressable';
import Seperator from '../../components/ui/seperator';
import { SvgView } from '../../components/ui/svg';
import { BouncingView } from '../../components/ui/transitions/bouncing-view';
import Heading from '../../components/ui/typography/heading';
import Paragraph from '../../components/ui/typography/paragraph';
import { presentSheet } from '../../services/event-manager';
import SettingsService from '../../services/settings';
import { useSettingStore } from '../../stores/use-setting-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { getElevation } from '../../utils';
import umami from '../../utils/analytics';
import { SIZE } from '../../utils/size';
const AppLock = ({ welcome, s = 0 }) => {
const AppLock = ({ navigation, route }) => {
const colors = useThemeStore(state => state.colors);
const appLockMode = useSettingStore(state => state.settings.appLockMode);
const [step, setStep] = useState(s);
const [step, setStep] = useState(0);
const welcome = route?.params?.welcome;
const modes = [
{
@@ -50,117 +52,155 @@ const AppLock = ({ welcome, s = 0 }) => {
return (
<>
{step === 0 ? (
<>
<View
style={{
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-between',
width: '95%',
paddingVertical: 12,
paddingHorizontal: 0,
alignSelf: 'center',
minHeight: 125,
borderBottomWidth: 1,
borderBottomColor: colors.nav
}}
>
<Icon
name="shield-lock"
color={colors.border}
size={100}
<Animated.View
exiting={!welcome ? undefined : FadeOutUp}
entering={!welcome ? undefined : FadeInDown}
style={{
justifyContent: !welcome ? undefined : 'center',
height: !welcome ? undefined : '100%',
width: !welcome ? undefined : '100%'
}}
>
{step === 0 ? (
<>
<View
style={{
position: 'absolute',
right: 0,
top: 6
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-between',
width: '95%',
paddingVertical: 12,
paddingHorizontal: 0,
alignSelf: 'center',
minHeight: 125,
borderBottomWidth: 1,
borderBottomColor: welcome ? 'transparent' : colors.nav
}}
/>
<View>
<Heading>Protect your notes</Heading>
<Paragraph size={SIZE.md}>
Choose how you want to secure your notes locally.
</Paragraph>
</View>
</View>
<Seperator />
<View
style={{
paddingHorizontal: 12
}}
>
{modes.map(item => (
<PressableButton
key={item.title}
type={appLockMode === item.value ? 'grayBg' : 'transparent'}
onPress={() => {
SettingsService.set({ appLockMode: item.value });
}}
customStyle={{
justifyContent: 'flex-start',
alignItems: 'flex-start',
paddingHorizontal: 12,
paddingVertical: 12,
marginTop: 0,
marginBottom: 12,
borderWidth: 1,
borderColor: appLockMode === item.value ? item.activeColor : colors.nav
}}
>
<Icon
name="shield-lock"
color={colors.border}
size={100}
style={{
marginBottom: 10
position: 'absolute',
right: 0,
top: 6
}}
/>
<View
style={{
alignItems: !welcome ? undefined : 'center',
width: '100%'
}}
>
<Heading
color={appLockMode === item.value ? item.activeColor : colors.pri}
style={{ maxWidth: '95%' }}
<Heading>Protect your notes</Heading>
<Paragraph
style={{
textAlign: !welcome ? undefined : 'center'
}}
size={SIZE.md}
>
{item.title}
</Heading>
<Paragraph
color={appLockMode === item.value ? item.activeColor : colors.icon}
style={{ maxWidth: '95%' }}
size={SIZE.sm}
>
{item.desc}
Choose how you want to secure your notes locally.
</Paragraph>
</PressableButton>
))}
</View>
</View>
<Seperator />
<View
style={{
paddingHorizontal: 12
}}
>
{modes.map(item => (
<PressableButton
key={item.title}
type={appLockMode === item.value ? 'grayBg' : 'transparent'}
onPress={() => {
SettingsService.set({ appLockMode: item.value });
}}
customStyle={{
justifyContent: 'flex-start',
alignItems: 'flex-start',
paddingHorizontal: 12,
paddingVertical: 12,
marginTop: 0,
marginBottom: 12,
borderWidth: 1,
borderColor: appLockMode === item.value ? item.activeColor : colors.nav
}}
style={{
marginBottom: 10
}}
>
<Heading
color={appLockMode === item.value ? item.activeColor : colors.pri}
style={{ maxWidth: '95%' }}
size={SIZE.md}
>
{item.title}
</Heading>
<Paragraph
color={appLockMode === item.value ? item.activeColor : colors.icon}
style={{ maxWidth: '95%' }}
size={SIZE.sm}
>
{item.desc}
</Paragraph>
</PressableButton>
))}
{welcome && (
<Button
fontSize={SIZE.md}
height={45}
width={250}
onPress={async () => {
LayoutAnimation.configureNext({
...LayoutAnimation.Presets.linear,
delete: {
duration: 50,
property: 'opacity',
type: 'linear'
}
});
umami.pageView('/privacymode', '/welcome');
setStep(1);
}}
style={{
paddingHorizontal: 24,
alignSelf: 'center',
borderRadius: 100,
...getElevation(5),
marginTop: 30
}}
type="accent"
title="Next"
/>
)}
</View>
</>
) : (
<WelcomeNotice />
)}
{welcome && (
<Button
fontSize={SIZE.md}
height={45}
width={250}
onPress={async () => {
LayoutAnimation.configureNext({
...LayoutAnimation.Presets.linear,
delete: {
duration: 50,
property: 'opacity',
type: 'linear'
}
});
umami.pageView('/privacymode', '/welcome');
setStep(1);
}}
style={{
paddingHorizontal: 24,
alignSelf: 'center',
borderRadius: 100,
...getElevation(5),
marginTop: 30
}}
type="accent"
title="Next"
/>
)}
</View>
</>
) : (
<WelcomeNotice />
)}
{welcome ? (
<BouncingView
style={{
position: 'absolute',
bottom: -130,
zIndex: -1
}}
animated={false}
duration={3000}
>
<SvgView
width={Dimensions.get('window').width}
height={Dimensions.get('window').width}
src={SVG_Z}
/>
</BouncingView>
) : null}
</Animated.View>
</>
);
};

View File

@@ -133,6 +133,7 @@ export const SectionItem = React.memo(
{item.type === 'switch' && item.property && (
<ToggleSwitch
//@ts-ignore
isOn={item.getter ? item.getter(item.property || current) : settings[item.property]}
onColor={colors.accent}
offColor={colors.icon}

View File

@@ -43,6 +43,7 @@ import { verifyUser } from './functions';
import { SettingSection } from './types';
import { getTimeLeft } from './user-section';
import { ConfigureToolbar } from './editor/configure-toolbar';
import { AuthMode } from '../../components/auth';
const format = (ver: number) => {
let parts = ver.toString().split('');
return `v${parts[0]}.${parts[1]}.${parts[2]?.startsWith('0') ? '' : parts[2]}${

View File

@@ -1,4 +1,3 @@
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import { StackActions } from '@react-navigation/native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useFavoriteStore } from '../stores/use-favorite-store';
@@ -7,7 +6,7 @@ import { useNotebookStore } from '../stores/use-notebook-store';
import { useNoteStore } from '../stores/use-notes-store';
import { useTagStore } from '../stores/use-tag-store';
import { useTrashStore } from '../stores/use-trash-store';
import { eOnNewTopicAdded, refreshNotesPage } from '../utils/events';
import { eOnNewTopicAdded } from '../utils/events';
import { rootNavigatorRef, tabBarRef } from '../utils/global-refs';
import { ColorType, NotebookType, TagType, TopicType } from '../utils/types';
import { eSendEvent } from './event-manager';
@@ -31,7 +30,13 @@ const routeNames = {
TaggedNotes: 'TaggedNotes',
ColoredNotes: 'ColoredNotes',
TopicNotes: 'TopicNotes',
Monographs: 'Monographs'
Monographs: 'Monographs',
Auth: 'Auth',
Intro: 'Intro',
Welcome: 'Welcome',
AppLock: 'AppLock',
Login: 'Login',
Signup: 'Signup'
};
type GenericRouteParam = { [name: string]: never };
@@ -48,6 +53,14 @@ export type NotesScreenParams = {
canGoBack: boolean;
};
export type AppLockRouteParams = {
welcome: boolean;
};
export type AuthParams = {
mode: number;
};
export type RouteParams = {
Notes: GenericRouteParam;
Notebooks: GenericRouteParam;
@@ -62,6 +75,8 @@ export type RouteParams = {
ColoredNotes: NotesScreenParams;
TopicNotes: NotesScreenParams;
Monographs: NotesScreenParams;
AppLock: AppLockRouteParams;
Auth: AuthParams;
};
export type NavigationProps<T extends RouteName> = NativeStackScreenProps<RouteParams, T>;
@@ -144,10 +159,10 @@ function push(screen: CurrentScreen, params: { [name: string]: any }) {
rootNavigatorRef.current?.dispatch(StackActions.push(name, params));
}
function replace(screen: CurrentScreen, params: { [name: string]: any }) {
useNavigationStore.getState().update(screen, !params.menu);
function replace<T extends RouteName>(screen: CurrentScreen, params: RouteParams[T]) {
useNavigationStore.getState().update(screen, params?.canGoBack);
//@ts-ignore
rootNavigatorRef.current?.dispatch(StackActions.replace(name, params));
rootNavigatorRef.current?.dispatch(StackActions.replace(screen.name, params));
}
function popToTop() {

View File

@@ -4,6 +4,7 @@ import { Platform } from 'react-native';
import Share from 'react-native-share';
import { editing, toTXT } from '..';
import { notesnook } from '../../../e2e/test.ids';
import { AuthMode } from '../../components/auth';
import { presentDialog } from '../../components/dialog/functions';
import NoteHistory from '../../components/note-history';
import { MoveNotes } from '../../components/sheets/move-notes/movenote';
@@ -232,7 +233,15 @@ export const useActions = ({ close = () => {}, item }) => {
message: 'Login to publish note',
context: 'local',
func: () => {
eSendEvent(eOpenLoginDialog);
Navigation.navigate(
{
name: 'Login'
},
{
mode: AuthMode.login,
canGoBack: true
}
);
},
actionText: 'Login'
});