Search
Duplicate

๋‹ค์ด์–ด๋ฆฌ ์•ฑ

Flutter ๋‹ค์ด์–ด๋ฆฌ ์•ฑ ๋งŒ๋“ค๊ธฐ

Flutter๋กœ ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์ผ๊ธฐ์žฅ ์•ฑ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋งŒ๋“œ๋Š” ๊ณผ์ •์„ ๋‹จ๊ณ„๋ณ„๋กœ ๊ฐœ๋ฐœํ•ด๋ณด์•„์š”.

๋ชฉ์ฐจ

1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์•ฑ ์†Œ๊ฐœ

โ€ข
์ด๋ฆ„: ๋‹ค์ด์–ด๋ฆฌ ์•ฑ (Diary App)
โ€ข
๋ชฉ์ : Flutter์˜ ํŒŒ์ผ I/O, ์ƒํƒœ ๊ด€๋ฆฌ, ํŽ˜์ด์ง€ ๋ผ์šฐํŒ…์„ ์ตํžˆ๊ธฐ ์œ„ํ•œ ์‹ค์Šต ํ”„๋กœ์ ํŠธ
โ€ข
ํŠน์ง•: DB ์—†์ด ํ…์ŠคํŠธ ํŒŒ์ผ๋งŒ์œผ๋กœ ์ผ๊ธฐ๋ฅผ ์ €์žฅยท๊ด€๋ฆฌ

์ฃผ์š” ๊ธฐ๋Šฅ

๊ธฐ๋Šฅ
์„ค๋ช…
์ผ๊ธฐ ์ž‘์„ฑ
๋‚ ์งœยท์ œ๋ชฉยท๋ณธ๋ฌธ ์ž…๋ ฅ ํ›„ ์ €์žฅ
์ผ๊ธฐ ๋ชฉ๋ก
์ตœ์‹ ์ˆœ ์ •๋ ฌ, ์Šค์™€์ดํ”„ ์‚ญ์ œ
์ผ๊ธฐ ์ƒ์„ธ
๋ณด๊ธฐ / ์ˆ˜์ • / ๋‚ ์งœ ๋ณ€๊ฒฝ
๋‹ฌ๋ ฅ ๋ทฐ
์›”๋ณ„ ๋‹ฌ๋ ฅ + ์ผ๊ธฐ ์žˆ๋Š” ๋‚  ๋งˆ์ปค ํ‘œ์‹œ
๊ฒ€์ƒ‰
๋‚ ์งœยท์ œ๋ชฉยท๋ณธ๋ฌธ ํ†ตํ•ฉ ๊ฒ€์ƒ‰ (๋””๋ฐ”์šด์Šค ์ ์šฉ)
๋‹ค์ค‘ ์„ ํƒ ์‚ญ์ œ
์—ฌ๋Ÿฌ ์ผ๊ธฐ ํ•œ ๋ฒˆ์— ์‚ญ์ œ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

diary_app/ โ”œโ”€โ”€ lib/ โ”‚ โ”œโ”€โ”€ main.dart # ์•ฑ ์ง„์ž…์  ยท ํ…Œ๋งˆ ์„ค์ • โ”‚ โ”œโ”€โ”€ services/ โ”‚ โ”‚ โ””โ”€โ”€ file_service.dart # ํŒŒ์ผ I/O ์„œ๋น„์Šค (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) โ”‚ โ””โ”€โ”€ pages/ โ”‚ โ”œโ”€โ”€ home_page.dart # ์ผ๊ธฐ ๋ชฉ๋ก ํ™”๋ฉด โ”‚ โ”œโ”€โ”€ write_page.dart # ์ผ๊ธฐ ์ž‘์„ฑ ํ™”๋ฉด โ”‚ โ”œโ”€โ”€ detail_page.dart # ์ผ๊ธฐ ์ƒ์„ธ๋ณด๊ธฐ ยท ์ˆ˜์ • ํ™”๋ฉด โ”‚ โ””โ”€โ”€ calendar_page.dart # ๋‹ฌ๋ ฅ ๋ทฐ ํ™”๋ฉด โ”œโ”€โ”€ guide/ # ๊ฐ•์˜ ๋…ธํŠธ ๋ฌธ์„œ ํด๋” โ””โ”€โ”€ pubspec.yaml
Plain Text
๋ณต์‚ฌ

2. ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ค์ •

Flutter ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

flutter create diary_app cd diary_app
Bash
๋ณต์‚ฌ

ํŒจํ‚ค์ง€ ์ถ”๊ฐ€

flutter pub add path_provider flutter pub add calendar_date_picker2 flutter pub add table_calendar
Bash
๋ณต์‚ฌ

pubspec.yaml ๊ฒฐ๊ณผ

dependencies: flutter: sdk: flutter path_provider: ^2.0.0 calendar_date_picker2: ^2.0.1 table_calendar: ^3.2.0 flutter_multi_select_items: ^0.4.3
YAML
๋ณต์‚ฌ
ํŒ: ๋ฒ„์ „์„ ์ง์ ‘ ์ง€์ •ํ•˜์ง€ ์•Š๊ณ  flutter pub add ํŒจํ‚ค์ง€๋ช…์„ ์‚ฌ์šฉํ•˜๋ฉด pub.dev์—์„œ ์ตœ์‹  ๋ฒ„์ „์„ ์ž๋™์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

ํด๋” ๊ตฌ์กฐ ์ƒ์„ฑ

lib/ โ”œโ”€โ”€ services/ โ† FileService ๋ฐฐ์น˜ โ””โ”€โ”€ pages/ โ† ๊ฐ ํ™”๋ฉด ๋ฐฐ์น˜
Plain Text
๋ณต์‚ฌ

main.dart โ€” ์•ฑ ์ง„์ž…์ 

void main() => runApp(const DiaryApp()); class DiaryApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber), useMaterial3: true, ), home: const HomePage(), ); } }
Dart
๋ณต์‚ฌ
useMaterial3: true + amber ์‹œ๋“œ ์ปฌ๋Ÿฌ๋กœ ์ „์ฒด ํ…Œ๋งˆ๋ฅผ ํ†ต์ผํ•ฉ๋‹ˆ๋‹ค.

3. ์‚ฌ์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

3-1. path_provider ^2.0.0

์—ญํ• : ํ”Œ๋žซํผ(Android/iOS/Windows ๋“ฑ)๋ณ„๋กœ ๋‹ค๋ฅธ ํŒŒ์ผ ์ €์žฅ ๊ฒฝ๋กœ๋ฅผ ์ž๋™์œผ๋กœ ๋ฐ˜ํ™˜
ํ”Œ๋žซํผ
๊ฒฝ๋กœ ์˜ˆ์‹œ
Android
/data/user/0/com.example.diary_app/app_flutter/
iOS
/var/mobile/.../Documents/
Windows
C:\\Users\\user\\Documents\\
import 'package:path_provider/path_provider.dart'; final dir = await getApplicationDocumentsDirectory(); // โ†’ ์ด ๊ฒฝ๋กœ ์•„๋ž˜์— .txt ํŒŒ์ผ ์ €์žฅ
Dart
๋ณต์‚ฌ

