chore(project): 初始化项目基础配置文件
- 添加 CodeGraph、Android 和通用 gitignore 配置 - 创建项目元数据文件跟踪 Flutter 项目属性 - 添加 Codex AI 指导文档 AGENTS.md 说明项目架构 - 配置代码分析选项 analysis_options.yaml - 设置 Android 应用清单权限和 Kiosk 模式配置 - 实现中英文国际化支持 AppLocalizations - 配置 GoRouter 应用路由导航 - 创建明亮工业控制风格的主题配置 AppTheme
This commit is contained in:
202
lib/features/home/pages/complete_page.dart
Normal file
202
lib/features/home/pages/complete_page.dart
Normal file
@@ -0,0 +1,202 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../shared/widgets/common_button.dart';
|
||||
import '../../device/providers/run_state_provider.dart';
|
||||
|
||||
/// 运行完成提示页面
|
||||
class CompletePage extends ConsumerWidget {
|
||||
const CompletePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final runState = ref.watch(runStateProvider);
|
||||
final runNotifier = ref.read(runStateProvider.notifier);
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
color: AppTheme.backgroundColor,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 600,
|
||||
padding: const EdgeInsets.all(40),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.15),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 成功图标
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.successColor.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
size: 60,
|
||||
color: AppTheme.successColor,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
l10n?.runComplete ?? '程序运行完成',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 提示信息
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.warningColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
l10n?.sampleDropGuide ?? '请将样本滴入检测卡',
|
||||
style: TextStyle(
|
||||
color: AppTheme.warningColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 操作示意图
|
||||
_buildOperationGuide(),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 按钮区域
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// 返回首页按钮
|
||||
CommonButton(
|
||||
text: l10n?.backToHome ?? '返回首页',
|
||||
icon: Icons.home,
|
||||
type: ButtonType.primary,
|
||||
onPressed: () {
|
||||
runNotifier.reset();
|
||||
context.go('/');
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
|
||||
// 重新运行按钮
|
||||
CommonButton(
|
||||
text: l10n?.runAgain ?? '重新运行',
|
||||
icon: Icons.refresh,
|
||||
type: ButtonType.secondary,
|
||||
onPressed: () {
|
||||
final program = runState.currentProgram;
|
||||
if (program != null) {
|
||||
runNotifier.reset();
|
||||
runNotifier.start(program);
|
||||
context.go('/');
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 操作指引示意图
|
||||
Widget _buildOperationGuide() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppTheme.idleColor.withValues(alpha: 0.2)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'操作步骤',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildStepItem(1, '取出样本', Icons.science),
|
||||
_buildStepItem(2, '滴入检测卡', Icons.water_drop),
|
||||
_buildStepItem(3, '等待反应', Icons.timer),
|
||||
_buildStepItem(4, '查看结果', Icons.visibility),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 步骤项
|
||||
Widget _buildStepItem(int number, String text, IconData icon) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, color: AppTheme.primaryColor, size: 24),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'$number',
|
||||
style: TextStyle(
|
||||
color: AppTheme.primaryColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: AppTheme.textSecondary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
176
lib/features/home/pages/home_page.dart
Normal file
176
lib/features/home/pages/home_page.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../core/database/database_service.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../device/providers/run_state_provider.dart';
|
||||
import '../../programs/pages/programs_page.dart';
|
||||
import '../../settings/pages/settings_page.dart';
|
||||
import '../widgets/status_bar.dart';
|
||||
import '../widgets/program_list.dart';
|
||||
import '../widgets/running_control_panel.dart';
|
||||
import '../widgets/run_status_monitor.dart';
|
||||
|
||||
/// 首页 - 设备控制面板 (暗色工业风格)
|
||||
/// 布局:状态栏 + 导航标签栏 + 内容区(设备控制/程序管理/系统设置)
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends ConsumerState<HomePage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool _lightOn = false;
|
||||
final bool _ceramicSleeveInstalled = false; // TODO: 后续对接硬件传感器后改为可变状态
|
||||
int _currentIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
DatabaseService.instance.initTestData();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final runState = ref.watch(runStateProvider);
|
||||
|
||||
// 监听运行完成状态,自动跳转
|
||||
ref.listen<RunState>(runStateProvider, (prev, next) {
|
||||
if (prev?.status != RunStatus.completed && next.status == RunStatus.completed) {
|
||||
// 仅首页才自动跳转
|
||||
if (_currentIndex == 0) {
|
||||
context.push('/complete');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
color: AppTheme.bgDeep,
|
||||
child: Column(
|
||||
children: [
|
||||
// 状态栏
|
||||
StatusBar(
|
||||
isRunning: runState.status == RunStatus.running,
|
||||
lightOn: _lightOn,
|
||||
onLightToggle: () {
|
||||
setState(() {
|
||||
_lightOn = !_lightOn;
|
||||
});
|
||||
},
|
||||
ceramicSleeveInstalled: _ceramicSleeveInstalled,
|
||||
),
|
||||
|
||||
// 导航标签栏
|
||||
_buildTabBar(),
|
||||
|
||||
// 内容区
|
||||
Expanded(
|
||||
child: IndexedStack(
|
||||
index: _currentIndex,
|
||||
children: [
|
||||
_buildDeviceControlPage(runState),
|
||||
const ProgramsPage(),
|
||||
const SettingsPage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 导航标签栏
|
||||
Widget _buildTabBar() {
|
||||
const tabs = [
|
||||
(icon: Icons.dashboard, label: '设备控制'),
|
||||
(icon: Icons.list_alt, label: '程序管理'),
|
||||
(icon: Icons.settings, label: '系统设置'),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
children: List.generate(tabs.length, (index) {
|
||||
final tab = tabs[index];
|
||||
final isSelected = _currentIndex == index;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _currentIndex = index),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppTheme.accentPrimary : AppTheme.cardBg,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? AppTheme.accentPrimary
|
||||
: AppTheme.borderSubtle,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
tab.icon,
|
||||
size: 18,
|
||||
color: isSelected ? Colors.white : AppTheme.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
tab.label,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.white : AppTheme.textSecondary,
|
||||
fontSize: 14,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 设备控制页面内容
|
||||
Widget _buildDeviceControlPage(RunState runState) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
|
||||
child: Row(
|
||||
children: [
|
||||
// 左侧:程序列表(运行时锁定)
|
||||
Opacity(
|
||||
opacity: runState.status == RunStatus.idle ? 1.0 : 0.6,
|
||||
child: IgnorePointer(
|
||||
ignoring: runState.status != RunStatus.idle,
|
||||
child: const ProgramList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
// 右侧:运行控制区域
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
const Expanded(child: RunningControlPanel()),
|
||||
if (runState.status != RunStatus.idle) ...[
|
||||
const SizedBox(height: 16),
|
||||
const Expanded(child: RunStatusMonitor()),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
222
lib/features/home/widgets/program_list.dart
Normal file
222
lib/features/home/widgets/program_list.dart
Normal file
@@ -0,0 +1,222 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../programs/models/program.dart';
|
||||
import '../../programs/providers/programs_provider.dart';
|
||||
|
||||
/// 程序列表组件 - 暗色工业风格
|
||||
/// 显示程序卡片列表,支持选择操作
|
||||
class ProgramList extends ConsumerWidget {
|
||||
const ProgramList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final programsState = ref.watch(programsProvider);
|
||||
final programsNotifier = ref.read(programsProvider.notifier);
|
||||
|
||||
return Container(
|
||||
width: 380,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardBg,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppTheme.borderSubtle, width: 1),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 标题
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.list_alt, color: AppTheme.textHeading, size: 18),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
l10n?.availablePrograms ?? '可用程序',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textHeading,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 程序列表
|
||||
Expanded(
|
||||
child: programsState.isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: programsState.programs.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
l10n?.noData ?? '暂无数据',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textSecondary,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
itemCount: programsState.programs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final program = programsState.programs[index];
|
||||
final isSelected =
|
||||
programsState.selectedProgramId == program.id;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _ProgramCard(
|
||||
program: program,
|
||||
isSelected: isSelected,
|
||||
onTap: () {
|
||||
programsNotifier.selectProgram(program.id);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 单个程序卡片 - 暗色工业风格
|
||||
class _ProgramCard extends StatelessWidget {
|
||||
final Program program;
|
||||
final bool isSelected;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const _ProgramCard({
|
||||
required this.program,
|
||||
this.isSelected = false,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dateFormat = DateFormat('yyyy-MM-dd HH:mm');
|
||||
final createdAt = _parseDate(program.createdAt);
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppTheme.cardSelectedBg : AppTheme.cardBg,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppTheme.accentPrimary : AppTheme.borderSubtle,
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 选择指示器
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isSelected
|
||||
? AppTheme.accentPrimary
|
||||
: Colors.transparent,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? AppTheme.accentPrimary
|
||||
: AppTheme.statusStopped,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: isSelected
|
||||
? const Icon(Icons.check, color: Colors.white, size: 12)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// 程序信息
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
program.code,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textSecondary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: program.status == 1
|
||||
? AppTheme.statusRunning.withValues(alpha: 0.15)
|
||||
: AppTheme.statusStopped.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
program.status == 1 ? '启用' : '停用',
|
||||
style: TextStyle(
|
||||
color: program.status == 1
|
||||
? AppTheme.statusRunning
|
||||
: AppTheme.statusStopped,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
program.name,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textHeading,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
createdAt != null
|
||||
? dateFormat.format(createdAt)
|
||||
: program.createdAt,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 11,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DateTime? _parseDate(String dateStr) {
|
||||
try {
|
||||
return DateTime.parse(dateStr);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
242
lib/features/home/widgets/run_status_monitor.dart
Normal file
242
lib/features/home/widgets/run_status_monitor.dart
Normal file
@@ -0,0 +1,242 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../device/providers/run_state_provider.dart';
|
||||
|
||||
/// 运行状态监控面板 - 暗色工业风格
|
||||
/// 显示当前孔位、步骤、倒计时、进度条、参数详情
|
||||
class RunStatusMonitor extends ConsumerWidget {
|
||||
const RunStatusMonitor({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final runState = ref.watch(runStateProvider);
|
||||
|
||||
if (runState.status == RunStatus.idle) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardBg,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppTheme.borderSubtle, width: 1),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题 + 程序名
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n?.runningMonitor ?? '运行状态监控',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textHeading,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
runState.currentProgram?.name ?? '',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.accentPrimary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// 进度信息横排 (孔位 / 步骤 / 剩余时间)
|
||||
Row(
|
||||
children: [
|
||||
// 当前孔位
|
||||
_buildInfoBlock(
|
||||
label: l10n?.currentHole ?? '当前孔位',
|
||||
value: runState.currentWell ?? '--',
|
||||
valueColor: AppTheme.textHeading,
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
// 当前步骤
|
||||
_buildInfoBlock(
|
||||
label: l10n?.currentStep ?? '当前步骤',
|
||||
value: '${l10n?.stepNo ?? '步骤'} ${runState.currentStepIndex + 1}',
|
||||
subValue: runState.currentStep?.name ?? '--',
|
||||
valueColor: AppTheme.accentInfo,
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
// 剩余时间
|
||||
_buildInfoBlock(
|
||||
label: l10n?.remainingTime ?? '剩余时间',
|
||||
value: runState.formattedRemainingTime,
|
||||
valueColor: AppTheme.textHeading,
|
||||
valueSize: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// 总进度条
|
||||
_buildProgressBar(l10n, runState),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// 步骤参数
|
||||
if (runState.currentStep != null)
|
||||
_buildStepParams(l10n, runState.currentStep!),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 信息块
|
||||
Widget _buildInfoBlock({
|
||||
required String label,
|
||||
required String value,
|
||||
String? subValue,
|
||||
Color valueColor = AppTheme.textHeading,
|
||||
double valueSize = 16,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
color: valueColor,
|
||||
fontSize: valueSize,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
if (subValue != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subValue,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textSecondary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 进度条
|
||||
Widget _buildProgressBar(AppLocalizations? l10n, RunState runState) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n?.progress ?? '总进度',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
runState.formattedProgress,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.accentPrimary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: runState.progress,
|
||||
minHeight: 8,
|
||||
backgroundColor: const Color(0xFF1E293B),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(AppTheme.accentPrimary),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 步骤参数详情
|
||||
Widget _buildStepParams(AppLocalizations? l10n, dynamic step) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n?.stepParams ?? '步骤参数',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
if (step.mixTime > 0)
|
||||
_buildParamRow(
|
||||
l10n?.speed ?? '转速',
|
||||
'${step.mixSpeed}',
|
||||
),
|
||||
if (step.magnetTime > 0)
|
||||
_buildParamRow(
|
||||
l10n?.temperature ?? '温度',
|
||||
'65.0 °C',
|
||||
),
|
||||
_buildParamRow(
|
||||
l10n?.duration ?? '持续时间',
|
||||
step.mixTime > 0 ? '${step.mixTime} min' : '--',
|
||||
),
|
||||
_buildParamRow(
|
||||
l10n?.sampleVolume ?? '样品体积',
|
||||
'10.0 mL',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 参数行
|
||||
Widget _buildParamRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 11,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
365
lib/features/home/widgets/running_control_panel.dart
Normal file
365
lib/features/home/widgets/running_control_panel.dart
Normal file
@@ -0,0 +1,365 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../shared/widgets/common_button.dart';
|
||||
import '../../device/providers/run_state_provider.dart';
|
||||
import '../../programs/providers/programs_provider.dart';
|
||||
|
||||
/// 运行控制面板 - 暗色工业风格
|
||||
/// 显示当前程序信息、瓷套棒状态和运行控制按钮
|
||||
class RunningControlPanel extends ConsumerWidget {
|
||||
const RunningControlPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final runState = ref.watch(runStateProvider);
|
||||
final programsState = ref.watch(programsProvider);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardBg,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppTheme.borderSubtle, width: 1),
|
||||
),
|
||||
child: runState.status == RunStatus.idle
|
||||
? _buildIdleState(context, ref, l10n, programsState.selectedProgram)
|
||||
: _buildRunningState(context, ref, l10n, runState),
|
||||
);
|
||||
}
|
||||
|
||||
/// 待机状态布局
|
||||
Widget _buildIdleState(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
AppLocalizations? l10n,
|
||||
dynamic selectedProgram,
|
||||
) {
|
||||
final runNotifier = ref.read(runStateProvider.notifier);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 当前选中程序显示
|
||||
if (selectedProgram != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardSelectedBg,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: AppTheme.accentPrimary, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${l10n?.selectedProgramLabel ?? '当前选中'}:',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${selectedProgram.code} ${selectedProgram.name}',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.accentPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardBg,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: AppTheme.borderSubtle, width: 1),
|
||||
),
|
||||
child: Text(
|
||||
l10n?.pleaseSelectProgram ?? '请选择要运行的程序',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 瓷套棒确认提示
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardBg,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: AppTheme.borderSubtle, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppTheme.statusStopped,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n?.ceramicNotInstalled ?? '瓷套棒: 未安装 — 禁止启动',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textSecondary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 控制按钮
|
||||
Row(
|
||||
children: [
|
||||
// 开始运行按钮
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: CommonButton(
|
||||
text: l10n?.startRun ?? '开始运行',
|
||||
icon: Icons.play_arrow,
|
||||
type: ButtonType.primary,
|
||||
enabled: selectedProgram != null,
|
||||
onPressed: selectedProgram != null
|
||||
? () => runNotifier.start(selectedProgram)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// 暂停/继续按钮(待机态禁用)
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: CommonButton(
|
||||
text: l10n?.pause ?? '暂停',
|
||||
icon: Icons.pause,
|
||||
type: ButtonType.secondary,
|
||||
enabled: false,
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// 停止按钮(待机态禁用)
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: CommonButton(
|
||||
text: l10n?.stop ?? '停止',
|
||||
icon: Icons.stop,
|
||||
type: ButtonType.danger,
|
||||
enabled: false,
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 运行状态布局
|
||||
Widget _buildRunningState(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
AppLocalizations? l10n,
|
||||
RunState runState,
|
||||
) {
|
||||
final runNotifier = ref.read(runStateProvider.notifier);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 当前程序名称
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardSelectedBg,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: AppTheme.accentPrimary, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${l10n?.selectedProgramLabel ?? '当前选中'}:',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textTertiary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
runState.currentProgram?.name ?? '',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.accentPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 控制按钮
|
||||
Row(
|
||||
children: [
|
||||
// 开始/继续按钮
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: CommonButton(
|
||||
text: runState.status == RunStatus.paused
|
||||
? (l10n?.continue_ ?? '继续')
|
||||
: (l10n?.run ?? '运行'),
|
||||
icon: runState.status == RunStatus.paused
|
||||
? Icons.play_arrow
|
||||
: Icons.play_arrow,
|
||||
type: ButtonType.primary,
|
||||
onPressed: () => runNotifier.resume(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// 暂停按钮
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: CommonButton(
|
||||
text: l10n?.pause ?? '暂停',
|
||||
icon: Icons.pause,
|
||||
type: ButtonType.warning,
|
||||
onPressed: runState.status == RunStatus.paused
|
||||
? null
|
||||
: () => runNotifier.pause(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// 停止按钮
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: CommonButton(
|
||||
text: l10n?.stop ?? '停止',
|
||||
icon: Icons.stop,
|
||||
type: ButtonType.danger,
|
||||
onPressed: () => _showStopConfirm(context, runNotifier, l10n),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 状态指示
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: runState.status == RunStatus.paused
|
||||
? AppTheme.accentWarning
|
||||
: AppTheme.statusRunning,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
runState.status == RunStatus.paused
|
||||
? (l10n?.paused ?? '已暂停')
|
||||
: (l10n?.running ?? '运行中'),
|
||||
style: TextStyle(
|
||||
color: runState.status == RunStatus.paused
|
||||
? AppTheme.accentWarning
|
||||
: AppTheme.statusRunning,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示停止确认对话框
|
||||
void _showStopConfirm(
|
||||
BuildContext context,
|
||||
RunStateNotifier runNotifier,
|
||||
AppLocalizations? l10n,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: AppTheme.cardBg,
|
||||
title: Text(
|
||||
l10n?.confirm ?? '确认',
|
||||
style: const TextStyle(color: AppTheme.textHeading),
|
||||
),
|
||||
content: Text(
|
||||
l10n?.stopConfirm ?? '确定要停止当前运行的程序吗?',
|
||||
style: const TextStyle(color: AppTheme.textPrimary),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
l10n?.cancel ?? '取消',
|
||||
style: const TextStyle(color: AppTheme.textSecondary),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.accentCritical,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
runNotifier.stop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(l10n?.confirm ?? '确认'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
181
lib/features/home/widgets/status_bar.dart
Normal file
181
lib/features/home/widgets/status_bar.dart
Normal file
@@ -0,0 +1,181 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../shared/widgets/status_indicator.dart';
|
||||
|
||||
/// 状态栏组件 - 明亮工业风格
|
||||
/// 显示设备名称、实时时钟、系统状态、照明控制、瓷套棒状态
|
||||
class StatusBar extends StatefulWidget {
|
||||
final bool isRunning;
|
||||
final bool lightOn;
|
||||
final VoidCallback? onLightToggle;
|
||||
final bool ceramicSleeveInstalled;
|
||||
|
||||
const StatusBar({
|
||||
super.key,
|
||||
this.isRunning = false,
|
||||
this.lightOn = false,
|
||||
this.onLightToggle,
|
||||
this.ceramicSleeveInstalled = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatusBar> createState() => _StatusBarState();
|
||||
}
|
||||
|
||||
class _StatusBarState extends State<StatusBar> {
|
||||
String _currentTime = '';
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateTime();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (_) => _updateTime());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateTime() {
|
||||
final now = DateTime.now();
|
||||
_currentTime =
|
||||
'${now.year}-${_twoDigits(now.month)}-${_twoDigits(now.day)} '
|
||||
'${_twoDigits(now.hour)}:${_twoDigits(now.minute)}:${_twoDigits(now.second)}';
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
String _twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
return Container(
|
||||
height: 56,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.precision_manufacturing, color: Colors.white, size: 22),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
l10n?.deviceName ?? '污水毒品前处理一体机',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
_LightToggleButton(isOn: widget.lightOn, onTap: widget.onLightToggle),
|
||||
const SizedBox(width: 16),
|
||||
_CeramicSleeveStatus(installed: widget.ceramicSleeveInstalled),
|
||||
const SizedBox(width: 20),
|
||||
StatusIndicator(
|
||||
text: widget.isRunning
|
||||
? (l10n?.running ?? '运行中')
|
||||
: (l10n?.idle ?? '未运行'),
|
||||
status: widget.isRunning
|
||||
? DeviceStatusType.running
|
||||
: DeviceStatusType.idle,
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Text(
|
||||
_currentTime,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CeramicSleeveStatus extends StatelessWidget {
|
||||
final bool installed;
|
||||
const _CeramicSleeveStatus({required this.installed});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: installed ? Colors.greenAccent : Colors.redAccent,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
installed ? '瓷套棒: 已安装' : '瓷套棒: 未安装',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LightToggleButton extends StatelessWidget {
|
||||
final bool isOn;
|
||||
final VoidCallback? onTap;
|
||||
const _LightToggleButton({this.isOn = false, this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: isOn
|
||||
? Colors.white.withValues(alpha: 0.25)
|
||||
: Colors.white.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
isOn ? Icons.lightbulb : Icons.lightbulb_outline_rounded,
|
||||
color: isOn ? Colors.yellowAccent : Colors.white.withValues(alpha: 0.8),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user