272 lines
7.8 KiB
Dart
272 lines
7.8 KiB
Dart
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 '../data/mock_data.dart'; // 暂时使用模拟数据
|
||
import '../services/database_service.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(); // 添加选中月份状态
|
||
|
||
// 定义摇晃检测的常量
|
||
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();
|
||
}
|
||
|
||
/// 重置摇<E7BDAE><E69187>检测状态
|
||
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: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
_buildMonthSelector(),
|
||
Text(
|
||
'总支出: ¥${records.fold(0.0, (sum, record) => sum + record.amount).toStringAsFixed(2)}',
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Expanded(
|
||
child: RecordList(
|
||
records: records,
|
||
onRecordTap: _navigateToRecordPage,
|
||
onRecordDelete: _deleteRecord,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
floatingActionButton: FloatingActionButton(
|
||
onPressed: () => _navigateToRecordPage(),
|
||
child: const Icon(Icons.add),
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_accelerometerSubscription?.cancel();
|
||
super.dispose();
|
||
}
|
||
} |