This commit is contained in:
Morten Olsen
2022-05-02 14:26:11 +02:00
commit d83a4aebc7
77 changed files with 16638 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
import { useCalendars, useSelectedCalendars, useSetSelectedCalendars } from "#/features/calendar"
import { Calendar } from "expo-calendar";
import { useCallback } from "react";
import styled from "styled-components/native";
const Wrapper = styled.View`
`;
const Button = styled.Button`
`;
const CalendarSelectScreen: React.FC = () => {
const calendars = useCalendars();
const selected = useSelectedCalendars();
const setSelected = useSetSelectedCalendars();
const toggle = useCallback(
(calendar: Calendar) => {
const isSelected = !!selected.find(c => c.id === calendar.id);
if (isSelected) {
setSelected(selected.filter(c => c.id !== calendar.id));
} else {
setSelected([
...selected,
calendar,
]);
}
},
[selected]
)
return (
<Wrapper>
{calendars.map((calendar) => (
<Button
key={calendar.id}
title={calendar.title + (selected.includes(calendar) ? ' -y' : '-n')}
onPress={() => toggle(calendar)}
/>
))}
</Wrapper>
)
}
export { CalendarSelectScreen };

View File

@@ -0,0 +1,44 @@
import { useLocations, useRemoveLocation } from "#/features/location"
import { Button, Cell } from "#/ui/components";
import { Row } from "#/ui/components/row/row";
import { useNavigation } from "@react-navigation/native";
import { FlatList } from "react-native";
import styled from "styled-components/native";
const Wrapper = styled.View`
`;
const Name = styled.Text`
`;
const LocationListScreen: React.FC = () => {
const locations = useLocations();
const removeLocation = useRemoveLocation();
const { navigate } = useNavigation();
return (
<Wrapper>
<Button icon="plus-circle" onPress={() => navigate('locationSet')} />
<FlatList
data={Object.values(locations)}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Row
title={item.title}
onPress={() => {
navigate('locationSet', { id: item.id });
}}
right={
<Cell>
<Button type="destructive" icon="trash" onPress={() => removeLocation(item.id)} />
</Cell>
}
/>
)}
/>
</Wrapper>
);
}
export { LocationListScreen };

View File

@@ -0,0 +1,62 @@
import { useLocations, useSetLocation } from "#/features/location";
import { useNavigation, useRoute } from '@react-navigation/native';
import { Button, TextInput } from "#/ui/components";
import { useCallback, useEffect, useMemo, useState } from "react";
import { nanoid } from 'nanoid';
import Popup from "#/ui/components/popup";
const LocationSetScreen: React.FC = () => {
const { params = {} } = useRoute() as any;
const id = useMemo(
() => params.id || nanoid(),
[params.id],
)
const locations = useLocations();
const { navigate, goBack } = useNavigation();
const [title, setTitle] = useState('');
const [lng, setLng] = useState('');
const [lat, setLat] = useState('');
const set = useSetLocation();
useEffect(
() => {
const current = locations[id];
if (!current) {
return;
}
setTitle(current.title);
setLng(current.location?.longitute.toString() || '');
setLat(current.location?.latitude.toString() || '');
},
[locations, id],
)
const save = useCallback(
() => {
const lngParsed = parseFloat(lng);
const latParsed = parseFloat(lat);
set({
id,
title,
location: {
longitute: lngParsed,
latitude: latParsed,
},
});
navigate('main');
},
[title, lng, lat, id],
)
return (
<Popup onClose={goBack}>
<TextInput value={title} onChangeText={setTitle} placeholder="Title" />
<TextInput value={lng} onChangeText={setLng} placeholder="Longitute" />
<TextInput value={lat} onChangeText={setLat} placeholder="Latitude" />
<Button title="Save" onPress={save} />
</Popup>
)
}
export { LocationSetScreen };

View File

