Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a66d91d
chore: todoを追加
273Do May 8, 2026
4257ae2
fix: 軽微な修正
273Do May 10, 2026
7e9aa6f
fix: エントリー一覧、詳細画面のフォントサイズを修正
273Do May 10, 2026
49f6f25
feat: エントリー作成画面を追加
273Do May 10, 2026
4633bcc
refactor: ジャーナルの型のリファクタ
273Do May 10, 2026
d0b02d1
feat: エントリー作成画面を追加
273Do May 10, 2026
1547bea
fix: エントリー詳細のフォントサイズを修正
273Do May 10, 2026
5f1e116
feat: エントリーのフィールドコンポーネントを作成
273Do May 10, 2026
90248e7
feat: ダミーのエントリーフィールドを作成
273Do May 10, 2026
e4b47c3
feat: 時間のフォーマット関数を実装
273Do May 10, 2026
e45f606
refactor: fieldobjの型名を適切なものにリファクタ
273Do May 10, 2026
c7fe546
feat: モックを削除
273Do May 10, 2026
14fcfe6
feat: エントリーを作成する画面を実装
273Do May 10, 2026
9cf09ed
refactor: フィールドが再レンダリングされないようリファクタ
273Do May 10, 2026
84aa68e
refactor: entryカスタムフックのディレクトリ変更
273Do May 10, 2026
19d1119
refactor: エントリー詳細をリファクタ
273Do May 10, 2026
c1701f3
refactor: フィールドが再レンダリングされないようリファクタ
273Do May 10, 2026
8cfa07b
feat: エントリー登録を実装
273Do May 10, 2026
f658d30
refactor: 軽微なリファクタ
273Do May 10, 2026
56d1628
feat: エントリー一覧を実装
273Do May 11, 2026
7802175
fix: 軽微な修正
273Do May 11, 2026
f8263f0
fix: 軽微な修正
273Do May 12, 2026
25ef476
feat: エントリーが空の時のは日付を表示し、作成ボタンは常に活性化
273Do May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/app/(journal)/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,14 @@ export default function JournalScreen() {
],
}}
/>
<EntryListView id={id} searchText={searchText} sortKey={sortKey} />
{/* TODO: 空の場合の処理を追加する */}
{/* エントリー一覧にはheaderTitleのデータは含まないので src/app/(journal)/entry/[id].tsx と混同しないように。 */}
<EntryListView
id={id}
journalName={name}
searchText={searchText}
sortKey={sortKey}
/>
</>
);
}
8 changes: 4 additions & 4 deletions src/app/(journal)/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ export default function JournalCreateScreen() {
formDisabled,
} = useJournalField();

const handleCreate = async () => {
const { id, name } = await createJournal();
const handleJournalCreate = async () => {
const { id: newJournalId, name } = await createJournal();

// replace でスタックせずにジャーナル詳細画面からジャーナル一覧へ戻れるようにする
router.replace(`/(journal)/${id}?name=${name}`);
router.replace(`/(journal)/${newJournalId}?name=${name}`);
};

return (
Expand All @@ -37,7 +37,7 @@ export default function JournalCreateScreen() {
options={{
title: "New Journal",
headerRight: () => (
<Pressable onPress={handleCreate} disabled={formDisabled}>
<Pressable onPress={handleJournalCreate} disabled={formDisabled}>
<SymbolView
name="checkmark"
tintColor={
Expand Down
1 change: 1 addition & 0 deletions src/app/(journal)/entry/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default function EntryDetailScreen() {
],
}}
/>
{/* TODO: 空の場合の処理を追加する */}
{entry && <EntryDetailView entry={entry} />}
</>
);
Expand Down
79 changes: 79 additions & 0 deletions src/app/(journal)/entry/create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
PlatformColor,
Pressable,
Text as RNText,
StyleSheet,
View,
} from "react-native";

import { useLiveQuery } from "drizzle-orm/expo-sqlite";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { SymbolView } from "expo-symbols";

import { EntryCreateView } from "@/components/entry/entry-create-view";
import { getFieldsQuery } from "@/db/queries/fields";
import { useEntry } from "@/utils/entry/use-entry";

/**
* エントリー作成
*/
export default function EntryCreateScreen() {
const router = useRouter();

const { id: jounalId, name } = useLocalSearchParams<{
id: string;
name: string;
}>();

const { data: fields } = useLiveQuery(getFieldsQuery(jounalId));
const { valuesRef, setValue, createEntry } = useEntry(fields);

const handleEntryCreate = async () => {
const { id: newEntryId } = await createEntry(jounalId);
router.replace(`/(journal)/entry/${newEntryId}`);
};

return (
<>
<Stack.Screen
options={{
headerTitle: () => (
<View style={styles.headerTitle}>
<RNText style={styles.title}>New Entry</RNText>
<RNText style={styles.subtitle}>{name}</RNText>
</View>
),
headerRight: () => (
// エントリー作成では disabled は設定しない
<Pressable onPress={handleEntryCreate}>
<SymbolView
name="checkmark"
tintColor={PlatformColor("systemIndigo")}
/>
</Pressable>
),
}}
/>
<EntryCreateView
id={jounalId}
values={valuesRef.current}
setValue={setValue}
/>
</>
);
}

const styles = StyleSheet.create({
headerTitle: {
alignItems: "center",
},
title: {
fontSize: 17,
fontWeight: "600",
color: PlatformColor("label"),
},
subtitle: {
fontSize: 14,
color: PlatformColor("secondaryLabel"),
},
});
1 change: 1 addition & 0 deletions src/app/(journal)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default function JournalListScreen() {
),
}}
/>
{/* TODO: 空の場合の処理を追加する */}
<JournalView />
</>
);
Expand Down
6 changes: 4 additions & 2 deletions src/app/explore.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PlatformColor, View } from "react-native";

import { Host, Text } from "@expo/ui/swift-ui";
import { Host } from "@expo/ui/swift-ui";

import { AllformList } from "@/components/all-form-list";

export default function ExploreScreen() {
return (
Expand All @@ -9,7 +11,7 @@ export default function ExploreScreen() {
style={{ flex: 1, backgroundColor: PlatformColor("systemBackground") }}
useViewportSizeMeasurement
>
<Text>Explore</Text>
<AllformList />
</Host>
</View>
);
Expand Down
45 changes: 45 additions & 0 deletions src/components/all-form-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { List, Section } from "@expo/ui/swift-ui";
import { frame, listStyle } from "@expo/ui/swift-ui/modifiers";

import { EntryNumber } from "./field/emtry-number";
import { EntryCheck } from "./field/entry-check";
import { EntryDate } from "./field/entry-date";
import { EntryLocation } from "./field/entry-location";
import { EntryLongText } from "./field/entry-long-text";
import { EntryMedia } from "./field/entry-media";
import { EntryText } from "./field/entry-text";
import { EntryTime } from "./field/entry-time";

const edit = false;
/**
* 全フィールドタイプのフォームサンプル
*/
export function AllformList() {
return (
<List
modifiers={[
frame({ maxWidth: 9999, maxHeight: 9999 }),
listStyle("plain"),
]}
>
<Section>
<EntryText
label="text"
defaultValue="Lorem ipsum dolor sit amet"
edit={edit}
/>
<EntryLongText
label="longText"
defaultValue="Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis atque hic accusamus sint saepe deleniti provident minus eum? Eaque repellat nobis dignissimos sapiente temporibus officia, alias similique illum ullam necessitatibus."
edit={edit}
/>
<EntryNumber label="number" edit={edit} />
<EntryCheck label="checklist" defaultValue={true} edit={edit} />
<EntryDate label="date" edit={edit} />
<EntryTime label="time" edit={edit} />
<EntryMedia label="media" edit={edit} />
<EntryLocation label="location" edit={edit} />
</Section>
</List>
);
}
57 changes: 57 additions & 0 deletions src/components/entry/entry-create-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { PlatformColor, View } from "react-native";

import { Host, List, Section } from "@expo/ui/swift-ui";
import { frame, listStyle } from "@expo/ui/swift-ui/modifiers";
import { useLiveQuery } from "drizzle-orm/expo-sqlite";

import { getFieldsQuery } from "@/db/queries/fields";
import { formatDate } from "@/utils/date";
import { FieldValue } from "@/utils/entry/use-entry";

import { EntryFieldItem } from "./entry-field-item";

type Props = {
/** ジャーナル id */
id: string;
/** 現在のフィールドの値 */
values: Record<string, FieldValue>;
/** フィールドに値を格納する関数 */
setValue: (id: string, value: FieldValue) => void;
};

/**
* エントリー作成画面
*/
export function EntryCreateView({ id, values, setValue }: Props) {
const now = Date.now();

const { data: fields } = useLiveQuery(getFieldsQuery(id));

return (
<View style={{ flex: 1 }}>
<Host
style={{ flex: 1, backgroundColor: PlatformColor("systemBackground") }}
useViewportSizeMeasurement
>
<List
modifiers={[
frame({ maxWidth: 9999, maxHeight: 9999 }),
listStyle("plain"),
]}
>
<Section title={formatDate(now)}>
{fields.map((field) => (
<EntryFieldItem
key={field.id}
field={field}
value={values[field.id]}
setValue={setValue}
edit
/>
))}
</Section>
</List>
</Host>
</View>
);
}
26 changes: 5 additions & 21 deletions src/components/entry/entry-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,19 @@ import {
Section,
Spacer,
Text,
VStack,
} from "@expo/ui/swift-ui";
import {
font,
foregroundStyle,
frame,
listStyle,
} from "@expo/ui/swift-ui/modifiers";
import { frame, listStyle } from "@expo/ui/swift-ui/modifiers";

import { EntryDetailObj } from "@/db/queries/entries";
import { formatDate } from "@/utils/date";

import { EntryFieldItem } from "./entry-field-item";

type Props = {
/** エントリーデータ */
entry: EntryDetailObj;
};

/**
* エントリー詳細画面
*/
Expand Down Expand Up @@ -63,20 +60,7 @@ export function EntryDetailView({ entry }: Props) {
}
>
{sorted.map((v) => (
<VStack key={v.id} alignment="leading" spacing={4}>
<Text
modifiers={[
font({ size: 12 }),
foregroundStyle({
type: "hierarchical",
style: "secondary",
}),
]}
>
{v.field.label}
</Text>
<Text>{v.value ?? ""}</Text>
</VStack>
<EntryFieldItem key={v.id} field={v.field} value={v.value} />
))}
</Section>
</List>
Expand Down
60 changes: 60 additions & 0 deletions src/components/entry/entry-field-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { EntryNumber } from "@/components/field/emtry-number";
import { EntryCheck } from "@/components/field/entry-check";
import { EntryDate } from "@/components/field/entry-date";
import { EntryLocation } from "@/components/field/entry-location";
import { EntryLongText } from "@/components/field/entry-long-text";
import { EntryMedia } from "@/components/field/entry-media";
import { EntryText } from "@/components/field/entry-text";
import { EntryTime } from "@/components/field/entry-time";
import { FieldType } from "@/core/constants";
import type { FieldlObj } from "@/db/schemas";
import { type FieldValue } from "@/utils/entry/use-entry";

type Props = {
/** フィールド定義 */
field: FieldlObj;
/** 現在の値 */
value: FieldValue;
/** 値変更時のコールバック(edit=true のときのみ必要) */
setValue?: (id: string, value: FieldValue) => void;
/** 入力かどうか */
edit?: boolean;
};

/**
* フィールドタイプによってコンポーネントを切り替えるコンポーネント
* React Compiler がこの単位で再レンダリングを最適化する
*/
export function EntryFieldItem({
field,
value,
setValue,
edit = false,
}: Props) {
const shared = {
label: field.label,
onValueChange: setValue
? <T extends FieldValue>(v: T) => setValue(field.id, v)
: undefined,
edit,
};

switch (field.type as FieldType) {
case "text":
return <EntryText {...shared} defaultValue={value as string} />;
case "longText":
return <EntryLongText {...shared} defaultValue={value as string} />;
case "number":
return <EntryNumber {...shared} defaultValue={(value ?? 0) as number} />;
case "check":
return <EntryCheck {...shared} defaultValue={value as boolean} />;
case "date":
return <EntryDate {...shared} defaultValue={value as Date} />;
case "time":
return <EntryTime {...shared} defaultValue={value as Date} />;
case "media":
return <EntryMedia {...shared} />;
case "location":
return <EntryLocation {...shared} />;
}
}
Loading
Loading