WIP ⑦ 밝은 화면 (나) 1: 파일 탐색기 · 주의 색 · 캡처 시험 밝은 화면 (검토 대기)
관리자 지시 10/21 (⑦ (나) 화면마다 차례로 - 첫째 파일 탐색기):
- 주황 · 빨강 · 노랑 · 초록 (orangeAccent · redAccent · amber · greenAccent) 을 테마 색 warning · alert · amber · positive 로.
어두운 화면은 예전 Material 값 그대로, 밝은 화면만 진하게 (글자 4.5:1 · 노랑 아이콘 3:1, 노랑 글자는 amberText)
- 탐색기: 흰 바탕에서 옅은 회색 파일 아이콘을 밝은 화면일 때만 진하게, 저장 공간 그림 "이 폴더의 파일" 칸 글자 (밝은 화면)
- 통합 캡처 시험에 JJ_SHOT_THEME=light (어두운 것과 견줌)
- 설정 설명 글을 새 이름 (밝은 화면 · 기기 따라) 에 맞춤
- 확인: 어두운 화면 캡처 95장을 9324e41 과 픽셀로 견줘 실행마다 바뀌는 곳 (시각 · CPU 숫자 · 임시 이름) 밖의 차이 0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xplore_p3b
parent
9324e4100f
commit
808300b69e
|
|
@ -1454,9 +1454,9 @@
|
||||||
"받지 않음": "Off",
|
"받지 않음": "Off",
|
||||||
"받지 않음 (처음 쓸 때 내려받기 {0})": "Not downloaded (downloads on first use, {0})",
|
"받지 않음 (처음 쓸 때 내려받기 {0})": "Not downloaded (downloads on first use, {0})",
|
||||||
"밝게": "Brighter",
|
"밝게": "Brighter",
|
||||||
"밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "Light and Follow device are still in testing - colors on some screens may not fit yet",
|
|
||||||
"밝기": "Brightness",
|
"밝기": "Brightness",
|
||||||
"밝은 화면": "Light",
|
"밝은 화면": "Light",
|
||||||
|
"밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "Light and Follow device are still in testing - colors on some screens may not fit yet",
|
||||||
"밤 0~6시": "Night 0-6",
|
"밤 0~6시": "Night 0-6",
|
||||||
"방법: {0}": "Method: {0}",
|
"방법: {0}": "Method: {0}",
|
||||||
"방식": "Type",
|
"방식": "Type",
|
||||||
|
|
|
||||||
|
|
@ -1454,9 +1454,9 @@
|
||||||
"받지 않음": "取得しない",
|
"받지 않음": "取得しない",
|
||||||
"받지 않음 (처음 쓸 때 내려받기 {0})": "未ダウンロード (初回使用時にダウンロード {0})",
|
"받지 않음 (처음 쓸 때 내려받기 {0})": "未ダウンロード (初回使用時にダウンロード {0})",
|
||||||
"밝게": "明るく",
|
"밝게": "明るく",
|
||||||
"밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "明るい・端末に合わせるはまだ試験中です - 一部の画面の色が合わない場合があります",
|
|
||||||
"밝기": "明るさ",
|
"밝기": "明るさ",
|
||||||
"밝은 화면": "明るい",
|
"밝은 화면": "明るい",
|
||||||
|
"밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "明るい・端末に合わせるはまだ試験中です - 一部の画面の色が合わない場合があります",
|
||||||
"밤 0~6시": "夜 0~6 時",
|
"밤 0~6시": "夜 0~6 時",
|
||||||
"방법: {0}": "方法: {0}",
|
"방법: {0}": "方法: {0}",
|
||||||
"방식": "方式",
|
"방식": "方式",
|
||||||
|
|
|
||||||
|
|
@ -1454,9 +1454,9 @@
|
||||||
"받지 않음": "不获取",
|
"받지 않음": "不获取",
|
||||||
"받지 않음 (처음 쓸 때 내려받기 {0})": "未下载 (首次使用时下载 {0})",
|
"받지 않음 (처음 쓸 때 내려받기 {0})": "未下载 (首次使用时下载 {0})",
|
||||||
"밝게": "调亮",
|
"밝게": "调亮",
|
||||||
"밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "浅色和跟随设备仍在测试中 - 部分界面的颜色可能还不协调",
|
|
||||||
"밝기": "亮度",
|
"밝기": "亮度",
|
||||||
"밝은 화면": "浅色",
|
"밝은 화면": "浅色",
|
||||||
|
"밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "浅色和跟随设备仍在测试中 - 部分界面的颜色可能还不协调",
|
||||||
"밤 0~6시": "夜间 0~6 点",
|
"밤 0~6시": "夜间 0~6 点",
|
||||||
"방법: {0}": "方式: {0}",
|
"방법: {0}": "方式: {0}",
|
||||||
"방식": "方式",
|
"방식": "方式",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import 'package:jj_mkvmaker/ui/theme.dart';
|
||||||
/// 환경 설정 > 파일 탐색기 의 복사 · 동기화 부분을 실제 글꼴로 그려 PNG 로 (JJ_SHOT_DIR). 화면 배치 확인용.
|
/// 환경 설정 > 파일 탐색기 의 복사 · 동기화 부분을 실제 글꼴로 그려 PNG 로 (JJ_SHOT_DIR). 화면 배치 확인용.
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||||
|
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||||
|
|
||||||
testWidgets('복사 · 동기화 설정 그림', (tester) async {
|
testWidgets('복사 · 동기화 설정 그림', (tester) async {
|
||||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import 'package:jj_mkvmaker/ui/theme.dart';
|
||||||
|
|
||||||
/// 파일 탐색기 화면을 실제 글꼴 · 실제 FFmpeg (썸네일 · 동영상 정보) 로 그려 PNG 로 저장 (JJ_SHOT_DIR).
|
/// 파일 탐색기 화면을 실제 글꼴 · 실제 FFmpeg (썸네일 · 동영상 정보) 로 그려 PNG 로 저장 (JJ_SHOT_DIR).
|
||||||
/// 왼쪽 · 오른쪽 창 폴더: JJ_SHOT_LEFT · JJ_SHOT_RIGHT. 화면 배치 확인용.
|
/// 왼쪽 · 오른쪽 창 폴더: JJ_SHOT_LEFT · JJ_SHOT_RIGHT. 화면 배치 확인용.
|
||||||
|
/// JJ_SHOT_THEME=light 면 밝은 테마로 (⑦ - 파일 이름 끝에 _light)
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
|
@ -21,6 +22,9 @@ void main() {
|
||||||
final c = AppController(PlatformServices.create());
|
final c = AppController(PlatformServices.create());
|
||||||
c.settings.explorerPaths = [env['JJ_SHOT_LEFT'] ?? Directory.current.path, env['JJ_SHOT_RIGHT'] ?? Directory.current.path];
|
c.settings.explorerPaths = [env['JJ_SHOT_LEFT'] ?? Directory.current.path, env['JJ_SHOT_RIGHT'] ?? Directory.current.path];
|
||||||
final key = GlobalKey();
|
final key = GlobalKey();
|
||||||
|
final light = env['JJ_SHOT_THEME'] == 'light';
|
||||||
|
JjColors.use(light ? Brightness.light : Brightness.dark);
|
||||||
|
addTearDown(() => JjColors.use(Brightness.dark));
|
||||||
for (final (name, size, layout, style) in [
|
for (final (name, size, layout, style) in [
|
||||||
('wide', const Size(1600, 900), 'dual', 'xplore'),
|
('wide', const Size(1600, 900), 'dual', 'xplore'),
|
||||||
('tall', const Size(900, 1400), 'dual', 'xplore'),
|
('tall', const Size(900, 1400), 'dual', 'xplore'),
|
||||||
|
|
@ -45,7 +49,7 @@ void main() {
|
||||||
final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
||||||
final image = await tester.runAsync(() => boundary.toImage());
|
final image = await tester.runAsync(() => boundary.toImage());
|
||||||
final bytes = await tester.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png));
|
final bytes = await tester.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png));
|
||||||
File('$out/explorer_$name.png').writeAsBytesSync(bytes!.buffer.asUint8List());
|
File('$out/explorer_$name${light ? '_light' : ''}.png').writeAsBytesSync(bytes!.buffer.asUint8List());
|
||||||
await tester.pumpWidget(const SizedBox());
|
await tester.pumpWidget(const SizedBox());
|
||||||
}
|
}
|
||||||
tester.view.reset();
|
tester.view.reset();
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import 'package:path/path.dart' as p;
|
||||||
/// 세 스타일 모두. 넘치면 (RenderFlex overflow) 시험이 실패한다
|
/// 세 스타일 모두. 넘치면 (RenderFlex overflow) 시험이 실패한다
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||||
|
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||||
|
|
||||||
testWidgets('40: 목록 글자 크기 · 줄 간격 화면 그림 (412 폭)', (tester) async {
|
testWidgets('40: 목록 글자 크기 · 줄 간격 화면 그림 (412 폭)', (tester) async {
|
||||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import 'package:path/path.dart' as p;
|
||||||
/// 모니터링 화면 (복사 진행 중 · lsync · 일정 격자) 을 실제 글꼴로 그려 PNG 로 (JJ_SHOT_DIR). 화면 배치 확인용.
|
/// 모니터링 화면 (복사 진행 중 · lsync · 일정 격자) 을 실제 글꼴로 그려 PNG 로 (JJ_SHOT_DIR). 화면 배치 확인용.
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||||
|
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||||
|
|
||||||
testWidgets('모니터링 화면 그림', (tester) async {
|
testWidgets('모니터링 화면 그림', (tester) async {
|
||||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import 'package:jj_mkvmaker/ui/theme.dart';
|
||||||
/// 환경 설정 화면을 실제 글꼴로 그려 PNG 로 저장 (JJ_SHOT_DIR). 화면 배치 확인용.
|
/// 환경 설정 화면을 실제 글꼴로 그려 PNG 로 저장 (JJ_SHOT_DIR). 화면 배치 확인용.
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||||
|
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||||
|
|
||||||
testWidgets('환경 설정 화면 그림', (tester) async {
|
testWidgets('환경 설정 화면 그림', (tester) async {
|
||||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import 'package:path/path.dart' as p;
|
||||||
/// 크기 412 (폰 세로) · 960, 언어 ko · en · ja. 시험 파일은 임시 폴더에만 (끝나면 지움). 실행: flutter test integration_test\windows_p3_shot_test.dart -d windows
|
/// 크기 412 (폰 세로) · 960, 언어 ko · en · ja. 시험 파일은 임시 폴더에만 (끝나면 지움). 실행: flutter test integration_test\windows_p3_shot_test.dart -d windows
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||||
|
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_p3');
|
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_p3');
|
||||||
for (final lang in ['ko', 'en', 'ja']) {
|
for (final lang in ['ko', 'en', 'ja']) {
|
||||||
for (final (w, h) in [(412, 900), (960, 600)]) {
|
for (final (w, h) in [(412, 900), (960, 600)]) {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import 'package:path/path.dart' as p;
|
||||||
/// 크기 412 (폰 세로, 한 창) · 960 (두 창), 언어 ko · en. 시험 파일은 임시 폴더에만, 설정은 메모리에만.
|
/// 크기 412 (폰 세로, 한 창) · 960 (두 창), 언어 ko · en. 시험 파일은 임시 폴더에만, 설정은 메모리에만.
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||||
|
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_xplore');
|
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_xplore');
|
||||||
|
|
||||||
for (final lang in ['ko', 'en']) {
|
for (final lang in ['ko', 'en']) {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import 'package:path/path.dart' as p;
|
||||||
/// (JJ_SHOT_DIR, 없으면 임시 폴더). 크기 412 · 960, 언어 ko · en. 시험 파일은 임시 폴더에만.
|
/// (JJ_SHOT_DIR, 없으면 임시 폴더). 크기 412 · 960, 언어 ko · en. 시험 파일은 임시 폴더에만.
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||||
|
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_xptools');
|
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_xptools');
|
||||||
|
|
||||||
for (final lang in ['ko', 'en']) {
|
for (final lang in ['ko', 'en']) {
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ class _AiImagePageState extends State<AiImagePage> with RouteAware {
|
||||||
_engineRow(),
|
_engineRow(),
|
||||||
if (!_local)
|
if (!_local)
|
||||||
// 인터넷 서비스 · 자기 서버를 쓸 때는 늘 알린다
|
// 인터넷 서비스 · 자기 서버를 쓸 때는 늘 알린다
|
||||||
_notice(Icons.public, Colors.orangeAccent,
|
_notice(Icons.public, JjColors.warning,
|
||||||
trf('그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})', [_service!.label])),
|
trf('그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})', [_service!.label])),
|
||||||
if (_local && !_modelsReady)
|
if (_local && !_modelsReady)
|
||||||
_notice(Icons.download_outlined, JjColors.accent,
|
_notice(Icons.download_outlined, JjColors.accent,
|
||||||
|
|
@ -287,7 +287,7 @@ class _AiImagePageState extends State<AiImagePage> with RouteAware {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(_error!, style: TextStyle(color: _cancelled ? JjColors.textDim : Colors.redAccent)),
|
Text(_error!, style: TextStyle(color: _cancelled ? JjColors.textDim : JjColors.alert)),
|
||||||
if (_detail.isNotEmpty)
|
if (_detail.isNotEmpty)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => setState(() => _showDetail = !_showDetail),
|
onPressed: () => setState(() => _showDetail = !_showDetail),
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ class _AiSettingsState extends State<AiSettings> {
|
||||||
free == null
|
free == null
|
||||||
? trf('필요한 공간 {0}', [AiStore.sizeText(need)])
|
? trf('필요한 공간 {0}', [AiStore.sizeText(need)])
|
||||||
: trf('필요한 공간 {0} · 남은 공간 {1}', [AiStore.sizeText(need), AiStore.sizeText(free)]),
|
: trf('필요한 공간 {0} · 남은 공간 {1}', [AiStore.sizeText(need), AiStore.sizeText(free)]),
|
||||||
style: TextStyle(fontSize: 12, color: free != null && free < need ? Colors.redAccent : JjColors.textDim),
|
style: TextStyle(fontSize: 12, color: free != null && free < need ? JjColors.alert : JjColors.textDim),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -189,7 +189,7 @@ class _AiSettingsState extends State<AiSettings> {
|
||||||
if (_error != null)
|
if (_error != null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Text(_error!, style: const TextStyle(color: Colors.redAccent)),
|
child: Text(_error!, style: TextStyle(color: JjColors.alert)),
|
||||||
),
|
),
|
||||||
for (final f in _files) _fileRow(f),
|
for (final f in _files) _fileRow(f),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ class _FileBrowserState extends State<_FileBrowser> {
|
||||||
: ListView(children: [
|
: ListView(children: [
|
||||||
for (final d in _dirs)
|
for (final d in _dirs)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.folder, color: Colors.amber),
|
leading: Icon(Icons.folder, color: JjColors.amber),
|
||||||
title: Text(p.basename(d.path)),
|
title: Text(p.basename(d.path)),
|
||||||
onTap: () => _open(d.path),
|
onTap: () => _open(d.path),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -189,12 +189,12 @@ Future<String?> extractArchiveFlow(BuildContext context, AppController c, String
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(trf('풀 수 없는 이름 {0}개 (건너뜀): {1}',
|
Text(trf('풀 수 없는 이름 {0}개 (건너뜀): {1}',
|
||||||
[plan.badNames.length, plan.badNames.take(5).map((x) => '${x.$1} (${x.$2})').join(', ')]),
|
[plan.badNames.length, plan.badNames.take(5).map((x) => '${x.$1} (${x.$2})').join(', ')]),
|
||||||
style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)),
|
style: TextStyle(color: JjColors.warning, fontSize: 12)),
|
||||||
],
|
],
|
||||||
if (plan.links.isNotEmpty) ...[
|
if (plan.links.isNotEmpty) ...[
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(trf('링크 {0}개는 만들지 않고 건너뜁니다: {1}', [plan.links.length, plan.links.take(5).join(', ')]),
|
Text(trf('링크 {0}개는 만들지 않고 건너뜁니다: {1}', [plan.links.length, plan.links.take(5).join(', ')]),
|
||||||
style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)),
|
style: TextStyle(color: JjColors.warning, fontSize: 12)),
|
||||||
],
|
],
|
||||||
if (plan.encrypted) ...[
|
if (plan.encrypted) ...[
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|
@ -223,7 +223,7 @@ Future<String?> extractArchiveFlow(BuildContext context, AppController c, String
|
||||||
c.settings.copySkipNewer
|
c.settings.copySkipNewer
|
||||||
? tr('덮어쓴 파일은 되돌릴 수 없습니다 (받는 쪽이 더 새 파일은 그대로 둡니다).')
|
? tr('덮어쓴 파일은 되돌릴 수 없습니다 (받는 쪽이 더 새 파일은 그대로 둡니다).')
|
||||||
: tr('덮어쓴 파일은 되돌릴 수 없습니다.'),
|
: tr('덮어쓴 파일은 되돌릴 수 없습니다.'),
|
||||||
style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)),
|
style: TextStyle(color: JjColors.warning, fontSize: 12)),
|
||||||
// 관리자 판단: 풀린 크기가 빈 공간보다 크면 풀기 전에 알리고 묻는다 (압축 폭탄 · 공간 부족)
|
// 관리자 판단: 풀린 크기가 빈 공간보다 크면 풀기 전에 알리고 묻는다 (압축 폭탄 · 공간 부족)
|
||||||
if (short)
|
if (short)
|
||||||
Text(trf('풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?', [formatSize(plan.totalSize), formatSize(f)]),
|
Text(trf('풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?', [formatSize(plan.totalSize), formatSize(f)]),
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ List<DropdownMenuItem<String>> folderItems(BookmarksController bm, {String? excl
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(left: depth * 14.0),
|
padding: EdgeInsets.only(left: depth * 14.0),
|
||||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
Icon(depth == 0 ? Icons.folder_special_outlined : Icons.folder_outlined, size: 16, color: Colors.amber),
|
Icon(depth == 0 ? Icons.folder_special_outlined : Icons.folder_outlined, size: 16, color: JjColors.amber),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(f.title),
|
Text(f.title),
|
||||||
]),
|
]),
|
||||||
|
|
@ -368,7 +368,7 @@ class _BookmarkDraggableState extends State<BookmarkDraggable> {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
Icon(n.isFolder ? Icons.folder : Icons.public, size: 16, color: n.isFolder ? Colors.amber : null),
|
Icon(n.isFolder ? Icons.folder : Icons.public, size: 16, color: n.isFolder ? JjColors.amber : null),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(n.title, style: TextStyle(fontSize: 12, color: JjColors.text)),
|
Text(n.title, style: TextStyle(fontSize: 12, color: JjColors.text)),
|
||||||
]),
|
]),
|
||||||
|
|
@ -610,7 +610,7 @@ class _BookmarkManagerPageState extends State<BookmarkManagerPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(depth == 0 ? Icons.folder_special : (selected ? Icons.folder_open : Icons.folder),
|
Icon(depth == 0 ? Icons.folder_special : (selected ? Icons.folder_open : Icons.folder),
|
||||||
size: 18, color: Colors.amber),
|
size: 18, color: JjColors.amber),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: Text(f.title, maxLines: 1, overflow: TextOverflow.ellipsis)),
|
Expanded(child: Text(f.title, maxLines: 1, overflow: TextOverflow.ellipsis)),
|
||||||
]),
|
]),
|
||||||
|
|
@ -711,7 +711,7 @@ class _BookmarkManagerPageState extends State<BookmarkManagerPage> {
|
||||||
BookmarkDraggable(
|
BookmarkDraggable(
|
||||||
n: n,
|
n: n,
|
||||||
onMenu: (at) => _menu(n, folder, at),
|
onMenu: (at) => _menu(n, folder, at),
|
||||||
child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? Colors.amber : JjColors.textDim),
|
child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? JjColors.amber : JjColors.textDim),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
|
|
@ -762,7 +762,7 @@ class _BookmarkManagerPageState extends State<BookmarkManagerPage> {
|
||||||
leading: BookmarkDraggable(
|
leading: BookmarkDraggable(
|
||||||
n: n,
|
n: n,
|
||||||
onMenu: (at) => _menu(n, bm.tree.parentOf(n.id) ?? bm.tree.bar, at),
|
onMenu: (at) => _menu(n, bm.tree.parentOf(n.id) ?? bm.tree.bar, at),
|
||||||
child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? Colors.amber : JjColors.textDim),
|
child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? JjColors.amber : JjColors.textDim),
|
||||||
),
|
),
|
||||||
title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
|
|
|
||||||
|
|
@ -870,7 +870,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
tooltip: starred ? keyHint(tr('즐겨찾기 수정'), 'Ctrl+D') : keyHint(tr('즐겨찾기 추가'), 'Ctrl+D'),
|
tooltip: starred ? keyHint(tr('즐겨찾기 수정'), 'Ctrl+D') : keyHint(tr('즐겨찾기 추가'), 'Ctrl+D'),
|
||||||
icon: Icon(starred ? Icons.star : Icons.star_border, size: 18,
|
icon: Icon(starred ? Icons.star : Icons.star_border, size: 18,
|
||||||
color: starred ? Colors.amber : JjColors.textDim),
|
color: starred ? JjColors.amber : JjColors.textDim),
|
||||||
onPressed: _star,
|
onPressed: _star,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1653,7 +1653,7 @@ class _BookmarkBar extends StatelessWidget {
|
||||||
menuChildren: _folderMenu(context, n),
|
menuChildren: _folderMenu(context, n),
|
||||||
builder: (context, ctl, _) => TextButton.icon(
|
builder: (context, ctl, _) => TextButton.icon(
|
||||||
onPressed: () => ctl.isOpen ? ctl.close() : ctl.open(),
|
onPressed: () => ctl.isOpen ? ctl.close() : ctl.open(),
|
||||||
icon: const Icon(Icons.folder, size: 15, color: Colors.amber),
|
icon: Icon(Icons.folder, size: 15, color: JjColors.amber),
|
||||||
label: Text(label, style: const TextStyle(fontSize: 12)),
|
label: Text(label, style: const TextStyle(fontSize: 12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1693,7 +1693,7 @@ class _BookmarkBar extends StatelessWidget {
|
||||||
for (final c in f.children!)
|
for (final c in f.children!)
|
||||||
c.isFolder
|
c.isFolder
|
||||||
? SubmenuButton(
|
? SubmenuButton(
|
||||||
leadingIcon: const Icon(Icons.folder, size: 16, color: Colors.amber),
|
leadingIcon: Icon(Icons.folder, size: 16, color: JjColors.amber),
|
||||||
menuChildren: _folderMenu(context, c),
|
menuChildren: _folderMenu(context, c),
|
||||||
child: Text(c.title),
|
child: Text(c.title),
|
||||||
)
|
)
|
||||||
|
|
@ -1911,7 +1911,7 @@ class _BookmarkPanelState extends State<BookmarkPanel> {
|
||||||
n: n,
|
n: n,
|
||||||
onMenu: menuAt,
|
onMenu: menuAt,
|
||||||
child: Icon(n.isFolder ? Icons.folder : Icons.public,
|
child: Icon(n.isFolder ? Icons.folder : Icons.public,
|
||||||
size: 18, color: n.isFolder ? Colors.amber : JjColors.textDim),
|
size: 18, color: n.isFolder ? JjColors.amber : JjColors.textDim),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ class _CleanupDialogState extends State<_CleanupDialog> {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
subtitle: Text(g.skipped ?? g.hint,
|
subtitle: Text(g.skipped ?? g.hint,
|
||||||
style: TextStyle(fontSize: 12, color: g.skipped != null ? Colors.amber : JjColors.textDim)),
|
style: TextStyle(fontSize: 12, color: g.skipped != null ? JjColors.amberText : JjColors.textDim)),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -327,10 +327,10 @@ class _LiveSyncTile extends StatelessWidget {
|
||||||
// 34: 안쪽 → 바깥은 지우기 없이만, 바깥 → 안쪽은 안 됨 (끝없이 돎)
|
// 34: 안쪽 → 바깥은 지우기 없이만, 바깥 → 안쪽은 안 됨 (끝없이 돎)
|
||||||
if (LiveSync.nestingProblem(LiveSyncPair(src!, dst!, delete: delete), allowInnerToOuter: c.settings.allowInnerToOuter)
|
if (LiveSync.nestingProblem(LiveSyncPair(src!, dst!, delete: delete), allowInnerToOuter: c.settings.allowInnerToOuter)
|
||||||
case final nest?)
|
case final nest?)
|
||||||
Text(nest, style: const TextStyle(fontSize: 12, color: Colors.redAccent))
|
Text(nest, style: TextStyle(fontSize: 12, color: JjColors.alert))
|
||||||
else if (isSameOrInside(src!, dst!))
|
else if (isSameOrInside(src!, dst!))
|
||||||
Text(tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (지우기는 쓸 수 없음).'),
|
Text(tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (지우기는 쓸 수 없음).'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Wrap(spacing: 6, children: [
|
Wrap(spacing: 6, children: [
|
||||||
for (final m in CopyMethod.values)
|
for (final m in CopyMethod.values)
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ class _DlnaPageState extends State<DlnaPage> {
|
||||||
final e = _entries[i];
|
final e = _entries[i];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: Icon(_icon(e), color: e.isContainer ? Colors.amber : JjColors.textDim),
|
leading: Icon(_icon(e), color: e.isContainer ? JjColors.amber : JjColors.textDim),
|
||||||
title: Text(e.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
title: Text(e.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
subtitle: e.isContainer
|
subtitle: e.isContainer
|
||||||
? (e.childCount == null ? null : Text(trf('{0}개', [e.childCount!])))
|
? (e.childCount == null ? null : Text(trf('{0}개', [e.childCount!])))
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ class _DownloadRow extends StatelessWidget {
|
||||||
DownloadState.queued => (tr('대기 중'), JjColors.textDim),
|
DownloadState.queued => (tr('대기 중'), JjColors.textDim),
|
||||||
DownloadState.downloading when t.extra['waiting'] == true => (tr('대기 중'), JjColors.textDim),
|
DownloadState.downloading when t.extra['waiting'] == true => (tr('대기 중'), JjColors.textDim),
|
||||||
DownloadState.downloading => (tr('받는 중'), JjColors.accent),
|
DownloadState.downloading => (tr('받는 중'), JjColors.accent),
|
||||||
DownloadState.paused => (tr('일시정지'), Colors.amber),
|
DownloadState.paused => (tr('일시정지'), JjColors.amberText),
|
||||||
DownloadState.done => (tr('완료'), JjColors.success),
|
DownloadState.done => (tr('완료'), JjColors.success),
|
||||||
DownloadState.failed => (tr('실패'), JjColors.danger),
|
DownloadState.failed => (tr('실패'), JjColors.danger),
|
||||||
DownloadState.cancelled => (tr('취소됨'), JjColors.textDim),
|
DownloadState.cancelled => (tr('취소됨'), JjColors.textDim),
|
||||||
|
|
@ -321,7 +321,7 @@ class _DownloadRow extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
'${trf('트래커 · 피어에 닿지 않습니다 ({0}초째)', [((DateTime.now().millisecondsSinceEpoch - ((t.extra['stallSince'] as int?) ?? 0)) / 1000).round()])} · '
|
'${trf('트래커 · 피어에 닿지 않습니다 ({0}초째)', [((DateTime.now().millisecondsSinceEpoch - ((t.extra['stallSince'] as int?) ?? 0)) / 1000).round()])} · '
|
||||||
'${trf('이유 후보: {0}', [[for (final r in (t.extra['stallReasons'] as List?) ?? const []) stallReasonText('$r')].join(' · ')])}',
|
'${trf('이유 후보: {0}', [[for (final r in (t.extra['stallReasons'] as List?) ?? const []) stallReasonText('$r')].join(' · ')])}',
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
if (((t.extra['stallReasons'] as List?) ?? const []).contains('dns') && d.useSystemDns != null)
|
if (((t.extra['stallReasons'] as List?) ?? const []).contains('dns') && d.useSystemDns != null)
|
||||||
TextButton(style: _small, onPressed: () => d.useSystemDns!(), child: Text(tr('시스템 DNS 로 바꾸기'))),
|
TextButton(style: _small, onPressed: () => d.useSystemDns!(), child: Text(tr('시스템 DNS 로 바꾸기'))),
|
||||||
TextButton(style: _small, onPressed: () => d.retryStalled(t), child: Text(tr('다시 시도'))),
|
TextButton(style: _small, onPressed: () => d.retryStalled(t), child: Text(tr('다시 시도'))),
|
||||||
|
|
@ -404,10 +404,10 @@ class DownloadProgressBar extends StatelessWidget {
|
||||||
// 주황으로 "멈춤" - 움직이는 "준비 중…" 이면 받는 중인 것처럼 보였다
|
// 주황으로 "멈춤" - 움직이는 "준비 중…" 이면 받는 중인 것처럼 보였다
|
||||||
final stalled = t.state == DownloadState.downloading && t.extra['stalled'] == true;
|
final stalled = t.state == DownloadState.downloading && t.extra['stalled'] == true;
|
||||||
final color = switch (t.state) {
|
final color = switch (t.state) {
|
||||||
_ when stalled => Colors.orangeAccent,
|
_ when stalled => JjColors.warning,
|
||||||
DownloadState.done => JjColors.success,
|
DownloadState.done => JjColors.success,
|
||||||
DownloadState.failed => JjColors.danger,
|
DownloadState.failed => JjColors.danger,
|
||||||
DownloadState.paused => Colors.amber,
|
DownloadState.paused => JjColors.amberText,
|
||||||
DownloadState.cancelled => JjColors.textDim,
|
DownloadState.cancelled => JjColors.textDim,
|
||||||
_ => JjColors.accent,
|
_ => JjColors.accent,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ class _BookmarksDialogState extends State<_BookmarksDialog> {
|
||||||
for (final b in mine)
|
for (final b in mine)
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: Icon(b.isPair ? Icons.compare_arrows : Icons.star, color: Colors.amber, size: 20),
|
leading: Icon(b.isPair ? Icons.compare_arrows : Icons.star, color: JjColors.amber, size: 20),
|
||||||
title: Text(b.name, maxLines: 1, overflow: TextOverflow.ellipsis),
|
title: Text(b.name, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
subtitle: Text(b.isPair ? '${vDisplay(b.path)}\n⇄ ${vDisplay(b.right!)}' : vDisplay(b.path),
|
subtitle: Text(b.isPair ? '${vDisplay(b.path)}\n⇄ ${vDisplay(b.right!)}' : vDisplay(b.path),
|
||||||
maxLines: b.isPair ? 2 : 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)),
|
maxLines: b.isPair ? 2 : 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)),
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ class _KeyCaptureDialogState extends State<_KeyCaptureDialog> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text(trf('이미 "{0}" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다', [explorerKeyLabel(clash)]),
|
child: Text(trf('이미 "{0}" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다', [explorerKeyLabel(clash)]),
|
||||||
style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)),
|
style: TextStyle(color: JjColors.warning, fontSize: 12)),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ int columnHideLevel(ExplorerStyle style, double width, {bool marks = false, doub
|
||||||
final x = e.ext;
|
final x = e.ext;
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case ExplorerStyle.xplore:
|
case ExplorerStyle.xplore:
|
||||||
if (e.isDir) return (open ? Icons.folder_open : Icons.folder, Colors.amber);
|
if (e.isDir) return (open ? Icons.folder_open : Icons.folder, JjColors.amber);
|
||||||
return (
|
return (
|
||||||
switch (x) {
|
switch (x) {
|
||||||
_ when _subs.contains(x) => Icons.subtitles_outlined,
|
_ when _subs.contains(x) => Icons.subtitles_outlined,
|
||||||
|
|
@ -128,16 +128,17 @@ int columnHideLevel(ExplorerStyle style, double width, {bool marks = false, doub
|
||||||
if (_sheets.contains(x)) return (Icons.table_chart, const Color(0xFF4CAF7A));
|
if (_sheets.contains(x)) return (Icons.table_chart, const Color(0xFF4CAF7A));
|
||||||
if (_slides.contains(x)) return (Icons.slideshow, const Color(0xFFE8804F));
|
if (_slides.contains(x)) return (Icons.slideshow, const Color(0xFFE8804F));
|
||||||
if (_programs.contains(x)) return (x == 'apk' ? Icons.android : Icons.terminal, const Color(0xFF8BC34A));
|
if (_programs.contains(x)) return (x == 'apk' ? Icons.android : Icons.terminal, const Color(0xFF8BC34A));
|
||||||
if (_texts.contains(x)) return (Icons.description, const Color(0xFFB0BEC5));
|
// ⑦ 밝은 화면: 옅은 회색은 흰 바탕에서 안 보여 진하게
|
||||||
return (Icons.insert_drive_file, const Color(0xFFB0BEC5));
|
if (_texts.contains(x)) return (Icons.description, JjColors.isLight ? const Color(0xFF78909C) : const Color(0xFFB0BEC5));
|
||||||
|
return (Icons.insert_drive_file, JjColors.isLight ? const Color(0xFF78909C) : const Color(0xFFB0BEC5));
|
||||||
case ExplorerStyle.totalcmd:
|
case ExplorerStyle.totalcmd:
|
||||||
// Total Commander 느낌: 단순한 작은 아이콘, 폴더는 노랑, 실행 파일은 초록
|
// Total Commander 느낌: 단순한 작은 아이콘, 폴더는 노랑, 실행 파일은 초록
|
||||||
if (e.isDir) return (Icons.folder, const Color(0xFFE8C547));
|
if (e.isDir) return (Icons.folder, const Color(0xFFE8C547));
|
||||||
if (_programs.contains(x)) return (Icons.settings_applications, const Color(0xFF7CB342));
|
if (_programs.contains(x)) return (Icons.settings_applications, const Color(0xFF7CB342));
|
||||||
if (isVideoFile(e.path)) return (Icons.movie, const Color(0xFF64B5F6));
|
if (isVideoFile(e.path)) return (Icons.movie, const Color(0xFF64B5F6));
|
||||||
if (_images.contains(x)) return (Icons.image, const Color(0xFF64B5F6));
|
if (_images.contains(x)) return (Icons.image, const Color(0xFF64B5F6));
|
||||||
if (_archives.contains(x)) return (Icons.inventory_2, const Color(0xFFBCAAA4));
|
if (_archives.contains(x)) return (Icons.inventory_2, JjColors.isLight ? const Color(0xFF8D6E63) : const Color(0xFFBCAAA4));
|
||||||
return (Icons.description, const Color(0xFFCFD8DC));
|
return (Icons.description, JjColors.isLight ? const Color(0xFF78909C) : const Color(0xFFCFD8DC));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -612,7 +612,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
? tr('목록 · 보기 · 꺼내기 (복사) 만 됩니다. 시스템 · 다른 앱의 개인 자료가 있으니 꺼낸 파일을 조심해서 다루세요')
|
? tr('목록 · 보기 · 꺼내기 (복사) 만 됩니다. 시스템 · 다른 앱의 개인 자료가 있으니 꺼낸 파일을 조심해서 다루세요')
|
||||||
: tr('목록 · 보기 · 꺼내기 (복사) 만 됩니다. 다른 앱의 데이터가 있을 수 있으니 꺼낸 파일을 조심해서 다루세요'),
|
: tr('목록 · 보기 · 꺼내기 (복사) 만 됩니다. 다른 앱의 데이터가 있을 수 있으니 꺼낸 파일을 조심해서 다루세요'),
|
||||||
actions: const [],
|
actions: const [],
|
||||||
color: Colors.orangeAccent,
|
color: JjColors.warning,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,7 +623,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
icon: const Icon(Icons.security, color: Colors.orangeAccent),
|
icon: Icon(Icons.security, color: JjColors.warning),
|
||||||
title: Text(tr('루트 폴더 보기 (읽기 전용)')),
|
title: Text(tr('루트 폴더 보기 (읽기 전용)')),
|
||||||
content: Text(tr('루트 (su) 권한으로 시스템 · 다른 앱의 폴더 전체를 봅니다. 다른 앱의 개인 자료 (대화 · 로그인 정보 · 비밀번호 등) 도 '
|
content: Text(tr('루트 (su) 권한으로 시스템 · 다른 앱의 폴더 전체를 봅니다. 다른 앱의 개인 자료 (대화 · 로그인 정보 · 비밀번호 등) 도 '
|
||||||
'보일 수 있습니다. 이 앱은 읽기만 하고 (쓰기 · 지우기 · 이름 바꾸기 없음), 꺼낸 파일은 고른 곳에만 둡니다.')),
|
'보일 수 있습니다. 이 앱은 읽기만 하고 (쓰기 · 지우기 · 이름 바꾸기 없음), 꺼낸 파일은 고른 곳에만 둡니다.')),
|
||||||
|
|
@ -662,7 +662,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
icon: const Icon(Icons.admin_panel_settings_outlined, color: Colors.orangeAccent),
|
icon: Icon(Icons.admin_panel_settings_outlined, color: JjColors.warning),
|
||||||
title: Text(tr('시스템 폴더 보기 (읽기 전용)')),
|
title: Text(tr('시스템 폴더 보기 (읽기 전용)')),
|
||||||
content: Text(tr('Shizuku 의 권한으로 Android/data · obb 처럼 막힌 폴더를 봅니다. 다른 앱의 데이터 (대화 · 로그인 정보 등) 가 '
|
content: Text(tr('Shizuku 의 권한으로 Android/data · obb 처럼 막힌 폴더를 봅니다. 다른 앱의 데이터 (대화 · 로그인 정보 등) 가 '
|
||||||
'있을 수 있습니다. 이 앱은 읽기만 하고 (쓰기 · 지우기 · 이름 바꾸기 없음), 꺼낸 파일은 고른 곳에만 둡니다.')),
|
'있을 수 있습니다. 이 앱은 읽기만 하고 (쓰기 · 지우기 · 이름 바꾸기 없음), 꺼낸 파일은 고른 곳에만 둡니다.')),
|
||||||
|
|
@ -726,7 +726,10 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
required String title,
|
required String title,
|
||||||
required String body,
|
required String body,
|
||||||
required List<Widget> actions,
|
required List<Widget> actions,
|
||||||
Color color = Colors.redAccent}) =>
|
Color? color}) =>
|
||||||
|
_noticeBox(icon, title, body, actions, color ?? JjColors.alert);
|
||||||
|
|
||||||
|
Widget _noticeBox(IconData icon, String title, String body, List<Widget> actions, Color color) =>
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.fromLTRB(6, 6, 6, 0),
|
margin: const EdgeInsets.fromLTRB(6, 6, 6, 0),
|
||||||
|
|
@ -764,7 +767,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
if (list.isEmpty) return null;
|
if (list.isEmpty) return null;
|
||||||
return _notice(
|
return _notice(
|
||||||
icon: Icons.broken_image_outlined,
|
icon: Icons.broken_image_outlined,
|
||||||
color: Colors.orangeAccent,
|
color: JjColors.warning,
|
||||||
title: trf('만들다 만 파일 {0}개', [list.length]),
|
title: trf('만들다 만 파일 {0}개', [list.length]),
|
||||||
body: tr('복사 · 동기화가 중간에 끊겨 남은 임시 파일입니다 (.jjpart). 완성된 파일이 아니므로 지워도 됩니다. '
|
body: tr('복사 · 동기화가 중간에 끊겨 남은 임시 파일입니다 (.jjpart). 완성된 파일이 아니므로 지워도 됩니다. '
|
||||||
'같은 파일을 다시 복사하면 새로 만듭니다.'),
|
'같은 파일을 다시 복사하면 새로 만듭니다.'),
|
||||||
|
|
@ -1531,8 +1534,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
volume,
|
volume,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
_Row.favorites => (Icons.star, Colors.amber, tr('즐겨찾기'), pane.favoritesOpen),
|
_Row.favorites => (Icons.star, JjColors.amber, tr('즐겨찾기'), pane.favoritesOpen),
|
||||||
_ => (Icons.star_outline, Colors.amber, fav!.name, null),
|
_ => (Icons.star_outline, JjColors.amber, fav!.name, null),
|
||||||
};
|
};
|
||||||
void tap() {
|
void tap() {
|
||||||
setState(() => _active = _panes.indexOf(pane));
|
setState(() => _active = _panes.indexOf(pane));
|
||||||
|
|
@ -2990,19 +2993,19 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text(trf('그중 폴더 {0}개 - 안의 파일 · 폴더도 모두 함께', [paths.where(vIsDirSync).length]),
|
child: Text(trf('그중 폴더 {0}개 - 안의 파일 · 폴더도 모두 함께', [paths.where(vIsDirSync).length]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
if (hiddenInside > 0)
|
if (hiddenInside > 0)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text(trf('지우는 폴더 안에 목록에서 숨긴 항목 {0}개가 있습니다 - 함께 지워집니다', [hiddenInside]),
|
child: Text(trf('지우는 폴더 안에 목록에서 숨긴 항목 {0}개가 있습니다 - 함께 지워집니다', [hiddenInside]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
if (local && !canRecycle)
|
if (local && !canRecycle)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text(tr('이 위치 (네트워크 드라이브 · 네트워크 공유 · USB 메모리 등) 에는 휴지통이 없어 바로 지워집니다.'),
|
child: Text(tr('이 위치 (네트워크 드라이브 · 네트워크 공유 · USB 메모리 등) 에는 휴지통이 없어 바로 지워집니다.'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
if (canTrash && !permanent)
|
if (canTrash && !permanent)
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -3080,7 +3083,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
icon: const Icon(Icons.warning_amber_rounded, color: Colors.orangeAccent),
|
icon: Icon(Icons.warning_amber_rounded, color: JjColors.warning),
|
||||||
title: Text(trf('{0}개를 휴지통으로 옮기지 못했습니다', [notTrashed.length])),
|
title: Text(trf('{0}개를 휴지통으로 옮기지 못했습니다', [notTrashed.length])),
|
||||||
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
for (final (x, why) in notTrashed.take(10)) Text('· ${vBasename(x)} - $why', style: const TextStyle(fontSize: 12)),
|
for (final (x, why) in notTrashed.take(10)) Text('· ${vBasename(x)} - $why', style: const TextStyle(fontSize: 12)),
|
||||||
|
|
@ -3112,7 +3115,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
icon: const Icon(Icons.error_outline, color: Colors.redAccent),
|
icon: Icon(Icons.error_outline, color: JjColors.alert),
|
||||||
title: Text(trf('{0}개 중 {1}개를 지우지 못했습니다', [paths.length, failed.length + tooLong.length])),
|
title: Text(trf('{0}개 중 {1}개를 지우지 못했습니다', [paths.length, failed.length + tooLong.length])),
|
||||||
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
// 144: 긴 경로는 이유 한 줄 + 이름만 (영구 삭제는 아래 버튼으로)
|
// 144: 긴 경로는 이유 한 줄 + 이름만 (영구 삭제는 아래 버튼으로)
|
||||||
|
|
@ -3138,7 +3141,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
if (tooLong.isNotEmpty) ...[
|
if (tooLong.isNotEmpty) ...[
|
||||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('그대로 두기'))),
|
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('그대로 두기'))),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
style: FilledButton.styleFrom(backgroundColor: Colors.redAccent),
|
style: FilledButton.styleFrom(backgroundColor: JjColors.alert),
|
||||||
onPressed: () => Navigator.pop(ctx, true),
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
child: Text(trf('영구 삭제 ({0}개, 되돌릴 수 없음)', [tooLong.length])),
|
child: Text(trf('영구 삭제 ({0}개, 되돌릴 수 없음)', [tooLong.length])),
|
||||||
),
|
),
|
||||||
|
|
@ -3501,7 +3504,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(trf('같은 이름이 이미 있습니다: {0}', [_namesOf([for (final x in clashes) x.src])]),
|
Text(trf('같은 이름이 이미 있습니다: {0}', [_namesOf([for (final x in clashes) x.src])]),
|
||||||
style: const TextStyle(color: Colors.orangeAccent)),
|
style: TextStyle(color: JjColors.warning)),
|
||||||
// 172: 두 파일의 크기 · 바뀐 날 (3개까지)
|
// 172: 두 파일의 크기 · 바뀐 날 (3개까지)
|
||||||
for (final x in clashes.take(3)) ...[
|
for (final x in clashes.take(3)) ...[
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -3522,7 +3525,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
: c.settings.copySkipNewer
|
: c.settings.copySkipNewer
|
||||||
? trf('안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)', [n.data])
|
? trf('안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)', [n.data])
|
||||||
: trf('안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다', [n.data]),
|
: trf('안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다', [n.data]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent),
|
style: TextStyle(fontSize: 12, color: JjColors.warning),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -3751,14 +3754,14 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
removeSource && !both
|
removeSource && !both
|
||||||
? tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).')
|
? tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).')
|
||||||
: tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).'),
|
: tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
// 1280×720 등 낮은 화면에서도 보이게: 원본 지우기 체크 · 경고는 비교 목록보다 위에 (178)
|
// 1280×720 등 낮은 화면에서도 보이게: 원본 지우기 체크 · 경고는 비교 목록보다 위에 (178)
|
||||||
if (both) ...[
|
if (both) ...[
|
||||||
Text(keepOptionsTogether(tr('양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).')),
|
Text(keepOptionsTogether(tr('양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).')),
|
||||||
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||||
if (optionMove)
|
if (optionMove)
|
||||||
Text(tr('양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).'),
|
Text(tr('양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
] else ...[
|
] else ...[
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
|
|
@ -3778,7 +3781,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
optionMove
|
optionMove
|
||||||
? tr('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에 --remove-source-files 가 있어 켜 두었습니다 - 끄면 이번에는 원본을 남깁니다.')
|
? tr('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에 --remove-source-files 가 있어 켜 두었습니다 - 끄면 이번에는 원본을 남깁니다.')
|
||||||
: tr('원본 파일이 지워집니다 (이동).'),
|
: tr('원본 파일이 지워집니다 (이동).'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning, fontWeight: FontWeight.w600)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(tr('끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:'),
|
Text(tr('끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:'),
|
||||||
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||||
|
|
@ -3824,7 +3827,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
),
|
),
|
||||||
if (nestedDelete())
|
if (nestedDelete())
|
||||||
Text(tr('원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).'),
|
Text(tr('원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)),
|
||||||
// 110: 이 줄은 늘 같은 자리를 차지한다 (글이 사라져 창 높이가 바뀌면 버튼이 움직인다)
|
// 110: 이 줄은 늘 같은 자리를 차지한다 (글이 사라져 창 높이가 바뀌면 버튼이 움직인다)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 34,
|
height: 34,
|
||||||
|
|
@ -3833,7 +3836,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
previewError != null
|
previewError != null
|
||||||
? tr('지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.')
|
? tr('지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.')
|
||||||
: tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.'),
|
: tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent),
|
style: TextStyle(fontSize: 12, color: JjColors.warning),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
|
@ -5485,7 +5488,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
c.settings.folderBookmarks.any((b) => !b.isPair && samePath(b.path, pane.current)) ? Icons.star : Icons.star_outline,
|
c.settings.folderBookmarks.any((b) => !b.isPair && samePath(b.path, pane.current)) ? Icons.star : Icons.star_outline,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Colors.amber),
|
color: JjColors.amber),
|
||||||
onPressed: () => _bookmarks(pane),
|
onPressed: () => _bookmarks(pane),
|
||||||
),
|
),
|
||||||
// 76: 빠른 필터 (켜져 있으면 강조)
|
// 76: 빠른 필터 (켜져 있으면 강조)
|
||||||
|
|
@ -5562,7 +5565,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
// 165: 열 수 없는 드라이브는 숨기지 않고 "연결 안 됨" - 누르면 다시 연결 (다시 읽기)
|
// 165: 열 수 없는 드라이브는 숨기지 않고 "연결 안 됨" - 누르면 다시 연결 (다시 읽기)
|
||||||
label: _driveOk[path] == false
|
label: _driveOk[path] == false
|
||||||
? Text('$label · ${_removableDrives.contains(path) ? tr('비어 있음') : tr('연결 안 됨')} ⟳',
|
? Text('$label · ${_removableDrives.contains(path) ? tr('비어 있음') : tr('연결 안 됨')} ⟳',
|
||||||
style: TextStyle(color: _removableDrives.contains(path) ? JjColors.textDim : Colors.orangeAccent))
|
style: TextStyle(color: _removableDrives.contains(path) ? JjColors.textDim : JjColors.warning))
|
||||||
: Text(label),
|
: Text(label),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => _active = _panes.indexOf(pane));
|
setState(() => _active = _panes.indexOf(pane));
|
||||||
|
|
@ -5830,7 +5833,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
if (slim)
|
if (slim)
|
||||||
const SizedBox.shrink()
|
const SizedBox.shrink()
|
||||||
else if (look.rich && e.isDir && pane.errors[e.path] != null)
|
else if (look.rich && e.isDir && pane.errors[e.path] != null)
|
||||||
Text(tr('읽을 수 없음'), style: dim.copyWith(color: Colors.redAccent))
|
Text(tr('읽을 수 없음'), style: dim.copyWith(color: JjColors.alert))
|
||||||
// 80: 열 없는 모양 (X-plore · 좁은 화면) 은 폴더 이름 아래에 크기
|
// 80: 열 없는 모양 (X-plore · 좁은 화면) 은 폴더 이름 아래에 크기
|
||||||
else if (e.isDir && !row.isRoot && !row.isUp && (!look.columns || compact) && _dirSize(pane, e) != null)
|
else if (e.isDir && !row.isRoot && !row.isUp && (!look.columns || compact) && _dirSize(pane, e) != null)
|
||||||
Text(_dirSize(pane, e)!, style: dim, maxLines: 1),
|
Text(_dirSize(pane, e)!, style: dim, maxLines: 1),
|
||||||
|
|
|
||||||
|
|
@ -451,13 +451,13 @@ class _SearchResultsPageState extends State<SearchResultsPage> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
child: Text(tr('처음 찾을 때 끝까지 찾기 전의 결과입니다. 더 찾으려면 다시 찾으세요.'),
|
child: Text(tr('처음 찾을 때 끝까지 찾기 전의 결과입니다. 더 찾으려면 다시 찾으세요.'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
if (_paused)
|
if (_paused)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||||
Text(trf('{0}개에서 멈췄습니다', [_found.length]), style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
Text(trf('{0}개에서 멈췄습니다', [_found.length]), style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
OutlinedButton(onPressed: _more, child: Text(trf('{0}개 더 보기', [widget.chunk]))),
|
OutlinedButton(onPressed: _more, child: Text(trf('{0}개 더 보기', [widget.chunk]))),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
@ -468,7 +468,7 @@ class _SearchResultsPageState extends State<SearchResultsPage> {
|
||||||
tilePadding: EdgeInsets.zero,
|
tilePadding: EdgeInsets.zero,
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(trf('읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다', [_skipped.length]),
|
title: Text(trf('읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다', [_skipped.length]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
children: [
|
children: [
|
||||||
for (final (dir, why) in _skipped.take(50))
|
for (final (dir, why) in _skipped.take(50))
|
||||||
Align(
|
Align(
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ Future<void> showFileHash(BuildContext context, String path) async {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 6),
|
padding: const EdgeInsets.only(top: 6),
|
||||||
child: Text(match ? tr('같습니다 ✔') : tr('다릅니다 - 파일이 바뀌었거나 다른 파일입니다'),
|
child: Text(match ? tr('같습니다 ✔') : tr('다릅니다 - 파일이 바뀌었거나 다른 파일입니다'),
|
||||||
style: TextStyle(color: match ? Colors.greenAccent : JjColors.danger)),
|
style: TextStyle(color: match ? JjColors.positive : JjColors.danger)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import '../core/webdav.dart';
|
||||||
import '../l10n/tr.dart';
|
import '../l10n/tr.dart';
|
||||||
import 'android_file_browser.dart';
|
import 'android_file_browser.dart';
|
||||||
import 'file_error.dart';
|
import 'file_error.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
/// 폴더 고르기. Android 는 앱 안 화면 (내장 저장소 · SD 카드 · USB 를 실제 경로로 고를 수 있음), PC 는 Windows 폴더 선택 창
|
/// 폴더 고르기. Android 는 앱 안 화면 (내장 저장소 · SD 카드 · USB 를 실제 경로로 고를 수 있음), PC 는 Windows 폴더 선택 창
|
||||||
Future<String?> pickFolder(BuildContext context, String title, [String? initial]) => Platform.isAndroid
|
Future<String?> pickFolder(BuildContext context, String title, [String? initial]) => Platform.isAndroid
|
||||||
|
|
@ -94,9 +95,9 @@ class _DavFolderDialogState extends State<_DavFolderDialog> {
|
||||||
final locked = isLockedError(error);
|
final locked = isLockedError(error);
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
Icon(locked ? Icons.lock_outline : Icons.cloud_off_outlined, color: Colors.redAccent),
|
Icon(locked ? Icons.lock_outline : Icons.cloud_off_outlined, color: JjColors.alert),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(title, textAlign: TextAlign.center, style: const TextStyle(color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
Text(title, textAlign: TextAlign.center, style: TextStyle(color: JjColors.alert, fontWeight: FontWeight.w600)),
|
||||||
if (body.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text(body, textAlign: TextAlign.center)),
|
if (body.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text(body, textAlign: TextAlign.center)),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
FilledButton.tonal(
|
FilledButton.tonal(
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,7 @@ Future<FtpServer?> editFtpServer(BuildContext context, AppController c, {FtpServ
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (testing) const LinearProgressIndicator(),
|
if (testing) const LinearProgressIndicator(),
|
||||||
if (result != null) Text(result!, style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)),
|
if (result != null) Text(result!, style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -300,7 +300,7 @@ Future<FtpServer?> editFtpServer(BuildContext context, AppController c, {FtpServ
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (await deleteFtpServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
if (await deleteFtpServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.redAccent),
|
style: TextButton.styleFrom(foregroundColor: JjColors.alert),
|
||||||
child: Text(tr('지우기')),
|
child: Text(tr('지우기')),
|
||||||
),
|
),
|
||||||
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ class _MasterDialogState extends State<_MasterDialog> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text(trf('여러 번 틀려 {0}초 뒤에 다시 넣을 수 있습니다', [wait.inSeconds + 1]),
|
child: Text(trf('여러 번 틀려 {0}초 뒤에 다시 넣을 수 있습니다', [wait.inSeconds + 1]),
|
||||||
style: const TextStyle(color: Colors.redAccent)),
|
style: TextStyle(color: JjColors.alert)),
|
||||||
),
|
),
|
||||||
if (_busy) const Padding(padding: EdgeInsets.only(top: 8), child: LinearProgressIndicator()),
|
if (_busy) const Padding(padding: EdgeInsets.only(top: 8), child: LinearProgressIndicator()),
|
||||||
if (!widget.forChange)
|
if (!widget.forChange)
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ Future<bool?> _liveDeleteDialog(BuildContext context, LiveSync live, LiveSyncPai
|
||||||
// 관리자 3a 판단 (10/20): 확인한 쌍인데 옵션이 바뀌어 지울 것이 늘어 다시 묻는 것
|
// 관리자 3a 판단 (10/20): 확인한 쌍인데 옵션이 바뀌어 지울 것이 늘어 다시 묻는 것
|
||||||
if (!now && live.reasked.contains(LiveSync.keyOf(x))) ...[
|
if (!now && live.reasked.contains(LiveSync.keyOf(x))) ...[
|
||||||
Text(tr('지우기를 확인한 뒤 rsync 옵션이 바뀌어 지울 것이 늘었습니다. 다시 확인해 주세요.'),
|
Text(tr('지우기를 확인한 뒤 rsync 옵션이 바뀌어 지울 것이 늘었습니다. 다시 확인해 주세요.'),
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.orangeAccent)),
|
style: TextStyle(fontWeight: FontWeight.w600, color: JjColors.warning)),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
],
|
],
|
||||||
Text(trf('"{0}" 에는 없고 대상 "{1}" 에만 있는 항목입니다. 지우기 포함으로 맞추면 대상에서 지워집니다 (되돌릴 수 없음).',
|
Text(trf('"{0}" 에는 없고 대상 "{1}" 에만 있는 항목입니다. 지우기 포함으로 맞추면 대상에서 지워집니다 (되돌릴 수 없음).',
|
||||||
|
|
@ -238,7 +238,7 @@ class _DiskInfo extends StatelessWidget {
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
minHeight: 6,
|
minHeight: 6,
|
||||||
value: used,
|
value: used,
|
||||||
color: used > 0.9 ? Colors.redAccent : JjColors.accent,
|
color: used > 0.9 ? JjColors.alert : JjColors.accent,
|
||||||
backgroundColor: JjColors.border,
|
backgroundColor: JjColors.border,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -524,7 +524,7 @@ class _CopyCardState extends State<_CopyCard> {
|
||||||
if (running && t.move) const SizedBox(height: 10),
|
if (running && t.move) const SizedBox(height: 10),
|
||||||
if (t.move)
|
if (t.move)
|
||||||
Text(tr('실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤). 원본을 비우려면 lsync 의 [최종 정리] 를 쓰세요.'),
|
Text(tr('실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤). 원본을 비우려면 lsync 의 [최종 정리] 를 쓰세요.'),
|
||||||
style: const TextStyle(color: Colors.orangeAccent)),
|
style: TextStyle(color: JjColors.warning)),
|
||||||
]),
|
]),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
|
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
|
||||||
|
|
@ -558,12 +558,12 @@ class _CopyCardState extends State<_CopyCard> {
|
||||||
// 235: 이동인데 원본에 남은 것이 있으면 "끝" 만 보이지 않게 (주황 · 남은 수)
|
// 235: 이동인데 원본에 남은 것이 있으면 "끝" 만 보이지 않게 (주황 · 남은 수)
|
||||||
'done' when t.lastLeft > 0 => (
|
'done' when t.lastLeft > 0 => (
|
||||||
Icons.warning_amber,
|
Icons.warning_amber,
|
||||||
Colors.orangeAccent,
|
JjColors.warning,
|
||||||
'${trf('끝 · {0} · 파일 {1}개', [_hm(t.lastRun), t.lastFiles])} · ${trf('{0}개는 원본에 남음 (건너뜀)', [t.lastLeft])}'
|
'${trf('끝 · {0} · 파일 {1}개', [_hm(t.lastRun), t.lastFiles])} · ${trf('{0}개는 원본에 남음 (건너뜀)', [t.lastLeft])}'
|
||||||
),
|
),
|
||||||
'done' => (Icons.check_circle, Colors.greenAccent, trf('끝 · {0} · 파일 {1}개', [_hm(t.lastRun), t.lastFiles])),
|
'done' => (Icons.check_circle, JjColors.positive, trf('끝 · {0} · 파일 {1}개', [_hm(t.lastRun), t.lastFiles])),
|
||||||
'failed' => (Icons.error, Colors.redAccent, trf('실패 · {0}', [_hm(t.lastRun)])),
|
'failed' => (Icons.error, JjColors.alert, trf('실패 · {0}', [_hm(t.lastRun)])),
|
||||||
'cancelled' => (Icons.pause_circle, Colors.orangeAccent, trf('멈춤 · {0}', [_hm(t.lastRun)])),
|
'cancelled' => (Icons.pause_circle, JjColors.warning, trf('멈춤 · {0}', [_hm(t.lastRun)])),
|
||||||
_ => (Icons.schedule, JjColors.textDim, tr('아직 실행 안 함')),
|
_ => (Icons.schedule, JjColors.textDim, tr('아직 실행 안 함')),
|
||||||
};
|
};
|
||||||
final optsHint = switch (CopyMethod.of(_method)) {
|
final optsHint = switch (CopyMethod.of(_method)) {
|
||||||
|
|
@ -612,7 +612,7 @@ class _CopyCardState extends State<_CopyCard> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 28, top: 4),
|
padding: const EdgeInsets.only(left: 28, top: 4),
|
||||||
child: Wrap(crossAxisAlignment: WrapCrossAlignment.center, spacing: 4, children: [
|
child: Wrap(crossAxisAlignment: WrapCrossAlignment.center, spacing: 4, children: [
|
||||||
Text(t.lastMessage, maxLines: 3, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11, color: Colors.redAccent)),
|
Text(t.lastMessage, maxLines: 3, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 11, color: JjColors.alert)),
|
||||||
// 237: 원문 (예외 이름 · 명령 줄) 은 [자세히] 에서 - 보고 복사해 물어볼 수 있게 (다운로드 실패 줄 136 과 같게)
|
// 237: 원문 (예외 이름 · 명령 줄) 은 [자세히] 에서 - 보고 복사해 물어볼 수 있게 (다운로드 실패 줄 136 과 같게)
|
||||||
if (t.lastDetail.trim().isNotEmpty)
|
if (t.lastDetail.trim().isNotEmpty)
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|
@ -660,13 +660,13 @@ class _CopyCardState extends State<_CopyCard> {
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
||||||
Text(
|
Text(
|
||||||
'${t.move ? tr('이동') : tr('복사')} · ${trf('실제 실행: {0}', [effectiveRunText(_method, _options.text.trim(), move: t.move, skipNewer: s.copySkipNewer)])}',
|
'${t.move ? tr('이동') : tr('복사')} · ${trf('실제 실행: {0}', [effectiveRunText(_method, _options.text.trim(), move: t.move, skipNewer: s.copySkipNewer)])}',
|
||||||
style: TextStyle(fontSize: 12, color: t.move ? Colors.orangeAccent : JjColors.textDim),
|
style: TextStyle(fontSize: 12, color: t.move ? JjColors.warning : JjColors.textDim),
|
||||||
),
|
),
|
||||||
// 관리자 지시 (10/20, 28 확인): 고쳐 읽어도 알아볼 수 없는 옵션 (실행은 작업이 막는다)
|
// 관리자 지시 (10/20, 28 확인): 고쳐 읽어도 알아볼 수 없는 옵션 (실행은 작업이 막는다)
|
||||||
if (_method == 'rsync' && optionProblems(_options.text).isNotEmpty) OptionProblemNote(optionProblems(_options.text)),
|
if (_method == 'rsync' && optionProblems(_options.text).isNotEmpty) OptionProblemNote(optionProblems(_options.text)),
|
||||||
if (!t.move && optionsRemoveSource(_options.text))
|
if (!t.move && optionsRemoveSource(_options.text))
|
||||||
Text(tr('복사 작업이라 칸의 원본 지우기 옵션 (--remove-source-files · /MOV) 은 쓰지 않습니다. 옮기려면 Rsync 화면에서 [원본 파일 지우기] 를 켜세요'),
|
Text(tr('복사 작업이라 칸의 원본 지우기 옵션 (--remove-source-files · /MOV) 은 쓰지 않습니다. 옮기려면 Rsync 화면에서 [원본 파일 지우기] 를 켜세요'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|
@ -781,7 +781,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
PruneChoice(value: prune, onChanged: (v) => set(() => prune = v)),
|
PruneChoice(value: prune, onChanged: (v) => set(() => prune = v)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(tr('원본이 비므로 이 실시간 동기화는 끕니다. 진행은 [복사 · rsync] 탭에서 봅니다.'),
|
Text(tr('원본이 비므로 이 실시간 동기화는 끕니다. 진행은 [복사 · rsync] 탭에서 봅니다.'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -928,12 +928,12 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
),
|
),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(e.empty ? Icons.folder_off_outlined : Icons.error_outline, color: Colors.redAccent, size: 20),
|
Icon(e.empty ? Icons.folder_off_outlined : Icons.error_outline, color: JjColors.alert, size: 20),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
e.empty ? tr('원본 폴더가 비어 있어 멈췄습니다') : tr('원본을 읽을 수 없어 멈췄습니다'),
|
e.empty ? tr('원본 폴더가 비어 있어 멈췄습니다') : tr('원본을 읽을 수 없어 멈췄습니다'),
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.redAccent),
|
style: TextStyle(fontWeight: FontWeight.w600, color: JjColors.alert),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
|
@ -1002,7 +1002,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
: active
|
: active
|
||||||
? Icons.sync
|
? Icons.sync
|
||||||
: Icons.bedtime_outlined,
|
: Icons.bedtime_outlined,
|
||||||
color: x.enabled && !held ? (active ? JjColors.accent : Colors.orangeAccent) : JjColors.textDim),
|
color: x.enabled && !held ? (active ? JjColors.accent : JjColors.warning) : JjColors.textDim),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
// 67: 끝 폴더가 보이게 짧게 · 69: 길게 누르면 (오른쪽 클릭) 고치기 · 지우기
|
// 67: 끝 폴더가 보이게 짧게 · 69: 길게 누르면 (오른쪽 클릭) 고치기 · 지우기
|
||||||
|
|
@ -1037,16 +1037,16 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 32, top: 2),
|
padding: const EdgeInsets.only(left: 32, top: 2),
|
||||||
child: Text(tr('멈춤 (이번 실행 동안) · 시작 버튼을 누르면 다시 동기화합니다'),
|
child: Text(tr('멈춤 (이번 실행 동안) · 시작 버튼을 누르면 다시 동기화합니다'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
// 42: 지우기를 아직 확인하지 않았고 지울 것이 있다 → 지우지 않고 맞추는 중. 목록을 보고 결정
|
// 42: 지우기를 아직 확인하지 않았고 지울 것이 있다 → 지우지 않고 맞추는 중. 목록을 보고 결정
|
||||||
if (live != null && (live.toDelete[k]?.isNotEmpty ?? false))
|
if (live != null && (live.toDelete[k]?.isNotEmpty ?? false))
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 32, top: 4),
|
padding: const EdgeInsets.only(left: 32, top: 4),
|
||||||
child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||||
const Icon(Icons.warning_amber_rounded, size: 16, color: Colors.orangeAccent),
|
Icon(Icons.warning_amber_rounded, size: 16, color: JjColors.warning),
|
||||||
Text(trf('대상에만 있는 {0}개를 지울지 확인이 필요합니다 (확인 전에는 지우지 않음)', [live.toDelete[k]!.length]),
|
Text(trf('대상에만 있는 {0}개를 지울지 확인이 필요합니다 (확인 전에는 지우지 않음)', [live.toDelete[k]!.length]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () => confirmLiveDeletes(context, live, x),
|
onPressed: () => confirmLiveDeletes(context, live, x),
|
||||||
child: Text(tr('지울 목록 보고 결정')),
|
child: Text(tr('지울 목록 보고 결정')),
|
||||||
|
|
@ -1058,10 +1058,10 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 32, top: 4),
|
padding: const EdgeInsets.only(left: 32, top: 4),
|
||||||
child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||||
const Icon(Icons.block, size: 16, color: Colors.redAccent),
|
Icon(Icons.block, size: 16, color: JjColors.alert),
|
||||||
Text(tr('이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다'),
|
Text(tr('이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)),
|
||||||
Text('(${live.noAutoDelete[k]!.map(unbreakableOption).join(' · ')})', style: const TextStyle(fontSize: 12, color: Colors.redAccent)),
|
Text('(${live.noAutoDelete[k]!.map(unbreakableOption).join(' · ')})', style: TextStyle(fontSize: 12, color: JjColors.alert)),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () => confirmLiveDeleteNow(context, live, x),
|
onPressed: () => confirmLiveDeleteNow(context, live, x),
|
||||||
child: Text(tr('지금 지우기 (확인)')),
|
child: Text(tr('지금 지우기 (확인)')),
|
||||||
|
|
@ -1086,7 +1086,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
// 257: 폰 세로에서 글이 줄을 바꿀 수 있게 (넘쳤다)
|
// 257: 폰 세로에서 글이 줄을 바꿀 수 있게 (넘쳤다)
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
||||||
Text(scheduleSummary(x.schedule), style: TextStyle(fontSize: 12, color: active ? JjColors.accent : Colors.orangeAccent)),
|
Text(scheduleSummary(x.schedule), style: TextStyle(fontSize: 12, color: active ? JjColors.accent : JjColors.warning)),
|
||||||
if (x.schedule.isNotEmpty)
|
if (x.schedule.isNotEmpty)
|
||||||
Text(x.schedule.join(' | '), style: TextStyle(fontSize: 11, fontFamily: 'monospace', color: JjColors.textDim)),
|
Text(x.schedule.join(' | '), style: TextStyle(fontSize: 11, fontFamily: 'monospace', color: JjColors.textDim)),
|
||||||
Text(tr('누르면 일정 바꾸기'), style: TextStyle(fontSize: 10, color: JjColors.textDim)),
|
Text(tr('누르면 일정 바꾸기'), style: TextStyle(fontSize: 10, color: JjColors.textDim)),
|
||||||
|
|
@ -1097,7 +1097,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
),
|
),
|
||||||
if (st != null)
|
if (st != null)
|
||||||
Text('${tr('마지막')}: ${st.$1.hour.toString().padLeft(2, '0')}:${st.$1.minute.toString().padLeft(2, '0')} ${st.$2}',
|
Text('${tr('마지막')}: ${st.$1.hour.toString().padLeft(2, '0')}:${st.$1.minute.toString().padLeft(2, '0')} ${st.$2}',
|
||||||
style: TextStyle(fontSize: 12, color: live?.problems[k] != null ? Colors.redAccent : JjColors.textDim)),
|
style: TextStyle(fontSize: 12, color: live?.problems[k] != null ? JjColors.alert : JjColors.textDim)),
|
||||||
_DiskInfo(path: x.target),
|
_DiskInfo(path: x.target),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
@ -1116,11 +1116,11 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
(live?.onlyTarget[k] ?? 0) > 0
|
(live?.onlyTarget[k] ?? 0) > 0
|
||||||
? trf('원본의 것은 모두 대상에 있습니다 (대상에만 있는 것 {0}개는 그대로 둠)', [live!.onlyTarget[k]])
|
? trf('원본의 것은 모두 대상에 있습니다 (대상에만 있는 것 {0}개는 그대로 둠)', [live!.onlyTarget[k]])
|
||||||
: tr('맞출 것 없음 (원본과 대상이 같습니다)'),
|
: tr('맞출 것 없음 (원본과 대상이 같습니다)'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.greenAccent))
|
style: TextStyle(fontSize: 12, color: JjColors.positive))
|
||||||
: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(
|
Text(
|
||||||
trf('맞출 것 {0}개{1}', [pend.length >= 500 ? '500+' : pend.length, active ? '' : tr(' · 다음 시작 시간에 맞춥니다')]),
|
trf('맞출 것 {0}개{1}', [pend.length >= 500 ? '500+' : pend.length, active ? '' : tr(' · 다음 시작 시간에 맞춥니다')]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent),
|
style: TextStyle(fontSize: 12, color: JjColors.warning),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Container(
|
Container(
|
||||||
|
|
@ -1131,7 +1131,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
for (final f in pend.take(100))
|
for (final f in pend.take(100))
|
||||||
Text(f,
|
Text(f,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11.5, fontFamily: 'monospace', color: f.startsWith('− ') ? Colors.redAccent : null)),
|
fontSize: 11.5, fontFamily: 'monospace', color: f.startsWith('− ') ? JjColors.alert : null)),
|
||||||
if (pend.length > 100) Text(trf('… 외 {0}개', [pend.length - 100]), style: const TextStyle(fontSize: 11)),
|
if (pend.length > 100) Text(trf('… 외 {0}개', [pend.length - 100]), style: const TextStyle(fontSize: 11)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@ class _WeekGridState extends State<_WeekGrid> {
|
||||||
onTap: () => _toggleRow(d),
|
onTap: () => _toggleRow(d),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(names[d],
|
child: Text(names[d],
|
||||||
style: TextStyle(fontSize: 12, color: d == 0 ? Colors.redAccent : d == 6 ? Colors.lightBlueAccent : null)),
|
style: TextStyle(fontSize: 12, color: d == 0 ? JjColors.alert : d == 6 ? Colors.lightBlueAccent : null)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import '../app/app_controller.dart';
|
import '../app/app_controller.dart';
|
||||||
import '../app/settings.dart';
|
import '../app/settings.dart';
|
||||||
import '../l10n/tr.dart';
|
import '../l10n/tr.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
/// 안전 저장소 문제 (40-1 · 40-3 · 40-5) 를 화면 위 알림으로 보여 준다: 생길 때마다 · 켤 때 이미 있으면 바로.
|
/// 안전 저장소 문제 (40-1 · 40-3 · 40-5) 를 화면 위 알림으로 보여 준다: 생길 때마다 · 켤 때 이미 있으면 바로.
|
||||||
/// 비밀번호를 평문으로 몰래 저장하지 않는 대신, 저장되지 않았다는 것을 분명히 알리고 [다시 시도] 를 둔다.
|
/// 비밀번호를 평문으로 몰래 저장하지 않는 대신, 저장되지 않았다는 것을 분명히 알리고 [다시 시도] 를 둔다.
|
||||||
|
|
@ -45,7 +46,7 @@ void watchSecretIssue(GlobalKey<ScaffoldMessengerState> messengerKey, AppControl
|
||||||
MaterialBanner secretIssueBanner(SecretIssue issue, {required VoidCallback onRetry, required VoidCallback onClose}) {
|
MaterialBanner secretIssueBanner(SecretIssue issue, {required VoidCallback onRetry, required VoidCallback onClose}) {
|
||||||
final lost = issue.kind == SecretIssueKind.lost;
|
final lost = issue.kind == SecretIssueKind.lost;
|
||||||
return MaterialBanner(
|
return MaterialBanner(
|
||||||
leading: const Icon(Icons.key_off_outlined, color: Colors.orangeAccent),
|
leading: Icon(Icons.key_off_outlined, color: JjColors.warning),
|
||||||
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(
|
Text(
|
||||||
lost ? tr('저장된 비밀번호를 되살리지 못했습니다') : tr('이 기기의 안전 저장소를 쓸 수 없어 비밀번호를 저장하지 못했습니다'),
|
lost ? tr('저장된 비밀번호를 되살리지 못했습니다') : tr('이 기기의 안전 저장소를 쓸 수 없어 비밀번호를 저장하지 못했습니다'),
|
||||||
|
|
@ -61,10 +62,10 @@ MaterialBanner secretIssueBanner(SecretIssue issue, {required VoidCallback onRet
|
||||||
// 91: 새로 넣은 비밀번호가 앱을 끄면 어떻게 되는지 분명히
|
// 91: 새로 넣은 비밀번호가 앱을 끄면 어떻게 되는지 분명히
|
||||||
if (issue.revertsToOld)
|
if (issue.revertsToOld)
|
||||||
Text(tr('새 비밀번호는 아직 저장되지 않았습니다. 앱을 끄면 예전 비밀번호로 돌아갑니다.'),
|
Text(tr('새 비밀번호는 아직 저장되지 않았습니다. 앱을 끄면 예전 비밀번호로 돌아갑니다.'),
|
||||||
style: const TextStyle(color: Colors.orangeAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(color: JjColors.warning, fontWeight: FontWeight.w600)),
|
||||||
if (issue.lostOnExit && !issue.revertsToOld)
|
if (issue.lostOnExit && !issue.revertsToOld)
|
||||||
Text(tr('새로 넣은 비밀번호는 아직 저장되지 않았습니다. 앱을 끄면 사라집니다.'),
|
Text(tr('새로 넣은 비밀번호는 아직 저장되지 않았습니다. 앱을 끄면 사라집니다.'),
|
||||||
style: const TextStyle(color: Colors.orangeAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(color: JjColors.warning, fontWeight: FontWeight.w600)),
|
||||||
]),
|
]),
|
||||||
actions: [
|
actions: [
|
||||||
if (!lost) TextButton(onPressed: onRetry, child: Text(tr('다시 시도'))),
|
if (!lost) TextButton(onPressed: onRetry, child: Text(tr('다시 시도'))),
|
||||||
|
|
|
||||||
|
|
@ -504,11 +504,11 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
||||||
decoration: BoxDecoration(border: Border.all(color: Colors.orangeAccent), borderRadius: BorderRadius.circular(4)),
|
decoration: BoxDecoration(border: Border.all(color: JjColors.warning), borderRadius: BorderRadius.circular(4)),
|
||||||
child: Text(tr('시험'), style: const TextStyle(fontSize: 11, color: Colors.orangeAccent)),
|
child: Text(tr('시험'), style: TextStyle(fontSize: 11, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
subtitle: Text(tr('밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다')),
|
subtitle: Text(tr('밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다')),
|
||||||
trailing: DropdownButton<String>(
|
trailing: DropdownButton<String>(
|
||||||
value: s.uiBrightness,
|
value: s.uiBrightness,
|
||||||
items: [
|
items: [
|
||||||
|
|
@ -1680,7 +1680,7 @@ Future<bool> confirmReadOnlyView(BuildContext context, {required IconData icon,
|
||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (ctx, set) => AlertDialog(
|
builder: (ctx, set) => AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
icon: Icon(icon, color: Colors.orangeAccent),
|
icon: Icon(icon, color: JjColors.warning),
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(body),
|
Text(body),
|
||||||
|
|
|
||||||
|
|
@ -336,7 +336,7 @@ Future<SftpServer?> editSftpServer(BuildContext context, AppController c, {SftpS
|
||||||
),
|
),
|
||||||
if (testing) const LinearProgressIndicator(),
|
if (testing) const LinearProgressIndicator(),
|
||||||
if (result != null)
|
if (result != null)
|
||||||
Text(result!, style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)),
|
Text(result!, style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -345,7 +345,7 @@ Future<SftpServer?> editSftpServer(BuildContext context, AppController c, {SftpS
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (await deleteSftpServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
if (await deleteSftpServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.redAccent),
|
style: TextButton.styleFrom(foregroundColor: JjColors.alert),
|
||||||
child: Text(tr('지우기')),
|
child: Text(tr('지우기')),
|
||||||
),
|
),
|
||||||
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
||||||
|
|
|
||||||
|
|
@ -245,14 +245,14 @@ Future<SmbServer?> editSmbServer(BuildContext context, AppController c, {SmbServ
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (testing) const LinearProgressIndicator(),
|
if (testing) const LinearProgressIndicator(),
|
||||||
if (result != null)
|
if (result != null)
|
||||||
Text(result!, style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)),
|
Text(result!, style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)),
|
||||||
if (warning != null)
|
if (warning != null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 6),
|
padding: const EdgeInsets.only(top: 6),
|
||||||
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
const Icon(Icons.warning_amber_rounded, size: 18, color: Colors.orangeAccent),
|
Icon(Icons.warning_amber_rounded, size: 18, color: JjColors.warning),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Expanded(child: Text(warning!, style: const TextStyle(fontSize: 12, color: Colors.orangeAccent))),
|
Expanded(child: Text(warning!, style: TextStyle(fontSize: 12, color: JjColors.warning))),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
|
@ -263,7 +263,7 @@ Future<SmbServer?> editSmbServer(BuildContext context, AppController c, {SmbServ
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (await deleteSmbServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
if (await deleteSmbServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.redAccent),
|
style: TextButton.styleFrom(foregroundColor: JjColors.alert),
|
||||||
child: Text(tr('지우기')),
|
child: Text(tr('지우기')),
|
||||||
),
|
),
|
||||||
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,10 @@ class StorageTreemap extends StatelessWidget {
|
||||||
Text(b.label,
|
Text(b.label,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.isLight && b.path == null ? JjColors.text : Colors.white, fontWeight: FontWeight.w600)),
|
||||||
if (c.maxHeight >= 46)
|
if (c.maxHeight >= 46)
|
||||||
Text('${formatSize(b.size)} · ${pct.toStringAsFixed(pct < 10 ? 1 : 0)}%',
|
Text('${formatSize(b.size)} · ${pct.toStringAsFixed(pct < 10 ? 1 : 0)}%',
|
||||||
maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11, color: Colors.white70)),
|
maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 11, color: JjColors.isLight && b.path == null ? JjColors.textDim : Colors.white70)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ class _StorageAnalysisPageState extends State<StorageAnalysisPage> {
|
||||||
if (s.unreadable > 0) trf('읽지 못한 폴더 {0}개 (크기가 실제보다 작을 수 있음)', [s.unreadable]),
|
if (s.unreadable > 0) trf('읽지 못한 폴더 {0}개 (크기가 실제보다 작을 수 있음)', [s.unreadable]),
|
||||||
if (s.links > 0) trf('링크 {0}개는 세지 않음', [s.links]),
|
if (s.links > 0) trf('링크 {0}개는 세지 않음', [s.links]),
|
||||||
].join(' · '),
|
].join(' · '),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent),
|
style: TextStyle(fontSize: 12, color: JjColors.warning),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -226,7 +226,7 @@ class _StorageAnalysisPageState extends State<StorageAnalysisPage> {
|
||||||
onSecondaryTapUp: (x) => _menuAt(x.globalPosition, d),
|
onSecondaryTapUp: (x) => _menuAt(x.globalPosition, d),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: const Icon(Icons.folder, color: Colors.amber),
|
leading: Icon(Icons.folder, color: JjColors.amber),
|
||||||
title: Text(scanName(d), maxLines: 1, overflow: TextOverflow.ellipsis),
|
title: Text(scanName(d), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,7 @@ class _SearchDialogState extends State<_SearchDialog> {
|
||||||
),
|
),
|
||||||
secondary: Row(mainAxisSize: MainAxisSize.min, children: [
|
secondary: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
if (s.hashMatch) _badge(tr('이 파일용'), JjColors.success),
|
if (s.hashMatch) _badge(tr('이 파일용'), JjColors.success),
|
||||||
if (s.machineTranslated) _badge(tr('기계 번역'), Colors.amber),
|
if (s.machineTranslated) _badge(tr('기계 번역'), JjColors.amberText),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
|
||||||
if (_error != null) {
|
if (_error != null) {
|
||||||
return Row(children: [
|
return Row(children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(trf('비교하지 못했습니다: {0}', [_error!]), style: const TextStyle(fontSize: 12, color: Colors.redAccent)),
|
child: Text(trf('비교하지 못했습니다: {0}', [_error!]), style: TextStyle(fontSize: 12, color: JjColors.alert)),
|
||||||
),
|
),
|
||||||
IconButton(tooltip: tr('다시 비교'), icon: const Icon(Icons.refresh, size: 18), onPressed: _run),
|
IconButton(tooltip: tr('다시 비교'), icon: const Icon(Icons.refresh, size: 18), onPressed: _run),
|
||||||
]);
|
]);
|
||||||
|
|
@ -120,26 +120,26 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
items.isEmpty ? tr('바뀌는 것 없음 (양쪽이 같습니다)') : parts.join(' · ') + (more ? tr(' · 더 있음') : ''),
|
items.isEmpty ? tr('바뀌는 것 없음 (양쪽이 같습니다)') : parts.join(' · ') + (more ? tr(' · 더 있음') : ''),
|
||||||
style: TextStyle(fontSize: 12, color: items.isEmpty ? Colors.greenAccent : null),
|
style: TextStyle(fontSize: 12, color: items.isEmpty ? JjColors.positive : null),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(tooltip: tr('다시 비교'), icon: const Icon(Icons.refresh, size: 18), onPressed: _run),
|
IconButton(tooltip: tr('다시 비교'), icon: const Icon(Icons.refresh, size: 18), onPressed: _run),
|
||||||
]),
|
]),
|
||||||
if (dels > 0)
|
if (dels > 0)
|
||||||
Text(keepOptionsTogether(trf('받는 쪽에서 {0}개가 지워집니다 (--delete, 되돌릴 수 없음)', [dels])),
|
Text(keepOptionsTogether(trf('받는 쪽에서 {0}개가 지워집니다 (--delete, 되돌릴 수 없음)', [dels])),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)),
|
||||||
// 15 검토 F4: 받는 쪽에만 있는 받다 만 파일 (*.jjpart 등) 도 지워진다는 것을 따로
|
// 15 검토 F4: 받는 쪽에만 있는 받다 만 파일 (*.jjpart 등) 도 지워진다는 것을 따로
|
||||||
if (partials > 0)
|
if (partials > 0)
|
||||||
Text(trf('받다 만 파일 {0}개도 지워집니다', [partials]),
|
Text(trf('받다 만 파일 {0}개도 지워집니다', [partials]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.alert)),
|
||||||
// 211: 내용이 다른 같은 이름은 받는 쪽 파일이 바뀐다 - 목록의 "~" 만으로는 알아채기 어려웠다
|
// 211: 내용이 다른 같은 이름은 받는 쪽 파일이 바뀐다 - 목록의 "~" 만으로는 알아채기 어려웠다
|
||||||
if (overwrites > 0)
|
if (overwrites > 0)
|
||||||
Text(trf('받는 쪽에서 {0}개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)', [overwrites]),
|
Text(trf('받는 쪽에서 {0}개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)', [overwrites]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning, fontWeight: FontWeight.w600)),
|
||||||
// 229: 받는 쪽이 더 새 파일을 예전 원본으로 덮어쓰게 되면 따로 크게 (건너뛰기를 끄거나 rsync 에 -u 가 없을 때)
|
// 229: 받는 쪽이 더 새 파일을 예전 원본으로 덮어쓰게 되면 따로 크게 (건너뛰기를 끄거나 rsync 에 -u 가 없을 때)
|
||||||
if (overNewer > 0)
|
if (overNewer > 0)
|
||||||
Text(trf('그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다', [overNewer]),
|
Text(trf('그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다', [overNewer]),
|
||||||
style: const TextStyle(fontSize: 12.5, color: Colors.orangeAccent, fontWeight: FontWeight.w800)),
|
style: TextStyle(fontSize: 12.5, color: JjColors.warning, fontWeight: FontWeight.w800)),
|
||||||
if (items.isNotEmpty)
|
if (items.isNotEmpty)
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.only(top: 6),
|
margin: const EdgeInsets.only(top: 6),
|
||||||
|
|
@ -171,8 +171,8 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
|
||||||
fontSize: 11.5,
|
fontSize: 11.5,
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
color: switch (x.action) {
|
color: switch (x.action) {
|
||||||
PreviewAction.delete => Colors.redAccent,
|
PreviewAction.delete => JjColors.alert,
|
||||||
PreviewAction.update || PreviewAction.skipDir => Colors.orangeAccent,
|
PreviewAction.update || PreviewAction.skipDir => JjColors.warning,
|
||||||
PreviewAction.onlyTarget || PreviewAction.skipNewer || PreviewAction.skipLink => JjColors.textDim,
|
PreviewAction.onlyTarget || PreviewAction.skipNewer || PreviewAction.skipLink => JjColors.textDim,
|
||||||
_ => null,
|
_ => null,
|
||||||
},
|
},
|
||||||
|
|
@ -253,7 +253,7 @@ Future<bool> confirmDeletingRun(BuildContext context, CopyTask t, {bool includeM
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
child: Text(tr('이동: 대상에 옮긴 파일은 원본에서 지워집니다'),
|
child: Text(tr('이동: 대상에 옮긴 파일은 원본에서 지워집니다'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// 15 검토 F2: 미리 볼 수 없는 거르기 규칙 (늘 같은 자리 - 비교 결과와 상관없이 옵션으로 정해짐)
|
// 15 검토 F2: 미리 볼 수 없는 거르기 규칙 (늘 같은 자리 - 비교 결과와 상관없이 옵션으로 정해짐)
|
||||||
|
|
@ -288,7 +288,7 @@ Future<bool> confirmDeletingRun(BuildContext context, CopyTask t, {bool includeM
|
||||||
: deletes
|
: deletes
|
||||||
? tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.')
|
? tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.')
|
||||||
: tr('원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.'),
|
: tr('원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent),
|
style: TextStyle(fontSize: 12, color: JjColors.warning),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
|
@ -362,7 +362,7 @@ class OptionProblemNote extends StatelessWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
// 28 지적 (10/20): 끝 문구는 문제 종류에 맞게 (따옴표 닫기 · - 로 시작 · 값 적기 · 곧은 글자로)
|
// 28 지적 (10/20): 끝 문구는 문제 종류에 맞게 (따옴표 닫기 · - 로 시작 · 값 적기 · 곧은 글자로)
|
||||||
optionProblemText(bad),
|
optionProblemText(bad),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,7 +376,7 @@ class FilterLimitNote extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Widget build(BuildContext context) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(trf('이 옵션의 일부 거르기 규칙은 미리 볼 수 없습니다 - 실제로 더 지워질 수 있습니다: {0}', [unsupported.map(unbreakableOption).join(' · ')]),
|
Text(trf('이 옵션의 일부 거르기 규칙은 미리 볼 수 없습니다 - 실제로 더 지워질 수 있습니다: {0}', [unsupported.map(unbreakableOption).join(' · ')]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)),
|
||||||
if (deletes)
|
if (deletes)
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@ class JjPalette {
|
||||||
|
|
||||||
/// 강조색 바탕 위 글자 (버튼 등)
|
/// 강조색 바탕 위 글자 (버튼 등)
|
||||||
final Color onAccent;
|
final Color onAccent;
|
||||||
|
|
||||||
|
/// 주의 (주황) · 경고 (빨강) · 노랑 · 초록 강조 - 어두운 화면은 예전에 쓰던 Material 색 그대로 (orangeAccent · redAccent · amber · greenAccent)
|
||||||
|
final Color warning, alert, amber, positive;
|
||||||
|
|
||||||
|
/// 노랑을 글자에 쓸 때 (밝은 화면은 [amber] (아이콘 · 폴더 · 별, 대비 3:1) 보다 진하게 - 글자 대비 4.5:1)
|
||||||
|
final Color amberText;
|
||||||
const JjPalette({
|
const JjPalette({
|
||||||
required this.brightness,
|
required this.brightness,
|
||||||
required this.bg,
|
required this.bg,
|
||||||
|
|
@ -19,6 +25,11 @@ class JjPalette {
|
||||||
required this.danger,
|
required this.danger,
|
||||||
required this.success,
|
required this.success,
|
||||||
required this.onAccent,
|
required this.onAccent,
|
||||||
|
required this.warning,
|
||||||
|
required this.alert,
|
||||||
|
required this.amber,
|
||||||
|
required this.positive,
|
||||||
|
required this.amberText,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// CapCut 참고 어두운 테마 (처음 값 - 예전 그대로)
|
/// CapCut 참고 어두운 테마 (처음 값 - 예전 그대로)
|
||||||
|
|
@ -34,6 +45,11 @@ class JjPalette {
|
||||||
danger: Color(0xFFFF5C6C),
|
danger: Color(0xFFFF5C6C),
|
||||||
success: Color(0xFF3DD68C),
|
success: Color(0xFF3DD68C),
|
||||||
onAccent: Colors.black,
|
onAccent: Colors.black,
|
||||||
|
warning: Color(0xFFFFAB40),
|
||||||
|
alert: Color(0xFFFF5252),
|
||||||
|
amber: Color(0xFFFFC107),
|
||||||
|
positive: Color(0xFF69F0AE),
|
||||||
|
amberText: Color(0xFFFFC107),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// X-plore 비교 5-1 ⑦ 밝은 테마 (관리자 지시 10/21, 시험). 흰 바탕에서 글자 대비 4.5:1 이상이 되게 강조 · 경고 · 성공을 어둡게
|
/// X-plore 비교 5-1 ⑦ 밝은 테마 (관리자 지시 10/21, 시험). 흰 바탕에서 글자 대비 4.5:1 이상이 되게 강조 · 경고 · 성공을 어둡게
|
||||||
|
|
@ -49,6 +65,11 @@ class JjPalette {
|
||||||
danger: Color(0xFFC62833),
|
danger: Color(0xFFC62833),
|
||||||
success: Color(0xFF186B42),
|
success: Color(0xFF186B42),
|
||||||
onAccent: Colors.white,
|
onAccent: Colors.white,
|
||||||
|
warning: Color(0xFFA14A00),
|
||||||
|
alert: Color(0xFFC62828),
|
||||||
|
amber: Color(0xFFB07800),
|
||||||
|
positive: Color(0xFF186B42),
|
||||||
|
amberText: Color(0xFF7A5900),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,6 +100,11 @@ class JjColors {
|
||||||
static Color get danger => _p.danger;
|
static Color get danger => _p.danger;
|
||||||
static Color get success => _p.success;
|
static Color get success => _p.success;
|
||||||
static Color get onAccent => _p.onAccent;
|
static Color get onAccent => _p.onAccent;
|
||||||
|
static Color get warning => _p.warning;
|
||||||
|
static Color get alert => _p.alert;
|
||||||
|
static Color get amber => _p.amber;
|
||||||
|
static Color get positive => _p.positive;
|
||||||
|
static Color get amberText => _p.amberText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 색을 바꾼 뒤 모든 화면 조각을 다시 그린다 (상태는 그대로 - [JjColors] 를 직접 읽는 곳도 새 색으로)
|
/// 색을 바꾼 뒤 모든 화면 조각을 다시 그린다 (상태는 그대로 - [JjColors] 를 직접 읽는 곳도 새 색으로)
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ class _TrashPageState extends State<TrashPage> {
|
||||||
value: on,
|
value: on,
|
||||||
onChanged: (v) => setState(() => v == true ? _picked.add(i.entryDir) : _picked.remove(i.entryDir)),
|
onChanged: (v) => setState(() => v == true ? _picked.add(i.entryDir) : _picked.remove(i.entryDir)),
|
||||||
secondary: Icon(i.isDir ? Icons.folder : Icons.insert_drive_file_outlined,
|
secondary: Icon(i.isDir ? Icons.folder : Icons.insert_drive_file_outlined,
|
||||||
color: i.isDir ? Colors.amber : null),
|
color: i.isDir ? JjColors.amber : null),
|
||||||
title: Text(i.name, maxLines: 1, overflow: TextOverflow.ellipsis),
|
title: Text(i.name, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
subtitle: Text('${i.original}\n${_date(i.deletedAt)} · ${formatSize(i.size)}',
|
subtitle: Text('${i.original}\n${_date(i.deletedAt)} · ${formatSize(i.size)}',
|
||||||
maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)),
|
maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)),
|
||||||
|
|
|
||||||
|
|
@ -230,8 +230,8 @@ class _VersionPickerState extends State<_VersionPicker> {
|
||||||
title: Row(children: [
|
title: Row(children: [
|
||||||
Text('v${r.version}', style: const TextStyle(fontWeight: FontWeight.w600)),
|
Text('v${r.version}', style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||||
if (r.version == widget.current) _Badge(tr('지금'), JjColors.accent),
|
if (r.version == widget.current) _Badge(tr('지금'), JjColors.accent),
|
||||||
if (r.version == newest) _Badge(tr('최신'), Colors.greenAccent),
|
if (r.version == newest) _Badge(tr('최신'), JjColors.positive),
|
||||||
if (r.prerelease) _Badge(tr('시험판'), Colors.orangeAccent),
|
if (r.prerelease) _Badge(tr('시험판'), JjColors.warning),
|
||||||
]),
|
]),
|
||||||
subtitle: Text(r.zipUrl == null ? '${_date(r.published)} · ${tr('이 기기용 파일 없음')}' : _date(r.published),
|
subtitle: Text(r.zipUrl == null ? '${_date(r.published)} · ${tr('이 기기용 파일 없음')}' : _date(r.published),
|
||||||
style: const TextStyle(fontSize: 11)),
|
style: const TextStyle(fontSize: 11)),
|
||||||
|
|
@ -255,7 +255,7 @@ class _VersionPickerState extends State<_VersionPicker> {
|
||||||
tr('Android 에서는 앱을 지운 뒤 예전 버전을 설치합니다. 받은 AI 모델 · 비밀번호 · 브라우저 로그인 등은 '
|
tr('Android 에서는 앱을 지운 뒤 예전 버전을 설치합니다. 받은 AI 모델 · 비밀번호 · 브라우저 로그인 등은 '
|
||||||
'사라집니다 (남는 것과 사라지는 것은 다음 창에서 보여 줍니다).'),
|
'사라집니다 (남는 것과 사라지는 것은 다음 창에서 보여 줍니다).'),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(tr('바뀐 내용'), style: const TextStyle(fontWeight: FontWeight.w600)),
|
Text(tr('바뀐 내용'), style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||||
|
|
@ -547,8 +547,8 @@ Future<void> _androidRollback(BuildContext context, AppController c, Updater up,
|
||||||
Text(tr('남는 것'), style: const TextStyle(fontWeight: FontWeight.w600)),
|
Text(tr('남는 것'), style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||||
for (final x in kept) Text('· $x'),
|
for (final x in kept) Text('· $x'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(tr('사라지는 것'), style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.orangeAccent)),
|
Text(tr('사라지는 것'), style: TextStyle(fontWeight: FontWeight.w600, color: JjColors.warning)),
|
||||||
for (final x in lost) Text('· $x', style: const TextStyle(color: Colors.orangeAccent)),
|
for (final x in lost) Text('· $x', style: TextStyle(color: JjColors.warning)),
|
||||||
if (modelSize > 0) ...[
|
if (modelSize > 0) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
|
@ -569,7 +569,7 @@ Future<void> _androidRollback(BuildContext context, AppController c, Updater up,
|
||||||
],
|
],
|
||||||
if (!targetRestoresSettings)
|
if (!targetRestoresSettings)
|
||||||
Text(trf('v{0} 은 저장해 둔 설정을 되살리지 못해 설정을 다시 해야 합니다.', [r.version]),
|
Text(trf('v{0} 은 저장해 둔 설정을 되살리지 못해 설정을 다시 해야 합니다.', [r.version]),
|
||||||
style: const TextStyle(color: Colors.orangeAccent)),
|
style: TextStyle(color: JjColors.warning)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -598,7 +598,7 @@ Future<void> _androidRollback(BuildContext context, AppController c, Updater up,
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
|
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
style: FilledButton.styleFrom(backgroundColor: Colors.redAccent),
|
style: FilledButton.styleFrom(backgroundColor: JjColors.alert),
|
||||||
onPressed: () => Navigator.pop(ctx, true),
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
child: Text(tr('되돌리기 계속')),
|
child: Text(tr('되돌리기 계속')),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,7 @@ class _VaultPageState extends State<VaultPage> with WidgetsBindingObserver {
|
||||||
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||||
for (final f in plan.failed.entries.take(3))
|
for (final f in plan.failed.entries.take(3))
|
||||||
Text(trf('읽지 못해 빼고 넣습니다: {0} ({1})', [vBasename(f.key), f.value]),
|
Text(trf('읽지 못해 빼고 넣습니다: {0} ({1})', [vBasename(f.key), f.value]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), maxLines: 2, overflow: TextOverflow.ellipsis),
|
style: TextStyle(fontSize: 12, color: JjColors.warning), maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
|
|
@ -358,7 +358,7 @@ class _VaultPageState extends State<VaultPage> with WidgetsBindingObserver {
|
||||||
),
|
),
|
||||||
if (move) ...[
|
if (move) ...[
|
||||||
Text(tr('원본은 휴지통으로 갑니다 - 휴지통에 평문 (암호화하지 않은 원본) 이 남습니다'),
|
Text(tr('원본은 휴지통으로 갑니다 - 휴지통에 평문 (암호화하지 않은 원본) 이 남습니다'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
RadioGroup<bool>(
|
RadioGroup<bool>(
|
||||||
groupValue: permanent,
|
groupValue: permanent,
|
||||||
onChanged: (v) => set(() => permanent = v ?? false),
|
onChanged: (v) => set(() => permanent = v ?? false),
|
||||||
|
|
|
||||||
|
|
@ -174,13 +174,13 @@ Future<DavServer?> editDavServer(BuildContext context, AppController c, {DavServ
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 6),
|
padding: const EdgeInsets.only(top: 6),
|
||||||
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
const Icon(Icons.warning_amber_rounded, size: 18, color: Colors.orangeAccent),
|
Icon(Icons.warning_amber_rounded, size: 18, color: JjColors.warning),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
tr('http:// 주소는 아이디 · 비밀번호와 파일이 암호화되지 않고 그대로 갑니다. 같은 네트워크의 다른 사람이 볼 수 있으니 '
|
tr('http:// 주소는 아이디 · 비밀번호와 파일이 암호화되지 않고 그대로 갑니다. 같은 네트워크의 다른 사람이 볼 수 있으니 '
|
||||||
'서버가 https 를 지원하면 https:// 로 바꾸세요.'),
|
'서버가 https 를 지원하면 https:// 로 바꾸세요.'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
|
style: TextStyle(fontSize: 12, color: JjColors.warning)),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
@ -208,7 +208,7 @@ Future<DavServer?> editDavServer(BuildContext context, AppController c, {DavServ
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(result!,
|
child: Text(result!,
|
||||||
style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)),
|
style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)),
|
||||||
),
|
),
|
||||||
if (toHttps)
|
if (toHttps)
|
||||||
Align(
|
Align(
|
||||||
|
|
@ -232,7 +232,7 @@ Future<DavServer?> editDavServer(BuildContext context, AppController c, {DavServ
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (await deleteDavServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
if (await deleteDavServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.redAccent),
|
style: TextButton.styleFrom(foregroundColor: JjColors.alert),
|
||||||
child: Text(tr('지우기')),
|
child: Text(tr('지우기')),
|
||||||
),
|
),
|
||||||
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))),
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ void main() {
|
||||||
expect(JjColors.bg, const Color(0xFF141417));
|
expect(JjColors.bg, const Color(0xFF141417));
|
||||||
expect(JjColors.accent, const Color(0xFF00C4CC));
|
expect(JjColors.accent, const Color(0xFF00C4CC));
|
||||||
expect(JjColors.textDim, const Color(0xFF8E8E99));
|
expect(JjColors.textDim, const Color(0xFF8E8E99));
|
||||||
|
// 예전에 쓰던 Material 색 그대로 (어두운 화면이 바뀌지 않게)
|
||||||
|
expect([JjColors.warning, JjColors.alert, JjColors.amber, JjColors.amberText, JjColors.positive], [Colors.orangeAccent, Colors.redAccent, Colors.amber, Colors.amber, Colors.greenAccent].map((c) => Color(c.toARGB32())).toList());
|
||||||
expect(resolveBrightness('dark', Brightness.light), Brightness.dark);
|
expect(resolveBrightness('dark', Brightness.light), Brightness.dark);
|
||||||
expect(resolveBrightness('light', Brightness.dark), Brightness.light);
|
expect(resolveBrightness('light', Brightness.dark), Brightness.light);
|
||||||
expect(resolveBrightness('system', Brightness.light), Brightness.light);
|
expect(resolveBrightness('system', Brightness.light), Brightness.light);
|
||||||
|
|
@ -58,11 +60,15 @@ void main() {
|
||||||
test('밝은 색: 바탕 · 칸 위 글자 대비 4.5:1 이상 (글 · 흐린 글 · 강조 · 경고 · 성공 · 강조 위 글자)', () {
|
test('밝은 색: 바탕 · 칸 위 글자 대비 4.5:1 이상 (글 · 흐린 글 · 강조 · 경고 · 성공 · 강조 위 글자)', () {
|
||||||
const l = JjPalette.light;
|
const l = JjPalette.light;
|
||||||
for (final bg in [l.bg, l.panel, l.panelHigh]) {
|
for (final bg in [l.bg, l.panel, l.panelHigh]) {
|
||||||
for (final (name, fg) in [('text', l.text), ('textDim', l.textDim), ('accent', l.accent), ('danger', l.danger), ('success', l.success)]) {
|
for (final (name, fg) in [('text', l.text), ('textDim', l.textDim), ('accent', l.accent), ('danger', l.danger), ('success', l.success), ('warning', l.warning), ('alert', l.alert), ('amberText', l.amberText), ('positive', l.positive)]) {
|
||||||
expect(contrast(fg, bg), greaterThanOrEqualTo(4.5), reason: '$name on ${bg.toARGB32().toRadixString(16)}');
|
expect(contrast(fg, bg), greaterThanOrEqualTo(4.5), reason: '$name on ${bg.toARGB32().toRadixString(16)}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(contrast(l.onAccent, l.accent), greaterThanOrEqualTo(4.5));
|
expect(contrast(l.onAccent, l.accent), greaterThanOrEqualTo(4.5));
|
||||||
|
// 아이콘 (폴더 · 별) 은 3:1
|
||||||
|
for (final bg in [l.bg, l.panel, l.panelHigh]) {
|
||||||
|
expect(contrast(l.amber, bg), greaterThanOrEqualTo(3));
|
||||||
|
}
|
||||||
expect(buildTheme(Brightness.light).brightness, Brightness.light);
|
expect(buildTheme(Brightness.light).brightness, Brightness.light);
|
||||||
expect(buildTheme(Brightness.light).scaffoldBackgroundColor, l.bg);
|
expect(buildTheme(Brightness.light).scaffoldBackgroundColor, l.bg);
|
||||||
expect(buildTheme(Brightness.dark).scaffoldBackgroundColor, JjPalette.dark.bg);
|
expect(buildTheme(Brightness.dark).scaffoldBackgroundColor, JjPalette.dark.bg);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue