rc2: 경로 줄은 좁으면 앞을 "…" 로 - 지금 폴더가 늘 보이게 (보관함 · 탐색기 위 경로 막대) · 보관함 정렬 상자는 다른 줄로 (28 지적, 관리자 지시 10/21)
- 보관함 깊은 폴더 (360 · 글자 1.3) 에서 "보관함 › 가족 › 2026 › 여름 휴가…" 로 끝이 잘려 지금 폴더가 안 보였고 정렬 상자와 붙어 두 줄로 눌렸다 - TailText (lib/ui/tail_text.dart): 들어가면 그대로, 모자라면 구분 ( › · \ · / ) 에서 앞을 잘라 "… › 여름 휴가 › 제주도 둘째 날", 마지막 조각도 길면 그 끝을 글자 단위로. 지금 글꼴 · 글자 배율로 잼 - 보관함: 폴더 안에서는 경로 줄 (TailText) 과 정렬 상자를 다른 줄로. 맨 위 (안내 글 + 정렬) 는 예전 그대로 - 파일 탐색기 위 경로 막대도 같은 문제 (끝이 "…" 로 잘림) 라 TailText 로 - 다 들어가는 경로는 예전 그대로 - 시험 tail_text_test (자르는 자리 · 1.3 배 좁은 칸에서 지금 폴더가 보임), vault_page · explorer 시험 그대로 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>rc2
parent
c65f4851f7
commit
e785ba96a6
|
|
@ -58,6 +58,7 @@ import '../l10n/tr.dart';
|
|||
import 'explorer_bookmarks.dart';
|
||||
import 'explorer_search.dart';
|
||||
import 'remote_thumb_view.dart';
|
||||
import 'tail_text.dart';
|
||||
import '../core/thumb_cache.dart' show thumbMemoryForgetters;
|
||||
import '../core/shizuku.dart';
|
||||
import 'vault_page.dart' show openVault;
|
||||
|
|
@ -4738,8 +4739,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
child: Text(_displayPath(d), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
],
|
||||
child: Text(_displayPath(_pane.current),
|
||||
maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: JjColors.textDim)),
|
||||
// 관리자 지시 (10/21, 28 지적): 좁으면 앞을 "…" 로 - 지금 폴더 (끝) 가 늘 보이게
|
||||
child: TailText(_displayPath(_pane.current), style: TextStyle(color: JjColors.textDim)),
|
||||
),
|
||||
),
|
||||
PopupMenuButton<Object>(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 경로처럼 끝 (지금 폴더) 이 중요한 글 (관리자 지시 10/21, 28 지적): 다 들어가면 그대로, 모자라면 앞을 "…" 로 줄인다.
|
||||
/// 구분 ([separators] - " › " · "\" · "/") 에서 자르는 것을 먼저 (가장 긴 뒤쪽), 마지막 조각도 안 들어가면 글자 단위로.
|
||||
/// 지금 글꼴 · 글자 배율로 잰다
|
||||
class TailText extends StatelessWidget {
|
||||
final String text;
|
||||
final TextStyle? style;
|
||||
final List<String> separators;
|
||||
const TailText(this.text, {super.key, this.style, this.separators = const [' › ', '\\', '/']});
|
||||
|
||||
/// [width] 에 들어가는 꼴 (시험에서 씀)
|
||||
static String fit(String text, double width, double Function(String) measure, {List<String> separators = const [' › ', '\\', '/']}) {
|
||||
if (measure(text) <= width) return text;
|
||||
// 구분에서 시작하는 뒤쪽들 (긴 것부터)
|
||||
final cuts = <int>[];
|
||||
for (final s in separators) {
|
||||
for (var i = text.indexOf(s); i >= 0; i = text.indexOf(s, i + 1)) {
|
||||
cuts.add(i); // 구분 기호는 남김 ("… › 여름 휴가")
|
||||
}
|
||||
}
|
||||
cuts.sort();
|
||||
for (final c in cuts) {
|
||||
final t = '…${text.substring(c)}';
|
||||
if (measure(t) <= width) return t;
|
||||
}
|
||||
// 마지막 조각도 길면 글자 단위로 (뒤쪽 가장 길게)
|
||||
var lo = 0, hi = text.length;
|
||||
while (lo < hi) {
|
||||
final mid = (lo + hi) ~/ 2;
|
||||
if (measure('…${text.substring(mid)}') <= width) {
|
||||
hi = mid;
|
||||
} else {
|
||||
lo = mid + 1;
|
||||
}
|
||||
}
|
||||
return '…${text.substring(lo)}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => LayoutBuilder(builder: (context, box) {
|
||||
final s = DefaultTextStyle.of(context).style.merge(style);
|
||||
final scaler = MediaQuery.textScalerOf(context);
|
||||
double measure(String t) =>
|
||||
(TextPainter(text: TextSpan(text: t, style: s), textDirection: TextDirection.ltr, textScaler: scaler, maxLines: 1)..layout()).width;
|
||||
final shown = box.maxWidth.isFinite ? fit(text, box.maxWidth, measure, separators: separators) : text;
|
||||
return Text(shown, style: style, maxLines: 1, overflow: TextOverflow.clip, softWrap: false);
|
||||
});
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import '../core/vfs.dart' show isDav, vBasename, vDisplay, vIsDir, vList, vLocal
|
|||
import '../l10n/tr.dart';
|
||||
import 'player_page.dart';
|
||||
import 'reader_page.dart' show openReader;
|
||||
import 'tail_text.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// X-plore 비교 5-2 암호 보관함 화면 (사용자 결정 10/21, 관리자 결정 - 설계안 위키 프로젝트/암호 보관함 · 루트 설계안 (10-21))
|
||||
|
|
@ -931,6 +932,15 @@ class _VaultPageState extends State<VaultPage> with WidgetsBindingObserver {
|
|||
|
||||
String _day(DateTime d) => '${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';
|
||||
|
||||
Widget _sortBox() => DropdownButton<String>(
|
||||
value: _sort,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'added', child: Text(tr('넣은 날'))),
|
||||
DropdownMenuItem(value: 'name', child: Text(tr('이름'))),
|
||||
],
|
||||
onChanged: (v) => setState(() => _sort = v!),
|
||||
);
|
||||
|
||||
Widget _list() {
|
||||
final v = _vault!;
|
||||
// 지운 · 옮긴 폴더 안에 있었으면 남은 가장 가까운 위 폴더로
|
||||
|
|
@ -945,27 +955,26 @@ class _VaultPageState extends State<VaultPage> with WidgetsBindingObserver {
|
|||
list.sort((a, b) => b.added.compareTo(a.added));
|
||||
}
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
// 관리자 지시 (10/21, 28 지적): 폴더 안에서는 경로 줄과 정렬 상자를 다른 줄로, 경로는 좁으면 앞을 "…" 로 (지금 폴더가 늘 보이게)
|
||||
if (_cwd.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 8, 16, 0),
|
||||
child: Row(children: [
|
||||
if (_cwd.isNotEmpty)
|
||||
IconButton(
|
||||
tooltip: tr('위 폴더로'), icon: const Icon(Icons.arrow_upward), onPressed: () => setState(() => _cwd = _parentOf(_cwd))),
|
||||
if (_cwd.isEmpty) const SizedBox(width: 8),
|
||||
Expanded(child: TailText(_cwdLabel(), style: const TextStyle(fontWeight: FontWeight.w600))),
|
||||
]),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.only(right: 16), child: Align(alignment: Alignment.centerRight, child: _sortBox())),
|
||||
] else
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 8, 16, 0),
|
||||
child: Row(children: [
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _cwd.isEmpty
|
||||
? Text(tr('넣기는 파일 탐색기에서 파일 · 폴더를 길게 눌러 [보관함에 넣기]'),
|
||||
style: TextStyle(fontSize: 12, color: JjColors.textDim))
|
||||
: Text(_cwdLabel(), maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
),
|
||||
DropdownButton<String>(
|
||||
value: _sort,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'added', child: Text(tr('넣은 날'))),
|
||||
DropdownMenuItem(value: 'name', child: Text(tr('이름'))),
|
||||
],
|
||||
onChanged: (v) => setState(() => _sort = v!),
|
||||
child: Text(tr('넣기는 파일 탐색기에서 파일 · 폴더를 길게 눌러 [보관함에 넣기]'), style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||
),
|
||||
_sortBox(),
|
||||
]),
|
||||
),
|
||||
if (list.isEmpty && dirs.isEmpty)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/ui/tail_text.dart';
|
||||
|
||||
/// 관리자 지시 (10/21, 28 지적): 경로 줄은 좁으면 앞을 "…" 로 - 지금 폴더 (끝) 가 늘 보이게
|
||||
void main() {
|
||||
double m(String t) => t.length * 10.0;
|
||||
|
||||
test('들어가면 그대로 · 모자라면 구분에서 앞을 줄임 (가장 긴 뒤쪽) · 마지막 조각도 길면 글자 단위', () {
|
||||
const path = '보관함 › 가족 › 2026 › 여름 휴가 › 제주도 둘째 날';
|
||||
expect(TailText.fit(path, 1000, m), path);
|
||||
expect(TailText.fit(path, 200, m), '… › 여름 휴가 › 제주도 둘째 날');
|
||||
expect(TailText.fit(path, 120, m), '… › 제주도 둘째 날');
|
||||
expect(TailText.fit(path, 60, m), '… 둘째 날', reason: '마지막 조각도 길면 그 끝을');
|
||||
expect(TailText.fit(r'C:\Users\haram\AppData\Local\Temp', 170, m), r'…\Local\Temp');
|
||||
expect(TailText.fit('dav://nas/사진/2026', 80, m), '…/2026');
|
||||
});
|
||||
|
||||
testWidgets('좁은 칸 (글자 1.3 배) 에서도 지금 폴더가 보임', (t) async {
|
||||
await t.pumpWidget(MaterialApp(
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(textScaler: TextScaler.linear(1.3)),
|
||||
child: Scaffold(
|
||||
body: Center(child: SizedBox(width: 220, child: TailText('보관함 › 가족 › 2026 › 여름 휴가 › 제주도 둘째 날'))),
|
||||
),
|
||||
),
|
||||
));
|
||||
final shown = t.widget<Text>(find.byType(Text)).data!;
|
||||
expect(shown, startsWith('…'));
|
||||
expect(shown, endsWith('제주도 둘째 날'));
|
||||
expect(t.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue