页面改名字

This commit is contained in:
ddshi 2025-01-03 14:00:51 +08:00
parent b665409d65
commit b479dbe519
4 changed files with 760 additions and 491 deletions

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import './pages/home_page.dart';
import './pages/record_page.dart';
import './pages/statistics_page.dart';
import './pages/profile_page.dart';
@ -38,7 +38,7 @@ class _MainPageState extends State<MainPage> {
//
final List<Widget> _pages = [
const HomePage(),
const RecordPage(),
const StatisticsPage(),
const ProfilePage(),
];

532
lib/pages/edit_page.dart Normal file
View File

@ -0,0 +1,532 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/record.dart';
import '../data/categories.dart';
import 'package:uuid/uuid.dart';
import 'package:decimal/decimal.dart';
class EditPage extends StatefulWidget {
final Record? record;
final Function(Record) onSave; //
const EditPage({
Key? key,
this.record,
required this.onSave,
}) : super(key: key);
@override
State<EditPage> createState() => _EditPageState();
}
class _EditPageState extends State<EditPage>
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; //
String? _pendingValue; //
String? _inputValue; //
final d = (String s) => Decimal.parse(s);
@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;
//
_inputValue = _formatNumberForDisplay(widget.record!.amount);
} 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 => CategoryConfig.getCategoriesByType(_currentType);
///
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('输入金额请大于0')),
);
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 ||
_inputValue != null) {
_inputBuffer =
'${_pendingValue ?? ""} ${_pendingOperation ?? ""} ${_inputValue ?? ""}';
} else {
_inputBuffer = '0.0';
}
return _inputBuffer;
}
///
void _handleNumber(String digit) {
setState(() {
if (_inputValue == '0' || _inputValue == null) {
// 0
_inputValue = digit;
} else {
//
_inputValue = _inputValue! + digit;
}
});
}
///
void _handleDecimal() {
setState(() {
if (_inputValue == null || _inputValue == '0') {
_inputValue = '0.';
} else if (_inputValue!.contains('.')) {
HapticFeedback.heavyImpact();
} else {
_inputValue = '$_inputValue.';
}
});
}
///
void _handleDelete() {
setState(() {
if (_inputValue != null) {
if (_inputValue!.length > 1) {
_inputValue = _inputValue!.substring(0, _inputValue!.length - 1);
} else {
_inputValue = null;
}
} else if (_pendingOperation != null) {
_pendingOperation = null;
_inputValue = _pendingValue;
_pendingValue = null;
} else if (_pendingValue != null) {
if (_pendingValue!.length > 1) {
_pendingValue =
_pendingValue!.substring(0, _pendingValue!.length - 1);
} else {
_pendingValue = null;
}
} else {
_pendingValue = null;
}
});
}
///
void _handleOperator(String operator) {
//
if (_pendingOperation != null) {
//
final newOperator = operator;
_calculateResult();
_pendingValue = _inputValue;
_inputValue = null;
//
setState(() {
// 0
if (_inputBuffer == '0.0') {
return;
} else {
_pendingOperation = newOperator;
}
});
} else if (_inputValue != null ) {
//
setState(() {
_pendingOperation = operator;
_pendingValue = _inputValue!;
_inputValue = null;
});
} else {
HapticFeedback.heavyImpact();
}
}
///
void _calculateResult() {
double result = 0;
if(_pendingValue == null && _inputValue== null){
return;
}
else if (_pendingOperation != null &&
_pendingValue != null &&
_inputValue != null) {
switch (_pendingOperation) {
case '+':
result = (d(_pendingValue!) + d(_inputValue!)).toDouble();
if (result == 0) {
_resetToDefault();
return;
}
break;
case '-':
result = (d(_pendingValue!) - d(_inputValue!)).toDouble();
if (result <= 0) {
_resetToDefault();
return;
}
break;
default:
return;
}
} else {
result = double.parse(_inputValue ?? _pendingValue!);
}
setState(() {
//
_pendingValue = null;
_amount = result;
_pendingOperation = null;
_inputValue = _formatNumberForDisplay(result);
});
}
///
void _resetToDefault() {
setState(() {
_inputBuffer = '0.0';
_amount = 0;
_pendingOperation = null;
_pendingValue = null;
_inputValue = null;
});
}
///
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) {
if (_inputBuffer.length >= 20 && value != '删除' && value != '保存'&& value != 'again'){
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('输入数字过大')),
);
return;
}
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();
}
}

View File

