mobile: add user profile

This commit is contained in:
Ammar Ahmed
2024-02-29 23:26:58 +05:00
committed by Abdullah Atta
parent 9e71c5e68a
commit 7f62262eaf
6 changed files with 149 additions and 77 deletions

View File

@@ -52,7 +52,7 @@ const ImagePreview = () => {
setLoading(true);
setTimeout(async () => {
let hash = image.hash;
if (!hash && dataurl.toObject(image.src)) {
if (!hash && image.src && dataurl.toObject(image.src)) {
const data = dataurl.toObject(image.src);
if (!data) return;
hash = await Sodium.hashFile({

View File

@@ -46,6 +46,8 @@ export const UserStatus = () => {
const { isInternetReachable } = useNetInfo();
const isOffline = !isInternetReachable;
const { progress } = useSyncProgress();
const userProfile = useUserStore((state) => state.profile);
return (
<View
style={{
@@ -81,16 +83,15 @@ export const UserStatus = () => {
alignItems: "center"
}}
>
{user ? (
{userProfile?.profilePicture ? (
<Image
source={{
uri: PROFILE_PIC_URL
uri: userProfile?.profilePicture
}}
style={{
width: 35,
height: 35,
borderRadius: 100,
backgroundColor: "red",
marginRight: 10
}}
/>
@@ -117,7 +118,9 @@ export const UserStatus = () => {
? `Syncing your notes${
progress ? ` (${progress.current})` : ""
}`
: "Ammar Ahmed"}
: !userProfile?.fullName
? "Tap to sync"
: userProfile.fullName}
</Paragraph>
<Paragraph

View File

@@ -389,6 +389,11 @@ export const useAppEvents = () => {
const isUserEmailConfirmed = SettingsService.get().userEmailConfirmed;
setUser(user);
useUserStore.setState({
profile: await db.user.getProfile()
});
if (SettingsService.get().sessionExpired) {
syncedOnLaunch.current = true;
return;

View File

@@ -70,7 +70,6 @@ import { useDragState } from "./editor/state";
import { verifyUser, verifyUserWithApplock } from "./functions";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
import ImagePicker from "react-native-image-crop-picker";
type User = any;
@@ -135,49 +134,53 @@ export const settingsGroups: SettingSection[] = [
description: "Manage account",
sections: [
{
id: "change-profile-picture",
name: "Change profile picture",
description: "Change your profile picture",
icon: "account-outline",
id: "remove-profile-picture",
name: "Remove profile picture",
description: "Remove your picture from profile",
useHook: () =>
useUserStore((state) => state.profile?.profilePicture),
hidden: () => !useUserStore.getState().profile?.profilePicture,
modifer: () => {
const theme =
useThemeStore.getState().colorScheme === "dark"
? useThemeStore.getState().darkTheme
: useThemeStore.getState().lightTheme;
ImagePicker.openPicker({
compressImageMaxWidth: 512,
compressImageMaxHeight: 512,
compressImageQuality: 0.8,
avoidEmptySpaceAroundImage: true,
cropping: true,
cropperCircleOverlay: true,
mediaType: "photo",
forceJpg: true,
includeBase64: true,
writeTempFile: false,
cropperToolbarColor: theme.scopes.base.primary.background,
cropperToolbarTitle: "Edit profile picture",
cropperActiveWidgetColor: theme.scopes.base.primary.accent,
cropperToolbarWidgetColor: theme.scopes.base.primary.icon
}).then((image) => {
console.log(image);
presentDialog({
title: "Remove profile picture",
paragraph:
"Are you sure you want to remove your profile picture?",
positiveText: "Remove",
positivePress: async () => {
db.user
.setProfile({
profilePicture: undefined
})
.then(async () => {
useUserStore.setState({
profile: await db.user.getProfile()
});
});
}
});
}
},
{
id: "change-name",
name: "Change name",
description: "Change your name",
id: "remove-name",
name: "Remove full name",
description: "Remove your name from profile",
useHook: () => useUserStore((state) => state.profile?.fullName),
hidden: () => !useUserStore.getState().profile?.fullName,
modifer: () => {
presentDialog({
title: "Change name",
paragraph: "Change your name",
positiveText: "Save",
input: true,
inputPlaceholder: "Enter new name",
defaultValue: "Ammar Ahmed",
positivePress(value) {
console.log(value);
title: "Remove name",
paragraph: "Are you sure you want to remove your name?",
positiveText: "Remove",
positivePress: async () => {
db.user
.setProfile({
fullName: undefined
})
.then(async () => {
useUserStore.setState({
profile: await db.user.getProfile()
});
});
}
});
}

View File

@@ -17,22 +17,24 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import dayjs from "dayjs";
import React from "react";
import { Image, View } from "react-native";
import { Image, TouchableOpacity, View } from "react-native";
import ImagePicker from "react-native-image-crop-picker";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { presentDialog } from "../../components/dialog/functions";
import AppIcon from "../../components/ui/AppIcon";
import { TimeSince } from "../../components/ui/time-since";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { useThemeColors } from "@notesnook/theme";
import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS_STRINGS } from "../../utils/constants";
import { SIZE } from "../../utils/size";
import { SectionItem } from "./section-item";
import { useNetInfo } from "@react-native-community/netinfo";
import { IconButton } from "../../components/ui/icon-button";
const PROFILE_PIC_URL = `https://picsum.photos/id/177/367/267`;
export const getTimeLeft = (t2) => {
let daysRemaining = dayjs(t2).diff(dayjs(), "days");
@@ -45,28 +47,56 @@ export const getTimeLeft = (t2) => {
const ProfilePicPlaceholder = () => {
const { colors } = useThemeColors();
return (
<View
<TouchableOpacity
style={{
alignItems: "center"
}}
activeOpacity={0.9}
onPress={() => {
const theme =
useThemeStore.getState().colorScheme === "dark"
? useThemeStore.getState().darkTheme
: useThemeStore.getState().lightTheme;
ImagePicker.openPicker({
compressImageMaxWidth: 256,
compressImageMaxHeight: 256,
compressImageQuality: 0.8,
avoidEmptySpaceAroundImage: true,
cropping: true,
cropperCircleOverlay: true,
mediaType: "photo",
forceJpg: true,
includeBase64: true,
writeTempFile: false,
cropperToolbarColor: theme.scopes.base.primary.background,
cropperToolbarTitle: "Edit profile picture",
cropperActiveWidgetColor: theme.scopes.base.primary.accent,
cropperToolbarWidgetColor: theme.scopes.base.primary.icon
}).then(async (image) => {
if (!image.data) return;
await db.user.setProfile({
profilePicture: "data:image/jpeg;base64," + image.data
});
useUserStore.setState({
profile: await db.user.getProfile()
});
});
}}
>
<View
style={{
backgroundColor: colors.primary.shade,
borderRadius: 100,
width: 50,
height: 50,
width: 100,
height: 100,
alignItems: "center",
justifyContent: "center"
}}
>
<Icon
size={SIZE.xl}
color={colors.primary.accent}
name="account-outline"
/>
<Icon size={50} color={colors.primary.accent} name="account-outline" />
</View>
</View>
</TouchableOpacity>
);
};
@@ -77,6 +107,7 @@ const SettingsUserSection = ({ item }) => {
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
const { isInternetReachable } = useNetInfo();
const isOffline = !isInternetReachable;
const userProfile = useUserStore((state) => state.profile);
return (
<>
@@ -98,8 +129,6 @@ const SettingsUserSection = ({ item }) => {
width: "100%"
}}
>
{/* <ProfilePicPlaceholder /> */}
<View
style={{
flexDirection: "column",
@@ -115,17 +144,20 @@ const SettingsUserSection = ({ item }) => {
borderColor: colors.primary.accent
}}
>
<Image
source={{
uri: PROFILE_PIC_URL
}}
style={{
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: "red"
}}
/>
{userProfile?.profilePicture ? (
<Image
source={{
uri: userProfile?.profilePicture
}}
style={{
width: 100,
height: 100,
borderRadius: 100
}}
/>
) : (
<ProfilePicPlaceholder />
)}
</View>
<View
@@ -139,8 +171,35 @@ const SettingsUserSection = ({ item }) => {
]?.toUpperCase() || "Basic"}
</Heading>
<Paragraph color={colors.primary.heading} size={SIZE.lg}>
Ammar Ahmed
<Paragraph
onPress={() => {
presentDialog({
title: "Set name",
paragraph: "Set your full name",
positiveText: "Save",
input: true,
inputPlaceholder: "Enter your full name",
defaultValue: userProfile?.fullName,
positivePress: async (value) => {
db.user
.setProfile({
fullName: value
})
.then(async () => {
useUserStore.setState({
profile: await db.user.getProfile()
});
});
}
});
}}
color={colors.primary.heading}
size={SIZE.lg}
>
{userProfile?.fullName
? userProfile.fullName + " "
: "Set your name "}
<AppIcon name="pencil" size={SIZE.lg} />
</Paragraph>
<Paragraph color={colors.primary.heading} size={SIZE.xs}>
@@ -149,7 +208,8 @@ const SettingsUserSection = ({ item }) => {
<Paragraph
style={{
flexWrap: "wrap"
flexWrap: "wrap",
marginTop: 5
}}
size={SIZE.xs}
color={colors.secondary.heading}
@@ -188,8 +248,6 @@ const SettingsUserSection = ({ item }) => {
</Paragraph>
</View>
</View>
<IconButton name="pencil" />
</View>
</View>

View File

@@ -17,7 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { User } from "@notesnook/core/dist/api/user-manager";
import { Profile } from "@notesnook/core";
import { User } from "@notesnook/core";
import create, { State } from "zustand";
export enum SyncStatus {
@@ -40,6 +41,7 @@ export interface UserStore extends State {
lockApp: (verified: boolean) => void;
disableAppLockRequests: boolean;
setDisableAppLockRequests: (shouldBlockVerifyUser: boolean) => void;
profile?: Partial<Profile>;
}
export const useUserStore = create<UserStore>((set) => ({
@@ -62,5 +64,6 @@ export const useUserStore = create<UserStore>((set) => ({
setTimeout(() => {
set({ disableAppLockRequests: false });
}, 1000);
}
},
profile: undefined
}));