@@ -0,0 +1,97 @@
import { useCurrentLocation } from "#/features/location"
import { usePlan } from "#/features/planner"
import { Button, Cell, Page, Row, TextInput } from "#/ui/components";
import { DayView } from "#/ui/components/specialized/plan/day";
import { Body1 } from "#/ui/typography";
import { useCallback, useMemo, useState } from "react";
import { useCommit, useDate } from "#/features/calendar";
import { format, formatDistance, formatDistanceToNow, set } from "date-fns";
import styled from "styled-components/native";
import { Status } from "#/features/planner/algorithm/build-graph";
const Wrapper = styled.ScrollView`
`;
const getStats = (status: Status) => {
console.log('status', status);
if (status.current === 'running') {
const runTime = formatDistanceToNow(status.start, { includeSeconds: true })
return `calulated ${status.nodes} nodes in ${runTime}`;
}
const runTime = formatDistance(status.start, status.end, { includeSeconds: true })
return `calulated ${status.nodes} nodes in ${runTime}`;
};
const PlanDayScreen: React.FC = () => {
const date = useDate();
const [location] = useCurrentLocation();
const [startTime, setStartTime] = useState('06:00');
const [commit] = useCommit();
const current = useMemo(
() => location || {
id: 'unknown',
title: 'foo',
},
[location]
)
const [plan, options] = usePlan({
location: current,
})
const update = useCallback(
() => {
const target = new Date(`2000-01-01T${startTime}:00`)
const corrected = set(date, {
hours: target.getHours(),
minutes: target.getMinutes(),
})
plan(corrected);
},
[date, plan, startTime],
)
return (
<Wrapper>
<Page>
<TextInput
overline="Start time"
value={startTime}
onChangeText={setStartTime}
right={(
<>
<Cell>
{!options.error && options.status && options.status.current === 'running' ? (
<Button type="destructive" onPress={options.status.cancel} icon="x" />
) : (
<Button icon="play" onPress={update} />
)}
</Cell>
{!!options.result?.agenda && (
<Cell>
<Button onPress={() => commit(options.result?.agenda || [])} icon="download" />
</Cell>
)}
</>
)}
/>
{!!options.error && (
<Row title={JSON.stringify(options.error)} />
)}
{options.status?.current === 'running' && (
<Row
title={getStats(options.status)}
/>
)}
{!!options.result && options.status?.current === 'completed' && (
<Row title={format(date, 'EEEE - do MMMM')} overline={getStats(options.status)}>
{options.result.impossible && options.result.impossible.length > 0 && <Body1>Impossible: {options.result.impossible.map(i => i.name).join(', ')}</Body1>}
<DayView plan={options.result.agenda} />
</Row>
)}
</Page>
</Wrapper>
)
}
export { PlanDayScreen };

View File

@@ -0,0 +1,82 @@
import { useLocations, useSetLocation } from "#/features/location";
import { useNavigation, useRoute } from '@react-navigation/native';
import { Button, Checkbok, TextInput } from "#/ui/components";
import { useCallback, useEffect, useMemo, useState } from "react";
import { nanoid } from 'nanoid';
import { useAgendaContext, useSetAgendaContext } from "#/features/agenda-context";
import { format } from "date-fns";
import Popup from "#/ui/components/popup";
const AgendaContextSetScreen: React.FC = () => {
const { params = {} } = useRoute() as any;
const id = useMemo(
() => params.id || nanoid(),
[params.id],
)
const contexts = useAgendaContext();
const { navigate, goBack } = useNavigation();
const locations = useLocations();
const [location, setLocation] = useState('');
const [enabled, setEnabled] = useState(true);
const [startMin, setStartMin] = useState('');
const [startMax, setStartMax] = useState('');
const [duration, setDuration] = useState('');
const [count, setCount] = useState('1');
const [set] = useSetAgendaContext();
useEffect(
() => {
const current = contexts[id];
if (!current) {
return;
}
const name = current.locations?.map(l => l.title).join(',') || '';
if (current.startMin) {
setStartMin(format(current.startMin, 'HH:mm'));
}
if (current.startMax) {
setStartMax(format(current.startMax, 'HH:mm'));
}
if (current.duration) {
setDuration((current.duration / 1000 / 60).toString());
}
if (current.count) {
setCount(current.count.toString());
}
setLocation(name);
setEnabled(current.enabled);
},
[contexts, id],
)
const save = useCallback(
() => {
const name = location.split(',').map(a => Object.values(locations).find(i => i.title.toLowerCase() === a.trim().toLowerCase())).filter(Boolean);
set(id, {
enabled,
locations: name as any,
count: parseInt(count),
startMin: startMin ? new Date(`2020-01-01T${startMin}:00`) : undefined,
startMax: startMax ? new Date(`2020-01-01T${startMax}:00`) : undefined,
duration: duration ? parseInt(duration) * 1000 * 60 : undefined,
});
navigate('main');
},
[set, id, enabled, location, count, locations, startMin, startMax, duration],
)
return (
<Popup onClose={goBack}>
<TextInput value={location} onChangeText={setLocation} placeholder="Locations" />
<TextInput value={startMin} onChangeText={setStartMin} placeholder="Start min" />
<TextInput value={startMax} onChangeText={setStartMax} placeholder="Start max" />
<TextInput value={duration} onChangeText={setDuration} placeholder="Duration" />
<TextInput value={count} onChangeText={setCount} placeholder="Count" />
<Checkbok label="Enabled" value={enabled} onChange={setEnabled} />
<Button title="Save" onPress={save} />
</Popup>
)
}
export { AgendaContextSetScreen };