@ -3,7 +3,7 @@ import 'package:sensors_plus/sensors_plus.dart';
import 'package:vibration/vibration.dart';
import 'dart:async';
import '../models/record.dart';
import './record_page.dart';
import 'edit_page.dart';
import '../widgets/record_list.dart';
import '../services/database_service.dart';
import '../widgets/weekly_spending_chart.dart';
@ -152,11 +152,11 @@ class _HomePageState extends State<HomePage> {
///
void _navigateToRecordPage([Record? record]) {
_isNavigating = true; //
_isNavigating = true;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecordPage(
builder: (context) => RecordEditPage(
record: record,
onSave: (newRecord) async {
if (record != null) {

View File

@ -1,327 +1,228 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sensors_plus/sensors_plus.dart';
import 'package:vibration/vibration.dart';
import 'dart:async';
import '../models/record.dart';
import '../data/categories.dart';
import 'package:uuid/uuid.dart';
import 'package:decimal/decimal.dart';
import 'edit_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 RecordPage extends StatefulWidget {
final Record? record;
final Function(Record) onSave; //
const RecordPage({
Key? key,
this.record,
required this.onSave,
}) : super(key: key);
const RecordPage({Key? key}) : 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; //
String? _pendingValue; //
String? _inputValue; //
class _RecordPageState extends State<RecordPage> {
StreamSubscription? _accelerometerSubscription;
DateTime? _lastShakeTime;
final List<double> _recentXValues = []; // x轴加速度值
bool _isNavigating = false;
final _dbService = DatabaseService();
List<Record> records = [];
DateTime _selectedMonth = DateTime.now(); //
bool _isAmountVisible = true; //
final d = (String s) => Decimal.parse(s);
//
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();
// 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;
//
_inputValue = _formatNumberForDisplay(widget.record!.amount);
} else {
//
_selectedCategoryId = _currentCategories.first.id;
}
_initShakeDetection();
_loadRecords();
}
///
void _onTabChanged() {
setState(() {
//
_selectedCategoryId = _currentCategories.first.id;
});
}
///
RecordType get _currentType =>
_tabController.index == 0 ? RecordType.expense : RecordType.income;
///
List<Category> get _currentCategories => CategoryConfig.getCategoriesByType(_currentType);
///
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('输入金额请大于0')),
);
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 ||
_inputValue != null) {
_inputBuffer =
'${_pendingValue ?? ""} ${_pendingOperation ?? ""} ${_inputValue ?? ""}';
} else {
_inputBuffer = '0.0';
}
return _inputBuffer;
}
///
void _handleNumber(String digit) {
setState(() {
if (_inputValue == '0' || _inputValue == null) {
// 0
_inputValue = digit;
} else {
//
_inputValue = _inputValue! + digit;
}
});
}
///
void _handleDecimal() {
setState(() {
if (_inputValue == null || _inputValue == '0') {
_inputValue = '0.';
} else if (_inputValue!.contains('.')) {
HapticFeedback.heavyImpact();
} else {
_inputValue = '$_inputValue.';
}
});
}
///
void _handleDelete() {
setState(() {
if (_inputValue != null) {
if (_inputValue!.length > 1) {
_inputValue = _inputValue!.substring(0, _inputValue!.length - 1);
} else {
_inputValue = null;
}
} else if (_pendingOperation != null) {
_pendingOperation = null;
_inputValue = _pendingValue;
_pendingValue = null;
} else if (_pendingValue != null) {
if (_pendingValue!.length > 1) {
_pendingValue =
_pendingValue!.substring(0, _pendingValue!.length - 1);
} else {
_pendingValue = null;
}
} else {
_pendingValue = null;
}
});
}
///
void _handleOperator(String operator) {
//
if (_pendingOperation != null) {
//
final newOperator = operator;
_calculateResult();
_pendingValue = _inputValue;
_inputValue = null;
//
setState(() {
// 0
if (_inputBuffer == '0.0') {
return;
} else {
_pendingOperation = newOperator;
}
});
} else if (_inputValue != null ) {
//
setState(() {
_pendingOperation = operator;
_pendingValue = _inputValue!;
_inputValue = null;
});
} else {
HapticFeedback.heavyImpact();
}
}
///
void _calculateResult() {
double result = 0;
if(_pendingValue == null && _inputValue== null){
return;
}
else if (_pendingOperation != null &&
_pendingValue != null &&
_inputValue != null) {
///
void _resetShakeDetection() {
_lastShakeTime = null;
_recentXValues.clear();
_isNavigating = false;
switch (_pendingOperation) {
case '+':
result = (d(_pendingValue!) + d(_inputValue!)).toDouble();
if (result == 0) {
_resetToDefault();
return;
}
break;
case '-':
result = (d(_pendingValue!) - d(_inputValue!)).toDouble();
if (result <= 0) {
_resetToDefault();
return;
}
break;
default:
return;
}
} else {
result = double.parse(_inputValue ?? _pendingValue!);
}
setState(() {
//
_pendingValue = null;
_amount = result;
_pendingOperation = null;
_inputValue = _formatNumberForDisplay(result);
});
//
_accelerometerSubscription?.cancel();
_initShakeDetection();
}
///
void _resetToDefault() {
///
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(() {
_inputBuffer = '0.0';
_amount = 0;
_pendingOperation = null;
_pendingValue = null;
_inputValue = null;
records = loadedRecords;
records.sort((a, b) => b.createTime.compareTo(a.createTime));
});
}
///
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) {
if (_inputBuffer.length >= 20 && value != '删除' && value != '保存'&& value != 'again'){
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('输入数字过大')),
);
return;
}
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}';
///
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);
}
//
return '${date.year}/${date.month}/${date.day}';
//
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) => EditPage(
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(); //
}
}
@ -329,204 +230,40 @@ class _RecordPageState extends State<RecordPage>
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '支出'),
Tab(text: '收入'),
],
),
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: 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);
},
child: RecordList(
records: records,
onRecordTap: _navigateToRecordPage,
onRecordDelete: _deleteRecord,
),
),
//
_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();
_accelerometerSubscription?.cancel();
super.dispose();
}
}
}