晃动待优化 键盘小数点逻辑待优化 时间显示ok
This commit is contained in:
parent
211fa195cb
commit
00e5316b52
@ -46,7 +46,7 @@ android {
|
|||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
minSdkVersion flutter.minSdkVersion
|
minSdkVersion flutter.minSdkVersion
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion 33
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<application
|
<application
|
||||||
android:label="swing_account"
|
android:label="摇晃记账"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:enableOnBackInvokedCallback="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@ -18,11 +18,41 @@ class HomePage extends StatefulWidget {
|
|||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
StreamSubscription? _accelerometerSubscription;
|
StreamSubscription? _accelerometerSubscription;
|
||||||
DateTime? _lastShakeTime;
|
DateTime? _lastShakeTime;
|
||||||
int _shakeCount = 0;
|
List<double> _recentXValues = []; // 存储原始x轴加速度值(不取绝对值)
|
||||||
bool _isNavigating = false;
|
bool _isNavigating = false;
|
||||||
final _dbService = DatabaseService();
|
final _dbService = DatabaseService();
|
||||||
List<Record> records = [];
|
List<Record> records = [];
|
||||||
|
|
||||||
|
// 定义摇晃检测的常量
|
||||||
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -30,6 +60,17 @@ class _HomePageState extends State<HomePage> {
|
|||||||
_loadRecords();
|
_loadRecords();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 重置摇晃检测状态
|
||||||
|
void _resetShakeDetection() {
|
||||||
|
_lastShakeTime = null;
|
||||||
|
_recentXValues.clear();
|
||||||
|
_isNavigating = false;
|
||||||
|
|
||||||
|
// 重新初始化传感器监听
|
||||||
|
_accelerometerSubscription?.cancel();
|
||||||
|
_initShakeDetection();
|
||||||
|
}
|
||||||
|
|
||||||
/// 加载记录
|
/// 加载记录
|
||||||
Future<void> _loadRecords() async {
|
Future<void> _loadRecords() async {
|
||||||
final loadedRecords = await _dbService.getAllRecords();
|
final loadedRecords = await _dbService.getAllRecords();
|
||||||
@ -41,30 +82,48 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
/// 初始化摇晃检测
|
/// 初始化摇晃检测
|
||||||
void _initShakeDetection() {
|
void _initShakeDetection() {
|
||||||
_accelerometerSubscription = accelerometerEvents.listen((event) {
|
_accelerometerSubscription = accelerometerEvents.listen(
|
||||||
// 主要检测左右摇晃(x轴)
|
(event) {
|
||||||
if (event.x.abs() > 25) {
|
if (_isNavigating) return; // 如果正在导航,忽略摇晃检测
|
||||||
|
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (_lastShakeTime == null) {
|
|
||||||
_lastShakeTime = now;
|
// 添加新的x轴值(保留正负号)
|
||||||
_shakeCount = 1;
|
_recentXValues.add(event.x);
|
||||||
} else {
|
if (_recentXValues.length > _sampleSize) {
|
||||||
// 缩短有效时间窗口,要求更快的摇晃
|
_recentXValues.removeAt(0);
|
||||||
if (now.difference(_lastShakeTime!) < const Duration(milliseconds: 500)) {
|
|
||||||
_shakeCount++;
|
|
||||||
if (_shakeCount >= 2) {
|
|
||||||
_navigateToRecordPageWithVibration();
|
|
||||||
_shakeCount = 0;
|
|
||||||
_lastShakeTime = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 重置计数
|
|
||||||
_shakeCount = 1;
|
|
||||||
}
|
|
||||||
_lastShakeTime = now;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
// 检查是否处于冷却期
|
||||||
|
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, // 错误时不取消订阅,而是重置
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 带震动的记账页面导航
|
/// 带震动的记账页面导航
|
||||||
@ -82,6 +141,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
/// 导航到记账页面
|
/// 导航到记账页面
|
||||||
void _navigateToRecordPage([Record? record]) {
|
void _navigateToRecordPage([Record? record]) {
|
||||||
|
_isNavigating = true; // 设置导航标志
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@ -99,7 +159,8 @@ class _HomePageState extends State<HomePage> {
|
|||||||
maintainState: false,
|
maintainState: false,
|
||||||
),
|
),
|
||||||
).then((_) {
|
).then((_) {
|
||||||
_isNavigating = false;
|
// 返回时重置检测状态
|
||||||
|
_resetShakeDetection();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import '../models/record.dart';
|
import '../models/record.dart';
|
||||||
import '../data/categories.dart';
|
import '../data/categories.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
@ -24,30 +25,43 @@ class _RecordPageState extends State<RecordPage> with SingleTickerProviderStateM
|
|||||||
double _amount = 0.0;
|
double _amount = 0.0;
|
||||||
DateTime _selectedDate = DateTime.now();
|
DateTime _selectedDate = DateTime.now();
|
||||||
final _uuid = const Uuid();
|
final _uuid = const Uuid();
|
||||||
|
String _inputBuffer = '0.0'; // 用于显示的字符串
|
||||||
|
String? _pendingOperation; // 等待执行的运算符
|
||||||
|
double? _pendingValue; // 等待计算的第一个值
|
||||||
|
bool _isNewInput = true; // 是否开始新的输入
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// 如果是编辑模式,初始化现有记录的数据
|
// 先初始化 TabController
|
||||||
if (widget.record != null) {
|
|
||||||
_selectedCategoryId = widget.record!.categoryId;
|
|
||||||
_note = widget.record!.note ?? '';
|
|
||||||
_amount = widget.record!.amount;
|
|
||||||
_selectedDate = widget.record!.createTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
_tabController = TabController(
|
_tabController = TabController(
|
||||||
length: 2,
|
length: 2,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
initialIndex: widget.record?.type == RecordType.income ? 1 : 0,
|
initialIndex: widget.record?.type == RecordType.income ? 1 : 0,
|
||||||
);
|
);
|
||||||
_tabController.addListener(_onTabChanged);
|
_tabController.addListener(_onTabChanged);
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
if (widget.record != null) {
|
||||||
|
// 编辑模式:初始化现有记录的数据
|
||||||
|
_selectedCategoryId = widget.record!.categoryId;
|
||||||
|
_note = widget.record!.note ?? '';
|
||||||
|
_amount = widget.record!.amount;
|
||||||
|
_selectedDate = widget.record!.createTime;
|
||||||
|
// 设置输入缓冲区为当前金额的格式化字符串
|
||||||
|
_inputBuffer = _formatNumberForDisplay(widget.record!.amount);
|
||||||
|
_isNewInput = false;
|
||||||
|
} else {
|
||||||
|
// 新建模式:默认选中第一个分类
|
||||||
|
_selectedCategoryId = _currentCategories.first.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 标签切换监听
|
/// 标签切换监听
|
||||||
void _onTabChanged() {
|
void _onTabChanged() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedCategoryId = null; // 切换类型时重置选中的分类
|
// 切换类型时选中新类型的第一个分类
|
||||||
|
_selectedCategoryId = _currentCategories.first.id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +101,234 @@ class _RecordPageState extends State<RecordPage> with SingleTickerProviderStateM
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 格式化显示金额
|
||||||
|
String get _displayAmount {
|
||||||
|
if (_pendingOperation != null && _pendingValue != null) {
|
||||||
|
return '$_pendingValue $_pendingOperation ${_isNewInput ? "" : _inputBuffer}';
|
||||||
|
}
|
||||||
|
return _inputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理数字输入
|
||||||
|
void _handleNumber(String digit) {
|
||||||
|
setState(() {
|
||||||
|
if (_isNewInput) {
|
||||||
|
// 新输入时直接替换
|
||||||
|
_inputBuffer = digit;
|
||||||
|
_isNewInput = false;
|
||||||
|
} else if (_inputBuffer == '0' && !_inputBuffer.contains('.')) {
|
||||||
|
// 当前是0且没有小数点时,直接替换0
|
||||||
|
_inputBuffer = digit;
|
||||||
|
} else if (_inputBuffer == '0.0') {
|
||||||
|
// 如果是默认状态,直接替换为新数字
|
||||||
|
_inputBuffer = digit;
|
||||||
|
_isNewInput = false;
|
||||||
|
} else {
|
||||||
|
// 其他情况追加数字
|
||||||
|
_inputBuffer = _inputBuffer + digit;
|
||||||
|
}
|
||||||
|
_amount = double.parse(_inputBuffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理小数点
|
||||||
|
void _handleDecimal() {
|
||||||
|
setState(() {
|
||||||
|
if (!_inputBuffer.contains('.')) {
|
||||||
|
// 如果是默认状态(0.0)或只有0,直接显示0.
|
||||||
|
if (_inputBuffer == '0.0' || _inputBuffer == '0') {
|
||||||
|
_inputBuffer = '0.';
|
||||||
|
_isNewInput = false;
|
||||||
|
} else {
|
||||||
|
// 其他数字直接加小数点
|
||||||
|
_inputBuffer = '$_inputBuffer.';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 已有小数点时震动反馈
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理删除
|
||||||
|
void _handleDelete() {
|
||||||
|
setState(() {
|
||||||
|
if (_pendingOperation != null && !_isNewInput) {
|
||||||
|
// 删除第二个数字
|
||||||
|
if (_inputBuffer.length > 1) {
|
||||||
|
_inputBuffer = _inputBuffer.substring(0, _inputBuffer.length - 1);
|
||||||
|
if (_inputBuffer.isEmpty || _inputBuffer == '0') {
|
||||||
|
_inputBuffer = '0.0';
|
||||||
|
_isNewInput = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_inputBuffer = '0.0';
|
||||||
|
_isNewInput = true;
|
||||||
|
}
|
||||||
|
} else if (_pendingOperation != null) {
|
||||||
|
// 删除运算符
|
||||||
|
_inputBuffer = _pendingValue.toString();
|
||||||
|
_pendingOperation = null;
|
||||||
|
_pendingValue = null;
|
||||||
|
_isNewInput = false;
|
||||||
|
} else {
|
||||||
|
// 删除普通数字
|
||||||
|
if (_inputBuffer.length > 1) {
|
||||||
|
_inputBuffer = _inputBuffer.substring(0, _inputBuffer.length - 1);
|
||||||
|
if (_inputBuffer.isEmpty || _inputBuffer == '0') {
|
||||||
|
_inputBuffer = '0.0';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_inputBuffer = '0.0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_amount = double.parse(_inputBuffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理运算符
|
||||||
|
void _handleOperator(String operator) {
|
||||||
|
// 如果有待处理的运算,先计算结果
|
||||||
|
if (_pendingOperation != null && !_isNewInput) {
|
||||||
|
// 先保存当前运算符,因为计算可能会重置状态
|
||||||
|
final newOperator = operator;
|
||||||
|
_calculateResult();
|
||||||
|
|
||||||
|
// 如果计算导致重置(结果为0),不添加新的运算符
|
||||||
|
if (_inputBuffer == '0.0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算完成后,设置新的运算符
|
||||||
|
setState(() {
|
||||||
|
_pendingOperation = newOperator;
|
||||||
|
_pendingValue = double.parse(_inputBuffer);
|
||||||
|
_isNewInput = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 正常设置运算符
|
||||||
|
setState(() {
|
||||||
|
_pendingOperation = operator;
|
||||||
|
_pendingValue = double.parse(_inputBuffer);
|
||||||
|
_isNewInput = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 计算结果
|
||||||
|
void _calculateResult() {
|
||||||
|
if (_pendingOperation != null && _pendingValue != null && !_isNewInput) {
|
||||||
|
final currentValue = double.parse(_inputBuffer);
|
||||||
|
double result;
|
||||||
|
|
||||||
|
switch (_pendingOperation) {
|
||||||
|
case '+':
|
||||||
|
result = _pendingValue! + currentValue;
|
||||||
|
if (result == 0) {
|
||||||
|
_resetToDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
result = _pendingValue! - currentValue;
|
||||||
|
if (result <= 0) {
|
||||||
|
_resetToDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
// 格式化结果,去掉不必要的小数位
|
||||||
|
_inputBuffer = _formatNumberForDisplay(result);
|
||||||
|
_amount = result;
|
||||||
|
_pendingOperation = null;
|
||||||
|
_pendingValue = null;
|
||||||
|
_isNewInput = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 重置到默认状态
|
||||||
|
void _resetToDefault() {
|
||||||
|
setState(() {
|
||||||
|
_inputBuffer = '0.0';
|
||||||
|
_amount = 0;
|
||||||
|
_pendingOperation = null;
|
||||||
|
_pendingValue = null;
|
||||||
|
_isNewInput = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 格式化数字用于显示
|
||||||
|
String _formatNumberForDisplay(double number) {
|
||||||
|
// 如果是整数,直接返回整数部分
|
||||||
|
if (number % 1 == 0) {
|
||||||
|
return number.toInt().toString();
|
||||||
|
}
|
||||||
|
// 如果是小数,去掉末尾的0
|
||||||
|
String numStr = number.toString();
|
||||||
|
while (numStr.endsWith('0')) {
|
||||||
|
numStr = numStr.substring(0, numStr.length - 1);
|
||||||
|
}
|
||||||
|
if (numStr.endsWith('.')) {
|
||||||
|
numStr = numStr.substring(0, numStr.length - 1);
|
||||||
|
}
|
||||||
|
return numStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 修改键盘按钮处理逻辑
|
||||||
|
void _onKeyboardButtonPressed(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case '删除':
|
||||||
|
_handleDelete();
|
||||||
|
break;
|
||||||
|
case '保存':
|
||||||
|
_calculateResult();
|
||||||
|
_saveRecord();
|
||||||
|
break;
|
||||||
|
case 'again':
|
||||||
|
// TODO: 实现again功能
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
case '-':
|
||||||
|
_handleOperator(value);
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
_handleDecimal();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_handleNumber(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 格式化日期显示
|
||||||
|
String _formatDate(DateTime date) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final dateToCheck = DateTime(date.year, date.month, date.day);
|
||||||
|
final difference = today.difference(dateToCheck).inDays;
|
||||||
|
|
||||||
|
// 检查是否是今天、昨天、前天
|
||||||
|
switch (difference) {
|
||||||
|
case 0:
|
||||||
|
return '今天';
|
||||||
|
case 1:
|
||||||
|
return '昨天';
|
||||||
|
case 2:
|
||||||
|
return '前天';
|
||||||
|
default:
|
||||||
|
// 如果是当年
|
||||||
|
if (date.year == now.year) {
|
||||||
|
return '${date.month}/${date.day}';
|
||||||
|
}
|
||||||
|
// 不是当年
|
||||||
|
return '${date.year}/${date.month}/${date.day}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -175,26 +417,7 @@ class _RecordPageState extends State<RecordPage> with SingleTickerProviderStateM
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// 备注和金额
|
// 备注和金额
|
||||||
Row(
|
_buildNoteAndAmount(),
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: '添加备注',
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
onChanged: (value) => setState(() => _note = value),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'¥${_amount.toStringAsFixed(2)}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// 配置按钮
|
// 配置按钮
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -218,9 +441,7 @@ class _RecordPageState extends State<RecordPage> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.calendar_today),
|
icon: const Icon(Icons.calendar_today),
|
||||||
label: Text(_selectedDate == DateTime.now()
|
label: Text(_formatDate(_selectedDate)),
|
||||||
? '今天'
|
|
||||||
: '${_selectedDate.month}月${_selectedDate.day}日'),
|
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -279,39 +500,29 @@ class _RecordPageState extends State<RecordPage> with SingleTickerProviderStateM
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 处理键盘按钮点击
|
/// 构建底部编辑区域中的备注和金额行
|
||||||
void _onKeyboardButtonPressed(String value) {
|
Widget _buildNoteAndAmount() {
|
||||||
switch (value) {
|
return Row(
|
||||||
case '删除':
|
children: [
|
||||||
setState(() {
|
Expanded(
|
||||||
final amountStr = _amount.toStringAsFixed(2);
|
child: TextField(
|
||||||
if (amountStr.length > 1) {
|
controller: TextEditingController(text: _note), // 使用控制器设置初始值
|
||||||
_amount = double.parse(amountStr.substring(0, amountStr.length - 1));
|
decoration: const InputDecoration(
|
||||||
} else {
|
hintText: '添加备注',
|
||||||
_amount = 0;
|
border: InputBorder.none,
|
||||||
}
|
),
|
||||||
});
|
onChanged: (value) => setState(() => _note = value),
|
||||||
break;
|
),
|
||||||
case '保存':
|
),
|
||||||
_saveRecord();
|
Text(
|
||||||
break;
|
_displayAmount,
|
||||||
case 'again':
|
style: const TextStyle(
|
||||||
// TODO: 实现again功能
|
fontSize: 24,
|
||||||
break;
|
fontWeight: FontWeight.bold,
|
||||||
case '+':
|
),
|
||||||
case '-':
|
),
|
||||||
// TODO: 实现加减功能
|
],
|
||||||
break;
|
);
|
||||||
case '.':
|
|
||||||
// TODO: 实现小数点功能
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (_amount == 0) {
|
|
||||||
setState(() => _amount = double.parse(value));
|
|
||||||
} else {
|
|
||||||
setState(() => _amount = double.parse('$_amount$value'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
49
lib/utils/date_formatter.dart
Normal file
49
lib/utils/date_formatter.dart
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
class DateFormatter {
|
||||||
|
/// 格式化日期显示
|
||||||
|
static String formatDate(DateTime date) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final dateToCheck = DateTime(date.year, date.month, date.day);
|
||||||
|
final difference = today.difference(dateToCheck).inDays;
|
||||||
|
|
||||||
|
// 格式化日期部分
|
||||||
|
String dateStr;
|
||||||
|
if (date.year == now.year) {
|
||||||
|
// 当年日期显示 月-日
|
||||||
|
dateStr = '${_padZero(date.month)}-${_padZero(date.day)}';
|
||||||
|
} else {
|
||||||
|
// 非当年日期显示 年-月-日
|
||||||
|
dateStr = '${date.year}-${_padZero(date.month)}-${_padZero(date.day)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取提示文案
|
||||||
|
String hint;
|
||||||
|
switch (difference) {
|
||||||
|
case 0:
|
||||||
|
hint = '今天';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
hint = '昨天';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
hint = '前天';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// 获取星期几
|
||||||
|
hint = _getWeekDay(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '$dateStr $hint';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 数字补零
|
||||||
|
static String _padZero(int number) {
|
||||||
|
return number.toString().padLeft(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取星期几
|
||||||
|
static String _getWeekDay(DateTime date) {
|
||||||
|
final weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||||
|
return weekDays[date.weekday % 7];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import '../models/record.dart';
|
import '../models/record.dart';
|
||||||
import '../data/categories.dart';
|
import '../data/categories.dart';
|
||||||
import './record_detail_dialog.dart';
|
import './record_detail_dialog.dart';
|
||||||
|
import '../utils/date_formatter.dart';
|
||||||
|
|
||||||
class RecordList extends StatelessWidget {
|
class RecordList extends StatelessWidget {
|
||||||
final List<Record> records;
|
final List<Record> records;
|
||||||
@ -22,23 +23,22 @@ class RecordList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 按日期分组记录
|
/// 按日期分组记录
|
||||||
Map<String, List<Record>> _groupByDate() {
|
Map<DateTime, List<Record>> _groupByDate() {
|
||||||
final groups = <String, List<Record>>{};
|
final groups = <DateTime, List<Record>>{};
|
||||||
for (final record in records) {
|
for (final record in records) {
|
||||||
final date = _formatDate(record.createTime);
|
final dateKey = DateTime(
|
||||||
if (!groups.containsKey(date)) {
|
record.createTime.year,
|
||||||
groups[date] = [];
|
record.createTime.month,
|
||||||
|
record.createTime.day,
|
||||||
|
);
|
||||||
|
if (!groups.containsKey(dateKey)) {
|
||||||
|
groups[dateKey] = [];
|
||||||
}
|
}
|
||||||
groups[date]!.add(record);
|
groups[dateKey]!.add(record);
|
||||||
}
|
}
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 格式化日期
|
|
||||||
String _formatDate(DateTime date) {
|
|
||||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 计算日汇总
|
/// 计算日汇总
|
||||||
String _getDailySummary(List<Record> dayRecords) {
|
String _getDailySummary(List<Record> dayRecords) {
|
||||||
double expense = 0;
|
double expense = 0;
|
||||||
@ -60,13 +60,15 @@ class RecordList extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final groups = _groupByDate();
|
final groups = _groupByDate();
|
||||||
final dates = groups.keys.toList()..sort((a, b) => b.compareTo(a));
|
final dates = groups.keys.toList()
|
||||||
|
..sort((a, b) => b.compareTo(a));
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: dates.length,
|
itemCount: dates.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final date = dates[index];
|
final dateKey = dates[index];
|
||||||
final dayRecords = groups[date]!;
|
final dayRecords = groups[dateKey]!;
|
||||||
|
final formattedDate = DateFormatter.formatDate(dateKey);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -78,13 +80,17 @@ class RecordList extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
date,
|
formattedDate,
|
||||||
style: const TextStyle(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 16,
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_getDailySummary(dayRecords),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.grey[600],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(_getDailySummary(dayRecords)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -359,4 +359,4 @@ packages:
|
|||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.2.5 <4.0.0"
|
dart: ">=3.2.5 <4.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.7.0"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user