Merge remote-tracking branch 'daodaoshi/main'

This commit is contained in:
daodaoshi 2024-12-18 21:05:42 +08:00
commit 9b2ee71519
10 changed files with 890 additions and 50 deletions

54
lib/data/categories.dart Normal file
View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import '../models/record.dart';
///
final List<Category> expenseCategories = [
Category(
id: 'food',
name: '餐饮',
icon: Icons.restaurant,
type: RecordType.expense,
),
Category(
id: 'shopping',
name: '购物',
icon: Icons.shopping_bag,
type: RecordType.expense,
),
Category(
id: 'transport',
name: '交通',
icon: Icons.directions_bus,
type: RecordType.expense,
),
Category(
id: 'entertainment',
name: '娱乐',
icon: Icons.sports_esports,
type: RecordType.expense,
),
// ...
];
///
final List<Category> incomeCategories = [
Category(
id: 'salary',
name: '工资',
icon: Icons.account_balance_wallet,
type: RecordType.income,
),
Category(
id: 'bonus',
name: '奖金',
icon: Icons.card_giftcard,
type: RecordType.income,
),
Category(
id: 'investment',
name: '投资',
icon: Icons.trending_up,
type: RecordType.income,
),
// ...
];

21
lib/data/mock_data.dart Normal file
View File

@ -0,0 +1,21 @@
import '../models/record.dart';
final List<Record> mockRecords = [
Record(
id: '1',
type: RecordType.expense,
categoryId: 'food',
note: '午餐',
amount: 25.0,
createTime: DateTime.now().subtract(const Duration(hours: 2)),
),
Record(
id: '2',
type: RecordType.income,
categoryId: 'salary',
note: '工资',
amount: 5000.0,
createTime: DateTime.now().subtract(const Duration(days: 1)),
),
// ...
];

73
lib/models/record.dart Normal file
View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
///
enum RecordType {
expense, //
income, //
}
///
class Category {
final String id;
final String name;
final IconData icon;
final RecordType type;
final String? parentId; //
const Category({
required this.id,
required this.name,
required this.icon,
required this.type,
this.parentId,
});
}
///
class Record {
final String id;
final RecordType type;
final String categoryId;
final String? note;
final double amount;
final DateTime createTime;
final String? accountId; //
final List<String>? imageUrls; //
Record({
required this.id,
required this.type,
required this.categoryId,
this.note,
required this.amount,
required this.createTime,
this.accountId,
this.imageUrls,
});
// JSON格式便
Map<String, dynamic> toJson() => {
'id': id,
'type': type.index,
'categoryId': categoryId,
'note': note,
'amount': amount,
'createTime': createTime.toIso8601String(),
'accountId': accountId,
'imageUrls': imageUrls,
};
// JSON格式转换回对象
factory Record.fromJson(Map<String, dynamic> json) => Record(
id: json['id'],
type: RecordType.values[json['type']],
categoryId: json['categoryId'],
note: json['note'],
amount: json['amount'],
createTime: DateTime.parse(json['createTime']),
accountId: json['accountId'],
imageUrls: json['imageUrls'] != null
? List<String>.from(json['imageUrls'])
: null,
);
}

View File

