426 lines
26 KiB
Dart
426 lines
26 KiB
Dart
import 'dart:async';
|
||
import 'dart:io';
|
||
import 'package:flutter/material.dart';
|
||
import '../../app/app_controller.dart';
|
||
import '../../core/file_ops.dart' show formatSize;
|
||
import '../../app/settings.dart';
|
||
import '../copy_sync_settings.dart';
|
||
import '../sftp_settings.dart';
|
||
import '../ftp_settings.dart';
|
||
import '../smb_settings.dart';
|
||
import '../webdav_settings.dart';
|
||
import '../explorer_look.dart' show ExplorerStyle;
|
||
import '../explorer_keys_page.dart';
|
||
import '../../core/shizuku.dart' show rootSupported, shizukuSupported;
|
||
import '../theme.dart';
|
||
import '../../l10n/tr.dart';
|
||
import '../setting_tile.dart';
|
||
import '../component_settings.dart';
|
||
import '../remote_thumb_view.dart' show remoteThumbsOf;
|
||
import '../hidden_items_dialog.dart';
|
||
import '../../core/remote_idle.dart';
|
||
|
||
// 약속 (설정 B): 환경 설정의 분야 - 파일 탐색기. 바꾸면 바로 설정에 저장 (c.updateSettings)
|
||
|
||
/// 환경 설정 > 파일 탐색기
|
||
class FilesSettingsSection extends StatefulWidget {
|
||
const FilesSettingsSection({super.key, required this.c, required this.desk});
|
||
|
||
final AppController c;
|
||
|
||
/// PC (Android 가 아님) - 창 · 트레이 · 탐색기 · 다운로드 · 내장 Chrome 설정
|
||
final bool desk;
|
||
|
||
@override
|
||
State<FilesSettingsSection> createState() => _FilesSettingsSectionState();
|
||
}
|
||
|
||
class _FilesSettingsSectionState extends State<FilesSettingsSection> {
|
||
AppController get c => widget.c;
|
||
bool get desk => widget.desk;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final s = c.settings;
|
||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [
|
||
if (desk)
|
||
SwitchListTile(
|
||
value: s.general.recycleOnDelete,
|
||
onChanged: (v) => c.updateSettings((x) => x.general.recycleOnDelete = v),
|
||
title: Text(tr('지우면 휴지통으로')),
|
||
subtitle: Text(tr('켜면 휴지통으로 보내 되살릴 수 있습니다 (Shift+Delete 는 영구 삭제). 끄면 늘 영구 삭제로 묻습니다. '
|
||
'네트워크 드라이브 · USB 메모리는 휴지통이 없어 늘 영구 삭제입니다')),
|
||
),
|
||
SettingTile(
|
||
title: Text(tr('누르기')),
|
||
subtitle: Text(tr('길게 누르기 · 오른쪽 클릭은 늘 기능 메뉴 (복사 · 이동 · 삭제 · 이름 변경 …). '
|
||
'Ctrl + 클릭: 여러 개 고르기')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.click,
|
||
items: [
|
||
DropdownMenuItem(
|
||
value: 'select', child: Text(desk ? tr('한 번: 선택 · 두 번: 실행 (기본)') : tr('한 번: 선택 · 두 번: 실행'))),
|
||
DropdownMenuItem(value: 'open', child: Text(desk ? tr('한 번: 바로 실행') : tr('한 번: 바로 실행 (기본)'))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.click = v!),
|
||
),
|
||
),
|
||
// 82: 창 사이 끌어 놓기 (처음: 묻기)
|
||
SettingTile(
|
||
title: Text(tr('끌어 놓기 기본')),
|
||
subtitle: Text(tr('다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.dropAction,
|
||
items: [
|
||
DropdownMenuItem(value: 'ask', child: Text(tr('묻기 (기본)'))),
|
||
DropdownMenuItem(value: 'copy', child: Text(tr('복사'))),
|
||
DropdownMenuItem(value: 'move', child: Text(tr('이동'))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.dropAction = v ?? 'ask'),
|
||
),
|
||
),
|
||
// 84: 정렬을 폴더마다 기억 (처음: 끔)
|
||
SettingTile(
|
||
title: Text(tr('정렬을 폴더마다 기억')),
|
||
subtitle: Text(tr('켜면 열 머리 · 정렬 기준에서 바꾼 정렬이 그 폴더에만 쓰입니다. 끄면 모든 폴더가 같은 정렬입니다')),
|
||
trailing: Switch(
|
||
value: s.explorer.sortPerFolder,
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.sortPerFolder = v),
|
||
),
|
||
),
|
||
// X-plore 비교 5-2 (사용자 결정 10/21): 시스템 폴더 보기 (Shizuku, 읽기 전용) - Android, 켤 때마다 경고
|
||
if (shizukuSupported())
|
||
SettingTile(
|
||
title: Text(tr('시스템 폴더 보기 (Shizuku · 읽기 전용)')),
|
||
subtitle: Text(tr('Shizuku 앱이 있으면 Android/data · obb 처럼 막힌 폴더를 읽기만 합니다. 탐색기 위 탭 [시스템 (읽기 전용)]')),
|
||
trailing: Switch(
|
||
value: s.tools.shizukuEnabled,
|
||
onChanged: (v) async {
|
||
if (v &&
|
||
!await confirmReadOnlyView(context,
|
||
icon: Icons.admin_panel_settings_outlined,
|
||
title: tr('시스템 폴더 보기를 켤까요?'),
|
||
body: tr('Shizuku (adb 권한) 로 다른 앱의 폴더 (Android/data · obb) 를 볼 수 있게 됩니다. 그 안에는 다른 앱의 '
|
||
'대화 · 로그인 정보 같은 개인 데이터가 있을 수 있습니다.'))) {
|
||
return;
|
||
}
|
||
await c.updateSettings((x) => x.tools.shizukuEnabled = v);
|
||
},
|
||
),
|
||
),
|
||
// X-plore 비교 5-2 (관리자 결정 10/21): 루트 폴더 보기 (su, 읽기 전용) - Shizuku 와 같은 틀
|
||
if (rootSupported())
|
||
SettingTile(
|
||
title: Text(tr('루트 폴더 보기 (su · 읽기 전용)')),
|
||
subtitle: Text(tr('루팅한 기기에서 su 로 시스템 · 다른 앱의 폴더를 읽기만 합니다. 탐색기 위 탭 [루트 (읽기 전용)]')),
|
||
trailing: Switch(
|
||
value: s.tools.rootEnabled,
|
||
onChanged: (v) async {
|
||
if (v &&
|
||
!await confirmReadOnlyView(context,
|
||
icon: Icons.security,
|
||
title: tr('루트 폴더 보기를 켤까요?'),
|
||
body: tr('루트 (su) 권한으로 시스템 · 다른 앱의 폴더 전체 (/data 등) 를 볼 수 있게 됩니다. 다른 앱의 개인 자료 '
|
||
'(대화 · 로그인 정보 · 비밀번호 등) 도 보일 수 있습니다.'))) {
|
||
return;
|
||
}
|
||
await c.updateSettings((x) => x.tools.rootEnabled = v);
|
||
},
|
||
),
|
||
),
|
||
// X-plore 비교 5-1 ① (관리자 지시 10/21): 종류별 기본 정렬 (처음: 끔)
|
||
SettingTile(
|
||
title: Text(tr('종류별 기본 정렬')),
|
||
subtitle: Text(tr('사진 · 동영상이 많은 폴더는 날짜 (새것 먼저), 음악이 많은 폴더는 이름으로. 그 폴더에서 정렬을 바꾸면 그 폴더는 바꾼 정렬을 기억합니다')),
|
||
trailing: Switch(
|
||
value: s.explorer.sortByKind,
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.sortByKind = v),
|
||
),
|
||
),
|
||
// X-plore 비교 5-1 ② (관리자 지시 10/21): 원격 썸네일 · 캐시
|
||
SettingTile(
|
||
title: Text(tr('원격 썸네일')),
|
||
subtitle: Text(tr('WebDAV · SFTP · SMB · FTP 의 그림 · 동영상을 X-plore 모양 목록에서 작은 그림으로. 보이는 줄만, 동시에 2개까지 받습니다')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.remoteThumbs,
|
||
items: [
|
||
DropdownMenuItem(value: 'off', child: Text(tr('받지 않음'))),
|
||
DropdownMenuItem(value: 'wifi', child: Text(tr('Wi-Fi 에서만 (기본)'))),
|
||
DropdownMenuItem(value: 'always', child: Text(tr('늘 (모바일 데이터에서도)'))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.remoteThumbs = v!),
|
||
),
|
||
),
|
||
SettingTile(
|
||
title: Text(tr('원격 썸네일 캐시')),
|
||
subtitle: Text(tr('넘으면 오래 보지 않은 것부터 지웁니다')),
|
||
trailing: DropdownButton<int>(
|
||
value: s.explorer.remoteThumbCacheMb,
|
||
items: [
|
||
for (final v in {...ExplorerSettings.remoteThumbCacheChoices, s.explorer.remoteThumbCacheMb}.toList()..sort())
|
||
DropdownMenuItem(value: v, child: Text(v == 200 ? trf('{0}MB (기본)', [v]) : '${v}MB')),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.remoteThumbCacheMb = v!),
|
||
),
|
||
),
|
||
RemoteThumbCacheTile(c: c),
|
||
// 80: 폴더 크기 (필요할 때 계산하고 이번 실행 동안 기억)
|
||
SettingTile(
|
||
title: Text(tr('폴더 크기')),
|
||
subtitle: Text(tr('폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. '
|
||
'길게 누르기 [폴더 크기 계산] 은 늘 됩니다')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.folderSize,
|
||
items: [
|
||
DropdownMenuItem(value: 'sort', child: Text(tr('크기순 정렬일 때만 (기본)'))),
|
||
DropdownMenuItem(value: 'always', child: Text(tr('늘 계산'))),
|
||
DropdownMenuItem(value: 'off', child: Text(tr('계산 안 함'))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.folderSize = v ?? 'sort'),
|
||
),
|
||
),
|
||
// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 단축키 바꾸기 (키보드가 있는 PC 에서만 보임)
|
||
if (desk)
|
||
ListTile(
|
||
title: Text(tr('파일 탐색기 단축키')),
|
||
subtitle: Text(s.explorer.keyOverrides.isEmpty
|
||
? tr('스타일대로 (바꾼 것 없음)')
|
||
: trf('바꾼 것 {0}개', [s.explorer.keyOverrides.length])),
|
||
trailing: const Icon(Icons.chevron_right),
|
||
onTap: () => ExplorerKeysPage.open(context, c),
|
||
),
|
||
SettingTile(
|
||
title: Text(tr('스타일')),
|
||
// 83: 스타일마다 단축키 - PC 에서만 (194: 키보드가 없는 폰 · 태블릿에는 쓸모없는 긴 글, 15 지적)
|
||
subtitle: Text([
|
||
tr('아이콘 · 목록 모양: X-plore (썸네일 · 두 줄) · Windows 탐색기 (컬러 아이콘 · 열) · '
|
||
'Total Commander (촘촘한 목록 · [폴더])'),
|
||
if (desk)
|
||
tr('단축키 - Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침. '
|
||
'그 밖: Ctrl+C · X · V (복사 · 잘라내기 · 붙여넣기) · Ctrl+A 모두 표시 · F5 새로 고침'),
|
||
].join('\n')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.style,
|
||
items: [
|
||
for (final st in ExplorerStyle.values)
|
||
DropdownMenuItem(value: st.name, child: Text(tr(st.label))),
|
||
],
|
||
// 직접 골랐으면 예전 모양을 묻지 않는다 (E6)
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer
|
||
..style = v!
|
||
..styleAskPending = false),
|
||
),
|
||
),
|
||
SettingTile(
|
||
title: Text(tr('창 배치')),
|
||
subtitle: Text(tr('파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.layout,
|
||
items: [
|
||
DropdownMenuItem(value: 'auto', child: Text(tr('화면 크기 따라'))),
|
||
DropdownMenuItem(value: 'dual', child: Text(tr('두 창 (기본)'))),
|
||
DropdownMenuItem(value: 'split', child: Text(tr('폴더 + 파일 목록'))),
|
||
DropdownMenuItem(value: 'single', child: Text(tr('한 창'))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.layout = v!),
|
||
),
|
||
),
|
||
// X-plore 비교 e: Android 앱 휴지통 보관 일
|
||
if (Platform.isAndroid)
|
||
SettingTile(
|
||
title: Text(tr('휴지통 보관')),
|
||
subtitle: Text(tr('지운 것을 저장 장치의 앱 휴지통에 두었다가 이 날이 지나면 지웁니다. 끄면 바로 영구 삭제')),
|
||
trailing: DropdownButton<int>(
|
||
value: const [0, 3, 7, 14, 30, 60].contains(s.explorer.androidTrashDays) ? s.explorer.androidTrashDays : 14,
|
||
items: [
|
||
for (final d in const [0, 3, 7, 14, 30, 60])
|
||
DropdownMenuItem(value: d, child: Text(d == 0 ? tr('끔') : trf('{0}일', [d]))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.androidTrashDays = v!),
|
||
),
|
||
),
|
||
// X-plore 비교 ⚖ (관리자 결정): X-plore 스타일의 표시 동그라미 · 밀기 · 폴더 표시 규칙
|
||
SwitchListTile(
|
||
title: Text(tr('X-plore 스타일: 선택 표시 늘 보이기')),
|
||
subtitle: Text(tr('끄면 (처음 값) [선택] 을 켰을 때만 보입니다. 다른 스타일은 늘 선택 모드에서만')),
|
||
value: s.explorer.alwaysMarks,
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.alwaysMarks = v),
|
||
),
|
||
SwitchListTile(
|
||
title: Text(tr('X-plore 스타일: 옆으로 밀면 창 바꾸기')),
|
||
subtitle: Text(tr('탐색기 안에서 밀면 다른 창으로. 다른 화면으로 가려면 위 막대에서 밉니다')),
|
||
value: s.explorer.swipePanes,
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.swipePanes = v),
|
||
),
|
||
// X-plore 비교 c: 저장 장치 · 서버 · 즐겨찾기를 트리 맨 위에 (X-plore 스타일)
|
||
SwitchListTile(
|
||
title: Text(tr('저장 장치 · 서버 · 즐겨찾기를 트리 맨 위에')),
|
||
subtitle: Text(tr('X-plore 스타일에서 X-plore 처럼 한 트리에 보입니다 (위 줄 탭 · ★ 는 그대로)')),
|
||
value: s.explorer.rootsInTree,
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.rootsInTree = v),
|
||
),
|
||
// X-plore 비교 d: 지금 폴더를 다시 누르면 접기
|
||
SwitchListTile(
|
||
title: Text(tr('펼친 폴더를 다시 누르면 접기')),
|
||
subtitle: Text(tr('처음 값 켬. 끄면 접기 · 펼치기는 › 화살표로만 (두 번 눌러도 목록이 움직이지 않음)')),
|
||
value: s.explorer.retapCollapse,
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.retapCollapse = v),
|
||
),
|
||
// X-plore 비교 40 (관리자 지시 10/20): 목록만의 글자 크기 · 줄 간격 (세 스타일 모두, 앱 화면 크기 · 기기 글자 크기와 곱함)
|
||
SettingTile(
|
||
title: Text(tr('목록 글자 크기')),
|
||
subtitle: Text(tr('파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다')),
|
||
trailing: DropdownButton<int>(
|
||
value: s.explorer.textScale,
|
||
items: [
|
||
for (var v = ExplorerSettings.textScaleMin; v <= ExplorerSettings.textScaleMax; v += 10)
|
||
DropdownMenuItem(value: v, child: Text(v == 100 ? trf('{0}% (기본)', [v]) : '$v%')),
|
||
if (s.explorer.textScale % 10 != 0)
|
||
DropdownMenuItem(value: s.explorer.textScale, child: Text('${s.explorer.textScale}%')),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.textScale = v!),
|
||
),
|
||
),
|
||
SettingTile(
|
||
title: Text(tr('줄 간격')),
|
||
subtitle: Text(tr('파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.rowSpacing,
|
||
items: [
|
||
DropdownMenuItem(value: 'compact', child: Text(tr('좁게'))),
|
||
DropdownMenuItem(value: 'normal', child: Text(tr('보통 (기본)'))),
|
||
DropdownMenuItem(value: 'wide', child: Text(tr('넓게'))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.rowSpacing = v!),
|
||
),
|
||
),
|
||
// X-plore 숨기기: 목록에서 숨긴 것 보기 · 풀기 (관리자 결정 10/18)
|
||
ListTile(
|
||
title: Text(trf('목록에서 숨긴 항목 ({0}개)', [s.explorer.hiddenItems.length])),
|
||
subtitle: Text(tr('파일 탐색기의 길게 누르기 [목록에서 숨기기] 로 숨긴 것. 파일은 그대로이고 목록에서만 빠집니다')),
|
||
trailing: const Icon(Icons.chevron_right),
|
||
enabled: s.explorer.hiddenItems.isNotEmpty,
|
||
onTap: () => showHiddenItems(context, c),
|
||
),
|
||
// X-plore 맞추기 (관리자 지시 10/20): 숨기기 전 확인 창
|
||
SwitchListTile(
|
||
title: Text(tr('목록에서 숨기기 전에 묻기')),
|
||
subtitle: Text(tr('무엇이 바뀌는지 (목록에서만 빠지고 파일은 그대로) 확인 창을 띄웁니다')),
|
||
value: s.explorer.hideConfirm,
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.hideConfirm = v),
|
||
),
|
||
// X-plore 비교 a: 한 창일 때 복사 · 이동이 갈 곳
|
||
SettingTile(
|
||
title: Text(tr('한 창일 때 복사 · 이동')),
|
||
subtitle: Text(tr('숨은 다른 창으로: X-plore 처럼 위에 "갈 곳" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다')),
|
||
trailing: DropdownButton<String>(
|
||
value: s.explorer.onePaneCopy,
|
||
items: [
|
||
DropdownMenuItem(value: 'clip', child: Text(tr('담아 두고 붙여넣기 (기본)'))),
|
||
DropdownMenuItem(value: 'other', child: Text(tr('숨은 다른 창으로'))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.explorer.onePaneCopy = v!),
|
||
),
|
||
),
|
||
// 복사 · 이동 방법 (현재 방식 · robocopy) · 속도 제한
|
||
CopySyncSettings(c: c),
|
||
// WebDAV 서버 (탐색기 · Rsync 화면 위쪽 탭)
|
||
WebDavSettings(c: c),
|
||
// 88: SFTP 서버 (WebDAV 옆 탭)
|
||
SftpSettings(c: c),
|
||
// l: FTP · FTPS 서버
|
||
FtpSettings(c: c),
|
||
// 88: SMB (Android 만 - Windows 는 운영체제가 \\서버\공유 를 연다)
|
||
if (Platform.isAndroid) SmbSettings(c: c),
|
||
// 관리자 판단 (10/20): 원격 서버가 멈췄을 때 기다리는 시간
|
||
SettingTile(
|
||
leading: const Icon(Icons.hourglass_bottom_outlined),
|
||
title: Text(tr('원격 서버 응답 기다리기')),
|
||
subtitle: Text(tr('WebDAV · SFTP · FTP · SMB 에서 받거나 올리는 중에 이 시간 동안 아무것도 오가지 않으면 실패로 알립니다 (서버가 멈춰도 계속 기다리지 않게)')),
|
||
trailing: DropdownButton<int>(
|
||
value: RemoteIdle.choices.contains(s.remote.idleSeconds) ? s.remote.idleSeconds : RemoteIdle.defaultSeconds,
|
||
items: [
|
||
for (final n in RemoteIdle.choices)
|
||
DropdownMenuItem(value: n, child: Text(n == RemoteIdle.defaultSeconds ? trf('{0}초 (기본)', [n]) : trf('{0}초', [n]))),
|
||
],
|
||
onChanged: (v) => c.updateSettings((x) => x.remote.idleSeconds = v!),
|
||
),
|
||
),
|
||
ViewerSettings(c: c),
|
||
]);
|
||
}
|
||
}
|
||
|
||
/// X-plore 비교 5-1 ② (관리자 지시 10/21): 원격 썸네일 캐시의 크기 · [캐시 지우기] (다시 보면 다시 받음)
|
||
class RemoteThumbCacheTile extends StatefulWidget {
|
||
final AppController c;
|
||
const RemoteThumbCacheTile({super.key, required this.c});
|
||
|
||
@override
|
||
State<RemoteThumbCacheTile> createState() => _RemoteThumbCacheTileState();
|
||
}
|
||
|
||
class _RemoteThumbCacheTileState extends State<RemoteThumbCacheTile> {
|
||
int? _bytes;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
unawaited(_measure());
|
||
}
|
||
|
||
Future<void> _measure() async {
|
||
final n = await (await remoteThumbsOf(widget.c)).size();
|
||
if (mounted) setState(() => _bytes = n);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) => ListTile(
|
||
title: Text(tr('원격 썸네일 캐시 지우기')),
|
||
subtitle: Text(_bytes == null ? tr('크기를 재는 중 …') : trf('지금 {0} · 다시 보면 다시 받습니다', [formatSize(_bytes!)])),
|
||
trailing: TextButton(
|
||
onPressed: _bytes == null || _bytes == 0
|
||
? null
|
||
: () async {
|
||
await (await remoteThumbsOf(widget.c)).clear();
|
||
await _measure();
|
||
},
|
||
child: Text(tr('캐시 지우기')),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// X-plore 비교 5-2: 시스템 폴더 · 루트 보기를 켤 때 경고 (체크해야 [켜기]). 켜면 true
|
||
Future<bool> confirmReadOnlyView(BuildContext context, {required IconData icon, required String title, required String body}) async {
|
||
final ok = await showDialog<bool>(
|
||
context: context,
|
||
builder: (ctx) {
|
||
var sure = false;
|
||
return StatefulBuilder(
|
||
builder: (ctx, set) => AlertDialog(
|
||
scrollable: true,
|
||
icon: Icon(icon, color: JjColors.warning),
|
||
title: Text(title),
|
||
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||
Text(body),
|
||
const SizedBox(height: 6),
|
||
Text(tr('이 앱은 읽기만 합니다 - 쓰기 · 지우기 · 이름 바꾸기 · 권한 바꾸기는 하지 않습니다. 꺼낸 (복사한) 파일은 고른 곳에만 둡니다.')),
|
||
CheckboxListTile(
|
||
contentPadding: EdgeInsets.zero,
|
||
controlAffinity: ListTileControlAffinity.leading,
|
||
value: sure,
|
||
onChanged: (x) => set(() => sure = x ?? false),
|
||
title: Text(tr('위 내용을 알고 켭니다')),
|
||
),
|
||
]),
|
||
actions: [
|
||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))),
|
||
FilledButton(onPressed: sure ? () => Navigator.pop(ctx, true) : null, child: Text(tr('켜기'))),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
return ok == true;
|
||
}
|