mirror of
https://github.com/morten-olsen/bob-the-algorithm.git
synced 2026-02-08 00:46:25 +01:00
fix
This commit is contained in:
committed by
Morten Olsen
parent
8259232a83
commit
6181eeb0c8
61
.github/workflows/expo-main.yml
vendored
Normal file
61
.github/workflows/expo-main.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Expo Publish
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- v3
|
||||||
|
jobs:
|
||||||
|
publish-web:
|
||||||
|
name: Publish web version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout 🛎️
|
||||||
|
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Install and Build 🔧
|
||||||
|
run: |
|
||||||
|
yarn install
|
||||||
|
yarn expo build:web
|
||||||
|
- name: Deploy 🚀
|
||||||
|
uses: JamesIves/github-pages-deploy-action@4.0.0
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
branch: gh-pages
|
||||||
|
folder: web-build
|
||||||
|
publish-native:
|
||||||
|
name: Publish native versions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 14.x
|
||||||
|
|
||||||
|
- uses: expo/expo-github-action@v6
|
||||||
|
with:
|
||||||
|
eas-version: latest
|
||||||
|
token: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
git config --global user.email "github-action@example.com"
|
||||||
|
git config --global user.name "Github Bot"
|
||||||
|
yarn version --new-version=$BUILD_VERSION
|
||||||
|
env:
|
||||||
|
BUILD_VERSION: 1.${{ github.run_id }}.${{ github.run_number }}
|
||||||
|
- run: yarn install
|
||||||
|
|
||||||
|
- run: echo $BUILD_VERSION
|
||||||
|
|
||||||
|
# - run: eas build -p android --non-interactive
|
||||||
|
|
||||||
|
- run: eas build -p ios --non-interactive
|
||||||
|
env:
|
||||||
|
EXPO_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
|
|
||||||
|
- run: eas submit --platform ios --non-interactive --latest
|
||||||
|
env:
|
||||||
|
EXPO_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
25
.github/workflows/publish-web.yml
vendored
25
.github/workflows/publish-web.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Build and Deploy web
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
build-and-deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout 🛎️
|
|
||||||
uses: actions/checkout@v2.3.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install and Build 🔧
|
|
||||||
run: |
|
|
||||||
yarn install
|
|
||||||
NODE_ENV=production yarn expo build:web
|
|
||||||
- name: Deploy 🚀
|
|
||||||
uses: JamesIves/github-pages-deploy-action@4.0.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
branch: gh-pages
|
|
||||||
folder: web-build
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
/*.log
|
||||||
node_modules/
|
node_modules/
|
||||||
.expo/
|
.expo/
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
51
app.config.js
Normal file
51
app.config.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const pkg = require('./package.json');
|
||||||
|
const config = {
|
||||||
|
expo: {
|
||||||
|
name: 'Bob',
|
||||||
|
slug: 'bob',
|
||||||
|
version: pkg.version,
|
||||||
|
orientation: 'portrait',
|
||||||
|
icon: './assets/images/icon.png',
|
||||||
|
scheme: 'bobthealgorithm',
|
||||||
|
userInterfaceStyle: 'automatic',
|
||||||
|
splash: {
|
||||||
|
image: './assets/images/splash.png',
|
||||||
|
resizeMode: 'contain',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
},
|
||||||
|
updates: {
|
||||||
|
fallbackToCacheTimeout: 0,
|
||||||
|
},
|
||||||
|
assetBundlePatterns: ['**/*'],
|
||||||
|
ios: {
|
||||||
|
supportsTablet: true,
|
||||||
|
bundleIdentifier: 'pro.mortenolsen.bob',
|
||||||
|
buildNumber: pkg.version,
|
||||||
|
config: {
|
||||||
|
usesNonExemptEncryption: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
adaptiveIcon: {
|
||||||
|
foregroundImage: './assets/images/adaptive-icon.png',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
},
|
||||||
|
package: 'pro.mortenolsen.bob',
|
||||||
|
},
|
||||||
|
web: {
|
||||||
|
favicon: './assets/images/favicon.png',
|
||||||
|
},
|
||||||
|
// hooks: {
|
||||||
|
// postPublish: [
|
||||||
|
// {
|
||||||
|
// file: 'sentry-expo/upload-sourcemaps',
|
||||||
|
// config: {
|
||||||
|
// setCommits: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
34
app.json
34
app.json
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"expo": {
|
|
||||||
"name": "bob",
|
|
||||||
"slug": "bob",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"orientation": "portrait",
|
|
||||||
"icon": "./assets/images/icon.png",
|
|
||||||
"scheme": "myapp",
|
|
||||||
"userInterfaceStyle": "automatic",
|
|
||||||
"splash": {
|
|
||||||
"image": "./assets/images/splash.png",
|
|
||||||
"resizeMode": "contain",
|
|
||||||
"backgroundColor": "#ffffff"
|
|
||||||
},
|
|
||||||
"updates": {
|
|
||||||
"fallbackToCacheTimeout": 0
|
|
||||||
},
|
|
||||||
"assetBundlePatterns": [
|
|
||||||
"**/*"
|
|
||||||
],
|
|
||||||
"ios": {
|
|
||||||
"supportsTablet": true
|
|
||||||
},
|
|
||||||
"android": {
|
|
||||||
"adaptiveIcon": {
|
|
||||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
|
||||||
"backgroundColor": "#ffffff"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"web": {
|
|
||||||
"favicon": "./assets/images/favicon.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
eas.json
Normal file
25
eas.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"cli": {
|
||||||
|
"version": ">= 0.42.4"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"development": {
|
||||||
|
"developmentClient": true,
|
||||||
|
"distribution": "internal"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"distribution": "internal"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"submit": {
|
||||||
|
"production": {
|
||||||
|
"ios": {
|
||||||
|
"appleId": "morten@olsen.pro",
|
||||||
|
"appleTeamId": "D2944KU2BE",
|
||||||
|
"ascAppId": "1623552387"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
"react-native": "0.64.3",
|
"react-native": "0.64.3",
|
||||||
"react-native-calendar-strip": "^2.2.5",
|
"react-native-calendar-strip": "^2.2.5",
|
||||||
|
"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.4.2",
|
||||||
"react-native-get-random-values": "^1.8.0",
|
"react-native-get-random-values": "^1.8.0",
|
||||||
@@ -61,7 +62,6 @@
|
|||||||
"@types/chroma-js": "^2.1.3",
|
"@types/chroma-js": "^2.1.3",
|
||||||
"@types/react": "~17.0.21",
|
"@types/react": "~17.0.21",
|
||||||
"@types/react-dom": "^18.0.3",
|
"@types/react-dom": "^18.0.3",
|
||||||
"@types/react-native": "^0.67.6",
|
|
||||||
"@types/styled-components-react-native": "^5.1.3",
|
"@types/styled-components-react-native": "^5.1.3",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"expo-cli": "^5.4.3",
|
"expo-cli": "^5.4.3",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const App: React.FC = () => {
|
|||||||
async (from: any, to: any) => ({
|
async (from: any, to: any) => ({
|
||||||
to,
|
to,
|
||||||
from,
|
from,
|
||||||
time: 45 * 60 * 1000,
|
time: 45,
|
||||||
usableTime: 0,
|
usableTime: 0,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const useAsyncCallback = <
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setLoading, setError, setResult, action],
|
[setLoading, setError, setResult, action, ...deps],
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
@@ -63,7 +63,7 @@ const useAsyncCallback = <
|
|||||||
];
|
];
|
||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
[invoke, result, loading, error, prevArgs],
|
[invoke, result, loading, error, prevArgs, ...deps],
|
||||||
);
|
);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
|
|||||||
@@ -26,12 +26,37 @@ const stringToTime = (input: string) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const largerThan = (a: Time, b: Time) => {
|
||||||
|
return timeToMinutes(a) > timeToMinutes(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
const max = (a: Time, b: Time) => largerThan(a, b) ? a : b;
|
||||||
|
const min = (a: Time, b: Time) => largerThan(a, b) ? b : a;
|
||||||
|
|
||||||
const timeToString = (input: Time) => `${input.hour}:${input.minute}`;
|
const timeToString = (input: Time) => `${input.hour}:${input.minute}`;
|
||||||
|
|
||||||
|
const timeToMinutes = (time: Time) => time.hour * 60 + time.minute;
|
||||||
|
|
||||||
|
const minutesToTime = (minutes: number): Time => {
|
||||||
|
const hour = Math.floor(minutes / 60);
|
||||||
|
const minute = minutes % 60;
|
||||||
|
return { hour, minute };
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = (a: Time, b: Time | number) => {
|
||||||
|
const toAdd = typeof b === 'number' ? b : b.hour * 60 + b.minute
|
||||||
|
const current = a.hour * 60 + a.minute + toAdd;
|
||||||
|
return minutesToTime(current);
|
||||||
|
}
|
||||||
|
|
||||||
const timeUtils = {
|
const timeUtils = {
|
||||||
timeToString,
|
timeToString,
|
||||||
stringToTime,
|
stringToTime,
|
||||||
equal,
|
equal,
|
||||||
|
largerThan,
|
||||||
|
timeToMinutes,
|
||||||
|
max,
|
||||||
|
add,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { timeUtils };
|
export { timeUtils };
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const dateToDay = (input: Date) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toId = (day: Day) => {
|
const toId = (day: Day) => {
|
||||||
return `${day.year}-${day.month}-${day.date}`;
|
return `${day.year.toString().padStart(4, '0')}-${day.month.toString().padStart(2, '0')}-${day.date.toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dayUtils = {
|
const dayUtils = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { UserLocation } from "../data";
|
import { Time, UserLocation } from "../data";
|
||||||
import { createContext } from "react"
|
import { createContext } from "react"
|
||||||
|
|
||||||
type Transition = {
|
type Transition = {
|
||||||
@@ -11,7 +11,7 @@ type Transition = {
|
|||||||
type GetTransition = (
|
type GetTransition = (
|
||||||
from: UserLocation,
|
from: UserLocation,
|
||||||
to: UserLocation,
|
to: UserLocation,
|
||||||
time: Date,
|
time: Time,
|
||||||
) => Promise<Transition>;
|
) => Promise<Transition>;
|
||||||
|
|
||||||
type LocationContextValue = {
|
type LocationContextValue = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useContext } from "react"
|
import { useContext } from "react"
|
||||||
import { useAsyncCallback } from "../async";
|
import { useAsyncCallback } from "../async";
|
||||||
import { Time } from "../data";
|
import { Time } from "../data";
|
||||||
import { OverrideContext } from "./context"
|
import { Override, OverrideContext } from "./context"
|
||||||
|
|
||||||
export const useOverrides = () => {
|
export const useOverrides = () => {
|
||||||
const { overrides } = useContext(OverrideContext);
|
const { overrides } = useContext(OverrideContext);
|
||||||
@@ -13,6 +13,46 @@ export const useSetOverride = () => {
|
|||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useGetOverride = () => {
|
||||||
|
const { get } = useContext(OverrideContext);
|
||||||
|
return get;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSetTaskOverride = () => {
|
||||||
|
const { set } = useContext(OverrideContext);
|
||||||
|
const setTaskOverride = useAsyncCallback(
|
||||||
|
async (id: string, overrides: Override) => {
|
||||||
|
set(current => ({
|
||||||
|
...current,
|
||||||
|
tasks: {
|
||||||
|
...current.tasks,
|
||||||
|
[id]: overrides,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[set],
|
||||||
|
);
|
||||||
|
return setTaskOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useClearTaskOverride = () => {
|
||||||
|
const { set } = useContext(OverrideContext);
|
||||||
|
const clearTaskOverride = useAsyncCallback(
|
||||||
|
async (id: string) => {
|
||||||
|
set(current => {
|
||||||
|
const tasks = {...current.tasks};
|
||||||
|
delete tasks[id]
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
tasks,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[set],
|
||||||
|
);
|
||||||
|
return clearTaskOverride;
|
||||||
|
}
|
||||||
|
|
||||||
export const useStartTimeOverride = () => {
|
export const useStartTimeOverride = () => {
|
||||||
const { overrides } = useContext(OverrideContext);
|
const { overrides } = useContext(OverrideContext);
|
||||||
return overrides.startTime;
|
return overrides.startTime;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Context, GraphNode } from "#/types/graph";
|
import { Task, Time, UserLocation } from "#/features/data";
|
||||||
import { UserLocation } from "#/types/location";
|
import { Context, GraphNode } from "../types";
|
||||||
import { Task } from "#/types/task";
|
|
||||||
import { getImpossible, getNext } from "./get-next";
|
import { getImpossible, getNext } from "./get-next";
|
||||||
|
|
||||||
enum Strategies {
|
enum Strategies {
|
||||||
@@ -29,7 +28,7 @@ type Status = RunningStatus | CompletedStatus;
|
|||||||
|
|
||||||
type BuildGraphOptions = {
|
type BuildGraphOptions = {
|
||||||
location: UserLocation;
|
location: UserLocation;
|
||||||
time: Date;
|
time: Time;
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
context: Context;
|
context: Context;
|
||||||
strategy?: Strategies;
|
strategy?: Strategies;
|
||||||
@@ -49,7 +48,7 @@ const fil = <T>(
|
|||||||
for (let b = 0; b < fn.length; b++) {
|
for (let b = 0; b < fn.length; b++) {
|
||||||
if (fn[b](input[i])) {
|
if (fn[b](input[i])) {
|
||||||
output[b].push(input[i]);
|
output[b].push(input[i]);
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,8 +149,10 @@ const buildGraph = async ({
|
|||||||
return complete([fullComplete]);
|
return complete([fullComplete]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (strategy !== Strategies.all) {
|
||||||
deadList.push(...dead);
|
deadList.push(...dead);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return complete(completedList);
|
return complete(completedList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { GraphNode } from "#/types/graph";
|
import { timeUtils } from "#/features/data";
|
||||||
import { PlanItem } from "#/types/plans";
|
import { GraphNode, PlannedEntry } from "../types";
|
||||||
|
|
||||||
const constructDay = (node: GraphNode) => {
|
const constructDay = (node: GraphNode) => {
|
||||||
let current: GraphNode | undefined = node;
|
let current: GraphNode | undefined = node;
|
||||||
const plans: PlanItem[] = [];
|
const plans: PlannedEntry[] = [];
|
||||||
|
|
||||||
while(current) {
|
while(current) {
|
||||||
if (current.task) {
|
if (current.task) {
|
||||||
plans.push({
|
plans.push({
|
||||||
type: 'task',
|
type: 'task',
|
||||||
name: current.task?.name || 'start',
|
name: current.task?.title || 'start',
|
||||||
external: current.task?.external,
|
start: timeUtils.add(current.time.start, (current.transition?.time || 0)),
|
||||||
start: new Date(
|
|
||||||
current.time.start.getTime()
|
|
||||||
+ (current.transition?.time || 0),
|
|
||||||
),
|
|
||||||
end: current.time.end,
|
end: current.time.end,
|
||||||
score: current.score,
|
score: current.score,
|
||||||
})
|
})
|
||||||
@@ -23,10 +19,7 @@ const constructDay = (node: GraphNode) => {
|
|||||||
plans.push({
|
plans.push({
|
||||||
type: 'transition',
|
type: 'transition',
|
||||||
start: current.time.start,
|
start: current.time.start,
|
||||||
end: new Date(
|
end: timeUtils.add(current.time.start, current.transition.time),
|
||||||
current.time.start.getTime()
|
|
||||||
+ current.transition.time,
|
|
||||||
),
|
|
||||||
from: current.transition.from,
|
from: current.transition.from,
|
||||||
to: current.transition.to,
|
to: current.transition.to,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { GraphNode, Context } from '#/types/graph';
|
import { Task, Time, timeUtils } from '#/features/data';
|
||||||
import { Transition } from '#/types/location';
|
import { Transition } from '#/features/location';
|
||||||
import { Task } from '#/types/task';
|
import { Context, GraphNode } from '../types';
|
||||||
import { getRemainingLocations, listContainLocation } from './utils';
|
import { getRemainingLocations, listContainLocation } from './utils';
|
||||||
|
|
||||||
|
const DEFAULT_PRIORITY = 50;
|
||||||
|
|
||||||
const isDead = (impossible: Task[]) => {
|
const isDead = (impossible: Task[]) => {
|
||||||
const missingRequered = impossible.find(t => t.required);
|
const missingRequered = impossible.find(t => t.required);
|
||||||
return !!missingRequered;
|
return !!missingRequered;
|
||||||
@@ -15,7 +17,7 @@ type GetImpossibleResult = {
|
|||||||
|
|
||||||
export const getImpossible = (
|
export const getImpossible = (
|
||||||
tasks: Task[],
|
tasks: Task[],
|
||||||
time: Date,
|
time: Time,
|
||||||
) => {
|
) => {
|
||||||
const result: GetImpossibleResult = {
|
const result: GetImpossibleResult = {
|
||||||
remaining: [],
|
remaining: [],
|
||||||
@@ -23,7 +25,7 @@ export const getImpossible = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let task of tasks) {
|
for (let task of tasks) {
|
||||||
if (time > task.start.max) {
|
if (timeUtils.largerThan(time, task.startTime.max)) {
|
||||||
result.impossible.push(task);
|
result.impossible.push(task);
|
||||||
} else {
|
} else {
|
||||||
result.remaining.push(task);
|
result.remaining.push(task);
|
||||||
@@ -47,17 +49,17 @@ const calculateScore = ({
|
|||||||
let score = 0;
|
let score = 0;
|
||||||
|
|
||||||
tasks?.forEach((task) => {
|
tasks?.forEach((task) => {
|
||||||
score += task.priority * 10;
|
score += (task.priority || DEFAULT_PRIORITY) * 10;
|
||||||
impossible.forEach((task) => {
|
impossible.forEach((task) => {
|
||||||
if (task.required) {
|
if (task.required) {
|
||||||
score -= 10000 + (1 * task.priority);
|
score -= 10000 + (1 * (task.priority || DEFAULT_PRIORITY));
|
||||||
} else {
|
} else {
|
||||||
score -= 100 + (1 * task.priority);
|
score -= 100 + (1 * (task.priority || DEFAULT_PRIORITY));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (transition) {
|
if (transition) {
|
||||||
const minutes = transition.time / 1000 / 60
|
const minutes = transition.time;
|
||||||
score -= 10 + (1 * minutes);
|
score -= 10 + (1 * minutes);
|
||||||
}
|
}
|
||||||
return score;
|
return score;
|
||||||
@@ -71,7 +73,7 @@ const getNext = async (
|
|||||||
const remainingLocations = getRemainingLocations(currentNode.remainingTasks, currentNode.location);
|
const remainingLocations = getRemainingLocations(currentNode.remainingTasks, currentNode.location);
|
||||||
await Promise.all(remainingLocations.map(async(location) => {
|
await Promise.all(remainingLocations.map(async(location) => {
|
||||||
const transition = await context.getTransition(currentNode.location, location, currentNode.time.end);
|
const transition = await context.getTransition(currentNode.location, location, currentNode.time.end);
|
||||||
const endTime = new Date(currentNode.time.end.getTime() + transition.time);
|
const endTime = timeUtils.add(currentNode.time.end, transition.time);
|
||||||
const { remaining, impossible } = getImpossible(currentNode.remainingTasks, endTime);
|
const { remaining, impossible } = getImpossible(currentNode.remainingTasks, endTime);
|
||||||
const score = calculateScore({
|
const score = calculateScore({
|
||||||
transition,
|
transition,
|
||||||
@@ -89,7 +91,7 @@ const getNext = async (
|
|||||||
score: currentNode.score + score,
|
score: currentNode.score + score,
|
||||||
status: {
|
status: {
|
||||||
completed: false,
|
completed: false,
|
||||||
dead: isDead(impossible),
|
dead: false, // TODO: fix isDead(impossible),
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
start: currentNode.time.end,
|
start: currentNode.time.end,
|
||||||
@@ -101,21 +103,14 @@ const getNext = async (
|
|||||||
const possibleTasks = currentNode.remainingTasks.filter(task => !task.locations || listContainLocation(task.locations, currentNode.location))
|
const possibleTasks = currentNode.remainingTasks.filter(task => !task.locations || listContainLocation(task.locations, currentNode.location))
|
||||||
await Promise.all(possibleTasks.map(async (orgTask) => {
|
await Promise.all(possibleTasks.map(async (orgTask) => {
|
||||||
const task = {...orgTask};
|
const task = {...orgTask};
|
||||||
task.count = (task.count || 1) - 1
|
let startTime =
|
||||||
let startTime = new Date(
|
timeUtils.max(
|
||||||
Math.max(
|
currentNode.time.end,
|
||||||
currentNode.time.end.getTime(),
|
task.startTime.min,
|
||||||
task.start.min.getTime(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const parentRemainging = currentNode.remainingTasks.filter(t => t !== orgTask);
|
const parentRemainging = currentNode.remainingTasks.filter(t => t !== orgTask);
|
||||||
let endTime = new Date(startTime.getTime() + task.duration.min);
|
let endTime = timeUtils.add(startTime, task.duration);
|
||||||
const { remaining, impossible } = getImpossible(
|
const { remaining, impossible } = getImpossible(parentRemainging, endTime);
|
||||||
task.count > 0
|
|
||||||
? [...parentRemainging, task]
|
|
||||||
: parentRemainging,
|
|
||||||
endTime,
|
|
||||||
);
|
|
||||||
const score = calculateScore({
|
const score = calculateScore({
|
||||||
tasks: [task],
|
tasks: [task],
|
||||||
impossible,
|
impossible,
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ export const locationEqual = (a: UserLocation, b: UserLocation) => {
|
|||||||
if (a === b) {
|
if (a === b) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (a.location === b.location) {
|
// if (a.location === b.location) {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
if (a.location && b.location && a.location.latitude === b.location.latitude && a.location.longitute === b.location.longitute) {
|
// if (a.location && b.location && a.location.latitude === b.location.latitude && a.location.longitute === b.location.longitute) {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
if (a.title === b.title) {
|
if (a.title === b.title) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { createDataContext } from '#/utils/data-context';
|
import { createDataContext } from '#/utils/data-context';
|
||||||
|
import { Time } from '../data';
|
||||||
import { Strategies } from "./algorithm/build-graph";
|
import { Strategies } from "./algorithm/build-graph";
|
||||||
|
|
||||||
type PlannerOptions = {
|
type PlannerOptions = {
|
||||||
strategy: Strategies;
|
strategy: Strategies;
|
||||||
|
startTime: Time;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -10,6 +12,7 @@ const {
|
|||||||
Provider: PlannerProvider,
|
Provider: PlannerProvider,
|
||||||
} = createDataContext<PlannerOptions>({
|
} = createDataContext<PlannerOptions>({
|
||||||
createDefault: () => ({
|
createDefault: () => ({
|
||||||
|
startTime: { hour: 7, minute: 0 },
|
||||||
strategy: Strategies.firstComplet,
|
strategy: Strategies.firstComplet,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +1,129 @@
|
|||||||
import { buildGraph, Status, Strategies } from "./algorithm/build-graph";
|
import { buildGraph, Status, Strategies } from "./algorithm/build-graph";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { PlanItem } from "#/types/plans";
|
import { add } from 'date-fns';
|
||||||
import { PlannerContext } from "./context";
|
import { PlannerContext } from "./context";
|
||||||
import { Task, UserLocation } from "../data";
|
import { Task, Time, UserLocation } from "../data";
|
||||||
|
import { useRoutines } from "../routines";
|
||||||
|
import { useGoals } from "../goals/hooks";
|
||||||
|
import { useAsyncCallback } from "../async";
|
||||||
|
import { Day, dayUtils } from "../day";
|
||||||
|
import { useGetOverride } from "../overrides";
|
||||||
|
import { useGetAppointments } from "../appointments";
|
||||||
|
import { useGetTransition } from "../location";
|
||||||
|
import { PlannedEntry } from "./types";
|
||||||
|
import { constructDay } from "./algorithm/construct-day";
|
||||||
|
|
||||||
export type UsePlanOptions = {
|
export type PreparePlanOptions = {
|
||||||
|
start: Day;
|
||||||
|
end: Day;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlanOptions = PreparePlanOptions & {
|
||||||
location: UserLocation;
|
location: UserLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UsePlan = [
|
export type PlanResultDay = {
|
||||||
(start?: Date) => Promise<any>,
|
day: Day;
|
||||||
{
|
start: Time;
|
||||||
result?: { agenda: PlanItem[], impossible: Task[] };
|
} & ({
|
||||||
status?: Status;
|
status: 'waiting',
|
||||||
loading: boolean;
|
} | {
|
||||||
error?: any;
|
status: 'running',
|
||||||
|
nodes: number;
|
||||||
|
strategy: Strategies;
|
||||||
|
} | {
|
||||||
|
status: 'done';
|
||||||
|
nodes: number;
|
||||||
|
strategy: Strategies;
|
||||||
|
plan: PlannedEntry[];
|
||||||
|
impossible: Task[];
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PlanResult = {
|
||||||
|
impossible: Task[];
|
||||||
|
days: {
|
||||||
|
[day: string]: PlanResultDay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDays = (start: Day, end: Day): Day[] => {
|
||||||
|
const result: Day[] = [];
|
||||||
|
let currentDate = dayUtils.dayToDate(start);
|
||||||
|
const stopDate = dayUtils.dayToDate(end);
|
||||||
|
while (currentDate <= stopDate) {
|
||||||
|
result.push(dayUtils.dateToDay(currentDate));
|
||||||
|
currentDate = add(currentDate, {
|
||||||
|
days: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstValue = <T>(...args: (T | undefined)[]): T => {
|
||||||
|
for (let arg of args) {
|
||||||
|
if (typeof arg !== 'undefined') {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined as unknown as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOptions = () => {
|
||||||
|
const { data } = useContext(PlannerContext);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSetOptions = () => {
|
||||||
|
const { setData } = useContext(PlannerContext);
|
||||||
|
return setData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usePreparePlan = () => {
|
||||||
|
const routines = useRoutines();
|
||||||
|
const goals = useGoals();
|
||||||
|
const getOverrides = useGetOverride();
|
||||||
|
const [getAppontments] = useGetAppointments();
|
||||||
|
|
||||||
|
const preparePlan = useAsyncCallback(
|
||||||
|
async ({ start, end }: PreparePlanOptions) => {
|
||||||
|
const days = await Promise.all(getDays(start, end).map(async (day) => {
|
||||||
|
const overrides = await getOverrides(day);
|
||||||
|
const start: Time = firstValue(overrides.startTime, { hour: 7, minute: 0 });
|
||||||
|
const appointments = await getAppontments(day);
|
||||||
|
const tasks = [...routines, ...appointments].map<Task | undefined>((task) => {
|
||||||
|
const override = overrides.tasks[task.id];
|
||||||
|
if (override?.enabled === false) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const result: Task = {
|
||||||
|
...task,
|
||||||
|
startTime: {
|
||||||
|
min: firstValue(override?.startMin, task.startTime.min),
|
||||||
|
max: firstValue(override?.startMax, task.startTime.max),
|
||||||
|
},
|
||||||
|
duration: firstValue(override?.duration, task.duration),
|
||||||
|
required: firstValue(override?.required, task.required),
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}).filter(Boolean).map(a => a as Exclude<typeof a, undefined>);
|
||||||
|
|
||||||
|
return {
|
||||||
|
day,
|
||||||
|
start,
|
||||||
|
tasks,
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
goals: [...goals],
|
||||||
|
days,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[routines, goals, getOverrides, getAppontments],
|
||||||
|
);
|
||||||
|
|
||||||
|
return preparePlan;
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
|
||||||
export const usePlanOptions = () => {
|
export const usePlanOptions = () => {
|
||||||
const { data } = useContext(PlannerContext);
|
const { data } = useContext(PlannerContext);
|
||||||
@@ -29,6 +136,87 @@ export const useSetPlanOptions = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const usePlan = () => {
|
export const usePlan = () => {
|
||||||
|
const [preparePlan] = usePreparePlan();
|
||||||
|
const getTransition = useGetTransition();
|
||||||
|
const options = usePlanOptions();
|
||||||
|
const createPlan = useAsyncCallback(
|
||||||
|
async ({ location, ...prepareOptions}: PlanOptions) => {
|
||||||
|
const prepared = await preparePlan(prepareOptions);
|
||||||
|
let result: PlanResult = {
|
||||||
|
impossible: [],
|
||||||
|
days: prepared.days.reduce((output, current) => ({
|
||||||
|
...output,
|
||||||
|
[dayUtils.toId(current.day)]: {
|
||||||
|
day: current.day,
|
||||||
|
start: current.start,
|
||||||
|
status: 'waiting',
|
||||||
|
},
|
||||||
|
}), {} as {[name: string]: PlanResultDay})
|
||||||
|
}
|
||||||
|
const update = (next: PlanResult) => {
|
||||||
|
result = next;
|
||||||
|
}
|
||||||
|
for (let day of prepared.days) {
|
||||||
|
const id = dayUtils.toId(day.day);
|
||||||
|
const dayGoal = prepared.goals;
|
||||||
|
const graph = await buildGraph({
|
||||||
|
location,
|
||||||
|
time: day.start,
|
||||||
|
tasks: [...day.tasks, ...dayGoal],
|
||||||
|
strategy: options.strategy,
|
||||||
|
context: {
|
||||||
|
getTransition,
|
||||||
|
},
|
||||||
|
callback: (status) => {
|
||||||
|
update({
|
||||||
|
...result,
|
||||||
|
days: {
|
||||||
|
...result.days,
|
||||||
|
[id]: {
|
||||||
|
day: day.day,
|
||||||
|
start: day.start,
|
||||||
|
status: 'running',
|
||||||
|
nodes: status.nodes,
|
||||||
|
strategy: status.strategy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const [winner] = graph;
|
||||||
|
if (!winner) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const plan = constructDay(winner);
|
||||||
|
update({
|
||||||
|
...result,
|
||||||
|
days: {
|
||||||
|
...result.days,
|
||||||
|
[id]: {
|
||||||
|
...result.days[id],
|
||||||
|
impossible: winner.impossibeTasks,
|
||||||
|
status: 'done',
|
||||||
|
plan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
prepared.goals = prepared.goals.filter((goal) => {
|
||||||
|
if (!dayGoal.find(d => d.id === goal.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!winner.impossibeTasks.find(d => d.id === goal.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
impossible: prepared.goals,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[preparePlan, getTransition, options],
|
||||||
|
);
|
||||||
|
return createPlan;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Task, Time, UserLocation } from "../data";
|
||||||
|
import { GetTransition, Transition } from "../location";
|
||||||
|
|
||||||
type Context = {
|
type Context = {
|
||||||
getTransition: GetTransition;
|
getTransition: GetTransition;
|
||||||
@@ -6,20 +8,22 @@ type Context = {
|
|||||||
export type PlannedTask = {
|
export type PlannedTask = {
|
||||||
type: 'task';
|
type: 'task';
|
||||||
name: string;
|
name: string;
|
||||||
start: Date;
|
start: Time;
|
||||||
external?: boolean;
|
external?: boolean;
|
||||||
end: Date;
|
end: Time;
|
||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlannedTransition = {
|
export type PlannedTransition = {
|
||||||
type: 'transition';
|
type: 'transition';
|
||||||
start: Date;
|
start: Time;
|
||||||
end: Date;
|
end: Time;
|
||||||
from: UserLocation;
|
from: UserLocation;
|
||||||
to: UserLocation;
|
to: UserLocation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PlannedEntry = PlannedTask | PlannedTransition;
|
||||||
|
|
||||||
type GraphNode = {
|
type GraphNode = {
|
||||||
location: UserLocation;
|
location: UserLocation;
|
||||||
task?: Task;
|
task?: Task;
|
||||||
@@ -29,8 +33,8 @@ type GraphNode = {
|
|||||||
impossibeTasks: Task[];
|
impossibeTasks: Task[];
|
||||||
score: number;
|
score: number;
|
||||||
time: {
|
time: {
|
||||||
start: Date;
|
start: Time;
|
||||||
end: Date;
|
end: Time;
|
||||||
};
|
};
|
||||||
status: {
|
status: {
|
||||||
dead: boolean;
|
dead: boolean;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useMemo } from "react";
|
|||||||
import { useAppointments } from "../appointments";
|
import { useAppointments } from "../appointments";
|
||||||
import { useAsyncCallback } from "../async";
|
import { useAsyncCallback } from "../async";
|
||||||
import { Task, TaskType } from "../data";
|
import { Task, TaskType } from "../data";
|
||||||
import { useGoals, useSetGoals } from "../goals/hooks";
|
import { useGoals, useRemoveGoal, useSetGoals } from "../goals/hooks";
|
||||||
import { useRoutines, useSetRoutine } from "../routines";
|
import { useRemoveRoutine, useRoutines, useSetRoutine } from "../routines";
|
||||||
|
|
||||||
export const useTasks = (type?: TaskType) => {
|
export const useTasks = (type?: TaskType) => {
|
||||||
const [appointments] = useAppointments();
|
const [appointments] = useAppointments();
|
||||||
@@ -44,3 +44,20 @@ export const useSetTask = () => {
|
|||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useRemoveTask = () => {
|
||||||
|
const removeRoutine = useRemoveRoutine();
|
||||||
|
const removeGoal = useRemoveGoal();
|
||||||
|
|
||||||
|
const result = useAsyncCallback(
|
||||||
|
async (task: Task) => {
|
||||||
|
if (task.type === TaskType.routine) {
|
||||||
|
removeRoutine(task.id);
|
||||||
|
} else if (task.type === TaskType.goal) {
|
||||||
|
removeGoal(task.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[removeRoutine, removeGoal],
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ export * from './popup';
|
|||||||
export * from './row';
|
export * from './row';
|
||||||
export * from './button';
|
export * from './button';
|
||||||
export * from './group';
|
export * from './group';
|
||||||
|
export * from './list';
|
||||||
|
|||||||
53
src/ui/components/base/list/index.tsx
Normal file
53
src/ui/components/base/list/index.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { FlatList } from "react-native";
|
||||||
|
import { Button } from "../button";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { Cell, Row, RowProps } from "../row";
|
||||||
|
|
||||||
|
type ListProps<T> = {
|
||||||
|
add?: () => void;
|
||||||
|
remove?: (item: T) => any;
|
||||||
|
getKey: (item: T) => string;
|
||||||
|
items: T[];
|
||||||
|
render: (item: T) => RowProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
function List<T>({
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
getKey,
|
||||||
|
items,
|
||||||
|
render,
|
||||||
|
}: ListProps<T>) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!!add && <Button title="Add" onPress={add}/>}
|
||||||
|
<FlatList
|
||||||
|
data={items}
|
||||||
|
keyExtractor={item => getKey(item)}
|
||||||
|
renderItem={({ item }) => {
|
||||||
|
const {right, ...props} = render(item);
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
{...props}
|
||||||
|
right={(
|
||||||
|
<>
|
||||||
|
{right}
|
||||||
|
{!!remove && (
|
||||||
|
<Cell onPress={() => remove(item)}>
|
||||||
|
<Icon
|
||||||
|
name="trash"
|
||||||
|
color="destructive"
|
||||||
|
/>
|
||||||
|
</Cell>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { List };
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode, useCallback, useRef } from 'react';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
import { Row, Cell, RowProps } from '../row';
|
import { Row, Cell, RowProps } from '../row';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
|
import { ScrollView } from 'react-native';
|
||||||
|
|
||||||
type Props = RowProps & {
|
type Props = RowProps & {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
@@ -54,7 +55,9 @@ const Popup: React.FC<Props> = ({ children, onClose, right, ...rowProps }) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Content>
|
<Content
|
||||||
|
alwaysBounceVertical={false}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Content>
|
</Content>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ const Wrapper = styled.View<{
|
|||||||
${({ opacity }) => (opacity? `opacity: ${opacity};` : '')}
|
${({ opacity }) => (opacity? `opacity: ${opacity};` : '')}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Touch = styled.TouchableOpacity``;
|
const Touch = styled.TouchableOpacity`
|
||||||
|
`;
|
||||||
|
|
||||||
const Cell: React.FC<CellProps> = ({ children, onPress, ...props}) => {
|
const Cell: React.FC<CellProps> = ({ children, onPress, ...props}) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Title1, Body1, Overline } from '#/ui/typography';
|
|||||||
import { Cell, CellProps } from './cell';
|
import { Cell, CellProps } from './cell';
|
||||||
|
|
||||||
type RowProps = CellProps & {
|
type RowProps = CellProps & {
|
||||||
background?: string;
|
|
||||||
top?: ReactNode;
|
top?: ReactNode;
|
||||||
left?: ReactNode;
|
left?: ReactNode;
|
||||||
right?: ReactNode;
|
right?: ReactNode;
|
||||||
@@ -12,8 +11,6 @@ type RowProps = CellProps & {
|
|||||||
overline?: ReactNode;
|
overline?: ReactNode;
|
||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
opacity?: number;
|
|
||||||
onPress?: () => any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Children = styled.View``;
|
const Children = styled.View``;
|
||||||
@@ -32,19 +29,16 @@ const componentOrString = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Row: React.FC<RowProps> = ({
|
const Row: React.FC<RowProps> = ({
|
||||||
background,
|
|
||||||
top,
|
top,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
title,
|
title,
|
||||||
opacity,
|
|
||||||
overline,
|
overline,
|
||||||
description,
|
description,
|
||||||
children,
|
children,
|
||||||
onPress,
|
|
||||||
...cellProps
|
...cellProps
|
||||||
}) => (
|
}) => (
|
||||||
<Cell {...cellProps} background={background} opacity={opacity} onPress={onPress}>
|
<Cell {...cellProps}>
|
||||||
{left}
|
{left}
|
||||||
<Cell flex={1} direction="column" align="stretch">
|
<Cell flex={1} direction="column" align="stretch">
|
||||||
{!!top}
|
{!!top}
|
||||||
|
|||||||
64
src/ui/components/form/date/index.tsx
Normal file
64
src/ui/components/form/date/index.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Calendar } from 'react-native-calendars';
|
||||||
|
import { useTheme } from 'styled-components/native';
|
||||||
|
import { Row, Button, Modal } from '#/ui/components/base';
|
||||||
|
import { Day, dayUtils } from '#/features/day';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string;
|
||||||
|
selected?: Day;
|
||||||
|
} & ({
|
||||||
|
allowClear: true,
|
||||||
|
onSelect: (input?: Day) => void;
|
||||||
|
} | {
|
||||||
|
allowClear?: false,
|
||||||
|
onSelect: (input: Day) => void;
|
||||||
|
})
|
||||||
|
|
||||||
|
const DateInput: React.FC<Props> = ({ label, selected, onSelect, allowClear }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const marked: any = {};
|
||||||
|
if (selected) {
|
||||||
|
marked[dayUtils.toId(selected)] = {
|
||||||
|
selected: true,
|
||||||
|
marked: true,
|
||||||
|
selectedColor: theme.colors.primary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
overline={label}
|
||||||
|
onPress={() => setVisible(true)}
|
||||||
|
title={selected ? dayUtils.toId(selected) : 'Not set'}
|
||||||
|
>
|
||||||
|
<Modal visible={visible} onClose={() => setVisible(false)}>
|
||||||
|
{visible && (<Calendar
|
||||||
|
showWeekNumbers={true}
|
||||||
|
markedDates={marked}
|
||||||
|
hideArrows={false}
|
||||||
|
enableSwipeMonths={true}
|
||||||
|
onDayPress={({ year, month, day }) => {
|
||||||
|
onSelect({ year, month, date: day });
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
current={selected ? dayUtils.toId(selected) : undefined}
|
||||||
|
/>)}
|
||||||
|
{allowClear && (
|
||||||
|
<Row>
|
||||||
|
<Button
|
||||||
|
title="Clear"
|
||||||
|
onPress={() => {
|
||||||
|
onSelect(undefined as any);
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DateInput;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './input';
|
export * from './input';
|
||||||
export * from './checkbox';
|
export * from './checkbox';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
|
export * from './date';
|
||||||
export * from './optional-selector';
|
export * from './optional-selector';
|
||||||
|
|||||||
@@ -20,32 +20,13 @@ const TimeField = styled.TextInput`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TimeInput: React.FC<Props> = ({ label, placeholder, value, onChange, children, ...row }) => {
|
const TimeInput: React.FC<Props> = ({ label, placeholder, value, onChange, children, ...row }) => {
|
||||||
const [innerValue, setValue] = useState(value ? timeUtils.timeToString(value) : '');
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!innerValue && value) {
|
|
||||||
onChange(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const parsed = timeUtils.stringToTime(innerValue)
|
|
||||||
if (!parsed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value && timeUtils.equal(parsed, value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onChange(parsed);
|
|
||||||
},
|
|
||||||
[innerValue, value, onChange],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row overline={label} {...row}>
|
<Row overline={label} {...row}>
|
||||||
<TimeField
|
<TimeField
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={innerValue}
|
value={value ? timeUtils.timeToString(value) : ''}
|
||||||
onChangeText={setValue}
|
onChangeText={(text) => onChange(text ? timeUtils.stringToTime(text) : undefined)}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
30
src/ui/components/plan/day/index.tsx
Normal file
30
src/ui/components/plan/day/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { dayUtils } from "#/features/day";
|
||||||
|
import { PlanResultDay } from "#/features/planner"
|
||||||
|
import { Body1, Jumbo } from "#/ui/typography";
|
||||||
|
import { PlanDayTask } from "./task";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
day: PlanResultDay;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PlanDay: React.FC<Props> = ({ day }) => {
|
||||||
|
if (day.status === 'waiting') {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
if (day.status === 'running') {
|
||||||
|
return <Body1>Running</Body1>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Jumbo>{dayUtils.toId(day.day)}</Jumbo>
|
||||||
|
{day.plan.map((item) => {
|
||||||
|
if (item.type === 'task') {
|
||||||
|
return <PlanDayTask task={item} />
|
||||||
|
}
|
||||||
|
return <Body1>Transit {item.from.title} to {item.to.title}</Body1>
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PlanDay };
|
||||||
91
src/ui/components/plan/day/task.tsx
Normal file
91
src/ui/components/plan/day/task.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { PlannedTask } from "#/features/planner/types";
|
||||||
|
import chroma from 'chroma-js';
|
||||||
|
import styled from "styled-components/native";
|
||||||
|
import stringToColor from 'string-to-color';
|
||||||
|
import { timeUtils } from '#/features/data';
|
||||||
|
import { Body1 } from '#/ui/typography';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
task: PlannedTask;
|
||||||
|
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 PlanDayTask: React.FC<Props> = ({ task, onPress }) => {
|
||||||
|
const color = useMemo(
|
||||||
|
() => chroma(stringToColor(task.name)).luminance(0.7).saturate(1).brighten(0.6).hex(),
|
||||||
|
[task.name],
|
||||||
|
);
|
||||||
|
const height = useMemo(
|
||||||
|
() => timeUtils.timeToMinutes(task.end) - timeUtils.timeToMinutes(task.start),
|
||||||
|
[task.start, task.end],
|
||||||
|
);
|
||||||
|
const view = (
|
||||||
|
<Block height={Math.max(70, height * 10)} background={color}>
|
||||||
|
<TimeBox background={color}>
|
||||||
|
<Time background={color}>{timeUtils.timeToString(task.start)}</Time>
|
||||||
|
<Time background={color}>{timeUtils.timeToString(task.end)}</Time>
|
||||||
|
</TimeBox>
|
||||||
|
<Main>
|
||||||
|
<Body1>{task.name}</Body1>
|
||||||
|
</Main>
|
||||||
|
<Filler />
|
||||||
|
</Block>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (onPress) {
|
||||||
|
return (
|
||||||
|
<Touch onPress={onPress}>
|
||||||
|
{view}
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { PlanDayTask };
|
||||||
1
src/ui/components/plan/index.ts
Normal file
1
src/ui/components/plan/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './day';
|
||||||
@@ -33,7 +33,7 @@ const TaskGroup: React.FC<Props> = ({ type }) => {
|
|||||||
<TaskListItem
|
<TaskListItem
|
||||||
item={task}
|
item={task}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigate('add-task', { id: task.id });
|
navigate('set-override', { id: task.id });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import { Platform } from 'react-native';
|
|||||||
import { MoreScreen } from '../screens/more';
|
import { MoreScreen } from '../screens/more';
|
||||||
import { LocationListScreen } from '../screens/locations/list';
|
import { LocationListScreen } from '../screens/locations/list';
|
||||||
import { LocationSetScreen } from '../screens/locations/set';
|
import { LocationSetScreen } from '../screens/locations/set';
|
||||||
|
import { TaskListScreen } from '../screens/task/list';
|
||||||
|
import { OverrideSetScreen } from '../screens/task/overrides';
|
||||||
|
import { PlanScreen } from '../screens/plan';
|
||||||
|
|
||||||
const MoreStackNavigator = createNativeStackNavigator();
|
const MoreStackNavigator = createNativeStackNavigator();
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ const MoreStack: React.FC = () => (
|
|||||||
<MoreStackNavigator.Navigator>
|
<MoreStackNavigator.Navigator>
|
||||||
<MoreStackNavigator.Screen name="more-main" component={MoreScreen} />
|
<MoreStackNavigator.Screen name="more-main" component={MoreScreen} />
|
||||||
<MoreStackNavigator.Screen name="locations" component={LocationListScreen} />
|
<MoreStackNavigator.Screen name="locations" component={LocationListScreen} />
|
||||||
|
<MoreStackNavigator.Screen name="tasks" component={TaskListScreen} />
|
||||||
</MoreStackNavigator.Navigator>
|
</MoreStackNavigator.Navigator>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -41,6 +45,15 @@ const MainTabs: React.FC = () => {
|
|||||||
name="day"
|
name="day"
|
||||||
component={DayScreen}
|
component={DayScreen}
|
||||||
/>
|
/>
|
||||||
|
<MainTabsNvaigator.Screen
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
tabBarLabel: 'Plan',
|
||||||
|
tabBarIcon: ({ focused }) => <Icon color={focused ? 'primary' : 'text'} name="check-square" />,
|
||||||
|
}}
|
||||||
|
name="plan"
|
||||||
|
component={PlanScreen}
|
||||||
|
/>
|
||||||
<MainTabsNvaigator.Screen
|
<MainTabsNvaigator.Screen
|
||||||
options={{
|
options={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
@@ -66,6 +79,7 @@ const Root: React.FC = () => (
|
|||||||
<RootNavigator.Group screenOptions={{ presentation: 'transparentModal' }}>
|
<RootNavigator.Group screenOptions={{ presentation: 'transparentModal' }}>
|
||||||
<RootNavigator.Screen name="add-task" component={TaskAddScreen} />
|
<RootNavigator.Screen name="add-task" component={TaskAddScreen} />
|
||||||
<RootNavigator.Screen name="set-location" component={LocationSetScreen} />
|
<RootNavigator.Screen name="set-location" component={LocationSetScreen} />
|
||||||
|
<RootNavigator.Screen name="set-override" component={OverrideSetScreen} />
|
||||||
</RootNavigator.Group>
|
</RootNavigator.Group>
|
||||||
</RootNavigator.Navigator>
|
</RootNavigator.Navigator>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,27 @@ export type RootStackParamList = {
|
|||||||
'set-location': {
|
'set-location': {
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
|
'set-override': {
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MainTabParamList = {
|
||||||
|
day: NavigatorScreenParams<RootStackParamList>;
|
||||||
|
plan: NavigatorScreenParams<RootStackParamList>;
|
||||||
|
more: NavigatorScreenParams<RootStackParamList>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MoreStackParamList = {
|
||||||
|
locations: undefined;
|
||||||
|
tasks: {
|
||||||
|
type: TaskType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MoreScreenNavigationProps = NativeStackNavigationProp<
|
||||||
|
MoreStackParamList
|
||||||
|
>;
|
||||||
|
|
||||||
|
|
||||||
export type RootRouteProp = RouteProp<RootStackParamList>;
|
export type RootRouteProp = RouteProp<RootStackParamList>;
|
||||||
@@ -26,9 +46,6 @@ export type TaskAddScreenNavigationProp = NativeStackNavigationProp<
|
|||||||
'add-task'
|
'add-task'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type MainTabParamList = {
|
export type TaskListScreenRouteProp = RouteProp<MoreStackParamList, 'tasks'>;
|
||||||
day: NavigatorScreenParams<RootStackParamList>;
|
|
||||||
more: NavigatorScreenParams<RootStackParamList>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DayScreenRouteProp = RouteProp<MainTabParamList, 'day'>;
|
export type DayScreenRouteProp = RouteProp<MainTabParamList, 'day'>;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useLocations, useRemoveLocation } from "#/features/location"
|
import { useLocations, useRemoveLocation } from "#/features/location"
|
||||||
import { Button, Cell, Icon, Page, Row } from "#/ui/components/base";
|
import { List, Page } from "#/ui/components/base";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import { FlatList } from "react-native";
|
|
||||||
|
|
||||||
const LocationListScreen: React.FC = () => {
|
const LocationListScreen: React.FC = () => {
|
||||||
const { navigate } = useNavigation();
|
const { navigate } = useNavigation();
|
||||||
@@ -10,23 +9,14 @@ const LocationListScreen: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Button title="Add" onPress={() => navigate('set-location', {})}/>
|
<List
|
||||||
<FlatList
|
items={locations}
|
||||||
data={locations}
|
remove={l => removeLocation(l.id)}
|
||||||
keyExtractor={item => item.id}
|
getKey={l => l.id}
|
||||||
renderItem={({ item }) => (
|
add={() => navigate('set-location', {})}
|
||||||
<Row
|
render={(item) => ({
|
||||||
title={item.title}
|
title: item.title,
|
||||||
right={(
|
})}
|
||||||
<Cell onPress={() => removeLocation(item.id)}>
|
|
||||||
<Icon
|
|
||||||
name="trash"
|
|
||||||
color="destructive"
|
|
||||||
/>
|
|
||||||
</Cell>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { TaskType } from "#/features/data";
|
||||||
import { Page, Row } from "#/ui/components/base";
|
import { Page, Row } from "#/ui/components/base";
|
||||||
|
import { MoreScreenNavigationProps } from "#/ui/router";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
|
|
||||||
const MoreScreen: React.FC = () => {
|
const MoreScreen: React.FC = () => {
|
||||||
const { navigate } = useNavigation();
|
const { navigate } = useNavigation<MoreScreenNavigationProps>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
@@ -15,11 +17,11 @@ const MoreScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
title="Routines"
|
title="Routines"
|
||||||
onPress={() => navigate('routines')}
|
onPress={() => navigate('tasks', { type: TaskType.routine })}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
title="Goals"
|
title="Goals"
|
||||||
onPress={() => navigate('goals')}
|
onPress={() => navigate('tasks', { type: TaskType.goal })}
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|||||||
57
src/ui/screens/plan/index.tsx
Normal file
57
src/ui/screens/plan/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Day, dayUtils, useDate } from "#/features/day"
|
||||||
|
import { usePlan } from "#/features/planner";
|
||||||
|
import { Button } from "#/ui/components/base";
|
||||||
|
import DateInput from "#/ui/components/form/date";
|
||||||
|
import { PlanDay } from "#/ui/components/plan";
|
||||||
|
import { Body1 } from "#/ui/typography";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import styled from "styled-components/native";
|
||||||
|
|
||||||
|
const Scroll = styled.ScrollView`
|
||||||
|
flex: 1;
|
||||||
|
`
|
||||||
|
const Wrapper = styled.View`
|
||||||
|
margin: 60px 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PlanScreen: React.FC = () => {
|
||||||
|
const [start, setStart] = useState<Day>(dayUtils.today());
|
||||||
|
const [end, setEnd] = useState<Day>(dayUtils.today());
|
||||||
|
const [plan, { result }] = usePlan();
|
||||||
|
|
||||||
|
const runPlanning = useCallback(
|
||||||
|
() => plan({
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
location: { id: 'sdf', title: 'sdf' },
|
||||||
|
}),
|
||||||
|
[start, end, plan],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Scroll>
|
||||||
|
<Wrapper>
|
||||||
|
<DateInput
|
||||||
|
label="Start date"
|
||||||
|
selected={start}
|
||||||
|
onSelect={setStart}
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
label="End date"
|
||||||
|
selected={end}
|
||||||
|
onSelect={setEnd}
|
||||||
|
/>
|
||||||
|
<Button onPress={runPlanning} title="Plan" />
|
||||||
|
{!!result && (
|
||||||
|
<>
|
||||||
|
{Object.entries(result.days).map(([key, day]) => (
|
||||||
|
<PlanDay day={day} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
</Scroll>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PlanScreen }
|
||||||
@@ -47,13 +47,14 @@ const TaskAddScreen: React.FC = () => {
|
|||||||
const [selectedLocations, setSelectedLocations] = useState<UserLocation[]>([]);
|
const [selectedLocations, setSelectedLocations] = useState<UserLocation[]>([]);
|
||||||
const [hasDays, setHasDays] = useState(false);
|
const [hasDays, setHasDays] = useState(false);
|
||||||
const [selectedDays, setSelectedDays] = useState<typeof days>([]);
|
const [selectedDays, setSelectedDays] = useState<typeof days>([]);
|
||||||
|
const [required, setRequired] = useState(false);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const current = tasks.find(t => t.id);
|
const current = tasks.find(t => t.id === id);
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,7 @@ const TaskAddScreen: React.FC = () => {
|
|||||||
setHasLocation(!!current.locations);
|
setHasLocation(!!current.locations);
|
||||||
setSelectedLocations(current.locations || []);
|
setSelectedLocations(current.locations || []);
|
||||||
setCurrentType(current.type || TaskType.goal);
|
setCurrentType(current.type || TaskType.goal);
|
||||||
|
setRequired(current.required);
|
||||||
if (current.type === TaskType.goal || current.type === TaskType.routine) {
|
if (current.type === TaskType.goal || current.type === TaskType.routine) {
|
||||||
setHasDays(!!current.days);
|
setHasDays(!!current.days);
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,7 @@ const TaskAddScreen: React.FC = () => {
|
|||||||
id: currentId,
|
id: currentId,
|
||||||
title,
|
title,
|
||||||
type: currentType,
|
type: currentType,
|
||||||
required: true,
|
required,
|
||||||
startTime: {
|
startTime: {
|
||||||
max: maxStart!,
|
max: maxStart!,
|
||||||
min: minStart!,
|
min: minStart!,
|
||||||
@@ -103,11 +105,12 @@ const TaskAddScreen: React.FC = () => {
|
|||||||
selectedLocations,
|
selectedLocations,
|
||||||
hasDays,
|
hasDays,
|
||||||
selectedDays,
|
selectedDays,
|
||||||
|
required,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popup title={`Add ${type}`} onClose={goBack}>
|
<Popup title={type ? `Add ${type}` : `Update ${title}`} onClose={goBack}>
|
||||||
<Group title="Basic">
|
<Group title="Basic">
|
||||||
<TextInput label="Title" value={title} onChangeText={setTitle} />
|
<TextInput label="Title" value={title} onChangeText={setTitle} />
|
||||||
<SideBySide>
|
<SideBySide>
|
||||||
@@ -150,10 +153,7 @@ const TaskAddScreen: React.FC = () => {
|
|||||||
disabledText="Any day"
|
disabledText="Any day"
|
||||||
enabledText="Specific days"
|
enabledText="Specific days"
|
||||||
/>
|
/>
|
||||||
<SideBySide>
|
<Checkbok label="Required" onChange={setRequired} value={required} />
|
||||||
<Checkbok label="Required" flex={1} />
|
|
||||||
<TextInput label="Priority" flex={1} />
|
|
||||||
</SideBySide>
|
|
||||||
{type === TaskType.goal && (
|
{type === TaskType.goal && (
|
||||||
<SideBySide>
|
<SideBySide>
|
||||||
<TextInput label="Start" flex={1} />
|
<TextInput label="Start" flex={1} />
|
||||||
|
|||||||
32
src/ui/screens/task/list.tsx
Normal file
32
src/ui/screens/task/list.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useRemoveTask, useTasks } from "#/features/tasks";
|
||||||
|
import { List, Page } from "#/ui/components/base";
|
||||||
|
import { RootNavigationProp, TaskListScreenRouteProp } from "#/ui/router";
|
||||||
|
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||||
|
|
||||||
|
const TaskListScreen: React.FC = () => {
|
||||||
|
const {
|
||||||
|
params: { type },
|
||||||
|
} = useRoute<TaskListScreenRouteProp>();
|
||||||
|
const { navigate } = useNavigation<RootNavigationProp>();
|
||||||
|
const tasks = useTasks(type);
|
||||||
|
const [removeTask] = useRemoveTask();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<List
|
||||||
|
items={tasks}
|
||||||
|
remove={removeTask}
|
||||||
|
getKey={l => l.id}
|
||||||
|
add={() => navigate('add-task', { type })}
|
||||||
|
render={(item) => ({
|
||||||
|
title: item.title,
|
||||||
|
onPress: () => {
|
||||||
|
navigate('add-task', { id: item.id })
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TaskListScreen };
|
||||||
171
src/ui/screens/task/overrides.tsx
Normal file
171
src/ui/screens/task/overrides.tsx
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import { useAsyncCallback } from "#/features/async";
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { Task, TaskType, Time, timeUtils, UserLocation } from "#/features/data";
|
||||||
|
import { useLocations } from "#/features/location";
|
||||||
|
import { useSetTask, useTasks } from "#/features/tasks";
|
||||||
|
import { Button, Cell, Group, Popup, Row } from "#/ui/components/base"
|
||||||
|
import { Checkbok, TextInput, TimeInput, OptionalSelector } from "#/ui/components/form";
|
||||||
|
import { RootNavigationProp, TaskAddScreenRouteProp } from "#/ui/router";
|
||||||
|
import { Overline } from "#/ui/typography";
|
||||||
|
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||||
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import styled from "styled-components/native";
|
||||||
|
import { Override, useClearTaskOverride, useOverrides, useSetOverride, useSetTaskOverride } from "#/features/overrides";
|
||||||
|
|
||||||
|
const SideBySide = styled.View`
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const dayNames = [
|
||||||
|
'Monday',
|
||||||
|
'Tuesday',
|
||||||
|
'Wednsday',
|
||||||
|
'Thursday',
|
||||||
|
'Friday',
|
||||||
|
'Saturday',
|
||||||
|
'Sunday',
|
||||||
|
]
|
||||||
|
|
||||||
|
const days = new Array(7).fill(undefined).map((_, i) => ({
|
||||||
|
id: i,
|
||||||
|
name: dayNames[i],
|
||||||
|
}))
|
||||||
|
|
||||||
|
const OverrideSetScreen: React.FC = () => {
|
||||||
|
const { params: { id }} = useRoute<TaskAddScreenRouteProp>();
|
||||||
|
const { navigate, goBack } = useNavigation<RootNavigationProp>();
|
||||||
|
const [setOverride] = useSetTaskOverride()
|
||||||
|
const [clearOverrides] = useClearTaskOverride();
|
||||||
|
const overrides = useOverrides();
|
||||||
|
const tasks = useTasks();
|
||||||
|
const task = useMemo(
|
||||||
|
() => tasks.find(t => t.id === id)!,
|
||||||
|
[tasks, id],
|
||||||
|
);
|
||||||
|
|
||||||
|
const locations = useLocations();
|
||||||
|
const [maxStart, setMaxStart] = useState<Time>();
|
||||||
|
const [minStart, setMinStart] = useState<Time>();
|
||||||
|
const [duration, setDuration] = useState('');
|
||||||
|
const [hasLocation, setHasLocation] = useState(false);
|
||||||
|
const [selectedLocations, setSelectedLocations] = useState<UserLocation[]>([]);
|
||||||
|
const [hasDays, setHasDays] = useState(false);
|
||||||
|
const [selectedDays, setSelectedDays] = useState<typeof days>([]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const current = overrides.tasks[id];
|
||||||
|
if (!current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMinStart(current.startMin);
|
||||||
|
setMaxStart(current.startMax);
|
||||||
|
setDuration(current.duration?.toString() || '');
|
||||||
|
setHasLocation(!!current.locations);
|
||||||
|
setSelectedLocations(current.locations || []);
|
||||||
|
},
|
||||||
|
[id],
|
||||||
|
)
|
||||||
|
|
||||||
|
const [save] = useAsyncCallback(
|
||||||
|
async () => {
|
||||||
|
const override: Override = {
|
||||||
|
startMin: minStart,
|
||||||
|
startMax: maxStart,
|
||||||
|
duration: duration ? parseInt(duration) : undefined,
|
||||||
|
locations: hasLocation ? selectedLocations: undefined,
|
||||||
|
};
|
||||||
|
await setOverride(id, override);
|
||||||
|
navigate('main');
|
||||||
|
},
|
||||||
|
[
|
||||||
|
id,
|
||||||
|
maxStart,
|
||||||
|
minStart,
|
||||||
|
duration,
|
||||||
|
hasLocation,
|
||||||
|
selectedLocations,
|
||||||
|
hasDays,
|
||||||
|
selectedDays,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [clear] = useAsyncCallback(
|
||||||
|
async () => {
|
||||||
|
await clearOverrides(id);
|
||||||
|
navigate('main');
|
||||||
|
},
|
||||||
|
[id, clearOverrides],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popup title={`Overrides for ${task.title}`} onClose={goBack}>
|
||||||
|
<SideBySide>
|
||||||
|
<TimeInput
|
||||||
|
flex={1}
|
||||||
|
placeholder={task.startTime.min ? timeUtils.timeToString(task.startTime.min) : undefined}
|
||||||
|
label="Min start"
|
||||||
|
value={minStart}
|
||||||
|
onChange={setMinStart}
|
||||||
|
/>
|
||||||
|
<TimeInput
|
||||||
|
flex={1}
|
||||||
|
placeholder={task.startTime.max ? timeUtils.timeToString(task.startTime.max) : undefined}
|
||||||
|
label="Max start"
|
||||||
|
value={maxStart}
|
||||||
|
onChange={setMaxStart}
|
||||||
|
/>
|
||||||
|
</SideBySide>
|
||||||
|
<TextInput
|
||||||
|
label="Duration"
|
||||||
|
value={duration}
|
||||||
|
onChangeText={setDuration}
|
||||||
|
right={<Cell><Overline>min</Overline></Cell>}
|
||||||
|
/>
|
||||||
|
<OptionalSelector
|
||||||
|
label="Location"
|
||||||
|
enabled={hasLocation}
|
||||||
|
items={locations}
|
||||||
|
selected={selectedLocations}
|
||||||
|
onChange={setSelectedLocations}
|
||||||
|
render={location => ({
|
||||||
|
title: location.title,
|
||||||
|
})}
|
||||||
|
getKey={location => location.id}
|
||||||
|
setEnabled={setHasLocation}
|
||||||
|
disabledText="Anywhere"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
<SideBySide>
|
||||||
|
<Checkbok label="Required" flex={1} />
|
||||||
|
<TextInput label="Priority" flex={1} />
|
||||||
|
</SideBySide>
|
||||||
|
<Row>
|
||||||
|
<SideBySide>
|
||||||
|
<Button onPress={clear} title="Clear" type="destructive" />
|
||||||
|
<Button onPress={save} title="Save" type="primary" />
|
||||||
|
</SideBySide>
|
||||||
|
</Row>
|
||||||
|
</Popup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { OverrideSetScreen };
|
||||||
@@ -32,12 +32,8 @@ function createDataContext<T extends {[name: string]: any}>({
|
|||||||
let next = typeof input === 'function'
|
let next = typeof input === 'function'
|
||||||
? input(current!)
|
? input(current!)
|
||||||
: input;
|
: input;
|
||||||
const result = {
|
setCurrent(next);
|
||||||
...current!,
|
await AsyncStorageLib.setItem(key, JSON.stringify(next));
|
||||||
...next,
|
|
||||||
};
|
|
||||||
setCurrent(result);
|
|
||||||
await AsyncStorageLib.setItem(key, JSON.stringify(result));
|
|
||||||
},
|
},
|
||||||
[key, current, setCurrent],
|
[key, current, setCurrent],
|
||||||
);
|
);
|
||||||
|
|||||||
57
yarn.lock
57
yarn.lock
@@ -2319,13 +2319,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-native@^0.67.6":
|
|
||||||
version "0.67.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.67.6.tgz#9a7de5feba6065aec9f44f9a1e8f6e55ee5d015c"
|
|
||||||
integrity sha512-NM6atxrefIXMLE/PyQ1bIQjQ/lWLdls3uVxItzKvNUUVZlGqgn/uGN4MarM9quSf90uSqJYPIAeAgTtBTUjhgg==
|
|
||||||
dependencies:
|
|
||||||
"@types/react" "*"
|
|
||||||
|
|
||||||
"@types/react@*", "@types/react@~17.0.21":
|
"@types/react@*", "@types/react@~17.0.21":
|
||||||
version "17.0.44"
|
version "17.0.44"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.44.tgz#c3714bd34dd551ab20b8015d9d0dbec812a51ec7"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.44.tgz#c3714bd34dd551ab20b8015d9d0dbec812a51ec7"
|
||||||
@@ -6194,6 +6187,11 @@ fs.realpath@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||||
|
|
||||||
|
fs@^0.0.1-security:
|
||||||
|
version "0.0.1-security"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4"
|
||||||
|
integrity sha1-invTcYa23d84E/I4WLV+yq9eQdQ=
|
||||||
|
|
||||||
fsevents@^1.2.7:
|
fsevents@^1.2.7:
|
||||||
version "1.2.13"
|
version "1.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
|
||||||
@@ -6596,7 +6594,7 @@ hmac-drbg@^1.0.1:
|
|||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0:
|
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
@@ -6882,6 +6880,11 @@ immer@8.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656"
|
resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656"
|
||||||
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
|
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
|
||||||
|
|
||||||
|
immutable@^4.0.0-rc.12:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
||||||
|
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
||||||
|
|
||||||
import-fresh@^2.0.0:
|
import-fresh@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
|
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
|
||||||
@@ -8555,6 +8558,11 @@ media-typer@0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
|
memoize-one@^5.2.1:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||||
|
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||||
|
|
||||||
memory-cache@~0.2.0:
|
memory-cache@~0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/memory-cache/-/memory-cache-0.2.0.tgz#7890b01d52c00c8ebc9d533e1f8eb17e3034871a"
|
resolved "https://registry.yarnpkg.com/memory-cache/-/memory-cache-0.2.0.tgz#7890b01d52c00c8ebc9d533e1f8eb17e3034871a"
|
||||||
@@ -9085,7 +9093,7 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
|
|
||||||
moment@>=2.0.0:
|
moment@>=2.0.0, moment@^2.24.0:
|
||||||
version "2.29.3"
|
version "2.29.3"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3"
|
||||||
integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==
|
integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==
|
||||||
@@ -10521,7 +10529,7 @@ prop-types@15.5.8:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fbjs "^0.8.9"
|
fbjs "^0.8.9"
|
||||||
|
|
||||||
prop-types@^15.6.0, prop-types@^15.7.2:
|
prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.7.2:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
@@ -10825,6 +10833,23 @@ react-native-calendar-strip@^2.2.5:
|
|||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
recyclerlistview "^3.0.0"
|
recyclerlistview "^3.0.0"
|
||||||
|
|
||||||
|
react-native-calendars@^1.1284.0:
|
||||||
|
version "1.1284.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-calendars/-/react-native-calendars-1.1284.0.tgz#afe82ae14568073b5873e37a2d035db420c65010"
|
||||||
|
integrity sha512-H4XvACg3zh6q6XDl1ELTkCpMrKY7JWnd4ioB4IeAWoRMOQW+n/IVQmxZ4045ghTXjP4zIke3Tn1i3OmashzIbw==
|
||||||
|
dependencies:
|
||||||
|
fs "^0.0.1-security"
|
||||||
|
hoist-non-react-statics "^3.3.1"
|
||||||
|
immutable "^4.0.0-rc.12"
|
||||||
|
lodash "^4.17.15"
|
||||||
|
memoize-one "^5.2.1"
|
||||||
|
prop-types "^15.5.10"
|
||||||
|
react-native-swipe-gestures "^1.0.5"
|
||||||
|
recyclerlistview "^3.0.5"
|
||||||
|
xdate "^0.8.0"
|
||||||
|
optionalDependencies:
|
||||||
|
moment "^2.24.0"
|
||||||
|
|
||||||
react-native-codegen@^0.0.6:
|
react-native-codegen@^0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.0.6.tgz#b3173faa879cf71bfade8d030f9c4698388f6909"
|
resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.0.6.tgz#b3173faa879cf71bfade8d030f9c4698388f6909"
|
||||||
@@ -10870,6 +10895,11 @@ react-native-screens@~3.10.1:
|
|||||||
react-freeze "^1.0.0"
|
react-freeze "^1.0.0"
|
||||||
warn-once "^0.1.0"
|
warn-once "^0.1.0"
|
||||||
|
|
||||||
|
react-native-swipe-gestures@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz#a172cb0f3e7478ccd681fd36b8bfbcdd098bde7c"
|
||||||
|
integrity sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==
|
||||||
|
|
||||||
react-native-web@0.17.1:
|
react-native-web@0.17.1:
|
||||||
version "0.17.1"
|
version "0.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.17.1.tgz#90d473c89dd99b88bc9830b2a9fcdd2fc5f04902"
|
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.17.1.tgz#90d473c89dd99b88bc9830b2a9fcdd2fc5f04902"
|
||||||
@@ -11063,7 +11093,7 @@ recursive-readdir@2.2.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimatch "3.0.4"
|
minimatch "3.0.4"
|
||||||
|
|
||||||
recyclerlistview@^3.0.0:
|
recyclerlistview@^3.0.0, recyclerlistview@^3.0.5:
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/recyclerlistview/-/recyclerlistview-3.0.5.tgz#50bf5bcaa401d56bb6bb264354083f4d424408eb"
|
resolved "https://registry.yarnpkg.com/recyclerlistview/-/recyclerlistview-3.0.5.tgz#50bf5bcaa401d56bb6bb264354083f4d424408eb"
|
||||||
integrity sha512-JVHz13u520faEsbVqFrJOMuJjc4mJlOXODe5QdqAJHdl5/IpyYeo83uiHrpzxyLb8QtJ0889JMlDik+Z1Ed0QQ==
|
integrity sha512-JVHz13u520faEsbVqFrJOMuJjc4mJlOXODe5QdqAJHdl5/IpyYeo83uiHrpzxyLb8QtJ0889JMlDik+Z1Ed0QQ==
|
||||||
@@ -13500,6 +13530,11 @@ xcode@^3.0.0, xcode@^3.0.1:
|
|||||||
simple-plist "^1.1.0"
|
simple-plist "^1.1.0"
|
||||||
uuid "^7.0.3"
|
uuid "^7.0.3"
|
||||||
|
|
||||||
|
xdate@^0.8.0:
|
||||||
|
version "0.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/xdate/-/xdate-0.8.2.tgz#d7b033c00485d02695baf0044f4eacda3fc961a3"
|
||||||
|
integrity sha1-17AzwASF0CaVuvAET06s2j/JYaM=
|
||||||
|
|
||||||
xdl@59.2.36:
|
xdl@59.2.36:
|
||||||
version "59.2.36"
|
version "59.2.36"
|
||||||
resolved "https://registry.yarnpkg.com/xdl/-/xdl-59.2.36.tgz#26a987cbc9e8fd799cce3c7e32279f85737e0936"
|
resolved "https://registry.yarnpkg.com/xdl/-/xdl-59.2.36.tgz#26a987cbc9e8fd799cce3c7e32279f85737e0936"
|
||||||
|
|||||||
Reference in New Issue
Block a user