This commit is contained in:
Morten Olsen
2022-05-23 10:36:28 +02:00
parent 42ca6c9559
commit 3c8ce8d2d2
14 changed files with 152 additions and 97 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@morten-olsen/bob", "name": "@morten-olsen/bob",
"version": "3.0.0-alpha.1", "version": "3.0.0",
"main": "./src/index.ts", "main": "./src/index.ts",
"homepage": "/bob-the-algorithm", "homepage": "/bob-the-algorithm",
"scripts": { "scripts": {
@@ -37,7 +37,7 @@
"expo-font": "~10.0.4", "expo-font": "~10.0.4",
"expo-linking": "~3.0.0", "expo-linking": "~3.0.0",
"expo-location": "~14.0.1", "expo-location": "~14.0.1",
"expo-random": "^12.1.2", "expo-random": "~12.1.1",
"expo-splash-screen": "~0.14.0", "expo-splash-screen": "~0.14.0",
"expo-status-bar": "~1.2.0", "expo-status-bar": "~1.2.0",
"expo-task-manager": "~10.1.0", "expo-task-manager": "~10.1.0",
@@ -50,8 +50,8 @@
"react-native-calendar-strip": "^2.2.5", "react-native-calendar-strip": "^2.2.5",
"react-native-calendars": "^1.1284.0", "react-native-calendars": "^1.1284.0",
"react-native-collapsible": "^1.6.0", "react-native-collapsible": "^1.6.0",
"react-native-gesture-handler": "^2.4.2", "react-native-gesture-handler": "~2.1.0",
"react-native-get-random-values": "^1.8.0", "react-native-get-random-values": "~1.7.0",
"react-native-safe-area-context": "3.3.2", "react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.10.1", "react-native-screens": "~3.10.1",
"react-native-web": "0.17.1", "react-native-web": "0.17.1",

View File

@@ -1,7 +1,9 @@
import { useAsync } from "#/features/async"; import { useAsync } from "#/features/async";
import { ReactNode } from "react" import { ReactNode, useState } from "react"
import { Platform } from "react-native"; import { Platform } from "react-native";
import { AppointmentsContext, AppointmentsContextValue, AppointmentsStatus } from './context'; import { AppointmentsContext, AppointmentsContextValue, AppointmentsStatus } from './context';
import { NativeIntegtration } from "./providers/native";
import { IntegrationProvider } from "./providers/provider";
type AppointmentsProviderProps = { type AppointmentsProviderProps = {
children: ReactNode; children: ReactNode;
@@ -10,12 +12,22 @@ type AppointmentsProviderProps = {
const AppointmentsProvider: React.FC<AppointmentsProviderProps> = ({ const AppointmentsProvider: React.FC<AppointmentsProviderProps> = ({
children, children,
}) => { }) => {
const [provider, setProvider] = useState<IntegrationProvider>();
const [value] = useAsync<AppointmentsContextValue>( const [value] = useAsync<AppointmentsContextValue>(
async () => { async () => {
if (Platform.OS !== 'ios') { if (Platform.OS !== 'ios') {
return { status: AppointmentsStatus.unavailable }; return { status: AppointmentsStatus.unavailable };
} }
return { status: AppointmentsStatus.unavailable }; const iosProvider = new NativeIntegtration();
const setupSuccess = await iosProvider.setup();
if (!setupSuccess) {
return { status: AppointmentsStatus.unavailable };
}
setProvider(iosProvider);
return {
status: AppointmentsStatus.approved,
getDay: iosProvider.getDay,
};
}, },
[], [],
); );

View File

@@ -0,0 +1,55 @@
import { Appointment, TaskType, timeUtils } from "#/features/data";
import { Day, dayUtils } from "#/features/day";
import { set } from "date-fns";
import { EntityTypes, getCalendarsAsync, getEventsAsync, requestCalendarPermissionsAsync } from "expo-calendar";
import { Platform } from "react-native";
import { IntegrationProvider } from "./provider";
class NativeIntegtration implements IntegrationProvider {
getAllCalendars = async () => {
let calendars = await getCalendarsAsync(EntityTypes.EVENT);
return calendars;
};
public setup = async () => {
if (Platform.OS !== 'ios') {
return false;
}
const { status } = await requestCalendarPermissionsAsync();
if (status !== 'granted') {
return false;
}
return true;
}
public getDay = async (day: Day) => {
const selectedCalendars = await this.getAllCalendars();
const start = dayUtils.dayToDate(day)
const end = set(start, {
hours: 24,
minutes: 0,
seconds: 0,
milliseconds: 0,
});
const events = await getEventsAsync(selectedCalendars.map(c => c.id), start, end!)
return events.filter(a => !a.allDay).map<Appointment>(e => {
const startTime = timeUtils.dateToTime(new Date(e.startDate));
const endTime = timeUtils.dateToTime(new Date(e.endDate));
const duration = timeUtils.timeToMinutes(endTime) - timeUtils.timeToMinutes(startTime);
return {
id: e.id,
type: TaskType.appointment,
calendarId: e.calendarId,
title: e.title,
required: true,
startTime: {
min: startTime,
max: startTime,
},
duration,
}
});
}
}
export { NativeIntegtration };

View File

@@ -0,0 +1,10 @@
import { Appointment } from "#/features/data";
import { Day } from "#/features/day";
interface IntegrationProvider {
getAllCalendars: () => Promise<any>;
setup: () => Promise<boolean>;
getDay: (day: Day) => Promise<Appointment[]>;
}
export { IntegrationProvider };

View File

@@ -47,6 +47,13 @@ const timeToDate = (time: Time) => {
return new Date(0, 0, 0, time.hour, time.minute); return new Date(0, 0, 0, time.hour, time.minute);
} }
const dateToTime = (date: Date) => {
return {
hour: date.getHours(),
minute: date.getMinutes(),
};
}
const add = (a: Time, b: Time | number) => { const add = (a: Time, b: Time | number) => {
const toAdd = typeof b === 'number' ? b : b.hour * 60 + b.minute const toAdd = typeof b === 'number' ? b : b.hour * 60 + b.minute
const current = a.hour * 60 + a.minute + toAdd; const current = a.hour * 60 + a.minute + toAdd;
@@ -60,6 +67,7 @@ const timeUtils = {
largerThan, largerThan,
timeToMinutes, timeToMinutes,
timeToDate, timeToDate,
dateToTime,
max, max,
add, add,
}; };

View File

@@ -1,5 +1,5 @@
import { buildGraph, Status, Strategies } from "./algorithm/build-graph"; import { buildGraph, Status, Strategies } from "./algorithm/build-graph";
import { useContext } from "react"; import { useContext, useMemo, useState } from "react";
import { add } from 'date-fns'; import { add } from 'date-fns';
import { PlannerContext } from "./context"; import { PlannerContext } from "./context";
import { Task, Time, UserLocation } from "../data"; import { Task, Time, UserLocation } from "../data";
@@ -103,6 +103,7 @@ const usePreparePlan = () => {
}, },
duration: firstValue(override?.duration, task.duration), duration: firstValue(override?.duration, task.duration),
required: firstValue(override?.required, task.required), required: firstValue(override?.required, task.required),
locations: firstValue(override?.locations, task.locations) || undefined,
} }
return result; return result;
}).filter(Boolean).map(a => a as Exclude<typeof a, undefined>); }).filter(Boolean).map(a => a as Exclude<typeof a, undefined>);
@@ -137,6 +138,7 @@ export const useSetPlanOptions = () => {
export const usePlan = () => { export const usePlan = () => {
const [preparePlan] = usePreparePlan(); const [preparePlan] = usePreparePlan();
const [result, setResult] = useState<PlanResult | undefined>(undefined);
const getTransition = useGetTransition(); const getTransition = useGetTransition();
const options = usePlanOptions(); const options = usePlanOptions();
const createPlan = useAsyncCallback( const createPlan = useAsyncCallback(
@@ -154,7 +156,9 @@ export const usePlan = () => {
}), {} as {[name: string]: PlanResultDay}) }), {} as {[name: string]: PlanResultDay})
} }
const update = (next: PlanResult) => { const update = (next: PlanResult) => {
setResult(next);
result = next; result = next;
return next;
} }
for (let day of prepared.days) { for (let day of prepared.days) {
const id = dayUtils.toId(day.day); const id = dayUtils.toId(day.day);
@@ -211,12 +215,19 @@ export const usePlan = () => {
}) })
} }
return { return update({
...result, ...result,
impossible: prepared.goals, impossible: prepared.goals,
}; });
}, },
[preparePlan, getTransition, options], [preparePlan, getTransition, options],
); );
return createPlan; const output = useMemo(
() => [
createPlan[0],
{ ...createPlan[1], result },
],
[createPlan, result],
);
return output;
} }