@ -2,7 +2,11 @@ import 'package:flutter/material.dart';
import 'package:sensors_plus/sensors_plus.dart'; import 'package:sensors_plus/sensors_plus.dart';
import 'package:vibration/vibration.dart'; import 'package:vibration/vibration.dart';
import 'dart:async'; import 'dart:async';
import '../models/record.dart';
import './record_page.dart'; import './record_page.dart';
import '../widgets/record_list.dart';
import '../data/mock_data.dart'; // 使
import '../services/database_service.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key); const HomePage({Key? key}) : super(key: key);
@ -16,11 +20,23 @@ class _HomePageState extends State<HomePage> {
DateTime? _lastShakeTime; DateTime? _lastShakeTime;
int _shakeCount = 0; int _shakeCount = 0;
bool _isNavigating = false; bool _isNavigating = false;
final _dbService = DatabaseService();
List<Record> records = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initShakeDetection(); _initShakeDetection();
_loadRecords();
}
///
Future<void> _loadRecords() async {
final loadedRecords = await _dbService.getAllRecords();
setState(() {
records = loadedRecords;
records.sort((a, b) => b.createTime.compareTo(a.createTime));
});
} }
/// ///
@ -51,7 +67,7 @@ class _HomePageState extends State<HomePage> {
}); });
} }
/// ///
void _navigateToRecordPageWithVibration() async { void _navigateToRecordPageWithVibration() async {
if (_isNavigating) return; if (_isNavigating) return;
@ -61,10 +77,25 @@ class _HomePageState extends State<HomePage> {
Vibration.vibrate(duration: 200); Vibration.vibrate(duration: 200);
} }
_navigateToRecordPage();
}
///
void _navigateToRecordPage([Record? record]) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const RecordPage(), builder: (context) => RecordPage(
record: record,
onSave: (newRecord) async {
if (record != null) {
await _dbService.updateRecord(newRecord);
} else {
await _dbService.insertRecord(newRecord);
}
await _loadRecords();
},
),
maintainState: false, maintainState: false,
), ),
).then((_) { ).then((_) {
@ -72,26 +103,10 @@ class _HomePageState extends State<HomePage> {
}); });
} }
/// ///
void _navigateToRecordPage() { void _deleteRecord(String recordId) async {
if (_isNavigating) return; await _dbService.deleteRecord(recordId);
await _loadRecords();
_isNavigating = true;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RecordPage(),
maintainState: false,
),
).then((_) {
_isNavigating = false;
});
}
@override
void dispose() {
_accelerometerSubscription?.cancel();
super.dispose();
} }
@override @override
@ -100,17 +115,21 @@ class _HomePageState extends State<HomePage> {
appBar: AppBar( appBar: AppBar(
title: const Text('记账本'), title: const Text('记账本'),
), ),
body: Center( body: RecordList(
child: Column( records: records,
mainAxisAlignment: MainAxisAlignment.center, onRecordTap: _navigateToRecordPage,
children: [ onRecordDelete: _deleteRecord,
ElevatedButton( ),
onPressed: _navigateToRecordPage, floatingActionButton: FloatingActionButton(
child: const Text('记账'), onPressed: () => _navigateToRecordPage(),
), child: const Icon(Icons.add),
],
),
), ),
); );
} }
@override
void dispose() {
_accelerometerSubscription?.cancel();
super.dispose();
}
} }

View File

