설정 A: 일반 분야 (GeneralSettings) · 남은 키 정리 - toJson · fromJson 은 분야 목록만 돈다
- 일반: 즐겨찾기 · 저장 공간 정리 · 바뀐 기본값 알림 · 안쪽 → 바깥 · 비밀번호 기억 · 휴지통 · 모든 파일 안내 (allFilesHint2) - imageExts 는 보기 화면 분야 (ReaderSettings) 로 - 표시 cookieScopeV2 → 다운로드, robocopyLinksV1 → 복사 분야로 - AppSettings 에 남은 것: 분야 목록 · 모르는 항목 보존 · 비밀 값 빼기 · 바뀐 기본값 알림 · 실행 중 값 (webViewDataDir) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
e828227693
commit
d83b0b5aef
|
|
@ -56,7 +56,7 @@ void main() {
|
|||
..explorer.layout = w < 720 ? 'single' : 'dual'
|
||||
..explorer.onePaneCopy = 'other'
|
||||
..explorer.paths = [a.path, b.path]
|
||||
..folderBookmarks = [FolderBookmark(name: 'Show S01', path: p.join(a.path, 'Show S01'))];
|
||||
..general.folderBookmarks = [FolderBookmark(name: 'Show S01', path: p.join(a.path, 'Show S01'))];
|
||||
final shotKey = GlobalKey();
|
||||
await t.runAsync(() => t.pumpWidget(RepaintBoundary(
|
||||
key: shotKey,
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ class LiveSync extends ChangeNotifier {
|
|||
continue;
|
||||
}
|
||||
// 대상이 원본 안이면 맞출 때마다 원본이 바뀌어 끝없이 돈다. 34: 원본이 대상 안 (안쪽 → 바깥) 은 지우기가 없을 때만
|
||||
if (nestingProblem(x, allowInnerToOuter: s.allowInnerToOuter) case final nest?) {
|
||||
if (nestingProblem(x, allowInnerToOuter: s.general.allowInnerToOuter) case final nest?) {
|
||||
status[k] = (DateTime.now(), nest);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -519,7 +519,7 @@ class LiveSync extends ChangeNotifier {
|
|||
final bad = unreadableOf(x);
|
||||
final nest = bad.isNotEmpty
|
||||
? optionProblemMessage(bad)
|
||||
: nestingProblem(x, allowInnerToOuter: c.settings.allowInnerToOuter);
|
||||
: nestingProblem(x, allowInnerToOuter: c.settings.general.allowInnerToOuter);
|
||||
if (nest != null) {
|
||||
status[k] = (DateTime.now(), nest);
|
||||
notifyListeners();
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ import 'dart:io';
|
|||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../core/reader_sources.dart' show defaultImageExtensions;
|
||||
import '../core/download_detect.dart';
|
||||
import 'settings/records.dart';
|
||||
import 'settings/settings_group.dart';
|
||||
import 'settings/general_settings.dart';
|
||||
import 'settings/playback_settings.dart';
|
||||
import 'settings/mkv_settings.dart';
|
||||
import 'settings/display_settings.dart';
|
||||
|
|
@ -25,6 +24,7 @@ import 'settings/explorer_settings.dart';
|
|||
export 'settings/records.dart';
|
||||
export 'settings/settings_group.dart';
|
||||
export 'settings/settings_store.dart';
|
||||
export 'settings/general_settings.dart';
|
||||
export 'settings/playback_settings.dart';
|
||||
export 'settings/mkv_settings.dart';
|
||||
export 'settings/display_settings.dart';
|
||||
|
|
@ -43,192 +43,9 @@ export 'settings/explorer_settings.dart';
|
|||
|
||||
/// 환경 설정 (settings.json 에 저장)
|
||||
class AppSettings {
|
||||
|
||||
|
||||
/// 73: 파일 탐색기 · Rsync 즐겨찾기 (폴더, Rsync 는 짝)
|
||||
List<FolderBookmark> folderBookmarks = [];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ── 네트워크 · DNS (사용자 요청 10/10: "DNS 주소를 강제로 내가 설정한 주소로", 기본 8.8.8.8 · 보조 8.8.4.4 - 관리자 판단) ──
|
||||
|
||||
|
||||
// ── Tor 브라우저 (사용자 요청 10/10) ──
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ── 파일 탐색기 (X-plore 참고) ──
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ── 복사 · 이동 (파일 탐색기) · rsync · lsync (Rsync 화면) ──
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// 그림 보기 (만화 보기) 대상 확장자 (파일 탐색기에서 두 번 누르면 보기로 연다)
|
||||
List<String> imageExts = [...defaultImageExtensions];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ── 102: 사용자에게 물어 정한 것 · 관리자 1차 판단 (기본값 = 그 답) ──
|
||||
|
||||
|
||||
|
||||
/// 저장 공간 정리 창에서 미리 체크할 묶음 (작업 임시 파일 · 받다 만 다운로드)
|
||||
List<String> cleanupPrechecked = ['work', 'download'];
|
||||
|
||||
/// 19: 업데이트로 바뀐 기본값을 켤 때 한 번 알림
|
||||
bool migrationNotice = true;
|
||||
|
||||
/// 34: Rsync · 동기화에서 원본이 대상 안 (안쪽 → 바깥) 을 허용 (지우기가 있을 때만 막음). 끄면 늘 막음
|
||||
bool allowInnerToOuter = true;
|
||||
|
||||
/// 40 · 54: 비밀번호를 이 기기의 안전 저장소에 기억 (끄면 저장하지 않고 앱을 켤 때마다 다시 넣음)
|
||||
bool rememberPasswords = true;
|
||||
|
||||
|
||||
/// 65 (Windows): 탐색기에서 지우면 휴지통으로 (끄면 늘 영구 삭제로 묻는다)
|
||||
bool recycleOnDelete = true;
|
||||
|
||||
|
||||
|
||||
|
||||
/// 39: 켤 때 "모든 파일 접근" 안내를 한 번 보였는지 (그 뒤로는 파일 탐색기 안의 안내로)
|
||||
bool allFilesHintShown = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ───────── 123 · 121: AI 그림 · 해상도 올리기 ─────────
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 121: 해상도 올리기
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// 앱 안 브라우저의 데이터 폴더 (로그인 · 쿠키). 실행 중에 정해지며 저장하지 않음
|
||||
String webViewDataDir = '';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AppSettings();
|
||||
|
||||
/// 파일 탐색기 (X-plore 참고)
|
||||
|
|
@ -237,7 +54,7 @@ class AppSettings {
|
|||
/// 네트워크 · DNS (사용자 요청 10/10)
|
||||
final network = NetworkSettings();
|
||||
|
||||
/// Tor 釉뚮씪?곗? (?ъ슜???붿껌 10/10)
|
||||
/// Tor 브라우저 (사용자 요청 10/10)
|
||||
final tor = TorBrowserSettings();
|
||||
|
||||
/// 복사 · 이동 (파일 탐색기) · rsync · 실시간 동기화 (Rsync 화면)
|
||||
|
|
@ -276,26 +93,16 @@ class AppSettings {
|
|||
/// 재생 (동영상 열기 · 재생 목록 · 외부 플레이어 · 자막 크기 · 기본 자막 언어)
|
||||
final playback = PlaybackSettings();
|
||||
|
||||
/// 일반 (즐겨찾기 · 저장 공간 정리 · 바뀐 기본값 알림 · 안쪽 → 바깥 동기화 · 비밀번호 기억 · 휴지통 · 모든 파일 안내)
|
||||
final general = GeneralSettings();
|
||||
|
||||
/// 설정 분야들 (설정 A) - 저마다 자기 키를 읽고 쓴다. toJson · fromJson 은 이 목록을 돌며 합치기만 한다
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader, download, browser, run, vault, tools, remote, display, mkv, playback];
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader, download, browser, run, vault, tools, remote, display, mkv, playback, general];
|
||||
|
||||
/// [plainSecrets]: 안전 저장소를 쓸 수 없을 때만 (예전 파일에 있던 비밀 값을 잃지 않게) 예전처럼 파일에 둔다
|
||||
Map<String, Object?> toJson({bool plainSecrets = false}) {
|
||||
final j = <String, Object?>{
|
||||
// 이 판이 모르는 항목 (새 판이 쓴 것 등) 은 지우지 않고 그대로 (40)
|
||||
...stripSecrets(extra),
|
||||
'folderBookmarks': [for (final b in folderBookmarks) b.toJson()],
|
||||
'cookieScopeV2': true,
|
||||
'robocopyLinksV1': true,
|
||||
'imageExts': imageExts,
|
||||
'cleanupPrechecked': cleanupPrechecked,
|
||||
'migrationNotice': migrationNotice,
|
||||
'allowInnerToOuter': allowInnerToOuter,
|
||||
'rememberPasswords': rememberPasswords,
|
||||
'recycleOnDelete': recycleOnDelete,
|
||||
// 116: 예전 판의 표시 ('allFilesHintShown') 는 권한이 없어진 것을 몰라 다시 띄우지 못했다 → 새 이름으로 한 번 더
|
||||
'allFilesHint2': allFilesHintShown,
|
||||
};
|
||||
// 이 판이 모르는 항목 (새 판이 쓴 것 등) 은 지우지 않고 그대로 (40)
|
||||
final j = <String, Object?>{...stripSecrets(extra)};
|
||||
for (final g in groups) {
|
||||
g.write(j, plainSecrets: plainSecrets);
|
||||
}
|
||||
|
|
@ -303,19 +110,7 @@ class AppSettings {
|
|||
}
|
||||
|
||||
factory AppSettings.fromJson(Map<String, dynamic> j) {
|
||||
final s = AppSettings()
|
||||
..folderBookmarks = [for (final x in (j['folderBookmarks'] as List?) ?? const []) ?FolderBookmark.fromJson(x)]
|
||||
..imageExts = j['imageExts'] is List
|
||||
? [for (final x in j['imageExts'] as List) '$x'.toLowerCase()]
|
||||
: [...defaultImageExtensions]
|
||||
..cleanupPrechecked = j['cleanupPrechecked'] is List
|
||||
? [for (final x in j['cleanupPrechecked'] as List) '$x']
|
||||
: ['work', 'download']
|
||||
..migrationNotice = j['migrationNotice'] != false
|
||||
..allowInnerToOuter = j['allowInnerToOuter'] != false
|
||||
..rememberPasswords = j['rememberPasswords'] != false
|
||||
..recycleOnDelete = j['recycleOnDelete'] != false
|
||||
..allFilesHintShown = j['allFilesHint2'] == true;
|
||||
final s = AppSettings();
|
||||
// 분야를 먼저 읽는다 - 바뀐 기본값 알림 ([_migrated]) 이 분야의 값을 본다
|
||||
for (final g in s.groups) {
|
||||
g.read(j);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ class CopySettings implements SettingsGroup {
|
|||
j['copyTasks'] = [for (final x in tasks) x.toJson()];
|
||||
j['rsyncPaths'] = rsyncPaths;
|
||||
j['syncStopToast'] = syncStopToast;
|
||||
// 193: robocopy 가 링크 · junction 을 따라가지 않게 (/XJ) 바뀐 것을 알린 판 (바뀐 기본값 알림 'robocopyLinks')
|
||||
j['robocopyLinksV1'] = true;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ class DownloadSettings implements SettingsGroup {
|
|||
j['loginCookieSites'] = loginCookieSites;
|
||||
j['ytCookiesFile'] = ytCookiesFile;
|
||||
j['maxParallelDownloads'] = maxParallel;
|
||||
// 53 · 28: 쿠키를 넘기는 사이트가 넓어진 것을 알린 판 (바뀐 기본값 알림 'cookieScope')
|
||||
j['cookieScopeV2'] = true;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import 'records.dart';
|
||||
import 'settings_group.dart';
|
||||
|
||||
/// 약속 (설정 A): 환경 설정 > 일반 (즐겨찾기 · 저장 공간 정리 · 바뀐 기본값 알림 · 안쪽 → 바깥 동기화 · 비밀번호 기억 · 휴지통 · 모든 파일 안내) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
/// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키)
|
||||
class GeneralSettings implements SettingsGroup {
|
||||
/// 73: 파일 탐색기 · Rsync 즐겨찾기 (폴더, Rsync 는 짝)
|
||||
List<FolderBookmark> folderBookmarks = [];
|
||||
|
||||
/// 저장 공간 정리 창에서 미리 체크할 묶음 (작업 임시 파일 · 받다 만 다운로드)
|
||||
List<String> cleanupPrechecked = ['work', 'download'];
|
||||
|
||||
/// 19: 업데이트로 바뀐 기본값을 켤 때 한 번 알림
|
||||
bool migrationNotice = true;
|
||||
|
||||
/// 34: Rsync · 동기화에서 원본이 대상 안 (안쪽 → 바깥) 을 허용 (지우기가 있을 때만 막음). 끄면 늘 막음
|
||||
bool allowInnerToOuter = true;
|
||||
|
||||
/// 40 · 54: 비밀번호를 이 기기의 안전 저장소에 기억 (끄면 저장하지 않고 앱을 켤 때마다 다시 넣음)
|
||||
bool rememberPasswords = true;
|
||||
|
||||
/// 65 (Windows): 탐색기에서 지우면 휴지통으로 (끄면 늘 영구 삭제로 묻는다)
|
||||
bool recycleOnDelete = true;
|
||||
|
||||
/// 39: 켤 때 "모든 파일 접근" 안내를 한 번 보였는지 (그 뒤로는 파일 탐색기 안의 안내로)
|
||||
bool allFilesHintShown = false;
|
||||
|
||||
@override
|
||||
void write(Map<String, Object?> j, {required bool plainSecrets}) {
|
||||
j['folderBookmarks'] = [for (final b in folderBookmarks) b.toJson()];
|
||||
j['cleanupPrechecked'] = cleanupPrechecked;
|
||||
j['migrationNotice'] = migrationNotice;
|
||||
j['allowInnerToOuter'] = allowInnerToOuter;
|
||||
j['rememberPasswords'] = rememberPasswords;
|
||||
j['recycleOnDelete'] = recycleOnDelete;
|
||||
// 116: 예전 판의 표시 ('allFilesHintShown') 는 권한이 없어진 것을 몰라 다시 띄우지 못했다 → 새 이름으로 한 번 더
|
||||
j['allFilesHint2'] = allFilesHintShown;
|
||||
}
|
||||
|
||||
@override
|
||||
void read(Map<String, dynamic> j) {
|
||||
folderBookmarks = [for (final x in (j['folderBookmarks'] as List?) ?? const []) ?FolderBookmark.fromJson(x)];
|
||||
cleanupPrechecked = j['cleanupPrechecked'] is List
|
||||
? [for (final x in j['cleanupPrechecked'] as List) '$x']
|
||||
: ['work', 'download'];
|
||||
migrationNotice = j['migrationNotice'] != false;
|
||||
allowInnerToOuter = j['allowInnerToOuter'] != false;
|
||||
rememberPasswords = j['rememberPasswords'] != false;
|
||||
recycleOnDelete = j['recycleOnDelete'] != false;
|
||||
allFilesHintShown = j['allFilesHint2'] == true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
import '../../core/reader_sources.dart' show defaultImageExtensions;
|
||||
import 'settings_group.dart';
|
||||
|
||||
/// 약속 (설정 A): 환경 설정 > 보기 화면 (그림 · 만화 · PDF · 문서) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
|
|
@ -32,6 +33,9 @@ class ReaderSettings implements SettingsGroup {
|
|||
/// ZIP · CBZ 를 두 번 누르면 안의 그림을 만화처럼 보기 (끄면 목록). 기본은 목록 (사용자 지시)
|
||||
bool zipComic = false;
|
||||
|
||||
/// 그림 보기 (만화 보기) 대상 확장자 (파일 탐색기에서 두 번 누르면 보기로 연다)
|
||||
List<String> imageExts = [...defaultImageExtensions];
|
||||
|
||||
@override
|
||||
void write(Map<String, Object?> j, {required bool plainSecrets}) {
|
||||
j['readerFit'] = fit;
|
||||
|
|
@ -43,6 +47,7 @@ class ReaderSettings implements SettingsGroup {
|
|||
j['readerVolumeKeys'] = volumeKeys;
|
||||
j['readerSplit'] = split;
|
||||
j['zipComic'] = zipComic;
|
||||
j['imageExts'] = imageExts;
|
||||
// 예전 기본값 (만화 보기 켜짐) 은 지시와 반대였으므로 한 번 꺼진 상태 (목록) 로
|
||||
j['zipComicV2'] = true;
|
||||
}
|
||||
|
|
@ -58,5 +63,6 @@ class ReaderSettings implements SettingsGroup {
|
|||
volumeKeys = j['readerVolumeKeys'] as bool? ?? false;
|
||||
split = const ['auto', 'on', 'off'].contains(j['readerSplit']) ? j['readerSplit'] as String : 'auto';
|
||||
zipComic = j['zipComic'] == true && j['zipComicV2'] == true;
|
||||
imageExts = j['imageExts'] is List ? [for (final x in j['imageExts'] as List) '$x'.toLowerCase()] : [...defaultImageExtensions];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class SettingsStore {
|
|||
/// 지금 설정의 비밀 값 (빈 값은 넣지 않음 = 저장소에서 지움)
|
||||
/// 규칙 (관리자 10/21): 새 종류의 비밀 값은 지금 꼴 ([secretForms]) 과 앞부분이 겹치지 않는 새 머리글 (dav2. 등) 로 만든다 -
|
||||
/// 겹치면 그 꼴을 아는 예전 판이 저장할 때 "없어진 항목" 으로 보고 지운다. 새 꼴은 [secretForms] 에도 더한다
|
||||
static Map<String, String> _secretsOf(AppSettings s) => !s.rememberPasswords ? const {} : {
|
||||
static Map<String, String> _secretsOf(AppSettings s) => !s.general.rememberPasswords ? const {} : {
|
||||
for (final e in _osKeys.entries)
|
||||
if (_os(s, e.key).isNotEmpty) e.value: _os(s, e.key),
|
||||
for (final x in s.remote.webdavServers)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import 'settings_group.dart';
|
||||
|
||||
/// 약속 (설정 A): 환경 설정 > Tor 釉뚮씪?곗? (?ъ슜???붿껌 10/10) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
/// 약속 (설정 A): 환경 설정 > Tor 브라우저 (사용자 요청 10/10) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
/// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키)
|
||||
class TorBrowserSettings implements SettingsGroup {
|
||||
/// Tor 화면의 쿠키 · 기록 · 캐시를 닫은 뒤에도 남기기 (기본: 닫으면 지움)
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ class _AiImagePageState extends State<AiImagePage> with RouteAware {
|
|||
}
|
||||
|
||||
Future<void> _pickInit() async {
|
||||
final files = await c.services.storage.pickFiles(title: tr('바탕 그림 고르기'), extensions: c.settings.imageExts);
|
||||
final files = await c.services.storage.pickFiles(title: tr('바탕 그림 고르기'), extensions: c.settings.reader.imageExts);
|
||||
if (files.isNotEmpty && mounted) setState(() => _init = files.first);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import '../l10n/tr.dart';
|
|||
/// 권한이 있으면 표시를 지워 두어 권한이 없어진 뒤 (사용자가 끔 · 재설치) 다음에 켤 때 한 번 더 띄운다.
|
||||
Future<bool> allFilesGuideDue(AppController c, {required bool hasAccess}) async {
|
||||
if (hasAccess) {
|
||||
if (c.settings.allFilesHintShown) await c.updateSettings((s) => s.allFilesHintShown = false);
|
||||
if (c.settings.general.allFilesHintShown) await c.updateSettings((s) => s.general.allFilesHintShown = false);
|
||||
return false;
|
||||
}
|
||||
if (c.settings.allFilesHintShown) return false;
|
||||
await c.updateSettings((s) => s.allFilesHintShown = true);
|
||||
if (c.settings.general.allFilesHintShown) return false;
|
||||
await c.updateSettings((s) => s.general.allFilesHintShown = true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class _CleanupDialogState extends State<_CleanupDialog> {
|
|||
_groups = g;
|
||||
// 처음에는 작업 임시 파일 · 받다 만 다운로드만 고른 상태로 (사용자 결정 10/8).
|
||||
// 받다 만 AI 모델 · 지난 작업 기록 · 업데이트 남은 파일은 직접 체크해야 지운다
|
||||
final pre = widget.cleaner.c.settings.cleanupPrechecked.toSet(); // 102: 환경 설정 (기본: 작업 임시 파일 · 받다 만 다운로드)
|
||||
final pre = widget.cleaner.c.settings.general.cleanupPrechecked.toSet(); // 102: 환경 설정 (기본: 작업 임시 파일 · 받다 만 다운로드)
|
||||
_picked.addAll([for (final x in g) if (x.skipped == null && x.items.isNotEmpty && pre.contains(x.id)) x.id]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ class ViewerSettings extends StatelessWidget {
|
|||
for (final e in _choices)
|
||||
FilterChip(
|
||||
label: Text(e),
|
||||
selected: s.imageExts.contains(e),
|
||||
onSelected: (on) => c.updateSettings((x) => x.imageExts = [
|
||||
for (final y in x.imageExts) if (y != e) y,
|
||||
selected: s.reader.imageExts.contains(e),
|
||||
onSelected: (on) => c.updateSettings((x) => x.reader.imageExts = [
|
||||
for (final y in x.reader.imageExts) if (y != e) y,
|
||||
if (on) e,
|
||||
]),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ class _LiveSyncTile extends StatelessWidget {
|
|||
pathRow(ctx, tr('원본'), src!, (v) => set(() => src = v)),
|
||||
pathRow(ctx, tr('대상'), dst!, (v) => set(() => dst = v)),
|
||||
// 34: 안쪽 → 바깥은 지우기 없이만, 바깥 → 안쪽은 안 됨 (끝없이 돎)
|
||||
if (LiveSync.nestingProblem(LiveSyncPair(src!, dst!, delete: delete), allowInnerToOuter: c.settings.allowInnerToOuter)
|
||||
if (LiveSync.nestingProblem(LiveSyncPair(src!, dst!, delete: delete), allowInnerToOuter: c.settings.general.allowInnerToOuter)
|
||||
case final nest?)
|
||||
Text(nest, style: TextStyle(fontSize: 12, color: JjColors.alert))
|
||||
else if (isSameOrInside(src!, dst!))
|
||||
|
|
@ -361,7 +361,7 @@ class _LiveSyncTile extends StatelessWidget {
|
|||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
|
||||
FilledButton(
|
||||
onPressed: LiveSync.nestingProblem(LiveSyncPair(src!, dst!, delete: delete), allowInnerToOuter: c.settings.allowInnerToOuter) !=
|
||||
onPressed: LiveSync.nestingProblem(LiveSyncPair(src!, dst!, delete: delete), allowInnerToOuter: c.settings.general.allowInnerToOuter) !=
|
||||
null
|
||||
? null
|
||||
: () => Navigator.pop(ctx, true),
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ExplorerDeleteFlow {
|
|||
// X-plore 비교 e: Android 는 앱 휴지통 (저장 장치의 .jj_trash, 보관 일이 0 이면 끔)
|
||||
final trash = appTrash;
|
||||
final canTrash = trash != null && paths.every((x) => !isDav(x) && trash.trashDirFor(x) != null && !trash.isInTrash(x));
|
||||
final canRecycle = (local && ctl.c.settings.recycleOnDelete && paths.every(hasRecycleBin)) || canTrash;
|
||||
final canRecycle = (local && ctl.c.settings.general.recycleOnDelete && paths.every(hasRecycleBin)) || canTrash;
|
||||
var permanent = !canRecycle || shift;
|
||||
// X-plore 숨기기: 지우는 폴더 안에 목록에서 숨긴 것이 있으면 알린다 (숨긴 것도 함께 지워진다)
|
||||
final hiddenInside = [
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ class ExplorerController extends ChangeNotifier {
|
|||
}
|
||||
final files = [for (final e in list ?? const <FileEntry>[]) if (!e.isDir) e];
|
||||
if (files.isEmpty) return null;
|
||||
final media = files.where((e) => c.settings.imageExts.contains(e.ext) || isVideoFile(e.path)).length;
|
||||
final media = files.where((e) => c.settings.reader.imageExts.contains(e.ext) || isVideoFile(e.path)).length;
|
||||
if (media * 2 > files.length) return ('date', true);
|
||||
if (files.where((e) => isAudioFile(e.path)).length * 2 > files.length) return ('name', false);
|
||||
return null;
|
||||
|
|
@ -425,7 +425,7 @@ class ExplorerController extends ChangeNotifier {
|
|||
List<TreeRow> rows(ExplorerPane q, {bool foldersOnly = false}) => buildTreeRows(q,
|
||||
foldersOnly: foldersOnly,
|
||||
roots: rootsInTree ? [for (final (path, _) in volumes) path] : const [],
|
||||
favorites: rootsInTree ? [for (final b in c.settings.folderBookmarks) b.path] : const [],
|
||||
favorites: rootsInTree ? [for (final b in c.settings.general.folderBookmarks) b.path] : const [],
|
||||
isHidden: appHidden,
|
||||
showHidden: showAppHidden,
|
||||
browsable: arc.contains);
|
||||
|
|
|
|||
|
|
@ -52,10 +52,10 @@ class ExplorerItemMenu {
|
|||
// 그림 → PDF: 표시한 것 중 그림 (누른 것이 표시에 없으면 누른 것만)
|
||||
final viewer = ctl.c.settings.display.components.contains('viewer');
|
||||
final picked = pane.sel.has(e.path) ? pane.sel.marked : [e.path];
|
||||
final images = [for (final x in picked) if (ctl.c.settings.imageExts.contains(extOf(x))) x];
|
||||
final images = [for (final x in picked) if (ctl.c.settings.reader.imageExts.contains(extOf(x))) x];
|
||||
final ext = extOf(e.path);
|
||||
final viewable = viewer && !e.isDir &&
|
||||
(ctl.c.settings.imageExts.contains(ext) || ext == 'pdf' || zipExtensions.contains(ext));
|
||||
(ctl.c.settings.reader.imageExts.contains(ext) || ext == 'pdf' || zipExtensions.contains(ext));
|
||||
final dual = cmd.dual;
|
||||
// 77: 누른 것이 표시한 것 중 하나면 표시한 것 모두에 (복사 · 이동 · 삭제 · 공유 · 경로 복사)
|
||||
final targets = sel.isMarked(pane, e.path) && pane.sel.marked.length > 1 ? pane.sel.marked : [e.path];
|
||||
|
|
@ -130,7 +130,7 @@ class ExplorerItemMenu {
|
|||
if (flows.transfers.clip.isNotEmpty)
|
||||
('paste', Icons.content_paste, trf('{0} 에 붙여넣기 ({1}개)', [vBasename(e.isDir ? e.path : vDirname(e.path)), flows.transfers.clip.items.length])),
|
||||
('copyPath', Icons.link, many ? trf('경로 복사 ({0}개)', [targets.length]) : tr('경로 복사')),
|
||||
if (e.isDir && !ctl.c.settings.folderBookmarks.any((b) => !b.isPair && samePath(b.path, e.path)))
|
||||
if (e.isDir && !ctl.c.settings.general.folderBookmarks.any((b) => !b.isPair && samePath(b.path, e.path)))
|
||||
('bookmark', Icons.star_outline, tr('즐겨찾기에 추가')),
|
||||
if (!dav && (!Platform.isAndroid || e.isDir)) ('reveal', Icons.folder_outlined, tr('파일 관리자에서 보기')),
|
||||
('info', Icons.info_outline, tr('정보')),
|
||||
|
|
@ -306,7 +306,7 @@ class ExplorerItemMenu {
|
|||
ctl.host.snack(many ? trf('경로 {0}개를 복사했습니다', [paths.length]) : trf('경로를 복사했습니다: {0}', [paths.single]));
|
||||
case 'bookmark':
|
||||
final name = vBasename(e.path).isEmpty ? e.path : vBasename(e.path);
|
||||
await ctl.c.updateSettings((s) => s.folderBookmarks = [...s.folderBookmarks, FolderBookmark(name: name, path: e.path)]);
|
||||
await ctl.c.updateSettings((s) => s.general.folderBookmarks = [...s.general.folderBookmarks, FolderBookmark(name: name, path: e.path)]);
|
||||
ctl.host.snack(trf('즐겨찾기에 넣었습니다: {0}', [name]));
|
||||
ctl.update();
|
||||
case 'view':
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class ExplorerOpener {
|
|||
// 여는 길은 file_opener (E5c) 가 고른다
|
||||
switch (openRouteFor(path,
|
||||
components: ctl.c.settings.display.components,
|
||||
imageExts: ctl.c.settings.imageExts,
|
||||
imageExts: ctl.c.settings.reader.imageExts,
|
||||
android: Platform.isAndroid,
|
||||
windows: Platform.isWindows,
|
||||
hwpEngine: RhwpEngine.available)) {
|
||||
|
|
@ -105,7 +105,7 @@ class ExplorerOpener {
|
|||
builder: (_) => ExplorerGridPage(
|
||||
title: vBasename(dir).isEmpty ? dir : vBasename(dir),
|
||||
entries: list,
|
||||
imageExts: ctl.c.settings.imageExts,
|
||||
imageExts: ctl.c.settings.reader.imageExts,
|
||||
videoThumb: (e) => BigVideoThumbs.of(ctl.c, e),
|
||||
onOpen: (e) => open(context, e.path, pane: pane),
|
||||
),
|
||||
|
|
@ -137,7 +137,7 @@ class ExplorerOpener {
|
|||
}
|
||||
final images = [
|
||||
for (final e in list)
|
||||
if (!e.isDir && ctl.c.settings.imageExts.contains(extOf(e.path))) e.path,
|
||||
if (!e.isDir && ctl.c.settings.reader.imageExts.contains(extOf(e.path))) e.path,
|
||||
];
|
||||
if (!images.any((x) => samePath(x, path))) images.insert(0, path);
|
||||
final start = images.indexWhere((x) => samePath(x, path));
|
||||
|
|
@ -157,7 +157,7 @@ class ExplorerOpener {
|
|||
}
|
||||
final images = [
|
||||
for (final e in list)
|
||||
if (!e.isDir && ctl.c.settings.imageExts.contains(extOf(e.path)) && (ctl.showAppHidden || !ctl.appHidden(e.path))) e.path,
|
||||
if (!e.isDir && ctl.c.settings.reader.imageExts.contains(extOf(e.path)) && (ctl.showAppHidden || !ctl.appHidden(e.path))) e.path,
|
||||
];
|
||||
if (images.isEmpty) {
|
||||
ctl.host.snack(tr('이 폴더에는 그림이 없습니다'));
|
||||
|
|
@ -361,7 +361,7 @@ class ExplorerOpener {
|
|||
index: i,
|
||||
probe: ctl.c.services.mediaTool.probe,
|
||||
thumbnail: (e) => BigVideoThumbs.of(ctl.c, e),
|
||||
imageExts: ctl.c.settings.imageExts,
|
||||
imageExts: ctl.c.settings.reader.imageExts,
|
||||
onOpen: (e) => e.isDir ? unawaited(ctl.goTo(pane, e.path)) : unawaited(open(context, e.path, pane: pane)),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ class ExplorerPaneHeader extends StatelessWidget {
|
|||
tooltip: tr('즐겨찾기'),
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: Icon(
|
||||
ctl.c.settings.folderBookmarks.any((b) => !b.isPair && samePath(b.path, pane.current)) ? Icons.star : Icons.star_outline,
|
||||
ctl.c.settings.general.folderBookmarks.any((b) => !b.isPair && samePath(b.path, pane.current)) ? Icons.star : Icons.star_outline,
|
||||
size: 16,
|
||||
color: JjColors.amber),
|
||||
onPressed: () => onBookmarks(),
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ class ExplorerExtraRow extends StatelessWidget {
|
|||
final path = row.entry.path;
|
||||
final volume = ctl.volumes.firstWhere((v) => samePath(v.$1, path), orElse: () => (path, vBasename(path))).$2;
|
||||
final fav = row.kind == TreeRow.favorite
|
||||
? ctl.c.settings.folderBookmarks.firstWhere((b) => b.path == path, orElse: () => FolderBookmark(name: vBasename(path), path: path))
|
||||
? ctl.c.settings.general.folderBookmarks.firstWhere((b) => b.path == path, orElse: () => FolderBookmark(name: vBasename(path), path: path))
|
||||
: null;
|
||||
final (IconData icon, Color color, String label, bool? open) = switch (row.kind) {
|
||||
TreeRow.otherRoot => (
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class ExplorerRsyncFlow {
|
|||
r: r,
|
||||
right: right,
|
||||
left: left,
|
||||
allowInnerToOuter: ctl.c.settings.allowInnerToOuter,
|
||||
allowInnerToOuter: ctl.c.settings.general.allowInnerToOuter,
|
||||
jobs: this.jobs.list,
|
||||
viaDav: l != null && r != null && ExplorerTransfers.viaDav([l, r]),
|
||||
copySkipNewer: ctl.c.settings.copy.skipNewer);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class _BookmarksDialogState extends State<_BookmarksDialog> {
|
|||
bool get _rsync => widget.right != null && widget.goPair != null;
|
||||
|
||||
Future<void> _save(List<FolderBookmark> list) async {
|
||||
await widget.c.updateSettings((x) => x.folderBookmarks = list);
|
||||
await widget.c.updateSettings((x) => x.general.folderBookmarks = list);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ class _BookmarksDialogState extends State<_BookmarksDialog> {
|
|||
: FolderBookmark(name: vBasename(widget.current).isEmpty ? widget.current : vBasename(widget.current), path: widget.current);
|
||||
final name = await _askName(b.name, pair ? tr('지금 짝을 즐겨찾기에') : tr('지금 폴더를 즐겨찾기에'));
|
||||
if (name == null) return;
|
||||
await _save([...s.folderBookmarks, b.copyWith(name: name)]);
|
||||
await _save([...s.general.folderBookmarks, b.copyWith(name: name)]);
|
||||
}
|
||||
|
||||
void _open(FolderBookmark b) {
|
||||
|
|
@ -71,7 +71,7 @@ class _BookmarksDialogState extends State<_BookmarksDialog> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mine = [for (final b in s.folderBookmarks) if (_rsync || !b.isPair) b];
|
||||
final mine = [for (final b in s.general.folderBookmarks) if (_rsync || !b.isPair) b];
|
||||
// 바로 가기: 다운로드 폴더 · MKV 결과 폴더 (넣지 않아도)
|
||||
final shortcuts = [
|
||||
(tr('다운로드'), s.resolvedDownloadRoot()),
|
||||
|
|
@ -105,14 +105,14 @@ class _BookmarksDialogState extends State<_BookmarksDialog> {
|
|||
icon: const Icon(Icons.edit_outlined, size: 18),
|
||||
onPressed: () async {
|
||||
final name = await _askName(b.name, tr('이름 바꾸기'));
|
||||
if (name != null) await _save([for (final x in s.folderBookmarks) identical(x, b) ? x.copyWith(name: name) : x]);
|
||||
if (name != null) await _save([for (final x in s.general.folderBookmarks) identical(x, b) ? x.copyWith(name: name) : x]);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
tooltip: tr('즐겨찾기에서 빼기 (폴더는 그대로)'),
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
onPressed: () => _save([for (final x in s.folderBookmarks) if (!identical(x, b)) x]),
|
||||
onPressed: () => _save([for (final x in s.general.folderBookmarks) if (!identical(x, b)) x]),
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import 'settings_page.dart';
|
|||
/// 업데이트하면서 예전 기본값을 새 기본값으로 한 번 옮겼으면, 켤 때 무엇이 바뀌었는지와
|
||||
/// 환경 설정에서 되돌릴 수 있다는 것을 한 번 알린다 ([AppSettings.migrated]).
|
||||
Future<void> showMigrationNotice(BuildContext context, AppController c) async {
|
||||
if (!c.settings.migrationNotice) return; // 102: 환경 설정에서 끌 수 있음
|
||||
if (!c.settings.general.migrationNotice) return; // 102: 환경 설정에서 끌 수 있음
|
||||
final items = c.settings.migrated;
|
||||
if (items.isEmpty) return;
|
||||
String line(String id) => switch (id) {
|
||||
|
|
|
|||
|
|
@ -143,4 +143,4 @@ Future<String?> askSaveChanges(BuildContext context) => showDialog<String>(
|
|||
|
||||
/// PDF 에 넣을 그림을 고른다 (설정의 그림 확장자). 고르지 않으면 빈 목록
|
||||
Future<List<String>> pickPdfImages(AppController c) =>
|
||||
c.services.storage.pickFiles(title: tr('PDF 에 넣을 그림 선택'), extensions: c.settings.imageExts);
|
||||
c.services.storage.pickFiles(title: tr('PDF 에 넣을 그림 선택'), extensions: c.settings.reader.imageExts);
|
||||
|
|
|
|||
|
|
@ -153,9 +153,9 @@ class _CleanupSettingsSectionState extends State<CleanupSettingsSection> {
|
|||
])
|
||||
FilterChip(
|
||||
label: Text(label),
|
||||
selected: s.cleanupPrechecked.contains(id),
|
||||
onSelected: (on) => c.updateSettings((x) => x.cleanupPrechecked = [
|
||||
for (final y in x.cleanupPrechecked) if (y != id) y,
|
||||
selected: s.general.cleanupPrechecked.contains(id),
|
||||
onSelected: (on) => c.updateSettings((x) => x.general.cleanupPrechecked = [
|
||||
for (final y in x.general.cleanupPrechecked) if (y != id) y,
|
||||
if (on) id,
|
||||
]),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ class _FilesSettingsSectionState extends State<FilesSettingsSection> {
|
|||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [
|
||||
if (desk)
|
||||
SwitchListTile(
|
||||
value: s.recycleOnDelete,
|
||||
onChanged: (v) => c.updateSettings((x) => x.recycleOnDelete = v),
|
||||
value: s.general.recycleOnDelete,
|
||||
onChanged: (v) => c.updateSettings((x) => x.general.recycleOnDelete = v),
|
||||
title: Text(tr('지우면 휴지통으로')),
|
||||
subtitle: Text(tr('켜면 휴지통으로 보내 되살릴 수 있습니다 (Shift+Delete 는 영구 삭제). 끄면 늘 영구 삭제로 묻습니다. '
|
||||
'네트워크 드라이브 · USB 메모리는 휴지통이 없어 늘 영구 삭제입니다')),
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ class _GeneralSettingsSectionState extends State<GeneralSettingsSection> {
|
|||
const LanguageSettings(),
|
||||
// 102: 물어 정한 것 · 1차 판단은 모두 여기서 바꿀 수 있다 (기본값 = 그 답)
|
||||
SwitchListTile(
|
||||
value: s.migrationNotice,
|
||||
onChanged: (v) => c.updateSettings((x) => x.migrationNotice = v),
|
||||
value: s.general.migrationNotice,
|
||||
onChanged: (v) => c.updateSettings((x) => x.general.migrationNotice = v),
|
||||
title: Text(tr('업데이트로 바뀐 기본값 알림')),
|
||||
subtitle: Text(tr('새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.rememberPasswords,
|
||||
onChanged: (v) => c.updateSettings((x) => x.rememberPasswords = v),
|
||||
value: s.general.rememberPasswords,
|
||||
onChanged: (v) => c.updateSettings((x) => x.general.rememberPasswords = v),
|
||||
title: Text(tr('비밀번호 기억 (안전 저장소)')),
|
||||
subtitle: Text(tr('WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다')),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ class _RsyncSettingsSectionState extends State<RsyncSettingsSection> {
|
|||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [
|
||||
CopySyncSettings(c: c, rsync: true),
|
||||
SwitchListTile(
|
||||
value: s.allowInnerToOuter,
|
||||
onChanged: (v) => c.updateSettings((x) => x.allowInnerToOuter = v),
|
||||
value: s.general.allowInnerToOuter,
|
||||
onChanged: (v) => c.updateSettings((x) => x.general.allowInnerToOuter = v),
|
||||
title: Text(tr('원본이 대상 안에 있어도 맞추기 (안쪽 → 바깥)')),
|
||||
subtitle: Text(tr('확인 창에서 알린 뒤 허용합니다. 지우기 (--delete) 가 있으면 늘 막습니다. 끄면 늘 막습니다')),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const vaultImageMax = 256 * 1024 * 1024;
|
|||
/// 보기: 그림은 메모리에서 바로 (파일로 풀지 않음 - 관리자 지시 10/21), 동영상 · 음악은 [session] 폴더에 잠깐 풀고 본 뒤 지운다
|
||||
Future<void> vaultViewEntry(BuildContext context, AppController c, VaultUiState s, VaultViewSession session, VaultEntry e) async {
|
||||
final ext = p.extension(e.name).replaceFirst('.', '').toLowerCase();
|
||||
if (c.settings.imageExts.contains(ext) && e.size <= vaultImageMax) {
|
||||
if (c.settings.reader.imageExts.contains(ext) && e.size <= vaultImageMax) {
|
||||
try {
|
||||
s.setBusy(true);
|
||||
final bytes = await s.vault!.readBytes(e, maxBytes: vaultImageMax);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Future<String?> openZip(BuildContext context, AppController c, String path, {Str
|
|||
/// ZIP 안 그림을 만화처럼 보기 (그림이 없으면 false)
|
||||
Future<bool> openZipComic(BuildContext context, AppController c, String path) async {
|
||||
final temp = await c.services.storage.tempDirectory();
|
||||
final src = await ZipImagesSource.open(path, tempDir: temp, imageExts: c.settings.imageExts);
|
||||
final src = await ZipImagesSource.open(path, tempDir: temp, imageExts: c.settings.reader.imageExts);
|
||||
if (src.length == 0) {
|
||||
await src.dispose();
|
||||
return false;
|
||||
|
|
@ -61,7 +61,7 @@ class _ZipPageState extends State<ZipPage> {
|
|||
}
|
||||
}
|
||||
|
||||
bool get _hasImages => _entries?.any((f) => c.settings.imageExts.contains(extOf(f.name))) ?? false;
|
||||
bool get _hasImages => _entries?.any((f) => c.settings.reader.imageExts.contains(extOf(f.name))) ?? false;
|
||||
|
||||
/// 86: 풀기는 압축 풀기 흐름 (목록 검사 - 밖으로 나가는 경로 · 링크 · 크기 · 비밀번호, 같은 이름 · 진행 · 취소) 으로
|
||||
Future<void> _extract({required bool all}) async {
|
||||
|
|
@ -114,7 +114,7 @@ class _ZipPageState extends State<ZipPage> {
|
|||
value: on,
|
||||
onChanged: (v) => setState(() => v == true ? _marked.add(f.name) : _marked.remove(f.name)),
|
||||
secondary: Icon(
|
||||
c.settings.imageExts.contains(extOf(f.name))
|
||||
c.settings.reader.imageExts.contains(extOf(f.name))
|
||||
? Icons.image_outlined
|
||||
: Icons.insert_drive_file_outlined,
|
||||
color: JjColors.textDim,
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ void main() {
|
|||
test('기본값 = 정한 답 · 저장 · 읽기', () {
|
||||
final s = AppSettings();
|
||||
expect([
|
||||
s.run.startMenuShortcut, s.copy.syncStopToast, s.display.swipeWrap, s.migrationNotice, s.allowInnerToOuter,
|
||||
s.rememberPasswords, s.recycleOnDelete, s.reader.systemBars,
|
||||
s.run.startMenuShortcut, s.copy.syncStopToast, s.display.swipeWrap, s.general.migrationNotice, s.general.allowInnerToOuter,
|
||||
s.general.rememberPasswords, s.general.recycleOnDelete, s.reader.systemBars,
|
||||
], everyElement(isTrue));
|
||||
expect(s.cleanupPrechecked, ['work', 'download']);
|
||||
expect(s.general.cleanupPrechecked, ['work', 'download']);
|
||||
expect(s.mkv.tripleTapAction, 'move');
|
||||
expect(s.remote.davExternalOpen, 'ask');
|
||||
// 이미 있던 설정의 기본값도 결정대로
|
||||
|
|
@ -44,17 +44,17 @@ void main() {
|
|||
..run.startMenuShortcut = false
|
||||
..copy.syncStopToast = false
|
||||
..display.swipeWrap = false
|
||||
..migrationNotice = false
|
||||
..allowInnerToOuter = false
|
||||
..rememberPasswords = false
|
||||
..recycleOnDelete = false
|
||||
..cleanupPrechecked = ['logs']
|
||||
..general.migrationNotice = false
|
||||
..general.allowInnerToOuter = false
|
||||
..general.rememberPasswords = false
|
||||
..general.recycleOnDelete = false
|
||||
..general.cleanupPrechecked = ['logs']
|
||||
..mkv.tripleTapAction = 'none'
|
||||
..remote.davExternalOpen = 'url')
|
||||
.toJson());
|
||||
expect([b.run.startMenuShortcut, b.copy.syncStopToast, b.display.swipeWrap, b.migrationNotice, b.allowInnerToOuter, b.rememberPasswords,
|
||||
b.recycleOnDelete], everyElement(isFalse));
|
||||
expect([b.cleanupPrechecked, b.mkv.tripleTapAction, b.remote.davExternalOpen], [['logs'], 'none', 'url']);
|
||||
expect([b.run.startMenuShortcut, b.copy.syncStopToast, b.display.swipeWrap, b.general.migrationNotice, b.general.allowInnerToOuter, b.general.rememberPasswords,
|
||||
b.general.recycleOnDelete], everyElement(isFalse));
|
||||
expect([b.general.cleanupPrechecked, b.mkv.tripleTapAction, b.remote.davExternalOpen], [['logs'], 'none', 'url']);
|
||||
});
|
||||
|
||||
test('안쪽 → 바깥 허용을 끄면 늘 막는다', () {
|
||||
|
|
@ -72,7 +72,7 @@ void main() {
|
|||
s.remote.webdavServers = [const DavServer(id: 'd', name: 'n', url: 'https://n', user: 'u', password: 'pw')];
|
||||
await st.save(s);
|
||||
expect(secrets.values['dav.pw.d'], 'pw');
|
||||
s.rememberPasswords = false;
|
||||
s.general.rememberPasswords = false;
|
||||
await st.save(s);
|
||||
expect(secrets.values, isEmpty);
|
||||
expect(File(p.join(dir.path, 'settings.json')).readAsStringSync(), isNot(contains('"pw"')));
|
||||
|
|
@ -108,8 +108,8 @@ void main() {
|
|||
}
|
||||
|
||||
test('116: 예전 판의 "권한 안내 보임" 표시는 읽지 않는다 (권한이 없으면 이 판에서 한 번 더 안내)', () {
|
||||
expect(AppSettings.fromJson({'allFilesHintShown': true}).allFilesHintShown, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..allFilesHintShown = true).toJson()).allFilesHintShown, isTrue);
|
||||
expect(AppSettings.fromJson({'allFilesHintShown': true}).general.allFilesHintShown, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..general.allFilesHintShown = true).toJson()).general.allFilesHintShown, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('126: "바뀐 기본 설정" 창을 닫으면 바로 저장 - 강제 종료 뒤 켜도 다시 뜨지 않음', (tester) async {
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ void main() {
|
|||
|
||||
test('73: 즐겨찾기 저장 · 읽기 (Rsync 짝 포함)', () {
|
||||
final s = AppSettings()
|
||||
..folderBookmarks = const [FolderBookmark(name: 'NAS/영화', path: r'\\nas\movies'), FolderBookmark(name: '백업', path: r'D:\a', right: r'E:\b')];
|
||||
final b = AppSettings.fromJson(s.toJson()).folderBookmarks;
|
||||
..general.folderBookmarks = const [FolderBookmark(name: 'NAS/영화', path: r'\\nas\movies'), FolderBookmark(name: '백업', path: r'D:\a', right: r'E:\b')];
|
||||
final b = AppSettings.fromJson(s.toJson()).general.folderBookmarks;
|
||||
expect([for (final x in b) (x.name, x.path, x.right, x.isPair)], [('NAS/영화', r'\\nas\movies', null, false), ('백업', r'D:\a', r'E:\b', true)]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ void main() {
|
|||
device.volumes = () => [(left, 'L:'), (right, 'R:')];
|
||||
c.settings
|
||||
..explorer.style = 'xplore'
|
||||
..folderBookmarks = [FolderBookmark(name: '내 즐겨찾기', path: p.join(left, 'sub'))];
|
||||
..general.folderBookmarks = [FolderBookmark(name: '내 즐겨찾기', path: p.join(left, 'sub'))];
|
||||
await open(tester);
|
||||
final otherRoot = find.byKey(ValueKey<String>('k1:$right'));
|
||||
expect(otherRoot, findsWidgets, reason: '왼쪽 창 트리에 R: (다른 저장 장치) 줄');
|
||||
|
|
|
|||
|
|
@ -156,14 +156,14 @@ void main() {
|
|||
await tester.enterText(find.byType(TextField).last, '왼쪽 폴더');
|
||||
await tester.tap(find.widgetWithText(FilledButton, '확인'));
|
||||
await calm(tester);
|
||||
expect([for (final b in c.settings.folderBookmarks) (b.name, b.path)], [('왼쪽 폴더', left)]);
|
||||
expect([for (final b in c.settings.general.folderBookmarks) (b.name, b.path)], [('왼쪽 폴더', left)]);
|
||||
expect(find.text('왼쪽 폴더'), findsOneWidget);
|
||||
await tester.tap(find.text('닫기'));
|
||||
await calm(tester);
|
||||
expect(find.byIcon(Icons.star), findsOneWidget, reason: '즐겨찾기한 폴더를 보는 창의 ★ 는 채움');
|
||||
|
||||
// 오른쪽 폴더를 즐겨찾기에 두고 왼쪽 창에서 한 번에 가기
|
||||
await c.updateSettings((x) => x.folderBookmarks = [...x.folderBookmarks, FolderBookmark(name: 'R', path: right)]);
|
||||
await c.updateSettings((x) => x.general.folderBookmarks = [...x.general.folderBookmarks, FolderBookmark(name: 'R', path: right)]);
|
||||
await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first));
|
||||
await calm(tester);
|
||||
await act(tester, () => tester.tap(find.text('R')));
|
||||
|
|
@ -175,7 +175,7 @@ void main() {
|
|||
await calm(tester);
|
||||
await tester.tap(find.byTooltip('즐겨찾기에서 빼기 (폴더는 그대로)').first);
|
||||
await calm(tester);
|
||||
expect([for (final b in c.settings.folderBookmarks) b.name], ['R']);
|
||||
expect([for (final b in c.settings.general.folderBookmarks) b.name], ['R']);
|
||||
expect(Directory(left).existsSync(), isTrue);
|
||||
await tester.tap(find.text('닫기'));
|
||||
await calm(tester);
|
||||
|
|
@ -194,12 +194,12 @@ void main() {
|
|||
await calm(tester);
|
||||
await tester.tap(find.widgetWithText(FilledButton, '확인'));
|
||||
await calm(tester);
|
||||
final pair = c.settings.folderBookmarks.single;
|
||||
final pair = c.settings.general.folderBookmarks.single;
|
||||
expect((pair.path, pair.right, pair.name), (left, right, 'left ⇄ right'));
|
||||
await tester.tap(find.text('닫기'));
|
||||
await calm(tester);
|
||||
// 반대 짝을 넣어 두고 열면 두 창이 함께 바뀐다
|
||||
await c.updateSettings((x) => x.folderBookmarks = [...x.folderBookmarks, FolderBookmark(name: '거꾸로', path: right, right: left)]);
|
||||
await c.updateSettings((x) => x.general.folderBookmarks = [...x.general.folderBookmarks, FolderBookmark(name: '거꾸로', path: right, right: left)]);
|
||||
await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first));
|
||||
await calm(tester);
|
||||
await act(tester, () => tester.tap(find.text('거꾸로')));
|
||||
|
|
@ -322,7 +322,7 @@ void main() {
|
|||
await calm(tester);
|
||||
await tester.tap(find.text('즐겨찾기에 추가'));
|
||||
await calm(tester);
|
||||
expect(c.settings.folderBookmarks.map((x) => x.path), contains(p.join(left, 'sub')));
|
||||
expect(c.settings.general.folderBookmarks.map((x) => x.path), contains(p.join(left, 'sub')));
|
||||
await tester.longPress(find.text('sub').first);
|
||||
await calm(tester);
|
||||
expect(find.text('즐겨찾기에 추가'), findsNothing);
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ void main() {
|
|||
c.settings
|
||||
..remote.webdavServers = [s]
|
||||
..explorer.paths = [left, left]
|
||||
..folderBookmarks = [const FolderBookmark(name: 'NAS 원격', path: 'dav://nas/원격')];
|
||||
..general.folderBookmarks = [const FolderBookmark(name: 'NAS 원격', path: 'dav://nas/원격')];
|
||||
DavRegistry.configure([s]);
|
||||
await pump(tester, ExplorerPage(device: device, c: c));
|
||||
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
|
||||
|
|
|
|||
|
|
@ -57,16 +57,16 @@ void main() {
|
|||
|
||||
test('보기 설정 저장 · 읽기', () {
|
||||
final s = AppSettings()
|
||||
..imageExts = ['png', 'webp']
|
||||
..reader.imageExts = ['png', 'webp']
|
||||
..reader.fit = 'width'
|
||||
..reader.rtl = true
|
||||
..reader.brightness = 0.3
|
||||
..reader.zipComic = false;
|
||||
final b = AppSettings.fromJson(s.toJson());
|
||||
expect([b.imageExts, b.reader.fit, b.reader.rtl, b.reader.brightness, b.reader.zipComic], [
|
||||
expect([b.reader.imageExts, b.reader.fit, b.reader.rtl, b.reader.brightness, b.reader.zipComic], [
|
||||
['png', 'webp'], 'width', true, 0.3, false,
|
||||
]);
|
||||
expect(AppSettings.fromJson({}).imageExts, defaultImageExtensions);
|
||||
expect(AppSettings.fromJson({}).reader.imageExts, defaultImageExtensions);
|
||||
});
|
||||
|
||||
testWidgets('224: 보는 동안 밖에서 지운 그림 - 앱으로 돌아오면 "파일이 없어졌습니다" 와 다음 그림, 다 없어지면 닫는다', (tester) async {
|
||||
|
|
|
|||
|
|
@ -170,11 +170,11 @@ void main() {
|
|||
|
||||
test('안내는 겹쳐 띄우지 않되, 권한이 있다가 없어지면 다음에 켤 때 한 번 더', () async {
|
||||
final c = AppController(PlatformServices(mediaTool: _Tool(), storage: DesktopStorageService()));
|
||||
expect(c.settings.allFilesHintShown, isFalse);
|
||||
expect(c.settings.general.allFilesHintShown, isFalse);
|
||||
expect(await allFilesGuideDue(c, hasAccess: false), isTrue, reason: '처음 켬, 권한 없음');
|
||||
expect(await allFilesGuideDue(c, hasAccess: false), isFalse, reason: '다시 켜도 권한이 계속 없으면 띄우지 않음');
|
||||
expect(await allFilesGuideDue(c, hasAccess: true), isFalse, reason: '허용함');
|
||||
expect(c.settings.allFilesHintShown, isFalse);
|
||||
expect(c.settings.general.allFilesHintShown, isFalse);
|
||||
expect(await allFilesGuideDue(c, hasAccess: true), isFalse);
|
||||
expect(await allFilesGuideDue(c, hasAccess: false), isTrue, reason: '사용자가 권한을 끈 뒤 켬 → 한 번 더');
|
||||
expect(await allFilesGuideDue(c, hasAccess: false), isFalse);
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ void main() {
|
|||
|
||||
testWidgets('일반: 스위치를 바꾸면 바로 설정에 (업데이트로 바뀐 기본값 알림)', (t) async {
|
||||
final c = app();
|
||||
final before = c.settings.migrationNotice;
|
||||
final before = c.settings.general.migrationNotice;
|
||||
await show(t, GeneralSettingsSection(c: c, desk: true));
|
||||
await t.tap(find.widgetWithText(SwitchListTile, '업데이트로 바뀐 기본값 알림'));
|
||||
await t.pump();
|
||||
expect(c.settings.migrationNotice, !before);
|
||||
expect(c.settings.general.migrationNotice, !before);
|
||||
});
|
||||
|
||||
testWidgets('공용 줄: 개수 고르기 · 글 넣는 줄 저장 · 폴더 줄 [기본값] 은 고른 것이 있을 때만', (t) async {
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('39: "모든 파일 접근" 시작 안내는 한 번만 (보였다는 표시가 설정에 남음)', () {
|
||||
expect(AppSettings.fromJson({}).allFilesHintShown, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..allFilesHintShown = true).toJson()).allFilesHintShown, isTrue);
|
||||
expect(AppSettings.fromJson({}).general.allFilesHintShown, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..general.allFilesHintShown = true).toJson()).general.allFilesHintShown, isTrue);
|
||||
});
|
||||
|
||||
test('103: 크기 때문에 저절로 H.264 로 바꾼 코덱은 원본 크기로 되돌리면 "원본 유지" 로 돌아온다 (직접 고른 코덱은 그대로)', () {
|
||||
|
|
|
|||
Loading…
Reference in New Issue