View File

@@ -1,18 +1,25 @@
import { dayUtils } from "#/features/day"; import { dayUtils } from "#/features/day";
import { PlanResultDay } from "#/features/planner" import { PlanResultDay } from "#/features/planner"
import { Body1, Jumbo } from "@morten-olsen/ui"; import { Body1, Jumbo } from "@morten-olsen/ui";
import styled from "styled-components/native";
import { PlanDayTask } from "./task"; import { PlanDayTask } from "./task";
type Props = { type Props = {
day: PlanResultDay; day: PlanResultDay;
}; };
const Transit = styled(Body1)`
align-items: center;
justify-content: center;
text-align: center;
`;
const PlanDay: React.FC<Props> = ({ day }) => { const PlanDay: React.FC<Props> = ({ day }) => {
if (day.status === 'waiting') { if (day.status === 'waiting') {
return <></> return <></>
} }
if (day.status === 'running') { if (day.status === 'running') {
return <Body1>Running</Body1> return <Body1>Running, nodes: {day.nodes}, strategy: {day.strategy}</Body1>
} }
return ( return (
<> <>
@@ -20,7 +27,7 @@ const PlanDay: React.FC<Props> = ({ day }) => {
if (item.type === 'task') { if (item.type === 'task') {
return <PlanDayTask task={item} /> return <PlanDayTask task={item} />
} }
return <Body1>Transit {item.from.title} to {item.to.title}</Body1> return <Transit>{item.from.title} {item.to.title}</Transit>
})} })}
</> </>
) )