@ -1,32 +1,322 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../models/record.dart';
import '../data/categories.dart';
import 'package:uuid/uuid.dart';
class RecordPage extends StatefulWidget { class RecordPage extends StatefulWidget {
const RecordPage({Key? key}) : super(key: key); final Record? record;
final Function(Record) onSave; //
const RecordPage({
Key? key,
this.record,
required this.onSave,
}) : super(key: key);
@override @override
State<RecordPage> createState() => _RecordPageState(); State<RecordPage> createState() => _RecordPageState();
} }
class _RecordPageState extends State<RecordPage> { 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();
@override
void initState() {
super.initState();
//
if (widget.record != null) {
_selectedCategoryId = widget.record!.categoryId;
_note = widget.record!.note ?? '';
_amount = widget.record!.amount;
_selectedDate = widget.record!.createTime;
}
_tabController = TabController(
length: 2,
vsync: this,
initialIndex: widget.record?.type == RecordType.income ? 1 : 0,
);
_tabController.addListener(_onTabChanged);
}
///
void _onTabChanged() {
setState(() {
_selectedCategoryId = null; //
});
}
///
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();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return Scaffold(
onWillPop: () async { appBar: AppBar(
Navigator.of(context).pop(); title: TabBar(
return false; controller: _tabController,
}, tabs: const [
child: Scaffold( Tab(text: '支出'),
appBar: AppBar( Tab(text: '收入'),
title: const Text('记账'), ],
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
), ),
body: const Center( ),
child: 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: [
//
Row(
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(
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(_selectedDate == DateTime.now()
? '今天'
: '${_selectedDate.month}${_selectedDate.day}'),
),
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,
),
),
);
}
///
void _onKeyboardButtonPressed(String value) {
switch (value) {
case '删除':
setState(() {
final amountStr = _amount.toStringAsFixed(2);
if (amountStr.length > 1) {
_amount = double.parse(amountStr.substring(0, amountStr.length - 1));
} else {
_amount = 0;
}
});
break;
case '保存':
_saveRecord();
break;
case 'again':
// TODO: again功能
break;
case '+':
case '-':
// TODO:
break;
case '.':
// TODO:
break;
default:
if (_amount == 0) {
setState(() => _amount = double.parse(value));
} else {
setState(() => _amount = double.parse('$_amount$value'));
}
}
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
} }

View File

@ -0,0 +1,94 @@
import 'package:sqflite/sqflite.dart' show Database, openDatabase, getDatabasesPath, ConflictAlgorithm;
import 'package:path/path.dart' as path_helper;
import '../models/record.dart';
class DatabaseService {
static Database? _database;
///
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
///
Future<Database> _initDatabase() async {
final path = await getDatabasesPath();
final dbPath = path_helper.join(path, 'records.db');
return await openDatabase(
dbPath,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE records(
id TEXT PRIMARY KEY,
type INTEGER NOT NULL,
categoryId TEXT NOT NULL,
note TEXT,
amount REAL NOT NULL,
createTime TEXT NOT NULL,
accountId TEXT,
imageUrls TEXT
)
''');
},
);
}
///
Future<void> insertRecord(Record record) async {
final db = await database;
await db.insert(
'records',
record.toJson()..addAll({
'imageUrls': record.imageUrls?.join(','),
}),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
///
Future<void> updateRecord(Record record) async {
final db = await database;
await db.update(
'records',
record.toJson()..addAll({
'imageUrls': record.imageUrls?.join(','),
}),
where: 'id = ?',
whereArgs: [record.id],
);
}
///
Future<void> deleteRecord(String id) async {
final db = await database;
await db.delete(
'records',
where: 'id = ?',
whereArgs: [id],
);
}
///
Future<List<Record>> getAllRecords() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query('records');
return List.generate(maps.length, (i) {
final map = maps[i];
return Record(
id: map['id'],
type: RecordType.values[map['type']],
categoryId: map['categoryId'],
note: map['note'],
amount: map['amount'],
createTime: DateTime.parse(map['createTime']),
accountId: map['accountId'],
imageUrls: map['imageUrls']?.split(','),
);
});
}
}

View File

@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import '../models/record.dart';
import '../data/categories.dart';
class RecordDetailDialog extends StatelessWidget {
final Record record;
final VoidCallback onEdit;
final VoidCallback onDelete;
const RecordDetailDialog({
Key? key,
required this.record,
required this.onEdit,
required this.onDelete,
}) : super(key: key);
///
Category _getCategory(String categoryId) {
return [...expenseCategories, ...incomeCategories]
.firstWhere((c) => c.id == categoryId);
}
@override
Widget build(BuildContext context) {
final category = _getCategory(record.categoryId);
return AlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('记录详情'),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
Navigator.of(context).pop();
onEdit();
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
Navigator.of(context).pop();
onDelete();
},
),
],
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text('分类'),
subtitle: Text(category.name),
leading: Icon(category.icon),
onTap: () {
// TODO:
},
),
if (record.note != null)
ListTile(
title: const Text('备注'),
subtitle: Text(record.note!),
),
ListTile(
title: const Text('金额'),
subtitle: Text(
'${record.type == RecordType.expense ? "-" : "+"}${record.amount.toStringAsFixed(2)}',
style: TextStyle(
color: record.type == RecordType.expense
? Colors.red
: Colors.green,
fontWeight: FontWeight.bold,
),
),
),
ListTile(
title: const Text('时间'),
subtitle: Text(
'${record.createTime.year}-${record.createTime.month}-${record.createTime.day} '
'${record.createTime.hour}:${record.createTime.minute}',
),
),
ListTile(
title: const Text('账户'),
subtitle: const Text('默认账户'),
onTap: () {
// TODO:
},
),
],
),
);
}
}

View File

@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import '../models/record.dart';
import '../data/categories.dart';
import './record_detail_dialog.dart';
class RecordList extends StatelessWidget {
final List<Record> records;
final Function(Record) onRecordTap;
final Function(String) onRecordDelete;
const RecordList({
Key? key,
required this.records,
required this.onRecordTap,
required this.onRecordDelete,
}) : super(key: key);
///
Category _getCategory(String categoryId) {
return [...expenseCategories, ...incomeCategories]
.firstWhere((c) => c.id == categoryId);
}
///
Map<String, List<Record>> _groupByDate() {
final groups = <String, List<Record>>{};
for (final record in records) {
final date = _formatDate(record.createTime);
if (!groups.containsKey(date)) {
groups[date] = [];
}
groups[date]!.add(record);
}
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) {
double expense = 0;
double income = 0;
for (final record in dayRecords) {
if (record.type == RecordType.expense) {
expense += record.amount;
} else {
income += record.amount;
}
}
final parts = <String>[];
if (expense > 0) parts.add('支:¥${expense.toStringAsFixed(2)}');
if (income > 0) parts.add('收:¥${income.toStringAsFixed(2)}');
return parts.join(' ');
}
@override
Widget build(BuildContext context) {
final groups = _groupByDate();
final dates = groups.keys.toList()..sort((a, b) => b.compareTo(a));
return ListView.builder(
itemCount: dates.length,
itemBuilder: (context, index) {
final date = dates[index];
final dayRecords = groups[date]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
date,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(_getDailySummary(dayRecords)),
],
),
),
//
...dayRecords.map((record) {
final category = _getCategory(record.categoryId);
return ListTile(
leading: Icon(category.icon),
title: Text(category.name),
subtitle: record.note != null ? Text(record.note!) : null,
trailing: Text(
'${record.type == RecordType.expense ? "-" : "+"}${record.amount.toStringAsFixed(2)}',
style: TextStyle(
color: record.type == RecordType.expense
? Colors.red
: Colors.green,
fontWeight: FontWeight.bold,
),
),
onTap: () => showDialog(
context: context,
builder: (context) => RecordDetailDialog(
record: record,
onEdit: () => onRecordTap(record),
onDelete: () => onRecordDelete(record.id),
),
),
);
}).toList(),
const Divider(),
],
);
},
);
}
}

View File

@ -41,6 +41,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -89,6 +97,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -153,7 +169,7 @@ packages:
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
path: path:
dependency: transitive dependency: "direct main"
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
@ -205,6 +221,30 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
sqflite:
dependency: "direct main"
description:
name: sqflite
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
url: "https://pub.dev"
source: hosted
version: "2.3.2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -229,6 +269,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -245,6 +293,22 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "0.6.1" version: "0.6.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -295,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"

View File

@ -32,6 +32,9 @@ dependencies:
sdk: flutter sdk: flutter
sensors_plus: ^1.4.1 # 用于检测手机摇晃 sensors_plus: ^1.4.1 # 用于检测手机摇晃
vibration: ^1.8.4 # 添加震动支持 vibration: ^1.8.4 # 添加震动支持
uuid: ^4.3.3 # 添加uuid包用于生成唯一ID
sqflite: ^2.3.2 # 添加 SQLite 支持
path: ^1.8.3 # 用于处理文件路径
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.