설정 A 분야 3: Tor 브라우저 (TorBrowserSettings) - 키 5 · defaultTorHome → TorBrowserSettings.defaultHome
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
1db0bda829
commit
ee7524e1c7
|
|
@ -127,7 +127,7 @@ void main() {
|
|||
expect(TorBundle.installedVersion(), TorBundle.version);
|
||||
c.settings
|
||||
..components = [...c.settings.components, 'tor']
|
||||
..torNoticeSeen = true;
|
||||
..tor.noticeSeen = true;
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
builder: (context, child) => AppScope(controller: c, bookmarks: bm, child: child!),
|
||||
home: TorPage(c: c),
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ void main() {
|
|||
Directory(out).createSync(recursive: true);
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..torNoticeSeen = true
|
||||
..tor.noticeSeen = true
|
||||
..components = [...c.settings.components, 'tor'];
|
||||
final key = GlobalKey();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ import '../services/app_shell.dart' show appIconOf;
|
|||
import '../core/remote_idle.dart';
|
||||
import 'settings/records.dart';
|
||||
import 'settings/settings_group.dart';
|
||||
import 'settings/tor_settings.dart';
|
||||
import 'settings/network_settings.dart';
|
||||
import 'settings/explorer_settings.dart';
|
||||
|
||||
export 'settings/records.dart';
|
||||
export 'settings/settings_group.dart';
|
||||
export 'settings/settings_store.dart';
|
||||
export 'settings/tor_settings.dart';
|
||||
export 'settings/network_settings.dart';
|
||||
export 'settings/explorer_settings.dart';
|
||||
|
||||
|
|
@ -161,21 +163,10 @@ class AppSettings {
|
|||
|
||||
|
||||
// ── Tor 브라우저 (사용자 요청 10/10) ──
|
||||
/// Tor 화면의 쿠키 · 기록 · 캐시를 닫은 뒤에도 남기기 (기본: 닫으면 지움)
|
||||
bool torKeepData = false;
|
||||
|
||||
/// Tor 화면을 떠난 뒤 tor 를 몇 분 더 켜 둘지 (0 = 바로 끔, 처음 2분 - 관리자 결정 10/11). 웹 뷰 · 다리 · 프록시는 늘 바로 정리
|
||||
int torIdleMinutes = 2;
|
||||
|
||||
/// Tor 화면의 처음 · 🏠 주소
|
||||
String torHomeUrl = defaultTorHome;
|
||||
static const defaultTorHome = 'https://check.torproject.org/';
|
||||
|
||||
/// Tor 화면의 JavaScript (끄면 더 안전하지만 많은 사이트가 보이지 않음)
|
||||
bool torJavaScript = true;
|
||||
|
||||
/// "공식 Tor Browser 가 아닙니다" 안내를 보았는지 (처음 열 때 한 번)
|
||||
bool torNoticeSeen = false;
|
||||
|
||||
// ── 파일 탐색기 (X-plore 참고) ──
|
||||
|
||||
|
|
@ -505,8 +496,11 @@ class AppSettings {
|
|||
/// 네트워크 · DNS (사용자 요청 10/10)
|
||||
final network = NetworkSettings();
|
||||
|
||||
/// Tor 釉뚮씪?곗? (?ъ슜???붿껌 10/10)
|
||||
final tor = TorBrowserSettings();
|
||||
|
||||
/// 설정 분야들 (설정 A) - 저마다 자기 키를 읽고 쓴다. toJson · fromJson 은 이 목록을 돌며 합치기만 한다
|
||||
List<SettingsGroup> get groups => [explorer, network];
|
||||
List<SettingsGroup> get groups => [explorer, network, tor];
|
||||
|
||||
/// [plainSecrets]: 안전 저장소를 쓸 수 없을 때만 (예전 파일에 있던 비밀 값을 잃지 않게) 예전처럼 파일에 둔다
|
||||
Map<String, Object?> toJson({bool plainSecrets = false}) {
|
||||
|
|
@ -568,11 +562,6 @@ class AppSettings {
|
|||
'externalBrowser': externalBrowser,
|
||||
'webTranslate': webTranslate,
|
||||
'webJavaScript': webJavaScript,
|
||||
'torKeepData': torKeepData,
|
||||
'torIdleMinutes': torIdleMinutes,
|
||||
'torHomeUrl': torHomeUrl,
|
||||
'torJavaScript': torJavaScript,
|
||||
'torNoticeSeen': torNoticeSeen,
|
||||
'zipLevel': zipLevel,
|
||||
'wifiShareMinutes': wifiShareMinutes,
|
||||
'wifiShareTailscale': wifiShareTailscale,
|
||||
|
|
@ -764,15 +753,6 @@ class AppSettings {
|
|||
..externalBrowser = j['externalBrowser'] as String? ?? 'system'
|
||||
..webTranslate = j['webTranslate'] as bool? ?? false
|
||||
..webJavaScript = j['webJavaScript'] as bool? ?? true
|
||||
..torKeepData = j['torKeepData'] == true
|
||||
..torIdleMinutes = j['torIdleMinutes'] is int && (j['torIdleMinutes'] as int) >= 0 && (j['torIdleMinutes'] as int) <= 60
|
||||
? j['torIdleMinutes'] as int
|
||||
: 2
|
||||
..torHomeUrl = j['torHomeUrl'] is String && (j['torHomeUrl'] as String).trim().isNotEmpty
|
||||
? (j['torHomeUrl'] as String).trim()
|
||||
: defaultTorHome
|
||||
..torJavaScript = j['torJavaScript'] != false
|
||||
..torNoticeSeen = j['torNoticeSeen'] == true
|
||||
..vaultEnabled = j['vaultEnabled'] == true
|
||||
..shizukuEnabled = j['shizukuEnabled'] == true
|
||||
..rootEnabled = j['rootEnabled'] == true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
import 'settings_group.dart';
|
||||
|
||||
/// 약속 (설정 A): 환경 설정 > Tor 釉뚮씪?곗? (?ъ슜???붿껌 10/10) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
/// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키)
|
||||
class TorBrowserSettings implements SettingsGroup {
|
||||
/// Tor 화면의 쿠키 · 기록 · 캐시를 닫은 뒤에도 남기기 (기본: 닫으면 지움)
|
||||
bool keepData = false;
|
||||
|
||||
/// Tor 화면을 떠난 뒤 tor 를 몇 분 더 켜 둘지 (0 = 바로 끔, 처음 2분 - 관리자 결정 10/11). 웹 뷰 · 다리 · 프록시는 늘 바로 정리
|
||||
int idleMinutes = 2;
|
||||
|
||||
/// Tor 화면의 처음 · 🏠 주소
|
||||
String homeUrl = defaultHome;
|
||||
static const defaultHome = 'https://check.torproject.org/';
|
||||
|
||||
/// Tor 화면의 JavaScript (끄면 더 안전하지만 많은 사이트가 보이지 않음)
|
||||
bool javaScript = true;
|
||||
|
||||
/// "공식 Tor Browser 가 아닙니다" 안내를 보았는지 (처음 열 때 한 번)
|
||||
bool noticeSeen = false;
|
||||
|
||||
@override
|
||||
void write(Map<String, Object?> j, {required bool plainSecrets}) {
|
||||
j['torKeepData'] = keepData;
|
||||
j['torIdleMinutes'] = idleMinutes;
|
||||
j['torHomeUrl'] = homeUrl;
|
||||
j['torJavaScript'] = javaScript;
|
||||
j['torNoticeSeen'] = noticeSeen;
|
||||
}
|
||||
|
||||
@override
|
||||
void read(Map<String, dynamic> j) {
|
||||
keepData = j['torKeepData'] == true;
|
||||
idleMinutes = j['torIdleMinutes'] is int && (j['torIdleMinutes'] as int) >= 0 && (j['torIdleMinutes'] as int) <= 60
|
||||
? j['torIdleMinutes'] as int
|
||||
: 2;
|
||||
homeUrl = j['torHomeUrl'] is String && (j['torHomeUrl'] as String).trim().isNotEmpty
|
||||
? (j['torHomeUrl'] as String).trim()
|
||||
: defaultHome;
|
||||
javaScript = j['torJavaScript'] != false;
|
||||
noticeSeen = j['torNoticeSeen'] == true;
|
||||
}
|
||||
}
|
||||
|
|
@ -275,8 +275,8 @@ class TorSettings extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.torKeepData,
|
||||
onChanged: (v) => c.updateSettings((x) => x.torKeepData = v),
|
||||
value: s.tor.keepData,
|
||||
onChanged: (v) => c.updateSettings((x) => x.tor.keepData = v),
|
||||
title: Text(tr('Tor 화면을 닫아도 쿠키 · 기록 · 캐시 남기기')),
|
||||
subtitle: Text(tr('끄면 (기본) Tor 화면을 떠날 때 (다른 화면 · 앱이 뒤로) 지웁니다. 일반 브라우저와는 늘 따로입니다.')),
|
||||
),
|
||||
|
|
@ -284,17 +284,17 @@ class TorSettings extends StatelessWidget {
|
|||
title: Text(tr('Tor 화면을 떠난 뒤 Tor 를 켜 둘 시간')),
|
||||
subtitle: Text(tr('다른 화면으로 가거나 앱이 뒤로 가면 Tor 화면의 페이지 · 연결은 바로 정리합니다. Tor 자체는 이 시간 동안 켜 두어 다시 들어오면 바로 쓸 수 있습니다.')),
|
||||
trailing: DropdownButton<int>(
|
||||
value: const [0, 1, 2, 5, 10].contains(s.torIdleMinutes) ? s.torIdleMinutes : 2,
|
||||
value: const [0, 1, 2, 5, 10].contains(s.tor.idleMinutes) ? s.tor.idleMinutes : 2,
|
||||
items: [
|
||||
DropdownMenuItem(value: 0, child: Text(tr('바로 끔'))),
|
||||
for (final m in const [1, 2, 5, 10]) DropdownMenuItem(value: m, child: Text(trf('{0}분', [m]))),
|
||||
],
|
||||
onChanged: (v) => c.updateSettings((x) => x.torIdleMinutes = v ?? 2),
|
||||
onChanged: (v) => c.updateSettings((x) => x.tor.idleMinutes = v ?? 2),
|
||||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.torJavaScript,
|
||||
onChanged: (v) => c.updateSettings((x) => x.torJavaScript = v),
|
||||
value: s.tor.javaScript,
|
||||
onChanged: (v) => c.updateSettings((x) => x.tor.javaScript = v),
|
||||
title: Text(tr('Tor 화면의 JavaScript 사용')),
|
||||
subtitle: Text(tr('끄면 더 안전하지만 많은 사이트가 보이지 않습니다 (다음에 열 때부터).')),
|
||||
),
|
||||
|
|
@ -304,9 +304,9 @@ class TorSettings extends StatelessWidget {
|
|||
trailing: SizedBox(
|
||||
width: 300,
|
||||
child: TextFormField(
|
||||
initialValue: s.torHomeUrl,
|
||||
initialValue: s.tor.homeUrl,
|
||||
decoration: const InputDecoration(isDense: true, border: OutlineInputBorder()),
|
||||
onChanged: (v) => c.updateSettings((x) => x.torHomeUrl = v.trim().isEmpty ? AppSettings.defaultTorHome : v.trim()),
|
||||
onChanged: (v) => c.updateSettings((x) => x.tor.homeUrl = v.trim().isEmpty ? TorBrowserSettings.defaultHome : v.trim()),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ Future<void> showTorNotice(BuildContext context) => showDialog<void>(
|
|||
|
||||
class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
||||
final _tor = TorService.instance;
|
||||
late String _url = widget.c.settings.torHomeUrl;
|
||||
late String _url = widget.c.settings.tor.homeUrl;
|
||||
late final _address = TextEditingController(text: _url);
|
||||
final _addressFocus = FocusNode();
|
||||
TorNav? _nav;
|
||||
|
|
@ -307,7 +307,7 @@ class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
|||
void _enter() {
|
||||
_tor.cancelIdleStop();
|
||||
if (!_away) return;
|
||||
final home = widget.c.settings.torHomeUrl;
|
||||
final home = widget.c.settings.tor.homeUrl;
|
||||
setState(() {
|
||||
_away = false;
|
||||
_url = home;
|
||||
|
|
@ -338,7 +338,7 @@ class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
|||
await (widget.onLeft ?? () async {})();
|
||||
return;
|
||||
}
|
||||
await _cleanup(widget.c.settings.torKeepData, widget.c.settings.torIdleMinutes);
|
||||
await _cleanup(widget.c.settings.tor.keepData, widget.c.settings.tor.idleMinutes);
|
||||
}
|
||||
|
||||
/// 떠난 뒤 정리 (화면을 닫았을 때도 같다)
|
||||
|
|
@ -368,9 +368,9 @@ class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
|||
if (_begun) return;
|
||||
_begun = true;
|
||||
final c = widget.c;
|
||||
if (!c.settings.torNoticeSeen && mounted) {
|
||||
if (!c.settings.tor.noticeSeen && mounted) {
|
||||
await showTorNotice(context);
|
||||
await c.updateSettings((x) => x.torNoticeSeen = true);
|
||||
await c.updateSettings((x) => x.tor.noticeSeen = true);
|
||||
}
|
||||
final missing = await (widget.missingFeatures ?? WebProxy.torMissingFeatures)();
|
||||
if (!mounted) return;
|
||||
|
|
@ -443,7 +443,7 @@ class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
|||
_shown = false;
|
||||
TorPage._visible--;
|
||||
if (widget.viewBuilder == null && widget.startTor == null) {
|
||||
final keep = widget.c.settings.torKeepData, idle = widget.c.settings.torIdleMinutes;
|
||||
final keep = widget.c.settings.tor.keepData, idle = widget.c.settings.tor.idleMinutes;
|
||||
// 남은 Tor 웹 뷰가 있는 동안은 WebProxy 가 Tor 쪽 · 닫힌 포트만 건다 ([WebProxy.torViewCreated]) - 기다리지 않고 정리
|
||||
unawaited(_cleanup(keep, idle));
|
||||
} else {
|
||||
|
|
@ -507,7 +507,7 @@ class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
|||
_progress < 1
|
||||
? btn(Icons.close, tr('중지'), ready ? () => _nav?.stop() : null)
|
||||
: btn(Icons.refresh, keyHint(tr('새로고침'), 'F5'), ready ? () => _nav?.reload() : null),
|
||||
if (!compact) btn(Icons.home_outlined, tr('홈'), ready ? () => _go(widget.c.settings.torHomeUrl) : null),
|
||||
if (!compact) btn(Icons.home_outlined, tr('홈'), ready ? () => _go(widget.c.settings.tor.homeUrl) : null),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
// 일반 브라우저의 기록 · 키보드 학습을 쓰지 않는다 (제안 없음)
|
||||
|
|
@ -550,7 +550,7 @@ class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
|||
tooltip: tr('더 보기'),
|
||||
icon: const Icon(Icons.more_vert, size: 20),
|
||||
onSelected: (v) => switch (v) {
|
||||
'home' => _go(widget.c.settings.torHomeUrl),
|
||||
'home' => _go(widget.c.settings.tor.homeUrl),
|
||||
'notice' => showTorNotice(context),
|
||||
_ => Navigator.push(context, MaterialPageRoute<void>(builder: (_) => SettingsPage(c: widget.c, initialGroup: 'tor'))),
|
||||
},
|
||||
|
|
@ -621,7 +621,7 @@ class _TorPageState extends State<TorPage> with WebProxyVisibility {
|
|||
}
|
||||
final b = widget.viewBuilder;
|
||||
if (b != null) return b(_host, _url);
|
||||
return _TorWebView(host: _host, url: _url, env: _env, javaScript: widget.c.settings.torJavaScript, keepData: widget.c.settings.torKeepData);
|
||||
return _TorWebView(host: _host, url: _url, env: _env, javaScript: widget.c.settings.tor.javaScript, keepData: widget.c.settings.tor.keepData);
|
||||
}
|
||||
|
||||
Widget _message(IconData icon, String title, String body, {bool busy = false}) => Center(
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ void main() {
|
|||
final back = AppSettings.fromJson(mine.toJson());
|
||||
expect(back.network.config.key, mine.network.config.key);
|
||||
// Tor 설정 처음 값
|
||||
expect((s.torKeepData, s.torHomeUrl, s.torJavaScript, s.torNoticeSeen), (false, 'https://check.torproject.org/', true, false));
|
||||
expect((s.tor.keepData, s.tor.homeUrl, s.tor.javaScript, s.tor.noticeSeen), (false, 'https://check.torproject.org/', true, false));
|
||||
});
|
||||
|
||||
test('너무 긴 이름은 이름 탓 (닿지 않음 아님) - DNS 안내 · 작업 기록에 사이트 이름이 남지 않는다 (15 지적)', () async {
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ void main() {
|
|||
addTearDown(() => t.binding.defaultBinaryMessenger.setMockMethodCallHandler(pp, null));
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..torNoticeSeen = true
|
||||
..tor.noticeSeen = true
|
||||
..components = [...c.settings.components, 'tor'];
|
||||
await t.pumpWidget(MaterialApp(
|
||||
navigatorKey: navKey,
|
||||
|
|
@ -270,7 +270,7 @@ void main() {
|
|||
nav.currentState!.pop();
|
||||
await t.pumpAndSettle();
|
||||
expect(applied, 2);
|
||||
expect(loads.last, c.settings.torHomeUrl);
|
||||
expect(loads.last, c.settings.tor.homeUrl);
|
||||
expect(find.textContaining('VIEW'), findsOneWidget);
|
||||
// 앱이 뒤로 가도 같다
|
||||
t.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
|
||||
|
|
@ -284,7 +284,7 @@ void main() {
|
|||
await t.pump();
|
||||
await t.pump();
|
||||
expect(applied, 3, reason: '돌아오면 프록시를 다시 건 뒤에');
|
||||
expect(loads.last, c.settings.torHomeUrl);
|
||||
expect(loads.last, c.settings.tor.homeUrl);
|
||||
expect(find.textContaining('VIEW'), findsOneWidget);
|
||||
});
|
||||
|
||||
|
|
@ -315,11 +315,11 @@ void main() {
|
|||
});
|
||||
|
||||
test('설정: Tor 를 켜 둘 시간 (처음 2분, 틀린 값은 2분)', () {
|
||||
expect(AppSettings().torIdleMinutes, 2);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': 0}).torIdleMinutes, 0);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': 5}).torIdleMinutes, 5);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': -1}).torIdleMinutes, 2);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': 'x'}).torIdleMinutes, 2);
|
||||
expect(AppSettings().tor.idleMinutes, 2);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': 0}).tor.idleMinutes, 0);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': 5}).tor.idleMinutes, 5);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': -1}).tor.idleMinutes, 2);
|
||||
expect(AppSettings.fromJson({'torIdleMinutes': 'x'}).tor.idleMinutes, 2);
|
||||
});
|
||||
|
||||
testWidgets('227: 막은 파일 주소는 주소 칸에 남지 않고 보이던 페이지 주소로', (t) async {
|
||||
|
|
|
|||
Loading…
Reference in New Issue