11줄 평시 + 3줄 펼침 (현재 RustDesk)
평시 1줄 (Ctrl·Alt·Shift·Tab 등) 항상 표시. 펼치기 버튼 탭 시 3줄 (전체) 로 확장. 위치: 화면 위쪽.
구현: flutter/lib/mobile/pages/remote_page.dart:904-1024.
Ctrl·Alt·Shift·Cmd·Win + 방향키·펑션키) UI 패턴 비교.
평가축: 한 손 조작성·키 조합 속도·화면 가림·발견성·접근성 (총 100%).
v4 새 옵션: 키보드 바로 위 1줄 + 모드 토글(텍스트 ↔ 명령) — Galaxy Fold 6 같은 큰 폴더블 화면에서 화면 위쪽 가림 문제 해결.
현재 구현 + 모바일 원격 제어 대안 + TeamViewer 방식 + 키보드 위 모드 토글(신규).
갤럭시 Fold 6 / 6.x" 한 손 사용 컨텍스트. 합계 100%.
| 기준 | 가중치 | 근거 |
|---|---|---|
| 한 손 조작성 | 25% | Fold 6 펼친 화면(7.6") 한 손 조작 시 위쪽 도달 매우 어려움. 키보드 영역에 가까운 위치가 절대 우위. |
| 키 조합 입력 속도 | 25% | Ctrl+Alt+Del·Win+E·Cmd+Tab 같은 multi-modifier 조합 속도. |
| 화면 가림 면적 | 20% | 원격 화면 가림. 키보드가 어차피 표시되면 그 위 1줄은 추가 가림 ~40dp 로 한정. |
| 발견성 | 15% | 설명 없이 사용자가 modifier 호출 방법을 찾을 수 있는가. |
| 접근성 (a11y) | 15% | 큰 터치 타깃, 시각 피드백, 정밀 제스처 회피. |
각 기준 10점 만점, 가중 점수 = 원점수 × 가중치 / 10. 총점 10점 만점.
| 순위 | 옵션 | 한 손 (25%) | 속도 (25%) | 가림 (20%) | 발견성 (15%) | 접근성 (15%) | 총점 |
|---|---|---|---|---|---|---|---|
| 1 | ⑦ 키보드 위 1줄 + 모드 토글 ★ 신규 | 10 | 7 | 6 | 8 | 9 | 8.00 |
| 2 | ① 1줄 평시 + 3줄 펼침 (현재 RustDesk) | 7 | 8 | 4 | 9 | 9 | 7.25 |
| 3 | ⑥ TeamViewer 방식 | 6 | 9 | 4 | 9 | 8 | 7.10 |
| 4 | ⑤ Pie menu / Quick wheel | 9 | 7 | 8 | 5 | 5 | 7.10 |
| 5 | ② FAB + Radial menu | 9 | 5 | 8 | 6 | 7 | 7.05 |
| 6 | ③ Edge swipe drawer | 8 | 6 | 9 | 4 | 5 | 6.65 |
| 7 | ④ Bottom sheet + pinch | 8 | 5 | 7 | 5 | 6 | 6.30 |
v4: ⑦ 추가, 8.00 으로 신규 1위. 키보드 위 배치 가 한 손 조작 만점(10) — Fold 6 펼친 화면(7.6") 에서 위쪽 도달 어려움을 근본 해결. 속도 7 은 모드 전환 1단계 추가 비용이지만, 모드 전환 자체가 두 모드를 명확히 분리해 인지 부담을 낮춤 (Vim 모달 UX 와 동일 원리).
Galaxy Fold 6 / 갤럭시 7.6" 같은 큰 폴더블·한 손 비중이 매우 높은 사용자에게 절대 우위. 위쪽 도달 어려움이 사용자가 명시한 핵심 불편이므로, 이를 근본 해결.
BT 키보드 위주 파워 유저는 별도 모드 (Direct keyboard mode, TeamViewer ⑥) 가 더 적합. 또 모드 전환 패턴이 캐주얼 사용자에게 학습 곡선 있을 수 있음 (Vim 모달 UX 익숙하지 않은 경우).
v4 매트릭스 1위 ⑦ 가 사용자(Galaxy Fold 6)의 핵심 불편(화면 위쪽 가림) 을 근본 해결. 8.00 / 10 점, 한 손 도달 만점.
MediaQuery.viewInsets.bottom 로 키보드 표시 감지, 키보드 위 ~44dp 영역에 toolbar 슬라이드 인⌨ ↔ ⌘ 아이콘 전환
왜 이게 최선인가: Fold 6 펼친 화면에서 한 손으로 위쪽 도달이 가장 큰 페인 포인트. 키보드 위 배치로 엄지 도달 거리를 화면 위쪽 → 키보드 상단으로 단축 (도달 거리 절반 이하). 모드 분리로 인지 부담 낮춤.
중간 비용 (예상 8~16시간). 핵심은 키보드 표시 상태 감지 + 동적 위치 + 모드 상태 관리.
| 변경 위치 | 해야 할 일 |
|---|---|
flutter/lib/mobile/pages/remote_page.dart body 구조 |
현재 화면 상단의 modifier Row 를 제거. Stack 의 Positioned + MediaQuery.of(context).viewInsets.bottom 로 키보드 위 동적 위치에 배치. |
remote_page.dart 모드 상태 |
enum InputMode { text, command } + InputMode _mode = InputMode.text; 추가. 모드별 동작 분기. |
remote_page.dart 토글 버튼 |
고정 위치(예: 좌하단) FloatingActionButton.small. _mode toggle. 텍스트→명령 전환 시 FocusNode.unfocus() 로 가상 키보드는 유지하되 텍스트 입력 비활성. |
remote_page.dart KeyToolbar 위젯 (신규) |
기존 wrap('Ctrl ', ...) Row 를 별도 위젯으로 추출. _mode == InputMode.command 일 때만 표시. AnimatedSlide 로 부드럽게. |
remote_page.dart 키보드 감지 |
KeyboardVisibilityBuilder (flutter_keyboard_visibility 패키지) 또는 WidgetsBinding.instance.addObserver + didChangeMetrics 로 키보드 표시 감지. |
flutter/lib/models/input_model.dart |
모드별 입력 처리 분기. 명령 모드에서 modifier 토글 상태 유지 (sticky modifier, Android TalkBack 표준). |
(BT 키보드 대응) remote_page.dart |
가상 키보드 미표시 + 외부 키보드 감지 시 화면 우하단 floating modifier toolbar 표시 (⑥ TeamViewer Direct mode 동일 패턴). |
flutter/lib/mobile/widgets/gesture_help.dart |
제스처 도움말에 "⌨ ↔ ⌘ 버튼으로 텍스트/명령 모드 전환" 안내 추가. |
// remote_page.dart build() 구조
enum InputMode { text, command }
InputMode _mode = InputMode.text;
bool _keyboardVisible = false;
@override
void didChangeMetrics() {
setState(() {
_keyboardVisible = WidgetsBinding.instance.window.viewInsets.bottom > 0;
});
}
// Scaffold body 안:
return Stack(children: [
remoteCanvas, // 원격 화면
// 키보드 위 modifier toolbar (명령 모드 + 키보드 표시 시)
if (_keyboardVisible && _mode == InputMode.command)
Positioned(
left: 0, right: 0,
bottom: MediaQuery.of(context).viewInsets.bottom,
child: Material(
elevation: 4,
child: SizedBox(
height: 44,
child: Row(children: [
wrap('Ctrl ', () { /* ... */ }),
wrap(' Alt ', () { /* ... */ }),
wrap('Shift', () { /* ... */ }),
wrap('Tab', () { /* ... */ }),
wrap('Esc', () { /* ... */ }),
// 더 긴 펼침은 swipe-up 으로 호출
PopupMenuButton<String>(
icon: const Icon(Icons.more_horiz),
tooltip: 'More keys',
itemBuilder: (_) => const [
PopupMenuItem(value: 'arrows', child: Text('Arrow keys')),
PopupMenuItem(value: 'functions', child: Text('F1-F12')),
PopupMenuItem(value: 'combos', child: Text('Ctrl+Alt+Del, Win+E, …')),
],
),
]),
),
),
),
// 모드 토글 FAB (좌하단 고정)
Positioned(
left: 16, bottom: 16 + MediaQuery.of(context).viewInsets.bottom + 44,
child: FloatingActionButton.small(
onPressed: () => setState(() {
_mode = _mode == InputMode.text ? InputMode.command : InputMode.text;
}),
child: Icon(_mode == InputMode.text ? Icons.keyboard : Icons.keyboard_command_key),
),
),
]);
flutter/lib/mobile/pages/remote_page.dart line 904-1024MediaQuery.viewInsets 키보드 감지 — api.flutter.dev/flutter/widgets/MediaQueryData/viewInsets.html