3-2. calendar_date_picker2 ^2.0.1

์—ญํ• : ๋‹ค์ด์–ผ๋กœ๊ทธ ํ˜•ํƒœ์˜ ๋‚ ์งœ ์„ ํƒ ๋‹ฌ๋ ฅ ์œ„์ ฏ
์ ์šฉ ์œ„์น˜
ํ™”๋ฉด
๊ธฐ๋Šฅ
WritePage
์ผ๊ธฐ๋ฅผ ์“ธ ๋‚ ์งœ ์„ ํƒ (๊ธฐ๋ณธ๊ฐ’: ์˜ค๋Š˜)
DetailPage (edit ๋ชจ๋“œ)
๊ธฐ์กด ์ผ๊ธฐ์˜ ๋‚ ์งœ ๋ณ€๊ฒฝ
Future<void> _pickDate() async { final result = await showCalendarDatePicker2Dialog( context: context, config: CalendarDatePicker2WithActionButtonsConfig( calendarType: CalendarDatePicker2Type.single, selectedDayHighlightColor: Colors.amber, okButton: const Text('ํ™•์ธ', style: TextStyle(color: Colors.amber)), cancelButton: const Text('์ทจ์†Œ'), ), dialogSize: const Size(325, 400), borderRadius: BorderRadius.circular(15), value: [_selectedDate], ); if (result != null && result.isNotEmpty && result.first != null) { setState(() => _selectedDate = result.first!); } }
Dart
๋ณต์‚ฌ

3-3. table_calendar ^3.2.0

