156 lines
5.2 KiB
Dart
156 lines
5.2 KiB
Dart
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';
|
|
import '../l10n/tr.dart';
|
|
|
|
/// 즐겨찾기 (bookmarks.json 에 저장). 바뀔 때마다 저장한다.
|
|
class BookmarksController extends ChangeNotifier {
|
|
final String? _path;
|
|
BookmarkTree tree = BookmarkTree.defaults();
|
|
|
|
/// 마지막으로 지운 것 (실행 취소용)
|
|
(BookmarkNode, String, int)? _lastRemoved;
|
|
|
|
BookmarksController([this._path]);
|
|
|
|
Future<String> _file() async =>
|
|
_path ?? p.join((await getApplicationSupportDirectory()).path, 'bookmarks.json');
|
|
|
|
Future<void> load() async {
|
|
try {
|
|
final f = File(await _file());
|
|
if (await f.exists()) {
|
|
tree = BookmarkTree.fromJson(jsonDecode(await f.readAsString()) as Map<String, dynamic>);
|
|
}
|
|
} catch (_) {
|
|
tree = BookmarkTree.defaults();
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> _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 ? tr('새 폴더') : 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<void> exportHtml(String file) async {
|
|
final f = File(file);
|
|
await f.parent.create(recursive: true);
|
|
await f.writeAsString(tree.toNetscapeHtml());
|
|
}
|
|
|
|
/// 즐겨찾기 HTML 가져오기 → 기타 즐겨찾기 안 "가져온 즐겨찾기" 폴더. 가져온 주소 수.
|
|
Future<int> importHtml(String file) async {
|
|
final n = tree.importNetscapeHtml(await File(file).readAsString(), trf('가져온 즐겨찾기 ({0})', [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<int> 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' => tr('Chrome 에서 가져옴'),
|
|
'edge' => tr('Edge 에서 가져옴'),
|
|
'whale' => tr('Whale 에서 가져옴'),
|
|
_ => tr('가져온 즐겨찾기'),
|
|
};
|
|
final n = tree.importChromium(jsonDecode(await f.readAsString()) as Map<String, dynamic>, name);
|
|
if (n > 0) _changed();
|
|
return n;
|
|
}
|
|
}
|