swing_account/lib/pages/record_page.dart

533 lines
15 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:flutter/services.dart';
import '../models/record.dart';
import '../data/categories.dart';
import 'package:uuid/uuid.dart';
class RecordPage extends StatefulWidget {
final Record? record;
final Function(Record) onSave; // 添加保存回调
const RecordPage({
Key? key,
this.record,
required this.onSave,
}) : super(key: key);
@override
State<RecordPage> createState() => _RecordPageState();
}
class _RecordPageState extends State<RecordPage> with SingleTickerProviderStateMixin {
late TabController _tabController;
String? _selectedCategoryId;
String _note = '';
double _amount = 0.0;
DateTime _selectedDate = DateTime.now();
final _uuid = const Uuid();
String _inputBuffer = '0.0'; // 用于显示的字符串
String? _pendingOperation; // 等待执行的运算符
double? _pendingValue; // 等待计算的第一个值
bool _isNewInput = true; // 是否开始新的输入
@override
void initState() {
super.initState();
// 先初始化 TabController
_tabController = TabController(
length: 2,
vsync: this,
initialIndex: widget.record?.type == RecordType.income ? 1 : 0,
);
_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() {
setState(() {
// 切换类型时选中新类型的第一个分类
_selectedCategoryId = _currentCategories.first.id;
});
}
/// 获取当前记录类型
RecordType get _currentType =>
_tabController.index == 0 ? RecordType.expense : RecordType.income;
/// 获取当前分类列表
List<Category> get _currentCategories =>
_currentType == RecordType.expense ? expenseCategories : incomeCategories;
/// 保存记录
void _saveRecord() {
if (_selectedCategoryId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请选择分类')),
);
return;
}
if (_amount <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入金额')),
);
return;
}
final record = Record(
id: widget.record?.id ?? _uuid.v4(),
type: _currentType,
categoryId: _selectedCategoryId!,
note: _note.isEmpty ? null : _note,
amount: _amount,
createTime: _selectedDate,
);
widget.onSave(record);
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
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '支出'),
Tab(text: '收入'),
],
),
),
body: Column(
children: [
// 分类网格
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
),
itemCount: _currentCategories.length,
itemBuilder: (context, index) {
final category = _currentCategories[index];
final isSelected = category.id == _selectedCategoryId;
return _buildCategoryItem(category, isSelected);
},
),
),
// 底部编辑区域
_buildBottomEditor(),
],
),
);
}
/// 构建分类项
Widget _buildCategoryItem(Category category, bool isSelected) {
return InkWell(
onTap: () => setState(() => _selectedCategoryId = category.id),
child: Container(
decoration: BoxDecoration(
color: isSelected ? Theme.of(context).primaryColor.withOpacity(0.1) : null,
border: Border.all(
color: isSelected ? Theme.of(context).primaryColor : Colors.grey,
),
borderRadius: BorderRadius.circular(8),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
category.icon,
color: isSelected ? Theme.of(context).primaryColor : Colors.grey,
),
const SizedBox(height: 4),
Text(
category.name,
style: TextStyle(
color: isSelected ? Theme.of(context).primaryColor : Colors.grey,
),
),
],
),
),
);
}
/// 构建底部编辑区域
Widget _buildBottomEditor() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 备注和金额
_buildNoteAndAmount(),
// 配置按钮
Row(
children: [
TextButton.icon(
onPressed: () {
// TODO: 实现账户选择
},
icon: const Icon(Icons.account_balance_wallet),
label: const Text('默认账户'),
),
TextButton.icon(
onPressed: () async {
final date = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(2000),
lastDate: DateTime.now(),
);
if (date != null) {
setState(() => _selectedDate = date);
}
},
icon: const Icon(Icons.calendar_today),
label: Text(_formatDate(_selectedDate)),
),
TextButton.icon(
onPressed: () {
// TODO: 实现图片选择
},
icon: const Icon(Icons.photo),
label: const Text('图片'),
),
],
),
// 数字键盘
_buildNumberKeyboard(),
],
),
);
}
/// 构建数字键盘
Widget _buildNumberKeyboard() {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 4,
childAspectRatio: 2,
children: [
_buildKeyboardButton('7'),
_buildKeyboardButton('8'),
_buildKeyboardButton('9'),
_buildKeyboardButton('删除', isFunction: true),
_buildKeyboardButton('4'),
_buildKeyboardButton('5'),
_buildKeyboardButton('6'),
_buildKeyboardButton('+'),
_buildKeyboardButton('1'),
_buildKeyboardButton('2'),
_buildKeyboardButton('3'),
_buildKeyboardButton('-'),
_buildKeyboardButton('again'),
_buildKeyboardButton('0'),
_buildKeyboardButton('.'),
_buildKeyboardButton('保存', isFunction: true),
],
);
}
/// 构建键盘按钮
Widget _buildKeyboardButton(String text, {bool isFunction = false}) {
return TextButton(
onPressed: () => _onKeyboardButtonPressed(text),
child: Text(
text,
style: TextStyle(
fontSize: 20,
color: isFunction ? Theme.of(context).primaryColor : Colors.black,
),
),
);
}
/// 构建底部编辑区域中的备注和金额行
Widget _buildNoteAndAmount() {
return Row(
children: [
Expanded(
child: TextField(
controller: TextEditingController(text: _note), // 使用控制器设置初始值
decoration: const InputDecoration(
hintText: '添加备注',
border: InputBorder.none,
),
onChanged: (value) => setState(() => _note = value),
),
),
Text(
_displayAmount,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
}