์—ญํ• : ์›”/์ฃผ ๋ทฐ ๋‹ฌ๋ ฅ ์œ„์ ฏ. ์ด๋ฒคํŠธ ๋งˆ์ปค, ๋‚ ์งœ ์„ ํƒ, ์ปค์Šคํ…€ ์Šคํƒ€์ผ ์ง€์›
TableCalendar<DiaryEntry>( firstDay: DateTime(2020), lastDay: DateTime(2100), focusedDay: _focusedDay, selectedDayPredicate: (day) => isSameDay(_selectedDay, day), eventLoader: _eventsFor, // ๋‚ ์งœ โ†’ ์ด๋ฒคํŠธ ๋ฆฌ์ŠคํŠธ ํ•จ์ˆ˜ onDaySelected: _onDaySelected, onPageChanged: (day) => setState(() => _focusedDay = day), calendarStyle: CalendarStyle( todayDecoration: BoxDecoration(color: Colors.amber.shade300, shape: BoxShape.circle), selectedDecoration: BoxDecoration(color: Colors.amber, shape: BoxShape.circle), markerDecoration: BoxDecoration(color: Colors.deepOrange, shape: BoxShape.circle), ), headerStyle: HeaderStyle( formatButtonVisible: false, titleCentered: true, ), )
Dart
๋ณต์‚ฌ
์ด๋ฒคํŠธ ๋งˆ์ปค ์›๋ฆฌ
Map<String, List<DiaryEntry>> _eventMap = {}; List<DiaryEntry> _eventsFor(DateTime day) { final key = '${day.year}-${day.month.toString().padLeft(2, '0')}' '-${day.day.toString().padLeft(2, '0')}'; return _eventMap[key] ?? []; } // ๋ฐ˜ํ™˜๋œ ๋ฆฌ์ŠคํŠธ ํฌ๊ธฐ๋งŒํผ ๋‚ ์งœ ์•„๋ž˜์— ์ (marker)์ด ํ‘œ์‹œ๋จ
Dart
๋ณต์‚ฌ

3-4. flutter_multi_select_items ^0.4.3

์„ค์น˜๋Š” ๋˜์–ด ์žˆ์œผ๋‚˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ
์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ด์œ : ๋‚ด๋ถ€์ ์œผ๋กœ Wrap์„ ์‚ฌ์šฉํ•˜๋Š” ์œ„์ ฏ์ด SingleChildScrollView + Expanded ์•ˆ์—์„œ ๋ฌดํ•œ ๋†’์ด ๋ ˆ์ด์•„์›ƒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
Plain Text
๋ณต์‚ฌ
๋Œ€์•ˆ: Set<String> + ListView.builder๋กœ ์ง์ ‘ ๊ตฌํ˜„
Set<String> _selectedPaths = {}; onTap: () => setState(() { if (_selectedPaths.contains(path)) { _selectedPaths.remove(path); } else { _selectedPaths.add(path); } });
Dart
๋ณต์‚ฌ

4. ๋ฐ์ดํ„ฐ ์„ค๊ณ„ โ€” FileService

์›์น™: UI์™€ ๋ฐ์ดํ„ฐ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•œ๋‹ค (์„œ๋น„์Šค ๋ ˆ์ด์–ด ํŒจํ„ด)

4-1. ํŒŒ์ผ ์ €์žฅ ์ „๋žต

๊ณ ๋ ค ์‚ฌํ•ญ
๊ฒฐ์ •
DB ์‚ฌ์šฉ ์—ฌ๋ถ€
์‚ฌ์šฉ ์•ˆ ํ•จ โ€” ํ…์ŠคํŠธ ํŒŒ์ผ๋กœ ์ถฉ๋ถ„
ํ•˜๋ฃจ 1๊ฐœ ์ œํ•œ ์—ฌ๋ถ€
์ œํ•œ ์—†์Œ โ€” ์‹œ๊ฐ(HHmmss)์„ ํŒŒ์ผ๋ช…์— ํฌํ•จ
ํŒŒ์ผ๋ช… ํ˜•์‹
YYYY-MM-DD_HHmmss.txt
๋‚ด์šฉ ์ง๋ ฌํ™”
์ œ๋ชฉ + \\n---DIARY---\\n + ๋ณธ๋ฌธ
ํŒŒ์ผ๋ช… ์˜ˆ์‹œ
2026-03-26_143022.txt
Plain Text
๋ณต์‚ฌ
โ€ข
๋‚ ์งœ(YYYY-MM-DD) + ์‹œ๊ฐ(HHmmss) ์กฐํ•ฉ โ†’ ํŒŒ์ผ ์ด๋ฆ„ ์ถฉ๋Œ ์—†์Œ
โ€ข
ํŒŒ์ผ ์ด๋ฆ„ ์ž์ฒด๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฒธํ•จ โ†’ DB ๋ถˆํ•„์š”
ํŒŒ์ผ ๋‚ด์šฉ ํฌ๋งท
์˜ค๋Š˜์˜ ์ผ๊ธฐ ์ œ๋ชฉ ---DIARY--- ์˜ค๋Š˜์€ ๋‚ ์”จ๊ฐ€ ๋ง‘์•˜๋‹ค. Flutter ๊ณต๋ถ€๋ฅผ ์—ด์‹ฌํžˆ ํ–ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ค„๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.
Plain Text
๋ณต์‚ฌ

4-2. DiaryEntry ๋ชจ๋ธ ํด๋ž˜์Šค

class DiaryEntry { final String path; // ํŒŒ์ผ ์ ˆ๋Œ€ ๊ฒฝ๋กœ (๊ธฐ๋ณธ ํ‚ค) final String date; // "YYYY-MM-DD" (UI ํ‘œ์‹œ์šฉ) final String time; // "HH:mm:ss" (UI ํ‘œ์‹œ์šฉ) final String title; // ์ œ๋ชฉ (๋ชฉ๋ก์—์„œ ๋ฏธ๋ฆฌ ๋กœ๋“œ) }
Dart
๋ณต์‚ฌ
์„ค๊ณ„ ์ด์œ : FileSystemEntity๋ฅผ ์ง์ ‘ ์“ฐ๋ฉด ๋ชฉ๋ก ๋ Œ๋”๋ง ์‹œ ๋งค๋ฒˆ ํŒŒ์ผ์„ ์ฝ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
DiaryEntry๋กœ ํ•œ ๋ฒˆ์— ๋กœ๋“œํ•ด๋‘๋ฉด ์ถ”๊ฐ€ I/O ์—†์ด ๋น ๋ฅธ ๋ชฉ๋ก ๊ฐฑ์‹ ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

4-3. ์ฃผ์š” ๋ฉ”์„œ๋“œ ์ •๋ฆฌ

์ง๋ ฌํ™” / ์—ญ์ง๋ ฌํ™”

// ์ €์žฅ: ์ œ๋ชฉ + ๊ตฌ๋ถ„์ž + ๋ณธ๋ฌธ โ†’ ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด String _serialize(String title, String content) => '$title\\n---DIARY---\\n$content'; // ์ฝ๊ธฐ: ์ œ๋ชฉ ์ถ”์ถœ String parseTitleFromRaw(String raw) { final idx = raw.indexOf('\\n---DIARY---\\n'); if (idx == -1) return ''; return raw.substring(0, idx); } // ์ฝ๊ธฐ: ๋ณธ๋ฌธ ์ถ”์ถœ String parseContentFromRaw(String raw) { final sep = '\\n---DIARY---\\n'; final idx = raw.indexOf(sep); if (idx == -1) return raw; // ๊ตฌ๋ถ„์ž ์—†์œผ๋ฉด ์ „์ฒด๊ฐ€ ๋ณธ๋ฌธ (๊ตฌ๋ฒ„์ „ ํ˜ธํ™˜) return raw.substring(idx + sep.length); }
Dart
๋ณต์‚ฌ

์ƒˆ ์ผ๊ธฐ ์ €์žฅ โ€” saveDiaryForDate()

Future<void> saveDiaryForDate(String date, String title, String content) async { final dirPath = await getDirPath(); final now = DateTime.now(); final t = '${now.hour.toString().padLeft(2, '0')}' '${now.minute.toString().padLeft(2, '0')}' '${now.second.toString().padLeft(2, '0')}'; final file = File('$dirPath/${date}_$t.txt'); await file.writeAsString(_serialize(title, content)); }
Dart
๋ณต์‚ฌ
โ€ข
ํ˜„์žฌ ์‹œ๊ฐ์„ ํŒŒ์ผ๋ช…์— ํฌํ•จ โ†’ ๊ฐ™์€ ๋‚  ์—ฌ๋Ÿฌ ์ผ๊ธฐ ์ €์žฅ ๊ฐ€๋Šฅ
โ€ข
padLeft(2, '0'): ํ•œ ์ž๋ฆฌ ์ˆซ์ž๋ฅผ ๋‘ ์ž๋ฆฌ๋กœ ํŒจ๋”ฉ (9 โ†’ 09)

๊ธฐ์กด ์ผ๊ธฐ ์ˆ˜์ • โ€” saveDiaryToPath()

Future<void> saveDiaryToPath(String path, String title, String content) async { await File(path).writeAsString(_serialize(title, content)); } // ๊ฒฝ๋กœ๊ฐ€ ๊ณ ์ • โ†’ ํŒŒ์ผ๋ช…(๋‚ ์งœยท์‹œ๊ฐ) ๋ณ€๊ฒฝ ์—†์ด ๋‚ด์šฉ๋งŒ ๋ฎ์–ด์”Œ์›€
Dart
๋ณต์‚ฌ

๋‚ ์งœ ๋ณ€๊ฒฝ โ€” changeDiaryDate()

Future<String> changeDiaryDate( String oldPath, String newDate, String title, String content) async { final dirPath = await getDirPath(); final name = oldPath.split(Platform.pathSeparator).last.replaceAll('.txt', ''); final timePart = name.contains('_') ? name.split('_').last : ''; final newPath = '$dirPath/${newDate}_$timePart.txt'; await File(newPath).writeAsString(_serialize(title, content)); await File(oldPath).delete(); return newPath; }
Dart
๋ณต์‚ฌ
โ€ข
๋‚ ์งœ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ํŒŒ์ผ๋ช…๋„ ๋ฐ”๋€Œ์–ด์•ผ ํ•จ โ†’ ์ƒˆ ํŒŒ์ผ ์ƒ์„ฑ ํ›„ ๊ธฐ์กด ํŒŒ์ผ ์‚ญ์ œ
โ€ข
HHmmss ์‹œ๊ฐ ๋ถ€๋ถ„์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€ (์ž‘์„ฑ ์‹œ๊ฐ ๋ณด์กด)

์ „์ฒด ๋ชฉ๋ก ์กฐํšŒ โ€” getDiaryEntries()

Future<List<DiaryEntry>> getDiaryEntries() async { final dir = Directory(await getDirPath()); final txts = dir.listSync().where((e) => e.path.endsWith('.txt')).toList(); txts.sort((a, b) => b.path.compareTo(a.path)); // ์ตœ์‹ ์ˆœ ์ •๋ ฌ final entries = <DiaryEntry>[]; for (final f in txts) { final raw = await File(f.path).readAsString(); entries.add(DiaryEntry( path: f.path, date: getDateFromPath(f.path), time: getTimeFromPath(f.path), title: parseTitleFromRaw(raw), )); } return entries; }
Dart
๋ณต์‚ฌ

๊ฒฝ๋กœ ํŒŒ์‹ฑ โ€” getDateFromPath() / getTimeFromPath()

// "2026-03-26_143022.txt" โ†’ "2026-03-26" String getDateFromPath(String path) { final name = path.split(Platform.pathSeparator).last.replaceAll('.txt', ''); return name.contains('_') ? name.split('_').first : name; } // "2026-03-26_143022.txt" โ†’ "14:30:22" String getTimeFromPath(String path) { final name = path.split(Platform.pathSeparator).last.replaceAll('.txt', ''); final t = name.split('_').last; return '${t.substring(0, 2)}:${t.substring(2, 4)}:${t.substring(4, 6)}'; }
Dart
๋ณต์‚ฌ
Platform.pathSeparator: Windows(\\) / macOSยทLinux(/) ์ž๋™ ๋Œ€์‘

๊ฒ€์ƒ‰ โ€” searchEntries()

Future<List<DiaryEntry>> searchEntries(String query) async { final all = await getDiaryEntries(); final q = query.toLowerCase(); final result = <DiaryEntry>[]; for (final entry in all) { // 1์ฐจ: ๋‚ ์งœ or ์ œ๋ชฉ (์ด๋ฏธ ๋กœ๋“œ๋œ ์ •๋ณด โ†’ ์ถ”๊ฐ€ I/O ์—†์Œ) if (entry.date.contains(q) || entry.title.toLowerCase().contains(q)) { result.add(entry); continue; } // 2์ฐจ: ๋ณธ๋ฌธ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํŒŒ์ผ ์ถ”๊ฐ€ ์ฝ๊ธฐ) final raw = await readDiaryRaw(entry.path); if (parseContentFromRaw(raw).toLowerCase().contains(q)) { result.add(entry); } } return result; }
Dart
๋ณต์‚ฌ
๊ฒ€์ƒ‰ ๋ฒ”์œ„: ๋‚ ์งœ โ†’ ์ œ๋ชฉ โ†’ ๋ณธ๋ฌธ ์ˆœ์„œ๋กœ ํƒ์ƒ‰, ์กฐ๊ฑด ๋งŒ์กฑ ์‹œ ์ฆ‰์‹œ ์ถ”๊ฐ€

๋‹ค์ค‘ ์‚ญ์ œ โ€” deleteMultiple()

Future<void> deleteMultiple(List<String> paths) async { for (final p in paths) { await deleteDiary(p); // File(path).delete() } }
Dart
๋ณต์‚ฌ

4-4. ์ „์ฒด ๋ฐ์ดํ„ฐ ํ๋ฆ„

์‚ฌ์šฉ์ž ๋™์ž‘ โ”‚ โ–ผ HomePage / WritePage / DetailPage โ”‚ ํ˜ธ์ถœ โ–ผ FileService (file_service.dart) โ”‚ โ”œโ”€โ”€ ์“ฐ๊ธฐ โ†’ saveDiaryForDate / saveDiaryToPath / changeDiaryDate โ”‚ โ””โ”€โ”€ File.writeAsString(_serialize(title, content)) โ”‚ โ”œโ”€โ”€ ์ฝ๊ธฐ โ†’ getDiaryEntries / readDiaryRaw โ”‚ โ””โ”€โ”€ File.readAsString โ†’ parseTitleFromRaw / parseContentFromRaw โ”‚ โ”œโ”€โ”€ ์‚ญ์ œ โ†’ deleteDiary / deleteMultiple โ”‚ โ””โ”€โ”€ File.delete() โ”‚ โ””โ”€โ”€ ๊ฒ€์ƒ‰ โ†’ searchEntries โ””โ”€โ”€ getDiaryEntries + ๋ณธ๋ฌธ ์ˆœ์ฐจ ๊ฒ€์ƒ‰
Plain Text
๋ณต์‚ฌ

5. ํ™”๋ฉด ๊ตฌํ˜„

5-1. HomePage โ€” ์ผ๊ธฐ ๋ชฉ๋ก ํ™”๋ฉด

๊ตฌํ˜„ ์ˆœ์„œ
1. _loadDiaries() โ†’ getDiaryEntries() ํ˜ธ์ถœ, ์ƒํƒœ ์ €์žฅ 2. _buildNormalList() โ†’ ListView.builder + Dismissible ์นด๋“œ 3. _buildAppBar() โ†’ ๊ฒ€์ƒ‰ ๋ชจ๋“œ TextField / ์ผ๋ฐ˜ ๋ชจ๋“œ ์ œ๋ชฉ 4. _buildDrawer() โ†’ ์‚ฌ์ด๋“œ ๋ฉ”๋‰ด 5. _buildMultiSelectList() โ†’ Set<String>์œผ๋กœ ๋‹ค์ค‘ ์„ ํƒ ๊ด€๋ฆฌ 6. ๊ฒ€์ƒ‰ ๋กœ์ง โ†’ Timer ๋””๋ฐ”์šด์Šค 500ms
Plain Text
๋ณต์‚ฌ
๊ฒ€์ƒ‰ ๋””๋ฐ”์šด์Šค ํŒจํ„ด
Timer? _debounce; void _onSearchChanged(String query) { _debounce?.cancel(); // ์ด์ „ ํƒ€์ด๋จธ ์ทจ์†Œ _debounce = Timer( const Duration(milliseconds: 500), () async { final results = await _fileService.searchEntries(query); setState(() => _filteredList = results); }, ); }
Dart
๋ณต์‚ฌ
์ด์œ : ํ‚ค ์ž…๋ ฅ๋งˆ๋‹ค ํŒŒ์ผ ๊ฒ€์ƒ‰์„ ์‹คํ–‰ํ•˜๋ฉด ์„ฑ๋Šฅ ๋‚ญ๋น„. 500ms ๋™์•ˆ ์ถ”๊ฐ€ ์ž…๋ ฅ์ด ์—†์„ ๋•Œ๋งŒ ์‹คํ–‰.
๋‹ค์ค‘ ์„ ํƒ ์ƒํƒœ ๊ด€๋ฆฌ
Set<String> _selectedPaths = {}; // ์ „์ฒด ์„ ํƒ _selectedPaths = _filteredList.map((e) => e.path).toSet(); // ์ „์ฒด ํ•ด์ œ _selectedPaths.clear(); // ํ† ๊ธ€ _selectedPaths.contains(path) ? _selectedPaths.remove(path) : _selectedPaths.add(path);
Dart
๋ณต์‚ฌ

5-2. WritePage โ€” ์ผ๊ธฐ ์ž‘์„ฑ ํ™”๋ฉด

๊ตฌํ˜„ ์ˆœ์„œ
1. _titleController โ†’ ์ œ๋ชฉ ์ž…๋ ฅ ํ•„๋“œ 2. _controller โ†’ ๋ณธ๋ฌธ ์ž…๋ ฅ ํ•„๋“œ (expands: true๋กœ ๋‚จ์€ ๊ณต๊ฐ„ ์ฑ„์›€) 3. _selectedDate โ†’ DateTime ์ƒํƒœ (๊ธฐ๋ณธ: ์˜ค๋Š˜) 4. _pickDate() โ†’ calendar_date_picker2 ๋‹ค์ด์–ผ๋กœ๊ทธ 5. _save() โ†’ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ โ†’ saveDiaryForDate() โ†’ Navigator.pop() 6. bottomSheet โ†’ "์ž‘์„ฑํ•˜๊ธฐ" ๋ฒ„ํŠผ (ํ™”๋ฉด ํ•˜๋‹จ ๊ณ ์ •)
Plain Text
๋ณต์‚ฌ
UI ๋ ˆ์ด์•„์›ƒ ๊ตฌ์กฐ
Scaffold โ”œโ”€โ”€ AppBar (์ €์žฅ ์•„์ด์ฝ˜) โ”œโ”€โ”€ bottomSheet (์ž‘์„ฑํ•˜๊ธฐ ElevatedButton) โ””โ”€โ”€ body (Padding fromLTRB(16, 16, 16, 80)) โ””โ”€โ”€ Column โ”œโ”€โ”€ InkWell โ†’ ๋‚ ์งœ ์„ ํƒ Row โ”œโ”€โ”€ SizedBox(height: 14) โ”œโ”€โ”€ TextField (์ œ๋ชฉ) โ”œโ”€โ”€ SizedBox(height: 12) โ””โ”€โ”€ Expanded โ†’ TextField (๋ณธ๋ฌธ, expands: true)
Plain Text
๋ณต์‚ฌ
bottomSheet๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด body๊ฐ€ ๋ฒ„ํŠผ ๋’ค๋กœ ๊ฐ€๋ ค์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ body ํ•˜๋‹จ ํŒจ๋”ฉ์„ ๋ฒ„ํŠผ ๋†’์ด(์•ฝ 80px)๋งŒํผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5-3. DetailPage โ€” ์ผ๊ธฐ ์ƒ์„ธ๋ณด๊ธฐ ยท ์ˆ˜์ • ํ™”๋ฉด

๊ตฌํ˜„ ์ˆœ์„œ
1. _loadContent() โ†’ readDiaryRaw() โ†’ parseTitleFromRaw / parseContentFromRaw 2. ๋ณด๊ธฐ ๋ชจ๋“œ โ†’ ์ œ๋ชฉ(๊ตต๊ฒŒ) + Divider + ๋ณธ๋ฌธ ํ…์ŠคํŠธ 3. ์ˆ˜์ • ๋ชจ๋“œ ์ง„์ž… โ†’ _isEditing = true 4. ํŽธ์ง‘ UI โ†’ ๋‚ ์งœ ์„ ํƒ ํ–‰ + ์ œ๋ชฉ TextField + ๋ณธ๋ฌธ TextField (Expanded) 5. _saveEdit() โ†’ ๋‚ ์งœ ๋ณ€๊ฒฝ ์—ฌ๋ถ€ ๋ถ„๊ธฐ โ”œโ”€โ”€ ๋‚ ์งœ ๋™์ผ โ†’ saveDiaryToPath() โ†’ setState(_isEditing = false) โ””โ”€โ”€ ๋‚ ์งœ ๋ณ€๊ฒฝ โ†’ changeDiaryDate() โ†’ Navigator.pop() (ํŒŒ์ผ๋ช…์ด ๋ฐ”๋€Œ๋ฏ€๋กœ)
Plain Text
๋ณต์‚ฌ
๋‚ ์งœ ๋ณ€๊ฒฝ ์‹œ ํŒŒ์ผ ์ฒ˜๋ฆฌ ์ „๋žต
if (_selectedDateStr != widget.date) { await _fileService.changeDiaryDate( widget.path, _selectedDateStr, title, content); Navigator.pop(context); // ๋ชฉ๋ก ํ™”๋ฉด์œผ๋กœ ๋Œ์•„๊ฐ€ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ return; }
Dart
๋ณต์‚ฌ
๋‚ ์งœ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ํŒŒ์ผ๋ช…๋„ ๋ฐ”๋€Œ์–ด์•ผ ํ•˜๋ฏ€๋กœ, ๊ธฐ์กด DetailPage๋ฅผ popํ•˜๊ณ  ๋ชฉ๋ก ํ™”๋ฉด์—์„œ ์ƒˆ๋กœ๊ณ ์นจํ•ฉ๋‹ˆ๋‹ค.

5-4. CalendarPage โ€” ๋‹ฌ๋ ฅ ๋ทฐ ํ™”๋ฉด

๊ตฌํ˜„ ์ˆœ์„œ
1. _loadEntries() โ†’ getDiaryEntries() โ†’ Map<String, List<DiaryEntry>> ๋ณ€ํ™˜ 2. _eventsFor(day) โ†’ ๋‚ ์งœ ํ‚ค โ†’ ์ผ๊ธฐ ๋ชฉ๋ก ๋ฐ˜ํ™˜ (eventLoader) 3. TableCalendar โ†’ ๋งˆ์ปค ํ‘œ์‹œ + ๋‚ ์งœ ์„ ํƒ ์ด๋ฒคํŠธ 4. _onDaySelected() โ†’ 1๊ฐœ: ๋ฐ”๋กœ DetailPage / 2๊ฐœ ์ด์ƒ: BottomSheet ์„ ํƒ 5. _buildSelectedDayList() โ†’ ๋‹ฌ๋ ฅ ์•„๋ž˜ ์„ ํƒ์ผ ์ผ๊ธฐ ๋ชฉ๋ก
Plain Text
๋ณต์‚ฌ
๋‚ ์งœ๋ณ„ Map ๊ตฌ์„ฑ ๋กœ์ง
final entries = await _fileService.getDiaryEntries(); final map = <String, List<DiaryEntry>>{}; for (final e in entries) { map.putIfAbsent(e.date, () => []).add(e); // "2026-03-26" โ†’ [DiaryEntry, DiaryEntry, ...] }
Dart
๋ณต์‚ฌ
๋‚ ์งœ ์„ ํƒ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
void _onDaySelected(DateTime selectedDay, DateTime focusedDay) { setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; }); final entries = _eventsFor(selectedDay); if (entries.isEmpty) return; if (entries.length == 1) { _openDetail(entries.first); // 1๊ฐœ โ†’ ๋ฐ”๋กœ ์ƒ์„ธ๋ณด๊ธฐ } else { _showPickerSheet(entries); // 2๊ฐœ ์ด์ƒ โ†’ BottomSheet ๋ชฉ๋ก } }
Dart
๋ณต์‚ฌ

6. ์ฃผ์š” UI ์ปดํฌ๋„ŒํŠธ

6-1. Drawer (์‚ฌ์ด๋“œ ๋ฉ”๋‰ด)

Scaffold์˜ drawer ์†์„ฑ์— ๋“ฑ๋กํ•˜๋ฉด AppBar ํ–„๋ฒ„๊ฑฐ ์•„์ด์ฝ˜(โ‰ก)์ด ์ž๋™ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
Widget _buildDrawer() { return Drawer( child: ListView( padding: EdgeInsets.zero, // DrawerHeader๊ฐ€ ์ƒ๋‹จ์— ๋”ฑ ๋ถ™๋„๋ก children: [ const DrawerHeader( decoration: BoxDecoration(color: Colors.amber), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ Icon(Icons.menu_book, size: 48, color: Colors.white), SizedBox(height: 8), Text('๋‚ด ์ผ๊ธฐ์žฅ', style: TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)), ], ), ), ListTile( leading: const Icon(Icons.calendar_month), title: const Text('๋‹ฌ๋ ฅ์œผ๋กœ ๋ณด๊ธฐ'), onTap: () { Navigator.pop(context); // Drawer ๋จผ์ € ๋‹ซ๊ธฐ Navigator.push(context, MaterialPageRoute( builder: (_) => const CalendarPage())); }, ), ], ), ); }
Dart
๋ณต์‚ฌ
ํ•ต์‹ฌ ํฌ์ธํŠธ
ํ•ญ๋ชฉ
์„ค๋ช…
padding: EdgeInsets.zero
ListView ๊ธฐ๋ณธ ์ƒ๋‹จ ํŒจ๋”ฉ ์ œ๊ฑฐ โ†’ DrawerHeader๊ฐ€ ์ƒ๋‹จ์— ๋”ฑ ๋ถ™์Œ
Navigator.pop(context)
Drawer๋ฅผ ๋จผ์ € ๋‹ซ๊ณ  ํŽ˜์ด์ง€ ์ด๋™ํ•ด์•ผ ์ž์—ฐ์Šค๋Ÿฌ์šด UX
DrawerHeader
๊ณ ์ • ๋†’์ด(160px)์˜ ํ—ค๋” ์˜์—ญ
ListTile
Drawer ๋ฉ”๋‰ด์˜ ํ‘œ์ค€ ํ•ญ๋ชฉ (leading ์•„์ด์ฝ˜ + title ํ…์ŠคํŠธ)

6-2. Dismissible (์Šค์™€์ดํ”„ ์‚ญ์ œ)

๋ฆฌ์ŠคํŠธ ํ•ญ๋ชฉ์„ ์Šค์™€์ดํ”„ํ•˜๋ฉด ์‚ฌ๋ผ์ง€๊ฒŒ ๋งŒ๋“œ๋Š” ์œ„์ ฏ
Dismissible( key: Key(entry.path), // ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ๊ณ ์œ  ํ‚ค๋กœ ์‚ฌ์šฉ direction: DismissDirection.endToStart, // ์™ผ์ชฝ ์Šค์™€์ดํ”„๋งŒ ํ—ˆ์šฉ background: Container( alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 4), decoration: BoxDecoration( color: Colors.redAccent, borderRadius: BorderRadius.circular(12), ), child: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.delete, color: Colors.white, size: 28), Text('์‚ญ์ œ', style: TextStyle(color: Colors.white, fontSize: 12)), ], ), ), confirmDismiss: (_) => showDialog<bool>( context: context, builder: (ctx) => AlertDialog( title: const Text('์ผ๊ธฐ ์‚ญ์ œ'), content: Text('[$date] ์ผ๊ธฐ๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('์ทจ์†Œ')), TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('์‚ญ์ œ', style: TextStyle(color: Colors.red))), ], ), ), onDismissed: (_) => _deleteSingle(entry.path), child: Card(child: ListTile(...)), )
Dart
๋ณต์‚ฌ
confirmDismiss vs onDismissed ์ฐจ์ด
์†์„ฑ
์‹คํ–‰ ์‹œ์ 
๋ฐ˜ํ™˜๊ฐ’
์—ญํ• 
confirmDismiss
์Šค์™€์ดํ”„ ํ›„, ํ•ญ๋ชฉ์ด ์‚ฌ๋ผ์ง€๊ธฐ ์ „
Future<bool?>
์‚ญ์ œ ์—ฌ๋ถ€ ์ตœ์ข… ํ™•์ธ
onDismissed
confirmDismiss๊ฐ€ true ๋ฐ˜ํ™˜ ํ›„
์—†์Œ
์‹ค์ œ ์‚ญ์ œ ๋กœ์ง ์‹คํ–‰
confirmDismiss์—์„œ false ๋˜๋Š” null์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ•ญ๋ชฉ์ด ์›์œ„์น˜๋กœ ๋Œ์•„์˜ต๋‹ˆ๋‹ค.
key๊ฐ€ ์ค‘์š”ํ•œ ์ด์œ 
// โŒ ์ž˜๋ชป๋œ ์˜ˆ โ€” ์ธ๋ฑ์Šค๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉ key: Key(index.toString()) // ์‚ญ์ œ ํ›„ ๋‹ค์Œ ํ•ญ๋ชฉ์ด ๊ฐ™์€ ํ‚ค๋ฅผ ๊ฐ€์ ธ ์˜ค๋™์ž‘ ๊ฐ€๋Šฅ // โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ โ€” ๊ณ ์œ ํ•œ ์‹๋ณ„์ž๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉ key: Key(entry.path) // ํŒŒ์ผ ๊ฒฝ๋กœ๋Š” ํ•ญ์ƒ ๊ณ ์œ 
Dart
๋ณต์‚ฌ

6-3. ModalBottomSheet (๋‹ฌ๋ ฅ ๋‹ค์ค‘ ์ผ๊ธฐ ์„ ํƒ)

๊ฐ™์€ ๋‚ ์— ์ผ๊ธฐ๊ฐ€ 2๊ฐœ ์ด์ƒ์ผ ๋•Œ ์„ ํƒ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ฃผ๋Š” ํŒจ๋„
showModalBottomSheet( context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (ctx) => Column( mainAxisSize: MainAxisSize.min, // ๋‚ด์šฉ ๋†’์ด์—๋งŒ ๋งž์ถค children: [ Container(width: 40, height: 4, decoration: BoxDecoration(color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2))), ListView.builder(shrinkWrap: true, ...), ], ), );
Dart
๋ณต์‚ฌ
์†์„ฑ
์„ค๋ช…
mainAxisSize: MainAxisSize.min
Column์ด ์ปจํ…์ธ  ๋†’์ด์—๋งŒ ๋งž์ถฐ ์ค„์–ด๋“ฆ
shrinkWrap: true
ListView๊ฐ€ Column ์•ˆ์—์„œ ์Šคํฌ๋กค ์—†์ด ์ „์ฒด ๋†’์ด ์ฐจ์ง€
shape
์ƒ๋‹จ ๋ชจ์„œ๋ฆฌ ๋‘ฅ๊ทผ ์ฒ˜๋ฆฌ

6-4. FloatingActionButton

ํ™”๋ฉด ์šฐํ•˜๋‹จ์— ๊ณ ์ •๋œ ์›ํ˜• ๋ฒ„ํŠผ (์ƒˆ ์ผ๊ธฐ ์“ฐ๊ธฐ ์•ก์…˜)
floatingActionButton: _isMultiSelect ? null // ๋‹ค์ค‘ ์„ ํƒ ๋ชจ๋“œ์—์„œ๋Š” ์ˆจ๊น€ : FloatingActionButton( onPressed: () async { await Navigator.push( context, MaterialPageRoute(builder: (_) => const WritePage()), ); _loadDiaries(); // ๋Œ์•„์˜จ ํ›„ ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ }, backgroundColor: Colors.amber, foregroundColor: Colors.black, child: const Icon(Icons.edit), ),
Dart
๋ณต์‚ฌ

6-5. BottomSheet (์ž‘์„ฑํ•˜๊ธฐ ๋ฒ„ํŠผ)

Scaffold.bottomSheet์— ์œ„์ ฏ์„ ์˜ฌ๋ฆฌ๋ฉด ํ™”๋ฉด ํ•˜๋‹จ์— ํ•ญ์ƒ ๊ณ ์ •๋œ ์˜์—ญ์ด ์ƒ๊น๋‹ˆ๋‹ค.
Scaffold( bottomSheet: SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 12), child: SizedBox( width: double.infinity, height: 52, child: ElevatedButton.icon( onPressed: _isSaving ? null : _save, icon: const Icon(Icons.edit_note), label: const Text('์ž‘์„ฑํ•˜๊ธฐ'), style: ElevatedButton.styleFrom( backgroundColor: Colors.amber, foregroundColor: Colors.black, ), ), ), ), ), body: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), // ํ•˜๋‹จ 80px ํŒจ๋”ฉ ์ถ”๊ฐ€ ... ), )
Dart
๋ณต์‚ฌ
SafeArea๋ฅผ ๊ฐ์‹ธ๋Š” ์ด์œ : iOS ํ™ˆ ์ธ๋””์ผ€์ดํ„ฐ ๋“ฑ ์‹œ์Šคํ…œ UI์™€ ๊ฒน์น˜์ง€ ์•Š๋„๋ก ์ž๋™์œผ๋กœ ์—ฌ๋ฐฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

7. ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

๋ฌธ์ œ 1: ํŒจํ‚ค์ง€ ๋ฒ„์ „์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ

# ์ž˜๋ชป๋œ ๋ฒ„์ „ ์ง€์ • ์˜ˆ calendar_date_picker2: ^0.9.0 # โŒ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฒ„์ „
Plain Text
๋ณต์‚ฌ
ํ•ด๊ฒฐ: flutter pub add ํŒจํ‚ค์ง€๋ช… ์‚ฌ์šฉ โ†’ pub.dev์—์„œ ์ตœ์‹  ๋ฒ„์ „ ์ž๋™ ์„ ํƒ
flutter pub add calendar_date_picker2 # โ†’ ^2.0.1 ์ž๋™ ์„ค์น˜
Bash
๋ณต์‚ฌ

๋ฌธ์ œ 2: MultiSelectContainer ๋ ˆ์ด์•„์›ƒ ์ถฉ๋Œ

์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
Plain Text
๋ณต์‚ฌ
์›์ธ: MultiSelectContainer(๋‚ด๋ถ€์— Wrap ์‚ฌ์šฉ)๋ฅผ SingleChildScrollView + Expanded ์•ˆ์— ๋„ฃ์œผ๋ฉด ๋†’์ด๊ฐ€ ๋ฌดํ•œ๋Œ€๋กœ ๊ณ„์‚ฐ๋จ
ํ•ด๊ฒฐ: ํŒจํ‚ค์ง€ ์ œ๊ฑฐ ํ›„ Set<String> + ListView.builder + ์ฒดํฌ ์•„์ด์ฝ˜์œผ๋กœ ์ง์ ‘ ๊ตฌํ˜„

๋ฌธ์ œ 3: ๊ธฐ์กด ํŒŒ์ผ์— ์ฝ”๋“œ๊ฐ€ ๋ง๋ถ™์—ฌ์ง

์›์ธ: ํŒŒ์ผ ํŽธ์ง‘ ๋„๊ตฌ์˜ ๋งค์นญ ์‹คํŒจ ํ›„ ํŒŒ์ผ ๋์— ๋‚ด์šฉ์ด append๋˜๋Š” ํ˜„์ƒ
ํ•ด๊ฒฐ: PowerShell Set-Content๋กœ ํŒŒ์ผ ์ „์ฒด๋ฅผ ๋ฎ์–ด์”Œ์›€
Set-Content -Path "๊ฒฝ๋กœ\\ํŒŒ์ผ.dart" -Value $content -Encoding UTF8
PowerShell
๋ณต์‚ฌ

๋ฌธ์ œ 4: System Navigation Bar, BottomSheet ๋ฒ„ํŠผ ๊ฒน์นจ

โ€ข
๋ฌธ์ œ ๋ฐœ์ƒ
โ—ฆ
ํ•˜๋‹จ ๋ฒ„ํŠผ์ด ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๋ฐ‘์œผ๋กœ ๋“ค์–ด๊ฐ
โ€ข
์›์ธ ์ถ”์ •
โ—ฆ
SafeArea ๋ฌธ์ œ์ธ๊ฐ€?
โ—ฆ
BottomSheet ๊ตฌ์กฐ ๋ฌธ์ œ์ธ๊ฐ€?
โ—ฆ
padding ๋ฌธ์ œ์ธ๊ฐ€?
โ€ข
์›์ธ ๋ฐœ๊ฒฌ
โ—ฆ
System Navigation Bar ๋†’์ด๋ฅผ ๊ณ ๋ ค ์•ˆ ํ•จ
โ€ข
ํ•ด๊ฒฐ
MediaQuery.of(context).padding.bottom
Plain Text
๋ณต์‚ฌ
โ€ข
์žฌ๋ฐœ ๋ฐฉ์ง€
โ—ฆ
ํ•˜๋‹จ ๊ณ ์ • ๋ฒ„ํŠผ ๋งŒ๋“ค ๋•Œ ํ•ญ์ƒ MediaQuery padding ์‚ฌ์šฉ
โ€ข
write_page.dart
โ€ข
detail_page.dart
... bottomSheet: SafeArea( child: Padding( // โšก System Navigation Bar [ ||| ใ… < ] ์œ„๋กœ BottomSheet ๋ฒ„ํŠผ ๊ฒน์น˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ // padding: const EdgeInsets.fromLTRB(16, 8, 16, 12), padding: EdgeInsets.fromLTRB(16, 8, 16,MediaQuery.of(context).padding.bottom + 12, ), ...
C
๋ณต์‚ฌ

MediaQuery๋ž€?

ํ˜„์žฌ ๊ธฐ๊ธฐ์˜ ํ™”๋ฉด ์ •๋ณด ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ
MediaQuery.of(context)
Dart
๋ณต์‚ฌ
๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด ์˜ˆ:
โ€ข
ํ™”๋ฉด ํฌ๊ธฐ
โ€ข
์ƒํƒœ๋ฐ” ๋†’์ด
โ€ข
ํ‚ค๋ณด๋“œ ๋†’์ด
โ€ข
ํ•˜๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๋†’์ด
โ€ข
ํ™”๋ฉด ๋ฐฉํ–ฅ ๋“ฑ

padding.bottom ์˜๋ฏธ

MediaQuery.of(context).padding.bottom
Dart
๋ณต์‚ฌ
์‹œ์Šคํ…œ UI ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋˜๋Š” ํ™”๋ฉด ์•„๋ž˜์ชฝ ์˜์—ญ ๋†’์ด
์ฆ‰,
โ€ข
์•ˆ๋“œ๋กœ์ด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๋†’์ด
โ€ข
์•„์ดํฐ ํ™ˆ ์ธ๋””์ผ€์ดํ„ฐ ๋†’์ด
โ€ข
์ œ์Šค์ฒ˜ ๋„ค๋น„๊ฒŒ์ด์…˜ ์˜์—ญ
์„ ์ž๋™์œผ๋กœ ์•Œ๋ ค์คŒ.

์˜ˆ์‹œ๋กœ ๋ณด๋ฉด

๊ธฐ๊ธฐ๋งˆ๋‹ค ๊ฐ’์ด ๋‹ค๋ฆ„
๊ธฐ๊ธฐ
padding.bottom
์•ˆ๋“œ๋กœ์ด๋“œ ๋ฒ„ํŠผ ์žˆ์Œ
48
์•ˆ๋“œ๋กœ์ด๋“œ ์ œ์Šค์ฒ˜
16
์•„์ดํฐ
34
PC / ์›น
0
๊ทธ๋ž˜์„œ ๊ณ ์ •๊ฐ’ padding ์ฃผ๋ฉด ์•ˆ ๋˜๊ณ  MediaQuery ์จ์•ผ ํ•จ

์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ

ํ•˜๋‹จ ๋ฒ„ํŠผ ์•ˆ ๊ฐ€๋ฆฌ๊ฒŒ

Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom ), child: ElevatedButton( onPressed: () {}, child: Text("์ €์žฅ"), ), )
Dart
๋ณต์‚ฌ
๊ทธ๋Ÿฌ๋ฉด ๋ฒ„ํŠผ์ด ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ์œ„๋กœ ์˜ฌ๋ผ์˜ด

padding / viewInsets ์ฐจ์ด (์ค‘์š”)

Flutter์—์„œ ์ด๊ฑฐ ์‹œํ—˜ ๋ฌธ์ œ๊ธ‰์œผ๋กœ ์ค‘์š”ํ•จ.
์ฝ”๋“œ
์˜๋ฏธ
padding.bottom
๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”
viewInsets.bottom
ํ‚ค๋ณด๋“œ
viewPadding.bottom
SafeArea ํฌํ•จ ์˜์—ญ

ํ‚ค๋ณด๋“œ ์˜ฌ๋ผ์™”์„ ๋•Œ

MediaQuery.of(context).viewInsets.bottom
Dart
๋ณต์‚ฌ

๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”

MediaQuery.of(context).padding.bottom
Dart
๋ณต์‚ฌ

์‹ค๋ฌด์—์„œ ์ œ์ผ ๋งŽ์ด ์“ฐ๋Š” ์ฝ”๋“œ

ํ•˜๋‹จ ๋ฒ„ํŠผ + ํ‚ค๋ณด๋“œ ๋Œ€์‘๊นŒ์ง€
padding: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom + MediaQuery.of(context).viewInsets.bottom )
Dart
๋ณต์‚ฌ
์ด๊ฑฐ ํ•˜๋‚˜ ๊ธฐ์–ตํ•˜๋ฉด Flutter ํ•˜๋‹จ UI ๋ฌธ์ œ ๊ฑฐ์˜ ๋‹ค ํ•ด๊ฒฐ๋จ.

ํ•œ ์ค„ ์ •๋ฆฌ

MediaQuery.of(context).padding.bottom
Dart
๋ณต์‚ฌ
ํฐ ํ™”๋ฉด ๋งจ ์•„๋ž˜ ์‹œ์Šคํ…œ ์˜์—ญ ๋†’์ด (๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๋†’์ด)

8. ํ•ต์‹ฌ Flutter ๊ฐœ๋… ์š”์•ฝ

๊ฐœ๋…
์‚ฌ์šฉ ์œ„์น˜
์„ค๋ช…
StatefulWidget
๋ชจ๋“  ํŽ˜์ด์ง€
์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋Š” ํ™”๋ฉด
setState()
๋ชจ๋“  ํŽ˜์ด์ง€
UI ์žฌ๋นŒ๋“œ ํŠธ๋ฆฌ๊ฑฐ
async/await
ํŒŒ์ผ I/O
๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
Navigator.push/pop
ํŽ˜์ด์ง€ ์ „ํ™˜
์Šคํƒ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…
TextEditingController
์ž…๋ ฅ ํ•„๋“œ
ํ…์ŠคํŠธ ์ฝ๊ธฐ/์ดˆ๊ธฐํ™”
mounted ์ฒดํฌ
๋น„๋™๊ธฐ ํ›„ setState
์œ„์ ฏ์ด ํŠธ๋ฆฌ์—์„œ ์ œ๊ฑฐ๋œ ๊ฒฝ์šฐ ๋ฐฉ์ง€
Timer
๊ฒ€์ƒ‰ ๋””๋ฐ”์šด์Šค
์ง€์—ฐ ์‹คํ–‰
Set<T>
๋‹ค์ค‘ ์„ ํƒ
O(1) ์ค‘๋ณต ์—†๋Š” ๊ฒฝ๋กœ ์ง‘ํ•ฉ
Map<K, List<V>>
๋‹ฌ๋ ฅ ์ด๋ฒคํŠธ
๋‚ ์งœ๋ณ„ ์ผ๊ธฐ ๊ทธ๋ฃนํ•‘
Platform.pathSeparator
๊ฒฝ๋กœ ํŒŒ์‹ฑ
Windows(\\) / macOSยทLinux(/) ์ž๋™ ๋Œ€์‘
SafeArea
bottomSheet
์‹œ์Šคํ…œ UI ๊ฒน์นจ ๋ฐฉ์ง€
expands: true
TextField
๋‚จ์€ ๊ณต๊ฐ„ ์ „์ฒด ์ฑ„์›€

9. ๊ธฐ๋Šฅ ๋ฆฌ์ŠคํŠธ

๊ธฐ๋Šฅ
๋‹ด๋‹น ํŒŒ์ผ
ํŒŒ์ผ ์ €์žฅ/์ฝ๊ธฐ/์‚ญ์ œ
file_service.dart
ํ•˜๋ฃจ ์—ฌ๋Ÿฌ ์ผ๊ธฐ
file_service.dart (HHmmss ํŒŒ์ผ๋ช…)
์ œ๋ชฉ + ๋ณธ๋ฌธ ๋ถ„๋ฆฌ ์ €์žฅ
file_service.dart (---DIARY--- ๊ตฌ๋ถ„์ž)
๋‚ ์งœ ์„ ํƒ (์ž‘์„ฑ)
write_page.dart
๋‚ ์งœ ์„ ํƒ (์ˆ˜์ •)
detail_page.dart
๋‚ ์งœ ๋ณ€๊ฒฝ ์‹œ ํŒŒ์ผ๋ช… ๊ต์ฒด
file_service.dart changeDiaryDate()
Drawer ์‚ฌ์ด๋“œ ๋ฉ”๋‰ด
home_page.dart
Dismissible ์Šค์™€์ดํ”„ ์‚ญ์ œ
home_page.dart
๋‹ค์ค‘ ์„ ํƒ ์‚ญ์ œ
home_page.dart (Set<String>)
๊ฒ€์ƒ‰ (๋‚ ์งœยท์ œ๋ชฉยท๋ณธ๋ฌธ)
home_page.dart + file_service.dart
๊ฒ€์ƒ‰ ๋””๋ฐ”์šด์Šค
home_page.dart (Timer)
๋‹ฌ๋ ฅ ๋ทฐ + ์ด๋ฒคํŠธ ๋งˆ์ปค
calendar_page.dart
๋‹ฌ๋ ฅ ๋‹ค์ค‘ ์ผ๊ธฐ BottomSheet
calendar_page.dart
bottomSheet ์ž‘์„ฑํ•˜๊ธฐ ๋ฒ„ํŠผ
write_page.dart, detail_page.dart