Trae Assistant
init
de7f206
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import 'models.dart';
import 'logic.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => TimeManager(),
child: const TimeVoyagerApp(),
),
);
}
class TimeVoyagerApp extends StatelessWidget {
const TimeVoyagerApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TimeVoyager',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const ProjectListPage(),
);
}
}
class ProjectListPage extends StatelessWidget {
const ProjectListPage({super.key});
@override
Widget build(BuildContext context) {
final manager = context.watch<TimeManager>();
return Scaffold(
appBar: AppBar(
title: const Text('TimeVoyager'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ListView.builder(
itemCount: manager.projects.length,
itemBuilder: (context, index) {
final project = manager.projects[index];
final isActive = manager.activeEntry?.projectId == project.id;
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: isActive ? Colors.green : Colors.blueGrey,
child: Icon(isActive ? Icons.play_arrow : Icons.folder),
),
title: Text(project.name),
subtitle: Text('${project.hourlyRate} ${project.currency} / 小时'),
trailing: isActive
? const Text('正在计时...', style: TextStyle(color: Colors.green))
: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProjectDetailPage(projectId: project.id),
),
);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// TODO: Implement Add Project Dialog
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('演示模式:默认项目已预置')),
);
},
child: const Icon(Icons.add),
),
);
}
}
class ProjectDetailPage extends StatelessWidget {
final String projectId;
const ProjectDetailPage({super.key, required this.projectId});
@override
Widget build(BuildContext context) {
final manager = context.watch<TimeManager>();
final project = manager.getProjectById(projectId);
if (project == null) return const Scaffold(body: Center(child: Text('项目不存在')));
final isActive = manager.activeEntry?.projectId == project.id;
return Scaffold(
appBar: AppBar(title: Text(project.name)),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
_buildStatusCard(context, project, manager, isActive),
const SizedBox(height: 40),
_buildControlButtons(context, project, manager, isActive),
],
),
),
);
}
Widget _buildStatusCard(BuildContext context, Project project, TimeManager manager, bool isActive) {
final duration = isActive ? manager.currentDuration : Duration.zero;
final earnings = isActive ? manager.currentEarnings : 0.0;
final formattedTime = _formatDuration(duration);
final formattedEarnings = NumberFormat.currency(symbol: project.currency == 'CNY' ? '¥' : '\$')
.format(earnings);
return Container(
width: double.infinity,
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: isActive ? Colors.blue.shade50 : Colors.grey.shade100,
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: isActive ? Colors.blue.shade200 : Colors.transparent,
width: 2,
),
),
child: Column(
children: [
Text(
isActive ? '工作中' : '就绪',
style: TextStyle(
color: isActive ? Colors.blue : Colors.grey,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const SizedBox(height: 16),
Text(
formattedTime,
style: const TextStyle(
fontSize: 64,
fontFamily: 'Monospace',
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 8),
Text(
formattedEarnings,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: isActive ? Colors.green : Colors.grey,
),
),
],
),
);
}
Widget _buildControlButtons(BuildContext context, Project project, TimeManager manager, bool isActive) {
if (isActive) {
return SizedBox(
width: double.infinity,
height: 60,
child: ElevatedButton.icon(
onPressed: () => manager.stopTracking(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade100,
foregroundColor: Colors.red,
),
icon: const Icon(Icons.stop_circle),
label: const Text('停止计时', style: TextStyle(fontSize: 20)),
),
);
} else {
// Check if another project is running
final isOtherRunning = manager.isTracking && !isActive;
return SizedBox(
width: double.infinity,
height: 60,
child: ElevatedButton.icon(
onPressed: isOtherRunning
? () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请先停止当前正在进行的项目')),
);
}
: () => manager.startTracking(project),
style: ElevatedButton.styleFrom(
backgroundColor: isOtherRunning ? Colors.grey : Colors.green.shade100,
foregroundColor: isOtherRunning ? Colors.white : Colors.green,
),
icon: const Icon(Icons.play_circle),
label: Text(
isOtherRunning ? '其他项目进行中' : '开始工作',
style: const TextStyle(fontSize: 20)
),
),
);
}
}
String _formatDuration(Duration d) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitMinutes = twoDigits(d.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(d.inSeconds.remainder(60));
return "${twoDigits(d.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
}
}