130 lines
5.2 KiB
Dart
130 lines
5.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../app/app_controller.dart';
|
|
import '../services/system_usage.dart';
|
|
import 'settings_page.dart';
|
|
import 'theme.dart';
|
|
|
|
/// 모든 화면의 위쪽 막대 높이 · 오른쪽 여백 (환경 설정 · 종료 버튼이 어느 화면에서나 같은 자리에 오도록)
|
|
const double appBarHeight = 56;
|
|
const double appBarRightPadding = 12;
|
|
|
|
/// 앱 전체에서 쓰는 것 (환경 설정을 열 컨트롤러 · 종료 동작) 을 화면들에 내려 준다
|
|
class AppScope extends InheritedWidget {
|
|
final AppController controller;
|
|
|
|
/// 종료 버튼을 눌렀을 때 (없으면 버튼을 숨김)
|
|
final VoidCallback? onExit;
|
|
|
|
const AppScope({super.key, required this.controller, this.onExit, required super.child});
|
|
|
|
static AppScope? maybeOf(BuildContext context) => context.dependOnInheritedWidgetOfExactType<AppScope>();
|
|
|
|
@override
|
|
bool updateShouldNotify(AppScope old) => controller != old.controller || onExit != old.onExit;
|
|
}
|
|
|
|
/// "CPU 23% MEM 61%" (90% 넘으면 빨갛게). 글자 폭이 바뀌어도 옆 버튼이 움직이지 않게 너비를 고정한다.
|
|
class UsageView extends StatelessWidget {
|
|
final UsageMonitor usage;
|
|
const UsageView({super.key, required this.usage});
|
|
|
|
static String _gb(int bytes) => (bytes / (1024 * 1024 * 1024)).toStringAsFixed(1);
|
|
|
|
/// 남은 용량: "128GB" · "1.8TB" · "950MB"
|
|
static String _size(int bytes) {
|
|
const gb = 1024 * 1024 * 1024;
|
|
if (bytes >= 1000 * gb) return '${(bytes / (1024 * gb)).toStringAsFixed(1)}TB';
|
|
if (bytes >= gb) return '${(bytes / gb).toStringAsFixed(bytes < 10 * gb ? 1 : 0)}GB';
|
|
return '${(bytes / (1024 * 1024)).round()}MB';
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => ValueListenableBuilder<UsageSample?>(
|
|
valueListenable: usage,
|
|
builder: (context, s, _) {
|
|
Widget cell(String label, String value, bool hot, double width) {
|
|
return SizedBox(
|
|
width: width,
|
|
child: Text.rich(
|
|
TextSpan(children: [
|
|
TextSpan(text: '$label ', style: const TextStyle(color: JjColors.textDim)),
|
|
TextSpan(
|
|
text: value,
|
|
style: TextStyle(
|
|
color: hot ? JjColors.danger : JjColors.text, fontWeight: FontWeight.w600)),
|
|
]),
|
|
maxLines: 1,
|
|
softWrap: false,
|
|
style: const TextStyle(fontSize: 11, fontFamily: 'Consolas'),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget item(String label, double? v) =>
|
|
cell(label, v == null ? '--%' : '${(v * 100).round()}%', v != null && v >= 0.9, 62);
|
|
final free = s?.diskFree;
|
|
|
|
return Tooltip(
|
|
message: s == null
|
|
? 'PC 전체 CPU · 메모리 사용량'
|
|
: 'PC 전체 사용량\nCPU ${(s.cpu * 100).round()}%\n'
|
|
'메모리 ${_gb(s.memUsed)} / ${_gb(s.memTotal)}GB (${(s.mem * 100).round()}%)'
|
|
'${free == null ? '' : '\n다운로드 디스크 ${s.disk} 남은 용량 ${_size(free)}'
|
|
'${s.diskTotal == null ? '' : ' / 전체 ${_size(s.diskTotal!)}'}'}',
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 8),
|
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
|
item('CPU', s?.cpu),
|
|
item('MEM', s?.mem),
|
|
// 다운로드 대상 디스크의 남은 용량
|
|
if (free != null) cell('DISK', _size(free), s!.diskLow, 84),
|
|
]),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 환경 설정 · 종료 버튼. 모든 화면의 위쪽 막대 맨 오른쪽에 같은 크기로 놓는다.
|
|
class AppActions extends StatelessWidget {
|
|
/// 직접 주지 않으면 [AppScope] 의 것을 쓴다
|
|
final AppController? c;
|
|
final VoidCallback? onExit;
|
|
|
|
/// 환경 설정 화면 자신: 설정 버튼을 "지금 여기" 로 표시하고 누를 수 없게
|
|
final bool onSettingsPage;
|
|
|
|
const AppActions({super.key, this.c, this.onExit, this.onSettingsPage = false});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final scope = AppScope.maybeOf(context);
|
|
final controller = c ?? scope?.controller;
|
|
final exit = onExit ?? scope?.onExit;
|
|
if (controller == null && exit == null) return const SizedBox();
|
|
final usage = controller?.usage;
|
|
return Row(mainAxisSize: MainAxisSize.min, children: [
|
|
// CPU · MEM: 변환 · AI 작업이 PC 를 얼마나 쓰는지 (PC 전체 기준)
|
|
if (usage != null) UsageView(usage: usage),
|
|
const SizedBox(width: 4),
|
|
if (controller != null)
|
|
IconButton(
|
|
tooltip: '환경 설정',
|
|
icon: Icon(onSettingsPage ? Icons.settings : Icons.settings_outlined,
|
|
color: onSettingsPage ? JjColors.accent : null),
|
|
onPressed: onSettingsPage
|
|
? null
|
|
: () => Navigator.push(
|
|
context, MaterialPageRoute<void>(builder: (_) => SettingsPage(c: controller))),
|
|
),
|
|
if (exit != null)
|
|
IconButton(
|
|
tooltip: '종료',
|
|
icon: const Icon(Icons.power_settings_new, color: JjColors.danger),
|
|
onPressed: exit,
|
|
),
|
|
]);
|
|
}
|
|
}
|