View File

@@ -0,0 +1,132 @@
import { useAgendaContext, useSetAgendaContext, useTasksWithContext } from "#/features/agenda-context";
import { set } from 'date-fns';
import chroma from 'chroma-js';
import stringToColor from 'string-to-color';
import { Button, Cell, Icon } from "#/ui/components";
import { Row } from "#/ui/components";
import { AgendaItemView } from "#/ui/components/specialized/plan/agenda-item";
import { Body1 } from "#/ui/typography";
import { useNavigation } from "@react-navigation/native";
import { useCallback, useMemo } from "react";
import { FlatList } from "react-native";
import CalendarStrip from 'react-native-calendar-strip';
import styled, { useTheme } from "styled-components/native";
import { useDate, useSetDate } from "#/features/calendar";
const Wrapper = styled.View`
`;
const Strip = () => {
const date = useDate();
const theme = useTheme();
const setDate = useSetDate();
const selected = useMemo(
() => [{
date,
lines: [{ color: theme.colors.icon }],
}],
[date],
);
return (
<CalendarStrip
markedDates={selected}
style={{
height: 150,
paddingTop: 60,
paddingBottom: 10,
backgroundColor: theme.colors.background,
}}
calendarColor={'#fff'}
selectedDate={date}
startingDate={date}
onDateSelected={(date) => {
setDate(set(date.toDate(), { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }));
}}
shouldAllowFontScaling={false}
iconContainer={{flex: 0.1}}
calendarHeaderStyle={{
color: theme.colors.text,
fontSize: theme.font.baseSize * 1.2,
}}
highlightDateNameStyle={{
color: theme.colors.icon,
fontSize: theme.font.baseSize * 0.6,
}}
iconLeftStyle={{
tintColor: theme.colors.text,
}}
iconRightStyle={{
tintColor: theme.colors.text,
}}
highlightDateNumberStyle={{
color: theme.colors.icon,
fontSize: theme.font.baseSize * 1.2,
}}
dateNumberStyle={{
color: theme.colors.text,
fontSize: theme.font.baseSize * 1.2,
}}
dateNameStyle={{
color: theme.colors.text,
fontSize: theme.font.baseSize * 0.6,
}}
/>
);
}
const TaskListScreen: React.FC = () => {
const tasks = useTasksWithContext();
const { navigate } = useNavigation();
const contexts = useAgendaContext();
const [set] = useSetAgendaContext();
const toggle = useCallback(
(task: any) => {
const context = contexts[task.id] || {};
set(task.id, {
...context,
enabled: !task.enabled,
})
},
[set],
)
return (
<Wrapper>
<FlatList
ListHeaderComponent={Strip}
data={Object.values(tasks)}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Row
onPress={() => {
toggle(item);
//navigate('agendaContextSet', { id: item.id });
}}
opacity={item.enabled ? undefined : 0.3}
right={(
<Button
icon="edit"
onPress={() => {
navigate('agendaContextSet', { id: item.id });
}}
/>
)}
>
<AgendaItemView
item={{
height: 1000 * 60 * 30,
body: <Body1>{item.name}</Body1>,
start: item.start.min,
color: chroma(stringToColor(item.name)).luminance(0.7).saturate(1).brighten(0.6).hex(),
end: new Date(item.start.max.getTime() + item.duration.min),
}}
/>
</Row>
)}
/>
</Wrapper>
);
}
export { TaskListScreen };

View File

@@ -0,0 +1,41 @@
import { useRemoveRoutine, useRoutines } from "#/features/routines";
import { Button, Cell } from "#/ui/components";
import { Row } from "#/ui/components/row/row";
import { useNavigation } from "@react-navigation/native";
import { FlatList } from "react-native";
import styled from "styled-components/native";
const Wrapper = styled.View`
`;
const RoutinesListScreen: React.FC = () => {
const routines = useRoutines();
const removeRoutine = useRemoveRoutine();
const { navigate } = useNavigation();
return (
<Wrapper>
<Button icon="plus-circle" onPress={() => navigate('routineSet')} />
<FlatList
data={Object.values(routines)}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Row
title={item.title}
subtitle={item.location?.map(l => l.title).join(', ')}
onPress={() => {
navigate('routineSet', { id: item.id });
}}
right={
<Cell>
<Button icon="trash" type="destructive" onPress={() => removeRoutine(item.id)} />
</Cell>
}
/>
)}
/>
</Wrapper>
);
}
export { RoutinesListScreen };

View File

@@ -0,0 +1,84 @@
import { useNavigation, useRoute } from '@react-navigation/native';
import { Button, Checkbok, TextInput } from "#/ui/components";
import { useCallback, useEffect, useMemo, useState } from "react";
import { nanoid } from 'nanoid';
import { useRoutines, useSetRoutine } from '#/features/routines';
import { format } from 'date-fns';
import { useLocations } from '#/features/location';
import Popup from '#/ui/components/popup';
const RoutineSetScreen: React.FC = () => {
const { params = {} } = useRoute() as any;
const id = useMemo(
() => params.id || nanoid(),
[params.id],
)
const routines = useRoutines();
const { navigate, goBack } = useNavigation();
const [title, setTitle] = useState('');
const [startMin, setStartMin] = useState('');
const [startMax, setStartMax] = useState('');
const [duration, setDuration] = useState('');
const locations = useLocations();
const [required, setRequired] = useState(false);
const [location, setLocation] = useState('');
const set = useSetRoutine();
useEffect(
() => {
const current = routines.find(r => r.id === id);
if (!current) {
return;
}
setTitle(current.title);
if (current.start.min) {
setStartMin(format(current.start.min, 'HH:mm'));
}
if (current.start.max) {
setStartMax(format(current.start.max, 'HH:mm'));
}
if (current.duration) {
setDuration((current.duration / 1000 / 60).toString());
}
setRequired(!!current.required);
const name = current.location?.map(l => l.title).join(',') || '';
setLocation(name);
},
[routines, id],
)
const save = useCallback(
() => {
const name = location.split(',').map(a => Object.values(locations).find(i => i.title.toLowerCase() === a.trim().toLowerCase())).filter(Boolean);
set({
id,
title,
priority: 50,
required: required,
location: name.length > 0 ? name as any : undefined,
start: {
min: new Date(`2020-01-01T${startMin}:00`),
max: new Date(`2020-01-01T${startMax}:00`),
},
duration: parseInt(duration) * 1000 * 60
});
navigate('main');
},
[title, startMin, startMax, duration, location, required],
)
return (
<Popup onClose={goBack}>
<TextInput value={title} onChangeText={setTitle} placeholder="Title" />
<TextInput value={startMin} onChangeText={setStartMin} placeholder="Start min" />
<TextInput value={startMax} onChangeText={setStartMax} placeholder="Start max" />
<TextInput value={duration} onChangeText={setDuration} placeholder="Duration" />
<TextInput value={location} onChangeText={setLocation} placeholder="Location" />
<Checkbok label="Required" value={required} onChange={setRequired} />
<Button title="Save" onPress={save} />
</Popup>
)
}
export { RoutineSetScreen };