import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../core/bookmarks.dart'; /// 즐겨찾기 (bookmarks.json 에 저장). 바뀔 때마다 저장한다. class BookmarksController extends ChangeNotifier { final String? _path; BookmarkTree tree = BookmarkTree.defaults(); /// 마지막으로 지운 것 (실행 취소용) (BookmarkNode, String, int)? _lastRemoved; BookmarksController([this._path]); Future _file() async => _path ?? p.join((await getApplicationSupportDirectory()).path, 'bookmarks.json'); Future load() async { try { final f = File(await _file()); if (await f.exists()) { tree = BookmarkTree.fromJson(jsonDecode(await f.readAsString()) as Map); } } catch (_) { tree = BookmarkTree.defaults(); } notifyListeners(); } Future _save() async { final f = File(await _file()); await f.parent.create(recursive: true); await f.writeAsString(const JsonEncoder.withIndent(' ').convert(tree.toJson())); } void _changed() { notifyListeners(); _save(); } BookmarkNode addLink(String title, String url, {String parentId = BookmarkTree.barId, int? index}) { final n = tree.add(parentId, BookmarkNode.link(tree.newId(), title.trim().isEmpty ? url : title.trim(), url), index: index); _changed(); return n; } BookmarkNode addFolder(String title, {String parentId = BookmarkTree.barId, int? index}) { final n = tree.add(parentId, BookmarkNode.folder(tree.newId(), title.trim().isEmpty ? '새 폴더' : title.trim()), index: index); _changed(); return n; } /// [id] 를 폴더 [folderId] 의 맨 뒤로 (끌어다 폴더에 놓기) bool moveInto(String id, String folderId) { final f = tree.find(folderId); if (f == null || !f.isFolder) return false; return move(id, folderId, f.children!.length); } /// 끌어다 놓을 수 있는지: 자기 자신 · 자기 안쪽 폴더 · 기본 폴더 자체는 안 됨 bool canMoveInto(String id, String folderId) => id != folderId && id != BookmarkTree.barId && id != BookmarkTree.otherId && (tree.find(folderId)?.isFolder ?? false) && !tree.isInside(folderId, id); /// 즐겨찾기 HTML 로 내보내기 (Chrome · Edge · Firefox 에서 가져올 수 있는 형식) Future exportHtml(String file) async { final f = File(file); await f.parent.create(recursive: true); await f.writeAsString(tree.toNetscapeHtml()); } /// 즐겨찾기 HTML 가져오기 → 기타 즐겨찾기 안 "가져온 즐겨찾기" 폴더. 가져온 주소 수. Future importHtml(String file) async { final n = tree.importNetscapeHtml(await File(file).readAsString(), '가져온 즐겨찾기 (${p.basename(file)})'); if (n > 0) _changed(); return n; } void update(String id, {String? title, String? url}) { final n = tree.find(id); if (n == null) return; if (title != null && title.trim().isNotEmpty) n.title = title.trim(); if (url != null && !n.isFolder && url.trim().isNotEmpty) n.url = url.trim(); _changed(); } bool remove(String id) { final r = tree.remove(id); if (r == null) return false; _lastRemoved = r; _changed(); return true; } /// 방금 지운 것 되살리기 bool undoRemove() { final r = _lastRemoved; if (r == null) return false; final (node, parentId, index) = r; tree.add(parentId, node, index: index); _lastRemoved = null; _changed(); return true; } bool move(String id, String newParentId, int index) { final ok = tree.move(id, newParentId, index); if (ok) _changed(); return ok; } /// 같은 폴더 안에서 순서 바꾸기. [newIndex] 는 옮긴 뒤의 최종 위치 (onReorderItem 규칙) void reorder(String parentId, int oldIndex, int newIndex) { final parent = tree.find(parentId); if (parent == null || !parent.isFolder) return; final list = parent.children!; if (oldIndex < 0 || oldIndex >= list.length) return; final item = list.removeAt(oldIndex); list.insert(newIndex.clamp(0, list.length), item); _changed(); } /// Chrome · Edge 즐겨찾기 가져오기. 가져온 주소 수 (파일이 없으면 -1) Future importFrom(String browser) async { final local = Platform.environment['LOCALAPPDATA'] ?? ''; final file = switch (browser) { 'chrome' => p.join(local, 'Google', 'Chrome', 'User Data', 'Default', 'Bookmarks'), 'edge' => p.join(local, 'Microsoft', 'Edge', 'User Data', 'Default', 'Bookmarks'), 'whale' => p.join(local, 'Naver', 'Naver Whale', 'User Data', 'Default', 'Bookmarks'), _ => browser, // 파일 경로 직접 }; final f = File(file); if (!await f.exists()) return -1; final name = switch (browser) { 'chrome' => 'Chrome 에서 가져옴', 'edge' => 'Edge 에서 가져옴', 'whale' => 'Whale 에서 가져옴', _ => '가져온 즐겨찾기', }; final n = tree.importChromium(jsonDecode(await f.readAsString()) as Map, name); if (n > 0) _changed(); return n; } }