swing_account/lib/pages/home_page.dart
2024-12-27 13:56:59 +08:00

270 lines
7.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:sensors_plus/sensors_plus.dart';
import 'package:vibration/vibration.dart';
import 'dart:async';
import '../models/record.dart';
import './record_page.dart';
import '../widgets/record_list.dart';
import '../services/database_service.dart';
import '../widgets/weekly_spending_chart.dart';
import '../widgets/monthly_summary_card.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
StreamSubscription? _accelerometerSubscription;
DateTime? _lastShakeTime;
List<double> _recentXValues = []; // 存储原始x轴加速度值不取绝对值
bool _isNavigating = false;
final _dbService = DatabaseService();
List<Record> records = [];
DateTime _selectedMonth = DateTime.now(); // 添加选中月份状态
bool _isAmountVisible = true; // 添加可视状态
// 定义摇晃检测的常量
static const double _shakeThreshold = 8.0; // 单次摇晃的加速度阈值
static const double _directionThreshold = 2.0; // 方向改变的最小加速
static const int _sampleSize = 3; // 采样数量
static const Duration _shakeWindow = Duration(milliseconds: 800);
static const Duration _cooldown = Duration(milliseconds: 50);
/// 检测有效的摇晃
bool _isValidShake() {
if (_recentXValues.length < _sampleSize) return false;
// 检查是否有足够大的加速度
bool hasStrongShake = _recentXValues.any((x) => x.abs() > _shakeThreshold);
if (!hasStrongShake) return false;
// 检查方向变化
bool hasDirectionChange = false;
for (int i = 1; i < _recentXValues.length; i++) {
// 如果相邻两个值的符号相反,且都超过方向阈值,说明发生了方向改变
if (_recentXValues[i].abs() > _directionThreshold &&
_recentXValues[i - 1].abs() > _directionThreshold &&
_recentXValues[i].sign != _recentXValues[i - 1].sign) {
hasDirectionChange = true;
break;
}
}
return hasDirectionChange;
}
@override
void initState() {
super.initState();
_initShakeDetection();
_loadRecords();
}
/// 重置摇晃检测状态
void _resetShakeDetection() {
_lastShakeTime = null;
_recentXValues.clear();
_isNavigating = false;
// 重新初始化传感器监听
_accelerometerSubscription?.cancel();
_initShakeDetection();
}
/// 加载记录
Future<void> _loadRecords() async {
// 计算选中月份的起始和结束日期
final startDate = DateTime(_selectedMonth.year, _selectedMonth.month, 1);
final endDate = DateTime(_selectedMonth.year, _selectedMonth.month + 1, 0);
final loadedRecords = await _dbService.getRecordsByDateRange(
startDate,
endDate,
);
setState(() {
records = loadedRecords;
records.sort((a, b) => b.createTime.compareTo(a.createTime));
});
}
/// 初始化摇晃检测
void _initShakeDetection() {
_accelerometerSubscription = accelerometerEvents.listen(
(event) {
if (_isNavigating) return; // 如果正在导航,忽略摇晃检测
final now = DateTime.now();
// 添加新的x轴值保留正负号
_recentXValues.add(event.x);
if (_recentXValues.length > _sampleSize) {
_recentXValues.removeAt(0);
}
// 检查是否处于冷却期
if (_lastShakeTime != null &&
now.difference(_lastShakeTime!) < _cooldown) {
return;
}
// 检测有效的摇晃
if (_isValidShake()) {
if (_lastShakeTime == null) {
_lastShakeTime = now;
_recentXValues.clear();
} else {
final timeDiff = now.difference(_lastShakeTime!);
if (timeDiff < _shakeWindow) {
_navigateToRecordPageWithVibration();
_lastShakeTime = null;
_recentXValues.clear();
} else {
_lastShakeTime = now;
_recentXValues.clear();
}
}
}
},
onError: (error) {
debugPrint('Accelerometer error: $error');
_resetShakeDetection(); // 发生错误时重置检测
},
cancelOnError: false, // 错误时不取消订阅,而是重置
);
}
/// 带震动的记账页面导航
void _navigateToRecordPageWithVibration() async {
if (_isNavigating) return;
_isNavigating = true;
// 添加震动反馈
if (await Vibration.hasVibrator() ?? false) {
Vibration.vibrate(duration: 200);
}
_navigateToRecordPage();
}
/// 导航到记账页面
void _navigateToRecordPage([Record? record]) {
_isNavigating = true; // 设置导航标志
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecordPage(
record: record,
onSave: (newRecord) async {
if (record != null) {
await _dbService.updateRecord(newRecord);
} else {
await _dbService.insertRecord(newRecord);
}
await _loadRecords();
},
),
maintainState: false,
),
).then((_) {
// 返回时重置检测状态
_resetShakeDetection();
});
}
/// 删除记录
void _deleteRecord(String recordId) async {
await _dbService.deleteRecord(recordId);
await _loadRecords();
}
// 添加月份选择器组件
Widget _buildMonthSelector() {
return GestureDetector(
onTap: () => _showMonthPicker(),
child: Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${_selectedMonth.year}${_selectedMonth.month}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const Icon(Icons.arrow_drop_down),
],
),
),
);
}
// 显示月份选择器
Future<void> _showMonthPicker() async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedMonth,
firstDate: DateTime(2000),
lastDate: DateTime(2100),
initialDatePickerMode: DatePickerMode.year,
);
if (picked != null) {
setState(() {
_selectedMonth = DateTime(picked.year, picked.month);
});
_loadRecords(); // 重新加载数据
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('记账本'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: _buildMonthSelector(),
),
MonthlySummaryCard(
records: records,
isVisible: _isAmountVisible,
onAddRecord: () => _navigateToRecordPage(),
onVisibilityChanged: (value) {
setState(() {
_isAmountVisible = value;
});
},
),
WeeklySpendingChart(records: records),
Expanded(
child: RecordList(
records: records,
onRecordTap: _navigateToRecordPage,
onRecordDelete: _deleteRecord,
),
),
],
),
);
}
@override
void dispose() {
_accelerometerSubscription?.cancel();
super.dispose();
}
}