@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
}
|
||||
@ -1,11 +1,7 @@
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
| 姓名 | 知士荟 | 头歌 | Github |
|
||||
| ------ | -------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------- |
|
||||
| 姓名 | 知士荟 | 头歌 | Github |
|
||||
| ------ | ----------------------------------------------------- | ------------------------------------------------ | ---------------------------------------- |
|
||||
| 葛兴海 | [@葛兴海](https://www.learnerhub.net/#/users/12147/docs) | [@葛兴海](https://code.educoder.net/user/ps9up4ig6) | [@Sheeet](https://github.com/icesheeet) |
|
||||
| 庞浩 | [@庞浩](https://www.learnerhub.net/#/users/12027/docs) | [@庞浩](https://code.educoder.net/user/mbhvfy6mx) | |
|
||||
| 庞浩 | [@庞浩](https://www.learnerhub.net/#/users/12027/docs) | [@庞浩](https://code.educoder.net/user/mbhvfy6mx) | |
|
||||
| 卫俊钢 | [@卫俊钢](https://www.learnerhub.net/#/users/12144/docs) | [@卫俊钢](https://www.educoder.net/users/p2jf6ytqz) | [@JungangWei](https://github.com/githubwjg) |
|
||||
| 邹兴云 | [@邹兴云](https://www.learnerhub.net/#/users/12026/docs) | [@邹兴云](https://www.educoder.net/users/p8fjyvg3u) | |
|
||||
| 蔡玉祥 | [@蔡玉祥](https://www.learnerhub.net/#/users/12015/docs) | [@蔡玉祥](https://www.educoder.net/users/mszfy297n) | |
|
||||
>>>>>>> gxh_branch
|
||||
|
||||
| 邹兴云 | [@邹兴云](https://www.learnerhub.net/#/users/12026/docs) | [@邹兴云](https://www.educoder.net/users/p8fjyvg3u) | |
|
||||
| 蔡玉祥 | [@蔡玉祥](https://www.learnerhub.net/#/users/12015/docs) | [@蔡玉祥](https://www.educoder.net/users/mszfy297n) | |
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,2 +1,2 @@
|
||||
#Tue Oct 24 13:39:52 CST 2023
|
||||
gradle.version=7.4.2
|
||||
#Sat Nov 04 11:50:30 CST 2023
|
||||
gradle.version=7.5
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.example.timemanage
|
||||
package com.example.timemaneger
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
@ -1,2 +1,5 @@
|
||||
sdk.dir=C:\\Users\\26538\\AppData\\Local\\Android\\sdk
|
||||
flutter.sdk=F:\\study\\school\\ruanjian\\flutter\\flutter
|
||||
sdk.dir=C:\\Users\\28749\\AppData\\Local\\Android\\sdk
|
||||
flutter.sdk=C:\\flutter
|
||||
flutter.buildMode=debug
|
||||
flutter.versionName=1.0.0
|
||||
flutter.versionCode=1
|
||||
@ -1,11 +1,7 @@
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
| 姓名 | 知士荟 | 头歌 | Github |
|
||||
| ------ | -------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------- |
|
||||
| 姓名 | 知士荟 | 头歌 | Github |
|
||||
| ------ | ----------------------------------------------------- | ------------------------------------------------ | ---------------------------------------- |
|
||||
| 葛兴海 | [@葛兴海](https://www.learnerhub.net/#/users/12147/docs) | [@葛兴海](https://code.educoder.net/user/ps9up4ig6) | [@Sheeet](https://github.com/icesheeet) |
|
||||
| 庞浩 | [@庞浩](https://www.learnerhub.net/#/users/12027/docs) | [@庞浩](https://code.educoder.net/user/mbhvfy6mx) | |
|
||||
| 庞浩 | [@庞浩](https://www.learnerhub.net/#/users/12027/docs) | [@庞浩](https://code.educoder.net/user/mbhvfy6mx) | |
|
||||
| 卫俊钢 | [@卫俊钢](https://www.learnerhub.net/#/users/12144/docs) | [@卫俊钢](https://www.educoder.net/users/p2jf6ytqz) | [@JungangWei](https://github.com/githubwjg) |
|
||||
| 邹兴云 | [@邹兴云](https://www.learnerhub.net/#/users/12026/docs) | [@邹兴云](https://www.educoder.net/users/p8fjyvg3u) | |
|
||||
| 蔡玉祥 | [@蔡玉祥](https://www.learnerhub.net/#/users/12015/docs) | [@蔡玉祥](https://www.educoder.net/users/mszfy297n) | |
|
||||
>>>>>>> gxh_branch
|
||||
|
||||
| 邹兴云 | [@邹兴云](https://www.learnerhub.net/#/users/12026/docs) | [@邹兴云](https://www.educoder.net/users/p8fjyvg3u) | |
|
||||
| 蔡玉祥 | [@蔡玉祥](https://www.learnerhub.net/#/users/12015/docs) | [@蔡玉祥](https://www.educoder.net/users/mszfy297n) | |
|
||||
|
||||
Binary file not shown.
@ -1,44 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:timemanage/model/project.dart';
|
||||
import 'package:timemanage/model/timer_entry.dart';
|
||||
|
||||
abstract class DataProvider {
|
||||
Future<Project> createProject({required String name, Color? colour});
|
||||
Future<List<Project>> listProjects();
|
||||
Future<void> editProject(Project project);
|
||||
Future<void> deleteProject(Project project);
|
||||
Future<TimerEntry> createTimer({
|
||||
String? description,
|
||||
int? projectID,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime,
|
||||
});
|
||||
Future<List<TimerEntry>> listTimers();
|
||||
Future<void> editTimer(TimerEntry timer);
|
||||
Future<void> deleteTimer(TimerEntry timer);
|
||||
|
||||
Future<void> import(DataProvider other) async {
|
||||
List<TimerEntry> otherEntries = await other.listTimers();
|
||||
List<Project> otherProjects = await other.listProjects();
|
||||
|
||||
List<Project> newOtherProjects = await Stream.fromIterable(otherProjects)
|
||||
.asyncMap((event) => createProject(name: event.name))
|
||||
.toList();
|
||||
|
||||
for (TimerEntry otherEntry in otherEntries) {
|
||||
int projectOffset = otherProjects
|
||||
.indexWhere((element) => element.id == otherEntry.projectID);
|
||||
int? projectID;
|
||||
if (projectOffset >= 0) {
|
||||
projectID = newOtherProjects[projectOffset].id;
|
||||
}
|
||||
|
||||
await createTimer(
|
||||
description: otherEntry.description,
|
||||
projectID: projectID,
|
||||
startTime: otherEntry.startTime,
|
||||
endTime: otherEntry.endTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,259 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:timemanage/db/data_provider.dart';
|
||||
import 'package:timemanage/model/timer_entry.dart';
|
||||
import 'package:timemanage/model/project.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:xdg_directories/xdg_directories.dart';
|
||||
|
||||
class DatabaseProvider extends DataProvider {
|
||||
final Database _db;
|
||||
static const int _dbVersion = 4;
|
||||
|
||||
DatabaseProvider(this._db);
|
||||
|
||||
Future<void> close() async {
|
||||
await _db.close();
|
||||
}
|
||||
|
||||
static void _onConfigure(Database db) async {
|
||||
await db.execute("PRAGMA foreign_keys = OFF");
|
||||
}
|
||||
|
||||
static void _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
create table if not exists projects(
|
||||
id integer not null primary key autoincrement,
|
||||
name text not null,
|
||||
colour int not null,
|
||||
archived boolean not null default 0
|
||||
)
|
||||
''');
|
||||
await db.execute('''
|
||||
create table if not exists timers(
|
||||
id integer not null primary key autoincrement,
|
||||
project_id integer default null,
|
||||
description text not null,
|
||||
start_time int not null,
|
||||
end_time int default null,
|
||||
notes text default null,
|
||||
foreign key(project_id) references projects(id) on delete set null
|
||||
)
|
||||
''');
|
||||
await db.execute('''
|
||||
create index if not exists timers_start_time on timers(start_time)
|
||||
''');
|
||||
}
|
||||
|
||||
static void _onUpgrade(Database db, int version, int newVersion) async {
|
||||
if (version < 2) {
|
||||
await db.execute('''
|
||||
alter table projects add column archived boolean not null default false
|
||||
''');
|
||||
}
|
||||
if (version < 3) {
|
||||
await db.execute('''
|
||||
alter table timers add column notes text default null
|
||||
''');
|
||||
}
|
||||
if (version < 4) {
|
||||
// fix the bug of the default value being `false` for project archives instead of `0`.
|
||||
// `false` works fine on sqlite >= 3.23.0. Unfortunately, some Android phones still have
|
||||
// ancient sqlite versions, so to them `false` is a string rather than an integer with
|
||||
// value `0`
|
||||
Batch b = db.batch();
|
||||
b.execute('''
|
||||
create table projects_tmp(
|
||||
id integer not null primary key autoincrement,
|
||||
name text not null,
|
||||
colour int not null,
|
||||
archived boolean not null default 0
|
||||
)
|
||||
''');
|
||||
b.execute("insert into projects_tmp select * from projects");
|
||||
b.execute("drop table projects");
|
||||
b.execute('''
|
||||
create table projects(
|
||||
id integer not null primary key autoincrement,
|
||||
name text not null,
|
||||
colour int not null,
|
||||
archived boolean not null default 0
|
||||
)
|
||||
''');
|
||||
b.execute('''
|
||||
insert into projects select id, name, colour,
|
||||
case archived
|
||||
when 'false' then 0
|
||||
when 'true' then 1
|
||||
when '0' then 0
|
||||
when '1' then 1
|
||||
when 0 then 0
|
||||
when 1 then 1
|
||||
else 0
|
||||
end as archived
|
||||
from projects_tmp
|
||||
''');
|
||||
b.execute("drop table projects_tmp");
|
||||
await b.commit(noResult: true);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<DatabaseProvider> open(String path) async {
|
||||
// open the database
|
||||
Database db = await openDatabase(path,
|
||||
onConfigure: _onConfigure,
|
||||
onCreate: _onCreate,
|
||||
onUpgrade: _onUpgrade,
|
||||
version: _dbVersion);
|
||||
await db.execute("PRAGMA foreign_keys = ON");
|
||||
DatabaseProvider repo = DatabaseProvider(db);
|
||||
|
||||
return repo;
|
||||
}
|
||||
|
||||
/// the c in crud
|
||||
@override
|
||||
Future<Project> createProject(
|
||||
{required String name, Color? colour, bool? archived}) async {
|
||||
colour ??= Color.fromARGB(255, 60, 108, 186);
|
||||
archived ??= false;
|
||||
|
||||
int id = await _db.rawInsert(
|
||||
"insert into projects(name, colour, archived) values(?, ?, ?)",
|
||||
<dynamic>[name, colour.value, archived ? 1 : 0]);
|
||||
return Project(id: id, name: name, colour: colour, archived: archived);
|
||||
}
|
||||
|
||||
/// the r in crud
|
||||
@override
|
||||
Future<List<Project>> listProjects() async {
|
||||
List<Map<String, dynamic>> rawProjects = await _db.rawQuery('''
|
||||
select id, name, colour,
|
||||
case archived
|
||||
when 'false' then 0
|
||||
when 'true' then 1
|
||||
when '0' then 0
|
||||
when '1' then 1
|
||||
when 0 then 0
|
||||
when 1 then 1
|
||||
else 0
|
||||
end as archived
|
||||
from projects order by name asc
|
||||
''');
|
||||
return rawProjects
|
||||
.map((Map<String, dynamic> row) => Project(
|
||||
id: row["id"] as int,
|
||||
name: row["name"] as String,
|
||||
colour: Color(row["colour"] as int),
|
||||
archived: (row["archived"] as int?) == 1))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// the u in crud
|
||||
@override
|
||||
Future<void> editProject(Project project) async {
|
||||
int rows = await _db.rawUpdate(
|
||||
"update projects set name=?, colour=?, archived=? where id=?",
|
||||
<dynamic>[
|
||||
project.name,
|
||||
project.colour,
|
||||
project.archived ? 1 : 0,
|
||||
project.id
|
||||
]);
|
||||
assert(rows == 1);
|
||||
}
|
||||
|
||||
/// the d in crud
|
||||
@override
|
||||
Future<void> deleteProject(Project project) async {
|
||||
await _db
|
||||
.rawDelete("delete from projects where id=?", <dynamic>[project.id]);
|
||||
}
|
||||
|
||||
/// the c in crud
|
||||
@override
|
||||
Future<TimerEntry> createTimer(
|
||||
{String? description,
|
||||
int? projectID,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime,
|
||||
String? notes}) async {
|
||||
int st = startTime?.millisecondsSinceEpoch ??
|
||||
DateTime.now().millisecondsSinceEpoch;
|
||||
int? et = endTime?.millisecondsSinceEpoch;
|
||||
int id = await _db.rawInsert(
|
||||
"insert into timers(project_id, description, start_time, end_time, notes) values(?, ?, ?, ?, ?)",
|
||||
<dynamic>[projectID, description, st, et, notes]);
|
||||
return TimerEntry(
|
||||
id: id,
|
||||
description: description,
|
||||
projectID: projectID,
|
||||
startTime: DateTime.fromMillisecondsSinceEpoch(st),
|
||||
endTime: endTime,
|
||||
notes: notes);
|
||||
}
|
||||
|
||||
/// the r in crud
|
||||
@override
|
||||
Future<List<TimerEntry>> listTimers() async {
|
||||
List<Map<String, dynamic>> rawTimers = await _db.rawQuery(
|
||||
"select id, project_id, description, start_time, end_time, notes from timers order by start_time asc");
|
||||
return rawTimers
|
||||
.map((Map<String, dynamic> row) => TimerEntry(
|
||||
id: row["id"] as int,
|
||||
projectID: row["project_id"] as int?,
|
||||
description: row["description"] as String?,
|
||||
startTime:
|
||||
DateTime.fromMillisecondsSinceEpoch(row["start_time"] as int),
|
||||
endTime: row["end_time"] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(row["end_time"] as int)
|
||||
: null,
|
||||
notes: row["notes"] as String?))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// the u in crud
|
||||
@override
|
||||
Future<void> editTimer(TimerEntry timer) async {
|
||||
int st = timer.startTime.millisecondsSinceEpoch;
|
||||
int? et = timer.endTime?.millisecondsSinceEpoch;
|
||||
await _db.rawUpdate(
|
||||
"update timers set project_id=?, description=?, start_time=?, end_time=?, notes=? where id=?",
|
||||
<dynamic>[
|
||||
timer.projectID,
|
||||
timer.description,
|
||||
st,
|
||||
et,
|
||||
timer.notes,
|
||||
timer.id
|
||||
]);
|
||||
}
|
||||
|
||||
/// the d in crud
|
||||
@override
|
||||
Future<void> deleteTimer(TimerEntry timer) async {
|
||||
await _db.rawDelete("delete from timers where id=?", <dynamic>[timer.id]);
|
||||
}
|
||||
|
||||
static Future<File> getDatabaseFile() async {
|
||||
final dbPath =
|
||||
(Platform.isLinux) ? dataHome.path : await getDatabasesPath();
|
||||
return File(p.join(dbPath, 'timecop.db'));
|
||||
}
|
||||
|
||||
static Future<bool> isValidDatabaseFile(String path) async {
|
||||
try {
|
||||
Database db = await openDatabase(path, readOnly: true);
|
||||
await db.rawQuery(
|
||||
"select id, name, colour, archived from projects order by name asc limit 1");
|
||||
await db.rawQuery(
|
||||
"select id, project_id, description, start_time, end_time, notes from timers order by start_time asc limit 1");
|
||||
await db.close();
|
||||
return true;
|
||||
} on Exception catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,166 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:timemanage/db/data_provider.dart';
|
||||
import 'package:timemanage/model/project.dart';
|
||||
import 'package:timemanage/model/timer_entry.dart';
|
||||
import 'dart:math';
|
||||
|
||||
class MockDataProvider extends DataProvider {
|
||||
String localeKey;
|
||||
static final Map<String, Map<String, String>> l10n = {
|
||||
"en": {
|
||||
"administration": "Administration",
|
||||
"mockups": "Mockups",
|
||||
"ui-layout": "UI Layout",
|
||||
"coffee": "Coffee",
|
||||
"app-development": "App development"
|
||||
},
|
||||
"zh-CN": {
|
||||
"ui-layout": "UI布局",
|
||||
"administration": "管理",
|
||||
"coffee": "咖啡",
|
||||
"mockups": "样机",
|
||||
"app-development": "应用程式开发",
|
||||
},
|
||||
};
|
||||
|
||||
MockDataProvider(Locale locale) : localeKey = locale.languageCode {
|
||||
if (locale.languageCode == "zh") {
|
||||
localeKey += "-${locale.countryCode!}";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Project>> listProjects() async {
|
||||
return <Project>[
|
||||
Project(
|
||||
id: 1,
|
||||
name: "Time Manager",
|
||||
colour: Colors.cyan[600]!,
|
||||
archived: false),
|
||||
Project(
|
||||
id: 2,
|
||||
name: l10n[localeKey]!["administration"]!,
|
||||
colour: Colors.pink[600]!,
|
||||
archived: false,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<TimerEntry>> listTimers() async {
|
||||
int tid = 1;
|
||||
Random rand = Random(42);
|
||||
|
||||
// start with running timers
|
||||
List<TimerEntry> entries = [
|
||||
TimerEntry(
|
||||
id: tid++,
|
||||
description: l10n[localeKey]!["ui-layout"],
|
||||
projectID: 1,
|
||||
startTime: DateTime.now()
|
||||
.subtract(const Duration(hours: 2, minutes: 10, seconds: 1)),
|
||||
endTime: null,
|
||||
),
|
||||
TimerEntry(
|
||||
id: tid++,
|
||||
description: l10n[localeKey]!["coffee"],
|
||||
projectID: 2,
|
||||
startTime:
|
||||
DateTime.now().subtract(const Duration(minutes: 3, seconds: 14)),
|
||||
endTime: null,
|
||||
),
|
||||
];
|
||||
|
||||
// add some fake March stuff
|
||||
for (int w = 0; w < 4; w++) {
|
||||
for (int d = 0; d < 5; d++) {
|
||||
String descriptionKey;
|
||||
double r = rand.nextDouble();
|
||||
if (r <= 0.2) {
|
||||
descriptionKey = 'mockups';
|
||||
} else if (r <= 0.5) {
|
||||
descriptionKey = 'ui-layout';
|
||||
} else {
|
||||
descriptionKey = 'app-development';
|
||||
}
|
||||
|
||||
entries.add(TimerEntry(
|
||||
id: tid++,
|
||||
description: l10n[localeKey]![descriptionKey],
|
||||
projectID: 1,
|
||||
startTime: DateTime(
|
||||
2020,
|
||||
3,
|
||||
(w * 7) + d + 2,
|
||||
rand.nextInt(3) + 8,
|
||||
rand.nextInt(60),
|
||||
rand.nextInt(60),
|
||||
),
|
||||
endTime: DateTime(
|
||||
2020,
|
||||
3,
|
||||
(w * 7) + d + 2,
|
||||
rand.nextInt(3) + 13,
|
||||
rand.nextInt(60),
|
||||
rand.nextInt(60),
|
||||
),
|
||||
));
|
||||
|
||||
entries.add(TimerEntry(
|
||||
id: tid++,
|
||||
description: l10n[localeKey]!['administration'],
|
||||
projectID: 2,
|
||||
startTime: DateTime(
|
||||
2020,
|
||||
3,
|
||||
(w * 7) + d + 2,
|
||||
14,
|
||||
rand.nextInt(30),
|
||||
rand.nextInt(60),
|
||||
),
|
||||
endTime: DateTime(
|
||||
2020,
|
||||
3,
|
||||
(w * 7) + d + 2,
|
||||
15,
|
||||
rand.nextInt(30),
|
||||
rand.nextInt(60),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Project> createProject(
|
||||
{required String name, Color? colour, bool? archived}) async {
|
||||
return Project(
|
||||
id: -1, name: name, colour: colour!, archived: archived ?? false);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> editProject(Project project) async {}
|
||||
@override
|
||||
Future<void> deleteProject(Project project) async {}
|
||||
@override
|
||||
Future<TimerEntry> createTimer(
|
||||
{String? description,
|
||||
int? projectID,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime}) async {
|
||||
DateTime st = startTime ?? DateTime.now();
|
||||
return TimerEntry(
|
||||
id: -1,
|
||||
description: description,
|
||||
projectID: projectID,
|
||||
startTime: st,
|
||||
endTime: endTime,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> editTimer(TimerEntry timer) async {}
|
||||
@override
|
||||
Future<void> deleteTimer(TimerEntry timer) async {}
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:timemanage/model/timer_entry.dart';
|
||||
|
||||
class TimerEntryDatabase {
|
||||
static final TimerEntryDatabase instance = TimerEntryDatabase._init();
|
||||
|
||||
static Database? _database;
|
||||
|
||||
TimerEntryDatabase._init();
|
||||
|
||||
/// 以上暂时理解为固定格式
|
||||
|
||||
// 获取数据库
|
||||
Future<Database> get database async {
|
||||
if (_database != null) {
|
||||
return _database!;
|
||||
}
|
||||
|
||||
_database = await _initDB('timer_entry.db');
|
||||
return _database!;
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
Future<Database> _initDB(String filePath) async {
|
||||
final dbPath = await getDatabasesPath();
|
||||
final path = dbPath + filePath;
|
||||
|
||||
// 打开数据库
|
||||
return await openDatabase(path, version: 1, onCreate: _createDB);
|
||||
}
|
||||
|
||||
// 创建数据库
|
||||
Future _createDB(Database db, int version) async {
|
||||
// 创建表
|
||||
const idType = 'INTEGER PRIMARY KEY AUTOINCREMENT';
|
||||
const textType = 'TEXT NOT NULL';
|
||||
const nullTextType = 'TEXT';
|
||||
|
||||
// 创建计时器表
|
||||
await db.execute('''
|
||||
CREATE TABLE $tableTimerEntry (
|
||||
${TimerEntryFields.id} $idType,
|
||||
${TimerEntryFields.name} $textType,
|
||||
${TimerEntryFields.createdAt} $textType,
|
||||
${TimerEntryFields.endAt} $nullTextType,
|
||||
${TimerEntryFields.isActive} $textType,
|
||||
${TimerEntryFields.stopWatch} $textType
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
/// CRUD
|
||||
|
||||
// 创建计时器
|
||||
Future<TimerEntry> create(TimerEntry timerEntry) async {
|
||||
final db = await instance.database;
|
||||
|
||||
// 获取计时器ID
|
||||
final id = await db.insert(tableTimerEntry, timerEntry.toJson());
|
||||
return timerEntry.copy(id: id);
|
||||
}
|
||||
|
||||
// 获取计时器
|
||||
Future<TimerEntry> read(int id) async {
|
||||
final db = await instance.database;
|
||||
final maps = await db.query(tableTimerEntry,
|
||||
columns: TimerEntryFields.values,
|
||||
where: '${TimerEntryFields.id} = ?',
|
||||
whereArgs: [id]);
|
||||
|
||||
if (maps.isNotEmpty) {
|
||||
return TimerEntry.fromJson(maps.first);
|
||||
} else {
|
||||
throw Exception('ID $id not found');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有计时器
|
||||
Future<List<TimerEntry>> readAll() async {
|
||||
final db = await instance.database;
|
||||
|
||||
// 插入一条测试数据
|
||||
// await db.insert(
|
||||
// tableTimerEntry,
|
||||
// TimerEntry(
|
||||
// name: '测试计时器',
|
||||
// ).toJson());
|
||||
|
||||
final result = await db.query(tableTimerEntry);
|
||||
|
||||
return result.map((json) => TimerEntry.fromJson(json)).toList();
|
||||
}
|
||||
|
||||
// 更新计时器
|
||||
Future<int> update(TimerEntry timerEntry) async {
|
||||
final db = await instance.database;
|
||||
|
||||
return db.update(tableTimerEntry, timerEntry.toJson(),
|
||||
where: '${TimerEntryFields.id} = ?', whereArgs: [timerEntry.id]);
|
||||
}
|
||||
|
||||
// 删除计时器
|
||||
Future<int> delete(int id) async {
|
||||
final db = await instance.database;
|
||||
|
||||
return await db.delete(tableTimerEntry,
|
||||
where: '${TimerEntryFields.id} = ?', whereArgs: [id]);
|
||||
}
|
||||
|
||||
// 关闭数据库
|
||||
Future close() async {
|
||||
final db = await instance.database;
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:timemanage/screen/reports_screen.dart';
|
||||
import 'package:timemanage/screen/export_screen.dart';
|
||||
import 'package:timemanage/screen/settings_screen.dart';
|
||||
import 'package:timemanage/screen/about_screen.dart';
|
||||
import 'package:timemanage/screen/course_screen.dart';
|
||||
|
||||
class HomeMenuBar extends StatefulWidget {
|
||||
const HomeMenuBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
HomeMenuBarState createState() => HomeMenuBarState();
|
||||
}
|
||||
|
||||
class HomeMenuBarState extends State<HomeMenuBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuBar(
|
||||
children: <Widget>[
|
||||
SubmenuButton(
|
||||
menuChildren: <Widget>[
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => CourseScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('课程表'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ReportsScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('统计报告'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const ExportScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('导入和导出'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SettingsScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('设置'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const AboutScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('关于'),
|
||||
),
|
||||
],
|
||||
child: const Icon(Icons.menu),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,244 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:timemanage/screen/projects_screen.dart';
|
||||
import 'package:timemanage/screen/reports_screen.dart';
|
||||
import 'package:timemanage/screen/export_screen.dart';
|
||||
import 'package:timemanage/screen/settings_screen.dart';
|
||||
import 'package:timemanage/screen/about_screen.dart';
|
||||
import 'package:timemanage/screen/course_screen.dart';
|
||||
|
||||
class DashBoardScreen extends StatefulWidget {
|
||||
const DashBoardScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_DashBoardScreenState createState() => _DashBoardScreenState();
|
||||
}
|
||||
|
||||
class _DashBoardScreenState extends State<DashBoardScreen> {
|
||||
List<TimerModel> timers = [];
|
||||
|
||||
void _addTimer() {
|
||||
final controller = TextEditingController();
|
||||
showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('新计时器'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
decoration: const InputDecoration(hintText: '计时器名称'),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final name = controller.text;
|
||||
if (name.isNotEmpty) {
|
||||
setState(() {
|
||||
timers.add(TimerModel(name: name));
|
||||
});
|
||||
}
|
||||
Navigator.pop(context, 'OK');
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.blueAccent, // 背景色
|
||||
// 最前面的菜单按钮
|
||||
leading: MenuBar(
|
||||
children: <Widget>[
|
||||
SubmenuButton(
|
||||
menuChildren: <Widget>[
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => SyllabusPage()),
|
||||
);
|
||||
},
|
||||
child: const Text('课程表'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ProjectsScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('项目'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ReportsScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('统计报告'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ExportScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('导入和导出'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SettingsScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('设置'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const AboutScreen()),
|
||||
);
|
||||
},
|
||||
child: const Text('关于'),
|
||||
),
|
||||
],
|
||||
child: const Icon(Icons.menu),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 标题
|
||||
title: const Text('时间管理'),
|
||||
actions: [
|
||||
// 搜索按钮
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {},
|
||||
),
|
||||
// 筛选按钮
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: _addTimer,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.filter_alt),
|
||||
onPressed: () {},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: timers.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
child: Text((index + 1).toString()),
|
||||
),
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
timers[index].name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
StreamBuilder<int>(
|
||||
stream: Stream.periodic(const Duration(milliseconds: 100),
|
||||
(_) => timers[index].stopwatch.elapsed.inSeconds),
|
||||
builder: (context, snapshot) {
|
||||
final seconds = snapshot.data ?? 0;
|
||||
final hours = seconds ~/ 3600;
|
||||
final minutes = (seconds % 3600) ~/ 60;
|
||||
final remainingSeconds = seconds % 60;
|
||||
return Text(
|
||||
'${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: timers[index].isActive
|
||||
? const Icon(Icons.pause)
|
||||
: const Icon(Icons.play_arrow),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (timers[index].isActive) {
|
||||
timers[index].stop();
|
||||
} else {
|
||||
timers[index].start();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
timers[index].stopwatch.stop(); // 确保计时器停止以防止内存泄漏
|
||||
timers.removeAt(index);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10), // 添加间隔
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimerModel {
|
||||
String name;
|
||||
Stopwatch stopwatch;
|
||||
bool isActive;
|
||||
|
||||
TimerModel({required this.name})
|
||||
: stopwatch = Stopwatch(),
|
||||
isActive = false;
|
||||
|
||||
void start() {
|
||||
stopwatch.start();
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
stopwatch.stop();
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
stopwatch.reset();
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProjectsScreen extends StatelessWidget {
|
||||
const ProjectsScreen({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('项目'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('项目界面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue