diff --git a/.metadata b/.metadata index 53163cb..e033a7e 100644 --- a/.metadata +++ b/.metadata @@ -18,18 +18,6 @@ migration: - platform: android create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - - platform: ios - create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - - platform: linux - create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - - platform: macos - create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - - platform: web - create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a - base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a # User provided section diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/CONTRIBUTORS.MD b/CONTRIBUTORS.MD index aa910b0..a906172 100644 --- a/CONTRIBUTORS.MD +++ b/CONTRIBUTORS.MD @@ -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) | | diff --git a/android/.gradle/7.4.2/checksums/checksums.lock b/android/.gradle/7.4.2/checksums/checksums.lock deleted file mode 100644 index 061a0e4..0000000 Binary files a/android/.gradle/7.4.2/checksums/checksums.lock and /dev/null differ diff --git a/android/.gradle/7.4.2/dependencies-accessors/dependencies-accessors.lock b/android/.gradle/7.4.2/dependencies-accessors/dependencies-accessors.lock deleted file mode 100644 index f9fae7c..0000000 Binary files a/android/.gradle/7.4.2/dependencies-accessors/dependencies-accessors.lock and /dev/null differ diff --git a/android/.gradle/7.4.2/dependencies-accessors/gc.properties b/android/.gradle/7.4.2/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android/.gradle/7.4.2/fileChanges/last-build.bin b/android/.gradle/7.4.2/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/android/.gradle/7.4.2/fileChanges/last-build.bin and /dev/null differ diff --git a/android/.gradle/7.4.2/fileHashes/fileHashes.bin b/android/.gradle/7.4.2/fileHashes/fileHashes.bin deleted file mode 100644 index ee330c9..0000000 Binary files a/android/.gradle/7.4.2/fileHashes/fileHashes.bin and /dev/null differ diff --git a/android/.gradle/7.4.2/fileHashes/fileHashes.lock b/android/.gradle/7.4.2/fileHashes/fileHashes.lock deleted file mode 100644 index f38361e..0000000 Binary files a/android/.gradle/7.4.2/fileHashes/fileHashes.lock and /dev/null differ diff --git a/android/.gradle/7.4.2/gc.properties b/android/.gradle/7.4.2/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 611539f..df7dfed 100644 Binary files a/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/android/.gradle/buildOutputCleanup/cache.properties b/android/.gradle/buildOutputCleanup/cache.properties index 97040f3..05c9ac1 100644 --- a/android/.gradle/buildOutputCleanup/cache.properties +++ b/android/.gradle/buildOutputCleanup/cache.properties @@ -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 diff --git a/android/app/build.gradle b/android/app/build.gradle index 9e37a04..53eea4a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,7 +23,7 @@ if (flutterVersionName == null) { } android { - namespace "com.example.timemanage" + namespace "com.example.timemaneger" compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion @@ -42,7 +42,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.timemanage" + applicationId "com.example.timemaneger" // 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. minSdkVersion flutter.minSdkVersion diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d83fd0b..433791a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ >>>>>> 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) | | diff --git a/build/flutter_assets/NOTICES b/build/flutter_assets/NOTICES index e9eeb5c..fbea4de 100644 --- a/build/flutter_assets/NOTICES +++ b/build/flutter_assets/NOTICES @@ -1986,6 +1986,31 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +bloc +flutter_bloc + +The MIT License (MIT) +Copyright (c) 2018 Felix Angelov + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- boolean_selector meta @@ -6836,6 +6861,9 @@ SOFTWARE. -------------------------------------------------------------------------------- flutter_lints flutter_markdown +path_provider +path_provider_android +path_provider_foundation path_provider_linux path_provider_platform_interface path_provider_windows @@ -28922,6 +28950,36 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------------------- +intl + +Copyright 2013, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + -------------------------------------------------------------------------------- js @@ -30781,6 +30839,31 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +nested +provider + +MIT License + +Copyright (c) 2019 Remi Rousselet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. -------------------------------------------------------------------------------- package_info_plus diff --git a/git-diagnostics-2023-11-03-1901.zip b/git-diagnostics-2023-11-03-1901.zip new file mode 100644 index 0000000..96c9dd3 Binary files /dev/null and b/git-diagnostics-2023-11-03-1901.zip differ diff --git a/ios/Runner/GeneratedPluginRegistrant.m b/ios/Runner/GeneratedPluginRegistrant.m index b37c01c..ae90871 100644 --- a/ios/Runner/GeneratedPluginRegistrant.m +++ b/ios/Runner/GeneratedPluginRegistrant.m @@ -12,6 +12,12 @@ @import package_info_plus; #endif +#if __has_include() +#import +#else +@import path_provider_foundation; +#endif + #if __has_include() #import #else @@ -34,6 +40,7 @@ + (void)registerWithRegistry:(NSObject*)registry { [FLTPackageInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTPackageInfoPlusPlugin"]]; + [PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]]; [SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]]; [SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]]; [FLTURLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTURLLauncherPlugin"]]; diff --git a/lib/db/data_provider.dart b/lib/db/data_provider.dart deleted file mode 100644 index 7a30880..0000000 --- a/lib/db/data_provider.dart +++ /dev/null @@ -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 createProject({required String name, Color? colour}); - Future> listProjects(); - Future editProject(Project project); - Future deleteProject(Project project); - Future createTimer({ - String? description, - int? projectID, - DateTime? startTime, - DateTime? endTime, - }); - Future> listTimers(); - Future editTimer(TimerEntry timer); - Future deleteTimer(TimerEntry timer); - - Future import(DataProvider other) async { - List otherEntries = await other.listTimers(); - List otherProjects = await other.listProjects(); - - List 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, - ); - } - } -} diff --git a/lib/db/database_provider.dart b/lib/db/database_provider.dart deleted file mode 100644 index 238c774..0000000 --- a/lib/db/database_provider.dart +++ /dev/null @@ -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 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 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 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(?, ?, ?)", - [name, colour.value, archived ? 1 : 0]); - return Project(id: id, name: name, colour: colour, archived: archived); - } - - /// the r in crud - @override - Future> listProjects() async { - List> 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 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 editProject(Project project) async { - int rows = await _db.rawUpdate( - "update projects set name=?, colour=?, archived=? where id=?", - [ - project.name, - project.colour, - project.archived ? 1 : 0, - project.id - ]); - assert(rows == 1); - } - - /// the d in crud - @override - Future deleteProject(Project project) async { - await _db - .rawDelete("delete from projects where id=?", [project.id]); - } - - /// the c in crud - @override - Future 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(?, ?, ?, ?, ?)", - [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> listTimers() async { - List> 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 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 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=?", - [ - timer.projectID, - timer.description, - st, - et, - timer.notes, - timer.id - ]); - } - - /// the d in crud - @override - Future deleteTimer(TimerEntry timer) async { - await _db.rawDelete("delete from timers where id=?", [timer.id]); - } - - static Future getDatabaseFile() async { - final dbPath = - (Platform.isLinux) ? dataHome.path : await getDatabasesPath(); - return File(p.join(dbPath, 'timecop.db')); - } - - static Future 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; - } - } -} diff --git a/lib/db/mock_data_provider.dart b/lib/db/mock_data_provider.dart deleted file mode 100644 index 9e3efcd..0000000 --- a/lib/db/mock_data_provider.dart +++ /dev/null @@ -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> 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> listProjects() async { - return [ - 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> listTimers() async { - int tid = 1; - Random rand = Random(42); - - // start with running timers - List 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 createProject( - {required String name, Color? colour, bool? archived}) async { - return Project( - id: -1, name: name, colour: colour!, archived: archived ?? false); - } - - @override - Future editProject(Project project) async {} - @override - Future deleteProject(Project project) async {} - @override - Future 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 editTimer(TimerEntry timer) async {} - @override - Future deleteTimer(TimerEntry timer) async {} -} diff --git a/lib/db/timer_entry_database.dart b/lib/db/timer_entry_database.dart new file mode 100644 index 0000000..e26c6a9 --- /dev/null +++ b/lib/db/timer_entry_database.dart @@ -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 get database async { + if (_database != null) { + return _database!; + } + + _database = await _initDB('timer_entry.db'); + return _database!; + } + + // 初始化数据库 + Future _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 create(TimerEntry timerEntry) async { + final db = await instance.database; + + // 获取计时器ID + final id = await db.insert(tableTimerEntry, timerEntry.toJson()); + return timerEntry.copy(id: id); + } + + // 获取计时器 + Future 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> 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 update(TimerEntry timerEntry) async { + final db = await instance.database; + + return db.update(tableTimerEntry, timerEntry.toJson(), + where: '${TimerEntryFields.id} = ?', whereArgs: [timerEntry.id]); + } + + // 删除计时器 + Future 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(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 28ea652..4a1b0be 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:timemanage/screen/dashboard_screen.dart'; +import 'package:timemanage/screen/dashboard/dashboard_screen.dart'; +/// 程序入口 +/// 不知道为什么,dart的doc注释是三个斜杠,而不是/** */ void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { @@ -11,15 +13,18 @@ class MyApp extends StatelessWidget { return MaterialApp( // 此处放所有组件的主题颜色设置 theme: ThemeData( + // 默认菜单栏主题颜色 menuBarTheme: MenuBarThemeData( style: MenuStyle( backgroundColor: MaterialStatePropertyAll(Colors.blueAccent), ), ), - menuTheme: MenuThemeData( - style: MenuStyle( - backgroundColor: MaterialStatePropertyAll(Colors.white)), - ), + // // 菜单的每一项的主题颜色 + // menuTheme: MenuThemeData( + // style: MenuStyle( + // backgroundColor: + // MaterialStatePropertyAll(Colors.lightBlueAccent)), + // ), ), // 主页 home: DashBoardScreen(), diff --git a/lib/model/timer_entry.dart b/lib/model/timer_entry.dart index e842a75..e0d5ae0 100644 --- a/lib/model/timer_entry.dart +++ b/lib/model/timer_entry.dart @@ -1,58 +1,106 @@ -import 'package:equatable/equatable.dart'; +const String tableTimerEntry = 'timer_entry'; // 表名 -// 计时器实体类 -class TimerEntry extends Equatable { - final int id; - final String? description; - final int? projectID; - final DateTime startTime; - final DateTime? endTime; - final String? notes; +// 数据库表结构 +class TimerEntryFields { + static const String id = '_id'; + static const String name = 'name'; + static const String createdAt = 'createdAt'; + static const String endAt = 'endAt'; + static const String isActive = 'isActive'; + static const String stopWatch = 'stopWatch'; + + static final List values = [ + /// Add all fields + id, name, createdAt, endAt, isActive, stopWatch + ]; +} + +class TimerEntry { + int? id; // 计时器ID + String name; // 计时器名称 + Stopwatch? stopwatch; // 计时器 + bool isActice; // 计时器是否在计时 + DateTime createdAt; // 计时器创建时间 + DateTime? endAt; // 计时器结束时间 // 构造函数 - const TimerEntry( - {required this.id, - required this.description, - required this.projectID, - required this.startTime, - required this.endTime, - this.notes = ""}); - - // 返回属性列表,用于比较两个对象是否相等 - @override - List get props => - [id, description, projectID, startTime, endTime, notes]; - @override - bool get stringify => true; - - // 克隆方法 - TimerEntry.clone(TimerEntry timer, - {String? description, - int? projectID, - DateTime? startTime, - DateTime? endTime, - String? notes}) - : this( - id: timer.id, - description: description ?? timer.description, - projectID: projectID ?? timer.projectID, - startTime: startTime ?? timer.startTime, - endTime: endTime ?? timer.endTime, - notes: notes ?? timer.notes, - ); - - // 格式化时间 - static String formatDuration(Duration d) { - if (d.inHours > 0) { - return "${d.inHours}:${(d.inMinutes - (d.inHours * 60)).toString().padLeft(2, "0")}:${(d.inSeconds - (d.inMinutes * 60)).toString().padLeft(2, "0")}"; - } else { - return "${d.inMinutes.toString().padLeft(2, "0")}:${(d.inSeconds - (d.inMinutes * 60)).toString().padLeft(2, "0")}"; - } + TimerEntry({required this.name}) + : stopwatch = Stopwatch(), + isActice = false, + createdAt = DateTime.now(), + endAt = DateTime.now(); + + bool get isActive => isActice; + + num get stopWatch => stopWatch; + + // 开始计时 + void start() { + stopwatch!.start(); + isActice = true; + } + + // 暂停计时 + void pause() { + stopwatch!.stop(); + isActice = false; + } + + // 停止计时 + void stop() { + stopwatch!.stop(); + isActice = false; + endAt = DateTime.now(); + } + + // 重置计时 + void reset() { + stopwatch!.reset(); + isActice = false; + endAt = null; + } + + // 复制计时器 + TimerEntry copy({ + int? id, + String? name, + DateTime? createdAt, + DateTime? endAt, + bool? isActive, + Stopwatch? stopwatch, + }) => + TimerEntry( + name: name ?? this.name, + ) + ..id = id ?? this.id + ..createdAt = createdAt ?? this.createdAt + ..endAt = endAt ?? this.endAt + ..isActice = isActive ?? this.isActice + ..stopwatch = stopwatch ?? this.stopwatch; + + // fromJson + static TimerEntry fromJson(Map json) { + return TimerEntry( + name: json[TimerEntryFields.name] as String, + ) + ..id = json[TimerEntryFields.id] as int? + ..createdAt = DateTime.parse(json[TimerEntryFields.createdAt] as String) + ..endAt = json[TimerEntryFields.endAt] == null + ? null + : DateTime.parse(json[TimerEntryFields.endAt] as String) + ..isActice = json[TimerEntryFields.isActive] == 1 ? true : false + ..stopwatch = Stopwatch(); } - // 格式化时间 - String formatTime() { - Duration d = (endTime ?? DateTime.now()).difference(startTime); - return formatDuration(d); + // toJson + Map toJson() { + return { + TimerEntryFields.id: id, + TimerEntryFields.name: name, + TimerEntryFields.createdAt: createdAt.toIso8601String(), + TimerEntryFields.endAt: endAt?.toIso8601String(), + TimerEntryFields.isActive: isActice ? 1 : 0, + TimerEntryFields.stopWatch: stopwatch!.elapsed.inMilliseconds, + }; } } diff --git a/lib/screen/about_screen.dart b/lib/screen/about_screen.dart index e68dff8..f98d11a 100644 --- a/lib/screen/about_screen.dart +++ b/lib/screen/about_screen.dart @@ -40,8 +40,11 @@ class AboutScreen extends StatelessWidget { textAlign: TextAlign.justify, ), // 应用程序的版权信息 - applicationLegalese: - "Copyright © 2023 中国民航大学 计算机科学与技术学院 \n 计算机科学与技术专业 21034102班 \n 庞浩,葛兴海,蔡玉祥,邹兴云,卫俊钢 小组", + applicationLegalese: ''' + Copyright © 2023 中国民航大学 计算机科学与技术学院 + 计算机科学与技术专业 21034102班 + 庞浩,葛兴海,蔡玉祥,邹兴云,卫俊钢 小组 + ''', /** * 列表的每一项,每一项都是一个ListTile */ diff --git a/lib/screen/course_screen.dart b/lib/screen/course_screen.dart index c1594d0..30a30d2 100644 --- a/lib/screen/course_screen.dart +++ b/lib/screen/course_screen.dart @@ -1,316 +1,13 @@ -// import 'package:flutter/cupertino.dart'; -// import 'package:flutter/material.dart'; - -// class SpaceWidget extends StatelessWidget { -// final double width; -// final double height; - -// SpaceWidget({this.width = 0.0, this.height = 0.0}); - -// @override -// Widget build(BuildContext context) { -// return SizedBox( -// width: width, -// height: height, -// ); -// } -// } - -// class SyllabusPage extends StatefulWidget { -// @override -// State createState() => PageState(); -// } - -// class PageState extends State { -// var colorList = [ -// Colors.red, -// Colors.lightBlueAccent, -// Colors.grey, -// Colors.cyan, -// Colors.amber, -// Colors.deepPurpleAccent, -// Colors.purpleAccent -// ]; -// var infoList = ["高等数学-周某某教授@综合楼201", "大学英语-王某某讲师@行政楼501"]; -// var weekList = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; - -// var dateList = []; -// var currentWeekIndex = 0; - -// @override -// void initState() { -// super.initState(); - -// var monday = 1; -// var mondayTime = DateTime.now(); - -// //获取本周星期一是几号 -// while (mondayTime.weekday != monday) { -// mondayTime = mondayTime.subtract(new Duration(days: 1)); -// } - -// mondayTime.year; //2020 年 -// mondayTime.month; //6(这里和js中的月份有区别,js中是从0开始,dart则从1开始,我们无需再进行加一处理) 月 -// mondayTime.day; //6 日 -// // nowTime.hour ;//6 时 -// // nowTime.minute ;//6 分 -// // nowTime.second ;//6 秒 -// for (int i = 0; i < 7; i++) { -// dateList.add( -// mondayTime.month.toString() + "/" + (mondayTime.day + i).toString()); -// if ((mondayTime.day + i) == DateTime.now().day) { -// setState(() { -// currentWeekIndex = i + 1; -// }); -// } -// } -// // print('Recent monday '+DateTime.now().day.toString()); -// } - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// body: Column( -// mainAxisAlignment: MainAxisAlignment.start, -// children: [ -// SizedBox( -// child: GridView.builder( -// shrinkWrap: true, -// physics: NeverScrollableScrollPhysics(), -// itemCount: 8, -// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( -// crossAxisCount: 8, childAspectRatio: 1 / 1), -// itemBuilder: (BuildContext context, int index) { -// return Container( -// color: index == this.currentWeekIndex -// ? Color(0xf7f7f7) -// : Colors.white, -// child: Center( -// child: index == 0 -// ? Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Text("星期", -// style: TextStyle( -// fontSize: 14, color: Colors.black87)), -// SpaceWidget(height: 5), -// Text("日期", style: TextStyle(fontSize: 12)), -// ], -// ) -// : Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Text(weekList[index - 1], -// style: TextStyle( -// fontSize: 14, -// color: index == currentWeekIndex -// ? Colors.lightBlue -// : Colors.black87)), -// SpaceWidget(height: 5), -// Text(dateList[index - 1], -// style: TextStyle( -// fontSize: 12, -// color: index == currentWeekIndex -// ? Colors.lightBlue -// : Colors.black87)), -// ], -// ), -// ), -// ); -// }), -// ), -// Expanded( -// child: SingleChildScrollView( -// child: Row( -// children: [ -// Expanded( -// flex: 1, -// child: GridView.builder( -// shrinkWrap: true, -// // physics:ClampingScrollPhysics(), -// itemCount: 10, -// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( -// crossAxisCount: 1, childAspectRatio: 1 / 2), -// itemBuilder: (BuildContext context, int index) { -// return Container( -// // width: 25, -// // height:s 80, -// child: Center( -// child: Text( -// (index + 1).toInt().toString(), -// style: TextStyle(fontSize: 15), -// ), -// ), -// decoration: BoxDecoration( -// color: Color(0xff5ff5), -// // border: Border.all(color: Colors.black12, width: 0.5), -// border: Border( -// bottom: BorderSide( -// color: Colors.black12, width: 0.5), -// right: BorderSide( -// color: Colors.black12, width: 0.5), -// ), -// )); -// }), -// ), -// Expanded( -// flex: 7, -// child: GridView.builder( -// shrinkWrap: true, -// physics: NeverScrollableScrollPhysics(), -// itemCount: 35, -// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( -// crossAxisCount: 7, childAspectRatio: 1 / 4), -// itemBuilder: (BuildContext context, int index) { -// return Container( -// child: Stack( -// children: [ -// Column( -// mainAxisAlignment: -// MainAxisAlignment.spaceBetween, -// children: [ -// Flexible( -// flex: 1, -// child: Container( -// width: double.infinity, -// height: double.infinity, -// decoration: BoxDecoration( -// color: Colors.white, -// // border: Border.all(color: Colors.black12, width: 0.5), -// border: Border( -// bottom: BorderSide( -// color: Colors.black12, -// width: 0.5), -// right: BorderSide( -// color: Colors.black12, -// width: 0.5), -// ), -// )), -// ), -// Flexible( -// flex: 1, -// child: Container( -// width: double.infinity, -// height: double.infinity, -// decoration: BoxDecoration( -// color: Colors.white, -// // border: Border.all(color: Colors.black12, width: 0.5), -// border: Border( -// bottom: BorderSide( -// color: Colors.black12, -// width: 0.5), -// right: BorderSide( -// color: Colors.black12, -// width: 0.5), -// ), -// )), -// ), -// ], -// ), -// if (index % 5 == 0 || index % 5 == 1) -// Container( -// margin: EdgeInsets.all(0.5), -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(2), -// color: colorList[index % 7], -// ), -// child: Center( -// child: Text( -// infoList[index % 2], -// textAlign: TextAlign.center, -// style: TextStyle( -// color: Colors.white, -// fontSize: 11, -// letterSpacing: 1), -// ), -// ), -// ) -// ], -// ), -// ); -// }), -// ) -// ], -// ), -// ), -// ), -// _bottomView -// ], -// ), -// ); -// } - -// @override -// String pageTitle() => "我的课表"; - -// Widget _topView = SizedBox( -// height: 30, -// child: Expanded( -// child: ListView.builder( -// scrollDirection: Axis.horizontal, -// itemCount: 7, -// itemBuilder: (BuildContext context, int index) { -// return Text("dd"); -// }), -// ), -// ); -// Widget _centerView = Expanded( -// child: GridView.builder( -// itemCount: 63, -// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( -// crossAxisCount: 7, -// ), -// itemBuilder: (BuildContext context, int index) { -// return Container( -// // width: 25, -// // height: 80, -// child: Center( -// child: Text( -// (index + 1).toString(), -// style: TextStyle(fontSize: 15), -// ), -// ), -// decoration: BoxDecoration( -// color: Color(0xff5ff5), -// border: Border.all(color: Colors.black12, width: 0.5), -// )); -// }), -// ); - -// Widget _bottomView = SizedBox( -// height: 30, -// child: Row( -// children: [ -// //底部view可自行扩充 -// ], -// ), -// ); -// } - -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class SpaceWidget extends StatelessWidget { - final double width; - final double height; - - SpaceWidget({this.width = 0.0, this.height = 0.0}); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: width, - height: height, - ); - } -} +class CourseScreen extends StatefulWidget { + const CourseScreen({super.key}); -class SyllabusPage extends StatefulWidget { @override - State createState() => PageState(); + State createState() => CourseScreenState(); } -class PageState extends State { +class CourseScreenState extends State { var colorList = [ Colors.red, Colors.lightBlueAccent, @@ -335,25 +32,13 @@ class PageState extends State { //获取本周星期一是几号 while (mondayTime.weekday != monday) { - mondayTime = mondayTime.subtract(new Duration(days: 1)); + mondayTime = mondayTime.subtract(Duration(days: 1)); } - mondayTime.year; //2020 年 - mondayTime.month; //6(这里和js中的月份有区别,js中是从0开始,dart则从1开始,我们无需再进行加一处理) 月 - mondayTime.day; //6 日 - // nowTime.hour ;//6 时 - // nowTime.minute ;//6 分 - // nowTime.second ;//6 秒 for (int i = 0; i < 7; i++) { - dateList.add( - mondayTime.month.toString() + "/" + (mondayTime.day + i).toString()); - if ((mondayTime.day + i) == DateTime.now().day) { - setState(() { - currentWeekIndex = i + 1; - }); - } + dateList.add("${mondayTime.month}-${mondayTime.day}"); + mondayTime = mondayTime.add(Duration(days: 1)); } - // print('Recent monday '+DateTime.now().day.toString()); } @override @@ -371,18 +56,18 @@ class PageState extends State { crossAxisCount: 8, childAspectRatio: 1 / 1), itemBuilder: (BuildContext context, int index) { return Container( - color: index == this.currentWeekIndex - ? Color(0xf7f7f7) + color: index == currentWeekIndex + ? Color(0x00f7f7f7) : Colors.white, child: Center( child: index == 0 ? Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: const [ Text("星期", style: TextStyle( fontSize: 14, color: Colors.black87)), - SpaceWidget(height: 5), + SizedBox(height: 5), Text("日期", style: TextStyle(fontSize: 12)), ], ) @@ -395,7 +80,7 @@ class PageState extends State { color: index == currentWeekIndex ? Colors.lightBlue : Colors.black87)), - SpaceWidget(height: 5), + SizedBox(height: 5), Text(dateList[index - 1], style: TextStyle( fontSize: 12, @@ -422,16 +107,8 @@ class PageState extends State { crossAxisCount: 1, childAspectRatio: 1 / 2), itemBuilder: (BuildContext context, int index) { return Container( - // width: 25, - // height:s 80, - child: Center( - child: Text( - (index + 1).toInt().toString(), - style: TextStyle(fontSize: 15), - ), - ), decoration: BoxDecoration( - color: Color(0xff5ff5), + color: Color(0x00ff5ff5), // border: Border.all(color: Colors.black12, width: 0.5), border: Border( bottom: BorderSide( @@ -439,6 +116,14 @@ class PageState extends State { right: BorderSide( color: Colors.black12, width: 0.5), ), + ), + // width: 25, + // height:s 80, + child: Center( + child: Text( + (index + 1).toInt().toString(), + style: TextStyle(fontSize: 15), + ), )); }), ), @@ -483,69 +168,67 @@ class PageState extends State { ); }, - child: Container( - child: Stack( - children: [ - Column( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Flexible( - flex: 1, - child: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.white, - border: Border( - bottom: BorderSide( - color: Colors.black12, - width: 0.5), - right: BorderSide( - color: Colors.black12, - width: 0.5), - ), - )), - ), - Flexible( - flex: 1, - child: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.white, - border: Border( - bottom: BorderSide( - color: Colors.black12, - width: 0.5), - right: BorderSide( - color: Colors.black12, - width: 0.5), - ), - )), - ), - ], - ), - if (index % 5 == 0 || index % 5 == 1) - Container( - margin: EdgeInsets.all(0.5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(2), - color: colorList[index % 7], - ), - child: Center( - child: Text( - infoList[index % 2], - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 11, - letterSpacing: 1), - ), + child: Stack( + children: [ + Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 1, + child: Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.5), + right: BorderSide( + color: Colors.black12, + width: 0.5), + ), + )), + ), + Flexible( + flex: 1, + child: Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.5), + right: BorderSide( + color: Colors.black12, + width: 0.5), + ), + )), + ), + ], + ), + if (index % 5 == 0 || index % 5 == 1) + Container( + margin: EdgeInsets.all(0.5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + color: colorList[index % 7], + ), + child: Center( + child: Text( + infoList[index % 2], + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 11, + letterSpacing: 1), ), - ) - ], - ), + ), + ) + ], ), ); }), @@ -560,77 +243,28 @@ class PageState extends State { ); } - @override String pageTitle() => "我的课表"; - Widget _topView = SizedBox( - height: 30, - child: Expanded( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: 7, - itemBuilder: (BuildContext context, int index) { - return Text("dd"); - }), - ), - ); - Widget _centerView = Expanded( - child: GridView.builder( - itemCount: 63, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 7, - ), - itemBuilder: (BuildContext context, int index) { - return Container( - // width: 25, - // height: 80, - child: Center( - child: Text( - (index + 1).toString(), - style: TextStyle(fontSize: 15), - ), - ), - decoration: BoxDecoration( - color: Color(0xff5ff5), - border: Border.all(color: Colors.black12, width: 0.5), - )); - }), - ); - - Widget _bottomView = SizedBox( + final Widget _bottomView = SizedBox( height: 30, child: Row( - children: [ + children: const [ //底部view可自行扩充 ], ), ); } -// class EditCoursePage extends StatelessWidget { -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// appBar: AppBar( -// title: Text('编辑课程'), -// ), -// body: Center( -// child: Text('在这里编辑课程信息'), -// ), -// ); -// } -// } - class EditCoursePage extends StatefulWidget { final Function(String) onSave; - EditCoursePage({required this.onSave}); + const EditCoursePage({super.key, required this.onSave}); @override - _EditCoursePageState createState() => _EditCoursePageState(); + EditCoursePageState createState() => EditCoursePageState(); } -class _EditCoursePageState extends State { +class EditCoursePageState extends State { final _formKey = GlobalKey(); final _courseController = TextEditingController(); diff --git a/lib/screen/dashboard/components/home_menu_bar.dart b/lib/screen/dashboard/components/home_menu_bar.dart new file mode 100644 index 0000000..88f7f28 --- /dev/null +++ b/lib/screen/dashboard/components/home_menu_bar.dart @@ -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 { + @override + Widget build(BuildContext context) { + return MenuBar( + children: [ + SubmenuButton( + menuChildren: [ + 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), + ), + ], + ); + } +} diff --git a/lib/screen/dashboard/dashboard_screen.dart b/lib/screen/dashboard/dashboard_screen.dart new file mode 100644 index 0000000..eca6180 --- /dev/null +++ b/lib/screen/dashboard/dashboard_screen.dart @@ -0,0 +1,214 @@ +import 'package:flutter/material.dart'; +import 'package:timemanage/db/timer_entry_database.dart'; +import 'package:timemanage/model/timer_entry.dart'; +import 'package:timemanage/screen/dashboard/components/home_menu_bar.dart'; + +// 主页 +class DashBoardScreen extends StatefulWidget { + const DashBoardScreen({Key? key}) : super(key: key); + + @override + DashBoardScreenState createState() => DashBoardScreenState(); +} + +class DashBoardScreenState extends State { + late List timers; // 计时器列表 + bool isLoading = false; // 是否正在加载 + + // 初始化状态 + @override + void initState() { + super.initState(); + refreshTimers(); + } + + // 销毁状态,关闭数据库 + @override + void dispose() { + TimerEntryDatabase.instance.close(); + super.dispose(); + } + + // 刷新计时器列表 + Future refreshTimers() async { + setState(() { + isLoading = true; + }); + + timers = await TimerEntryDatabase.instance.readAll(); + + setState(() { + timers = timers; + isLoading = false; + }); + } + + // 添加计时器 + void _addTimer() { + final controller = TextEditingController(); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('新建计时器'), + // 输入计时器名称和计时器时长 + content: SizedBox( + height: 100, + child: Column( + children: [ + TextField( + decoration: InputDecoration(hintText: '计时器名称'), + controller: controller, + ), + ], + ), + ), + // 确定or取消 + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, 'Cancel'), + child: const Text('取消'), + ), + TextButton( + onPressed: () { + final name = controller.text; + // TODO: 检查持续时间输入的是否合法,如果合法则添加计时器,否则提示错误 + if (name.isNotEmpty) { + TimerEntry timerEntry = TimerEntry( + name: name, + ); + setState(() { + timers.add(timerEntry); + }); + // 写入数据库 + TimerEntryDatabase.instance.create(timerEntry); + } + Navigator.pop(context, 'OK'); + }, + child: const Text('确定'), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // 应用栏 + appBar: AppBar( + backgroundColor: Colors.blueAccent, // 背景色 + // 最前面的菜单按钮 + leading: HomeMenuBar(), + // 标题 + title: const Text('时间管理'), + actions: [ + // 搜索按钮 + // TODO: 实现搜索功能 + IconButton( + icon: const Icon(Icons.search), + onPressed: () {}, + ), + // // 筛选按钮 + // IconButton( + // icon: const Icon(Icons.filter_alt), + // onPressed: () {}, + // ) + ], + ), + // 显示计时器列表 + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : timers.isEmpty + ? const Center(child: Text('还没有计时器,先添加一个吧!')) + : buildTimers(), + // 添加计时器的悬浮按钮 + floatingActionButton: FloatingActionButton( + onPressed: _addTimer, + child: const Icon(Icons.add), + ), + ); + } + + // 计时器显示列表,显示创建时间和计时器名称,计时时间,以及开始、暂停、停止按钮 + Widget buildTimers() { + return 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].createdAt.toString().substring(0, 16), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + Text( + timers[index].name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + StreamBuilder( + 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), // 添加间隔 + ], + ); + }, + ); + } +} diff --git a/lib/screen/dashboard_screen.dart b/lib/screen/dashboard_screen.dart deleted file mode 100644 index 2942a8a..0000000 --- a/lib/screen/dashboard_screen.dart +++ /dev/null @@ -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 { - List timers = []; - - void _addTimer() { - final controller = TextEditingController(); - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('新计时器'), - content: TextField( - controller: controller, - decoration: const InputDecoration(hintText: '计时器名称'), - ), - actions: [ - 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: [ - SubmenuButton( - menuChildren: [ - 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( - 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(); - } -} diff --git a/lib/screen/projects_screen.dart b/lib/screen/projects_screen.dart deleted file mode 100644 index 15e1650..0000000 --- a/lib/screen/projects_screen.dart +++ /dev/null @@ -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('项目界面'), - ), - ); - } -} diff --git a/lib/screen/reports_screen.dart b/lib/screen/reports_screen.dart index e245c6a..205acec 100644 --- a/lib/screen/reports_screen.dart +++ b/lib/screen/reports_screen.dart @@ -1,15 +1,109 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:timemanage/db/timer_entry_database.dart'; -class ReportsScreen extends StatelessWidget { +class ReportsScreen extends StatefulWidget { const ReportsScreen({super.key}); + + @override + State createState() => _ReportsScreenState(); +} + +// 计时器分类统计 +class _ReportsScreenState extends State { + // 读取数据库中的计时器 + var timers = TimerEntryDatabase.instance.readAll(); + Map persentMap = {}; + @override + void initState() { + super.initState(); + classifyTimers().then((value) { + setState(() { + persentMap = value; + }); + }); + for (var key in persentMap.keys) { + log(key); + } + } + + Future> classifyTimers() async { + var timers = await TimerEntryDatabase.instance.readAll(); + Map persentMap = {}; + for (var timer in timers) { + var response = await postData(timer.name); + var classification = + jsonDecode(response.body)['item']['lv1_tag_list'][0]['tag']; + // 解决百度API返回的分类中文乱码问题,转换为ISO-8859-1编码 + classification = utf8.decode(classification.runes.toList()); + persentMap[classification] = timer.stopWatch; + log('classification: $classification'); + } + return persentMap; + } + + // 调用百度API进行分类 + Future postData(String name) async { + // ignore: non_constant_identifier_names + var API_KEY = "YGxSMNdWKO5Gpt12dNVY2nGq"; + // ignore: non_constant_identifier_names + var SECRET_KEY = "G5NBDTiRPhsBqMi1Od0XZw4RSMlFgeek"; + var accessToken = await getAccessToken(API_KEY, SECRET_KEY); + var url = Uri.parse( + 'https://aip.baidubce.com/rpc/2.0/nlp/v1/topic?charset=UTF-8&access_token=$accessToken'); + var payload = jsonEncode({ + "title": name, + "content": name, + }); + var headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + var response = await http.post( + url, + headers: headers, + body: payload, + ); + return response; + } + + // ignore: non_constant_identifier_names + Future getAccessToken(String API_KEY, String SECRET_KEY) async { + var url = 'https://aip.baidubce.com/oauth/2.0/token'; + var params = { + 'grant_type': 'client_credentials', + 'client_id': API_KEY, + 'client_secret': SECRET_KEY, + }; + + var response = await http.post( + Uri.parse(url), + body: params, + ); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return data['access_token']; + } else { + throw Exception('Failed to get access token'); + } + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('统计报告'), ), - body: const Center( - child: Text('统计报告界面'), + body: ListView.builder( + itemCount: persentMap.length, + itemBuilder: (context, index) => ListTile( + title: Text(persentMap.keys.toList()[index]), + subtitle: Text(persentMap.values.toList()[index].toString()), + ), ), ); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index aefca13..fb60284 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,12 +6,14 @@ import FlutterMacOS import Foundation import package_info_plus +import path_provider_foundation import shared_preferences_foundation import sqflite import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 27e5032..a20babd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: "direct main" + description: + name: bloc + sha256: "6f1b87b6eca9041d5672b6e29273cd1594db48ebb66fd2471066e9f3c3a516bd" + url: "https://pub.dev" + source: hosted + version: "7.2.1" boolean_selector: dependency: transitive description: @@ -134,6 +142,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.1" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cdd1351ced09eeb46cfa7946e095b7679344af927415ca9cd972928fa6d5b23f + url: "https://pub.dev" + source: hosted + version: "7.3.3" flutter_launcher_icons: dependency: "direct dev" description: @@ -185,13 +201,13 @@ packages: source: hosted version: "9.2.0" http: - dependency: transitive + dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "0.13.6" http_parser: dependency: transitive description: @@ -208,6 +224,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" + source: hosted + version: "0.17.0" js: dependency: transitive description: @@ -264,6 +288,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_info_plus: dependency: "direct main" description: @@ -281,7 +313,7 @@ packages: source: hosted version: "2.0.1" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" @@ -296,6 +328,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" path_provider_linux: dependency: transitive description: @@ -352,6 +408,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.3" + provider: + dependency: transitive + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b36eb33..c7728bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,13 +37,19 @@ dependencies: font_awesome_flutter: ^9.2.0 url_launcher: ^6.1.14 equatable: ^2.0.3 # 用于对象比较 - sqflite: ^2.0.0+3 xdg_directories: ^1.0.0 + flutter_bloc: ^7.0.0 + bloc: ^7.0.0 + path: ^1.8.0 shared_preferences: ^2.0.8 + sqflite: ^2.0.0+3 # 用于数据库 + intl: ^0.17.0 # 用于时间格式化 + http: ^0.13.3 # 用于网络请求 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + path_provider: ^2.1.1 dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart index e2ae07f..629cab6 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,26 +5,6 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:timemanage/main.dart'; - void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); + print('Hello World!'); }