View File

@@ -11,47 +11,6 @@ type Props = {
onPress?: () => void; onPress?: () => void;
}; };
const Time = styled.Text<{background : string}>`
font-size: 10px;
font-weight: bold;
`;
const TimeBox = styled.View<{
background: string;
}>`
margin-right: 10px;
width: 50px;
height: 100%;
align-items: center;
justify-content: center;
`;
const Filler = styled.View`
margin: 10px;
width: 50px;
height: 50px;
align-items: center;
justify-content: center;
`;
const Block = styled.View<{
background: string;
height: number;
}>`
background: ${({ background }) => background};
height: ${({ height }) => height / 3}px;
max-height: 100px;
margin: 5px;
flex-direction: row;
align-items: center;
border-radius: 15px;
overflow: hidden;
border: solid 1px ${({ background }) => background === 'transparent' ? background : chroma(background).darken(0.3).hex()};
`;
const Main = styled.View`
flex: 1;
`
const Touch = styled.TouchableOpacity` const Touch = styled.TouchableOpacity`
`; `;
@@ -68,7 +27,6 @@ const PlanDayTask: React.FC<Props> = ({ task, onPress }) => {
const view = ( const view = (
<Row> <Row>
<CalendarEntry <CalendarEntry
location={task.location?.join(', ') || 'anywhere'}
start={timeUtils.timeToDate(task.start)} start={timeUtils.timeToDate(task.start)}
end={timeUtils.timeToDate(task.end)} end={timeUtils.timeToDate(task.end)}
title={task.name} title={task.name}

View File

@@ -37,7 +37,7 @@ const DayScreen: React.FC = () => {
value={startTimeOverride} value={startTimeOverride}
onChange={setStartTimeOverride} onChange={setStartTimeOverride}
/> />
{appointmentStatus === AppointmentsStatus.rejected && ( {appointmentStatus === AppointmentsStatus.approved && (
<TaskGroup type={TaskType.appointment} /> <TaskGroup type={TaskType.appointment} />
)} )}
<TaskGroup type={TaskType.routine} /> <TaskGroup type={TaskType.routine} />

View File

@@ -49,8 +49,6 @@ const OverrideSetScreen: React.FC = () => {
const [duration, setDuration] = useState(''); const [duration, setDuration] = useState('');
const [hasLocation, setHasLocation] = useState(false); const [hasLocation, setHasLocation] = useState(false);
const [selectedLocations, setSelectedLocations] = useState<UserLocation[]>([]); const [selectedLocations, setSelectedLocations] = useState<UserLocation[]>([]);
const [hasDays, setHasDays] = useState(false);
const [selectedDays, setSelectedDays] = useState<typeof days>([]);
useEffect( useEffect(
() => { () => {
@@ -88,8 +86,6 @@ const OverrideSetScreen: React.FC = () => {
duration, duration,
hasLocation, hasLocation,
selectedLocations, selectedLocations,
hasDays,
selectedDays,
], ],
); );
@@ -101,6 +97,10 @@ const OverrideSetScreen: React.FC = () => {
[id, clearOverrides], [id, clearOverrides],
); );
if (!task) {
return <></>;
}
return ( return (
<Popup title={`Overrides for ${task.title}`} onClose={goBack}> <Popup title={`Overrides for ${task.title}`} onClose={goBack}>
<SideBySide> <SideBySide>
@@ -139,20 +139,6 @@ const OverrideSetScreen: React.FC = () => {
disabledText="Anywhere" disabledText="Anywhere"
enabledText="Specific location" enabledText="Specific location"
/> />
<OptionalSelector
label="Days"
enabled={hasDays}
items={days}
selected={selectedDays}
onChange={setSelectedDays}
render={day=> ({
title: day.name
})}
getKey={day => day.id.toString()}
setEnabled={setHasDays}
disabledText="Any day"
enabledText="Specific days"
/>
<Checkbox label="Required" /> <Checkbox label="Required" />
<TextInput <TextInput
label="Priority" label="Priority"

View File

@@ -1,4 +1,4 @@
import { Body1, Caption, Overline } from "../../../typography"; import { Title1, Title2, Body1, Caption, Overline } from "../../../typography";
import { Row, Cell, Icon, RowProps } from '../../base'; import { Row, Cell, Icon, RowProps } from '../../base';
import styled from "styled-components/native" import styled from "styled-components/native"
import stringToColor from 'string-to-color'; import stringToColor from 'string-to-color';
@@ -61,8 +61,8 @@ const CalendarEntry = ({
height={height} height={height}
color={color} color={color}
> >
<Overline color="background">{location}</Overline> {!!location && <Overline color="background">{location}</Overline>}
<Body1 color="background">{title}</Body1> <Title1 color="background">{title}</Title1>
<Caption color="background">{time}</Caption> <Caption color="background">{time}</Caption>
</Wrapper> </Wrapper>
); );

View File

@@ -70,7 +70,6 @@ const CalendarStrip: React.FC<Props> = ({ start, selected, onSelect }) => {
}, [firstDayOfWeek]); }, [firstDayOfWeek]);
const monthLabel = useMemo(() => { const monthLabel = useMemo(() => {
console.log(months);
if (months[0] === months[1]) { if (months[0] === months[1]) {
return new Date(0, months[0], 1).toLocaleString('en-us', { month: 'long' }); return new Date(0, months[0], 1).toLocaleString('en-us', { month: 'long' });
} else { } else {

View File

@@ -50,7 +50,7 @@ const light: Theme = {
}, },
font: { font: {
family: Platform.OS === 'web' ? 'Montserrat' : undefined, family: Platform.OS === 'web' ? 'Montserrat' : undefined,
baseSize: Platform.OS === 'web' ? 12 : 10, baseSize: Platform.OS === 'web' ? 12 : 11,
}, },
}; };

View File

@@ -4430,7 +4430,7 @@
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
"@types/react-dom@^18.0.3": "@types/react-dom@^18.0.3", "@types/react-dom@~18.0.3":
version "18.0.4" version "18.0.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.4.tgz#dcbcadb277bcf6c411ceff70069424c57797d375" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.4.tgz#dcbcadb277bcf6c411ceff70069424c57797d375"
integrity sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q== integrity sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==
@@ -4451,16 +4451,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*": "@types/react@*", "@types/react@~17.0.21":
version "18.0.9"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878"
integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@~17.0.21":
version "17.0.45" version "17.0.45"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.45.tgz#9b3d5b661fd26365fefef0e766a1c6c30ccf7b3f" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.45.tgz#9b3d5b661fd26365fefef0e766a1c6c30ccf7b3f"
integrity sha512-YfhQ22Lah2e3CHPsb93tRwIGNiSwkuz1/blk4e6QrWS0jQzCSNbGLtOEYhPg02W0yGTTmpajp7dCTbBAMN3qsg== integrity sha512-YfhQ22Lah2e3CHPsb93tRwIGNiSwkuz1/blk4e6QrWS0jQzCSNbGLtOEYhPg02W0yGTTmpajp7dCTbBAMN3qsg==
@@ -9026,10 +9017,10 @@ expo-pwa@0.0.119:
commander "2.20.0" commander "2.20.0"
update-check "1.5.3" update-check "1.5.3"
expo-random@^12.1.2: expo-random@~12.1.1:
version "12.2.0" version "12.1.2"
resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-12.2.0.tgz#a3c8a9ce84ef2c85900131d96eea6c7123285482" resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-12.1.2.tgz#ed4e7f85728b609bcb373bc4c4a1a05192ec44b9"
integrity sha512-SihCGLmDyDOALzBN8XXpz2hCw0RSx9c4/rvjcS4Bfqhw6luHjL2rHNTLrFYrPrPRmG1jHM6dXXJe/Zm8jdu+2g== integrity sha512-ajB+Mwff9PdglsyLliaU4K9BtVwKvAVVI2hQhnvlS3QgsAhHf+jQVUfAysQJHuioF6ADMEsab/kRUy4Dy03aoQ==
dependencies: dependencies:
base64-js "^1.3.0" base64-js "^1.3.0"
@@ -16126,10 +16117,10 @@ react-element-to-jsx-string@^14.3.4:
is-plain-object "5.0.0" is-plain-object "5.0.0"
react-is "17.0.2" react-is "17.0.2"
react-error-overlay@^6.0.9: react-error-overlay@6.0.9, react-error-overlay@^6.0.9:
version "6.0.11" version "6.0.9"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
react-feather@^2.0.9: react-feather@^2.0.9:
version "2.0.9" version "2.0.9"
@@ -16219,6 +16210,17 @@ react-native-gesture-handler@^2.4.2:
lodash "^4.17.21" lodash "^4.17.21"
prop-types "^15.7.2" prop-types "^15.7.2"
react-native-gesture-handler@~2.1.0:
version "2.1.3"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.1.3.tgz#b96f1e61932d5062cb1023259e1649d65f78338b"
integrity sha512-y5W2MVB+J6vjIT/mUidDv0BqVRbWXn0cP7R2o6fsSYsHh9M0btT979+bCI7nPuhmRHjkhg5xCm4HNMIH0IQO4w==
dependencies:
"@egjs/hammerjs" "^2.0.17"
hoist-non-react-statics "^3.3.0"
invariant "^2.2.4"
lodash "^4.17.21"
prop-types "^15.7.2"
react-native-get-random-values@^1.8.0: react-native-get-random-values@^1.8.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.8.0.tgz#1cb4bd4bd3966a356e59697b8f372999fe97cb16" resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.8.0.tgz#1cb4bd4bd3966a356e59697b8f372999fe97cb16"
@@ -16226,6 +16228,13 @@ react-native-get-random-values@^1.8.0:
dependencies: dependencies:
fast-base64-decode "^1.0.0" fast-base64-decode "^1.0.0"
react-native-get-random-values@~1.7.0:
version "1.7.2"
resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.7.2.tgz#60a9b6497d22e713779b71139f016a5fcec7ac04"
integrity sha512-28KRYGpIG/upV8+k/qFA+TwGW+yGjmtOHaCduJHpOQK1QUTyhiA6E2IgL4UvvU2dybeCTYFmUi9wcEQ0GiWe5g==
dependencies:
fast-base64-decode "^1.0.0"
react-native-safe-area-context@3.3.2: react-native-safe-area-context@3.3.2:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz#9549a2ce580f2374edb05e49d661258d1b8bcaed" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz#9549a2ce580f2374edb05e49d661258d1b8bcaed"