feat: add local database persistence
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||||||
|
|
||||||
import '../core/ambience.dart';
|
import '../core/ambience.dart';
|
||||||
import '../data/app_preferences_store.dart';
|
import '../data/app_preferences_store.dart';
|
||||||
|
import '../data/focus_seed_store.dart';
|
||||||
import '../features/shell/focus_seed_home.dart';
|
import '../features/shell/focus_seed_home.dart';
|
||||||
import '../l10n/generated/app_localizations.dart';
|
import '../l10n/generated/app_localizations.dart';
|
||||||
import '../models/appearance_preference.dart';
|
import '../models/appearance_preference.dart';
|
||||||
@@ -18,9 +19,10 @@ Locale focusSeedDefaultLocale({bool isRelease = kReleaseMode}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FocusSeedApp extends StatefulWidget {
|
class FocusSeedApp extends StatefulWidget {
|
||||||
const FocusSeedApp({this.locale, super.key});
|
const FocusSeedApp({this.locale, this.focusSeedStore, super.key});
|
||||||
|
|
||||||
final Locale? locale;
|
final Locale? locale;
|
||||||
|
final FocusSeedStore? focusSeedStore;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FocusSeedApp> createState() => _FocusSeedAppState();
|
State<FocusSeedApp> createState() => _FocusSeedAppState();
|
||||||
@@ -114,6 +116,7 @@ class _FocusSeedAppState extends State<FocusSeedApp>
|
|||||||
],
|
],
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
home: FocusSeedHome(
|
home: FocusSeedHome(
|
||||||
|
focusSeedStore: widget.focusSeedStore,
|
||||||
appearance: _appearance,
|
appearance: _appearance,
|
||||||
onAppearanceChanged: _setAppearance,
|
onAppearanceChanged: _setAppearance,
|
||||||
language: _language,
|
language: _language,
|
||||||
|
|||||||
@@ -1,33 +1,245 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../core/json_utils.dart';
|
import '../core/date_keys.dart' as date_keys;
|
||||||
import '../models/app_state.dart';
|
import '../models/app_state.dart';
|
||||||
|
import '../models/focus_session.dart';
|
||||||
|
import '../models/habit.dart';
|
||||||
|
import '../models/mood.dart';
|
||||||
|
import '../models/reflection_entry.dart';
|
||||||
|
import 'local/focus_seed_database.dart';
|
||||||
|
|
||||||
class FocusSeedStore {
|
class FocusSeedStore {
|
||||||
static const _storageKey = 'focus_seed_state_v1';
|
FocusSeedStore({FocusSeedDatabase? database})
|
||||||
|
: _database = database ?? FocusSeedDatabase();
|
||||||
|
|
||||||
|
static const _legacyStorageKey = 'focus_seed_state_v1';
|
||||||
|
static const _legacyMigrationKey = 'focus_seed_state_v1_migrated_to_sqlite';
|
||||||
|
|
||||||
|
final FocusSeedDatabase _database;
|
||||||
|
|
||||||
Future<AppState> load({List<String>? defaultHabitTitles}) async {
|
Future<AppState> load({List<String>? defaultHabitTitles}) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
await _migrateLegacyJsonIfNeeded();
|
||||||
final raw = prefs.getString(_storageKey);
|
final snapshot = await _readSnapshot();
|
||||||
if (raw == null || raw.isEmpty) {
|
if (!snapshot.isFreshStart) {
|
||||||
return AppState.starter(defaultHabitTitles: defaultHabitTitles);
|
return snapshot;
|
||||||
}
|
|
||||||
try {
|
|
||||||
return AppState.fromJson(jsonDecode(raw) as Map<String, dynamic>);
|
|
||||||
} on Object {
|
|
||||||
return AppState.starter(defaultHabitTitles: defaultHabitTitles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final starter = AppState.starter(defaultHabitTitles: defaultHabitTitles);
|
||||||
|
await save(starter);
|
||||||
|
return starter;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> save(AppState state) async {
|
Future<void> save(AppState state) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
await _database.transaction(() async {
|
||||||
await prefs.setString(_storageKey, prettyJson(state.toJson()));
|
await _database.delete(_database.habitLogRows).go();
|
||||||
|
await _database.delete(_database.reflectionRows).go();
|
||||||
|
await _database.delete(_database.focusSessionRows).go();
|
||||||
|
await _database.delete(_database.habitRows).go();
|
||||||
|
await _database.delete(_database.dailyIntentionRows).go();
|
||||||
|
|
||||||
|
if (state.anchor.trim().isNotEmpty) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
await _database
|
||||||
|
.into(_database.dailyIntentionRows)
|
||||||
|
.insert(
|
||||||
|
DailyIntentionRowsCompanion.insert(
|
||||||
|
dateKey: state.anchorDateKey,
|
||||||
|
intentionText: state.anchor,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final (index, habit) in state.habits.indexed) {
|
||||||
|
await _database
|
||||||
|
.into(_database.habitRows)
|
||||||
|
.insert(
|
||||||
|
HabitRowsCompanion.insert(
|
||||||
|
id: habit.id,
|
||||||
|
title: habit.title,
|
||||||
|
sortOrder: index,
|
||||||
|
createdAt: habit.createdAt,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (final completedDate in habit.completedDates) {
|
||||||
|
await _database
|
||||||
|
.into(_database.habitLogRows)
|
||||||
|
.insert(
|
||||||
|
HabitLogRowsCompanion.insert(
|
||||||
|
id: '${habit.id}-$completedDate',
|
||||||
|
habitId: habit.id,
|
||||||
|
dateKey: completedDate,
|
||||||
|
completedAt: DateTime.parse('$completedDate 12:00:00'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final session in state.sessions) {
|
||||||
|
await _database
|
||||||
|
.into(_database.focusSessionRows)
|
||||||
|
.insert(_focusSessionCompanion(session));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final reflection in state.reflections) {
|
||||||
|
await _database
|
||||||
|
.into(_database.reflectionRows)
|
||||||
|
.insert(
|
||||||
|
ReflectionRowsCompanion.insert(
|
||||||
|
id: reflection.id,
|
||||||
|
dateKey: reflection.dateKey,
|
||||||
|
mood: reflection.mood.name,
|
||||||
|
note: reflection.note,
|
||||||
|
createdAt: reflection.createdAt,
|
||||||
|
updatedAt: reflection.createdAt,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final preferences = await SharedPreferences.getInstance();
|
||||||
|
await preferences.setBool(_legacyMigrationKey, true);
|
||||||
|
await preferences.remove(_legacyStorageKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
await _database.clearProductData();
|
||||||
await prefs.remove(_storageKey);
|
final preferences = await SharedPreferences.getInstance();
|
||||||
|
await preferences.remove(_legacyStorageKey);
|
||||||
|
await preferences.setBool(_legacyMigrationKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AppState> _readSnapshot() async {
|
||||||
|
final intentionRows =
|
||||||
|
await (_database.select(_database.dailyIntentionRows)
|
||||||
|
..orderBy([(row) => OrderingTerm.desc(row.dateKey)])
|
||||||
|
..limit(1))
|
||||||
|
.get();
|
||||||
|
final latestIntention = intentionRows.firstOrNull;
|
||||||
|
final habitRows =
|
||||||
|
await (_database.select(_database.habitRows)
|
||||||
|
..where((row) => row.archivedAt.isNull())
|
||||||
|
..orderBy([
|
||||||
|
(row) => OrderingTerm.asc(row.sortOrder),
|
||||||
|
(row) => OrderingTerm.asc(row.createdAt),
|
||||||
|
]))
|
||||||
|
.get();
|
||||||
|
final habitLogRows =
|
||||||
|
await (_database.select(_database.habitLogRows)
|
||||||
|
..orderBy([(row) => OrderingTerm.asc(row.dateKey)])).get();
|
||||||
|
final focusSessionRows =
|
||||||
|
await (_database.select(_database.focusSessionRows)
|
||||||
|
..orderBy([(row) => OrderingTerm.asc(row.endedAt)])).get();
|
||||||
|
final reflectionRows =
|
||||||
|
await (_database.select(_database.reflectionRows)
|
||||||
|
..orderBy([(row) => OrderingTerm.asc(row.createdAt)])).get();
|
||||||
|
|
||||||
|
final completedDatesByHabit = <String, Set<String>>{};
|
||||||
|
for (final log in habitLogRows) {
|
||||||
|
completedDatesByHabit.putIfAbsent(log.habitId, () => {}).add(log.dateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppState(
|
||||||
|
anchor: latestIntention?.intentionText ?? '',
|
||||||
|
anchorDateKey: latestIntention?.dateKey ?? date_keys.todayKey(),
|
||||||
|
habits: [
|
||||||
|
for (final habit in habitRows)
|
||||||
|
Habit(
|
||||||
|
id: habit.id,
|
||||||
|
title: habit.title,
|
||||||
|
createdAt: habit.createdAt,
|
||||||
|
completedDates: completedDatesByHabit[habit.id] ?? const {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
sessions: [
|
||||||
|
for (final session in focusSessionRows)
|
||||||
|
FocusSession(
|
||||||
|
id: session.id,
|
||||||
|
minutes: session.plannedMinutes,
|
||||||
|
completedAt: session.endedAt,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
reflections: [
|
||||||
|
for (final reflection in reflectionRows)
|
||||||
|
ReflectionEntry(
|
||||||
|
id: reflection.id,
|
||||||
|
dateKey: reflection.dateKey,
|
||||||
|
note: reflection.note,
|
||||||
|
mood: Mood.values.firstWhere(
|
||||||
|
(mood) => mood.name == reflection.mood,
|
||||||
|
orElse: () => Mood.clear,
|
||||||
|
),
|
||||||
|
createdAt: reflection.createdAt,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _migrateLegacyJsonIfNeeded() async {
|
||||||
|
final preferences = await SharedPreferences.getInstance();
|
||||||
|
if (preferences.getBool(_legacyMigrationKey) ?? false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await _hasDatabaseContent()) {
|
||||||
|
await preferences.setBool(_legacyMigrationKey, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final raw = preferences.getString(_legacyStorageKey);
|
||||||
|
if (raw == null || raw.isEmpty) {
|
||||||
|
await preferences.setBool(_legacyMigrationKey, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final legacyState = AppState.fromJson(
|
||||||
|
jsonDecode(raw) as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
await save(legacyState);
|
||||||
|
} on Object {
|
||||||
|
await preferences.setBool(_legacyMigrationKey, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _hasDatabaseContent() async {
|
||||||
|
final intention =
|
||||||
|
await (_database.select(_database.dailyIntentionRows)
|
||||||
|
..limit(1)).getSingleOrNull();
|
||||||
|
if (intention != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final habit =
|
||||||
|
await (_database.select(_database.habitRows)
|
||||||
|
..limit(1)).getSingleOrNull();
|
||||||
|
if (habit != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final session =
|
||||||
|
await (_database.select(_database.focusSessionRows)
|
||||||
|
..limit(1)).getSingleOrNull();
|
||||||
|
if (session != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final reflection =
|
||||||
|
await (_database.select(_database.reflectionRows)
|
||||||
|
..limit(1)).getSingleOrNull();
|
||||||
|
return reflection != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusSessionRowsCompanion _focusSessionCompanion(FocusSession session) {
|
||||||
|
final completedAt = session.completedAt;
|
||||||
|
return FocusSessionRowsCompanion.insert(
|
||||||
|
id: session.id,
|
||||||
|
dateKey: date_keys.dateKey(completedAt),
|
||||||
|
plannedMinutes: session.minutes,
|
||||||
|
actualSeconds: session.minutes * 60,
|
||||||
|
startedAt: completedAt.subtract(Duration(minutes: session.minutes)),
|
||||||
|
endedAt: completedAt,
|
||||||
|
status: 'completed',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift/native.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
part 'focus_seed_database.g.dart';
|
||||||
|
|
||||||
|
class DailyIntentionRows extends Table {
|
||||||
|
@override
|
||||||
|
String get tableName => 'daily_intentions';
|
||||||
|
|
||||||
|
TextColumn get dateKey => text()();
|
||||||
|
TextColumn get intentionText => text().named('text')();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
DateTimeColumn get updatedAt => dateTime()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {dateKey};
|
||||||
|
}
|
||||||
|
|
||||||
|
class FocusSessionRows extends Table {
|
||||||
|
@override
|
||||||
|
String get tableName => 'focus_sessions';
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get dateKey => text()();
|
||||||
|
IntColumn get plannedMinutes => integer()();
|
||||||
|
IntColumn get actualSeconds => integer()();
|
||||||
|
DateTimeColumn get startedAt => dateTime()();
|
||||||
|
DateTimeColumn get endedAt => dateTime()();
|
||||||
|
TextColumn get status => text()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
|
||||||
|
class HabitRows extends Table {
|
||||||
|
@override
|
||||||
|
String get tableName => 'habits';
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get title => text()();
|
||||||
|
IntColumn get sortOrder => integer()();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
DateTimeColumn get archivedAt => dateTime().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
|
||||||
|
class HabitLogRows extends Table {
|
||||||
|
@override
|
||||||
|
String get tableName => 'habit_logs';
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get habitId => text()();
|
||||||
|
TextColumn get dateKey => text()();
|
||||||
|
DateTimeColumn get completedAt => dateTime()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReflectionRows extends Table {
|
||||||
|
@override
|
||||||
|
String get tableName => 'reflections';
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get dateKey => text()();
|
||||||
|
TextColumn get mood => text()();
|
||||||
|
TextColumn get note => text()();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
DateTimeColumn get updatedAt => dateTime()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppSettingRows extends Table {
|
||||||
|
@override
|
||||||
|
String get tableName => 'app_settings';
|
||||||
|
|
||||||
|
TextColumn get key => text()();
|
||||||
|
TextColumn get value => text()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {key};
|
||||||
|
}
|
||||||
|
|
||||||
|
@DriftDatabase(
|
||||||
|
tables: [
|
||||||
|
DailyIntentionRows,
|
||||||
|
FocusSessionRows,
|
||||||
|
HabitRows,
|
||||||
|
HabitLogRows,
|
||||||
|
ReflectionRows,
|
||||||
|
AppSettingRows,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class FocusSeedDatabase extends _$FocusSeedDatabase {
|
||||||
|
FocusSeedDatabase([QueryExecutor? executor])
|
||||||
|
: super(executor ?? _openConnection());
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
|
||||||
|
Future<void> clearProductData() async {
|
||||||
|
await transaction(() async {
|
||||||
|
await delete(habitLogRows).go();
|
||||||
|
await delete(reflectionRows).go();
|
||||||
|
await delete(focusSessionRows).go();
|
||||||
|
await delete(habitRows).go();
|
||||||
|
await delete(dailyIntentionRows).go();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyDatabase _openConnection() {
|
||||||
|
return LazyDatabase(() async {
|
||||||
|
final directory = await getApplicationDocumentsDirectory();
|
||||||
|
final file = File(path.join(directory.path, 'focus_seed.sqlite'));
|
||||||
|
return NativeDatabase.createInBackground(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
import '../../application/focus_seed_controller.dart';
|
import '../../application/focus_seed_controller.dart';
|
||||||
import '../../application/focus_timer_controller.dart';
|
import '../../application/focus_timer_controller.dart';
|
||||||
|
import '../../data/focus_seed_store.dart';
|
||||||
import '../../l10n/l10n.dart';
|
import '../../l10n/l10n.dart';
|
||||||
import '../../models/appearance_preference.dart';
|
import '../../models/appearance_preference.dart';
|
||||||
import '../../models/language_preference.dart';
|
import '../../models/language_preference.dart';
|
||||||
@@ -18,6 +19,7 @@ class FocusSeedHome extends StatefulWidget {
|
|||||||
required this.language,
|
required this.language,
|
||||||
required this.showLanguageTools,
|
required this.showLanguageTools,
|
||||||
required this.onLanguageChanged,
|
required this.onLanguageChanged,
|
||||||
|
this.focusSeedStore,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ class FocusSeedHome extends StatefulWidget {
|
|||||||
final LanguagePreference language;
|
final LanguagePreference language;
|
||||||
final bool showLanguageTools;
|
final bool showLanguageTools;
|
||||||
final ValueChanged<LanguagePreference> onLanguageChanged;
|
final ValueChanged<LanguagePreference> onLanguageChanged;
|
||||||
|
final FocusSeedStore? focusSeedStore;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FocusSeedHome> createState() => _FocusSeedHomeState();
|
State<FocusSeedHome> createState() => _FocusSeedHomeState();
|
||||||
@@ -43,6 +46,7 @@ class _FocusSeedHomeState extends State<FocusSeedHome> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_controller = FocusSeedController(
|
_controller = FocusSeedController(
|
||||||
|
store: widget.focusSeedStore,
|
||||||
defaultHabitTitles: localizedStarterHabits(context.l10n),
|
defaultHabitTitles: localizedStarterHabits(context.l10n),
|
||||||
);
|
);
|
||||||
_timerController = FocusTimerController(
|
_timerController = FocusTimerController(
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ class AppState {
|
|||||||
final List<FocusSession> sessions;
|
final List<FocusSession> sessions;
|
||||||
final List<ReflectionEntry> reflections;
|
final List<ReflectionEntry> reflections;
|
||||||
|
|
||||||
bool get isFreshStart => habits.isEmpty && sessions.isEmpty && anchor.isEmpty;
|
bool get isFreshStart =>
|
||||||
|
habits.isEmpty &&
|
||||||
|
sessions.isEmpty &&
|
||||||
|
reflections.isEmpty &&
|
||||||
|
anchor.isEmpty;
|
||||||
|
|
||||||
String activeAnchor(String dateKey) {
|
String activeAnchor(String dateKey) {
|
||||||
return anchorDateKey == dateKey ? anchor : '';
|
return anchorDateKey == dateKey ? anchor : '';
|
||||||
|
|||||||
+369
-1
@@ -1,6 +1,30 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "88.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.1"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -17,6 +41,54 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.6"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.15.0"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "8.12.6"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -25,6 +97,30 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,6 +137,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -49,6 +161,30 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
drift:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift
|
||||||
|
sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.31.0"
|
||||||
|
drift_dev:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: drift_dev
|
||||||
|
sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.31.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -73,6 +209,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -101,6 +245,38 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -109,6 +285,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.19.0"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
json_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.0"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -141,6 +333,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -165,14 +365,54 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
path:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
path:
|
||||||
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.19"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -213,6 +453,38 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
recase:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: recase
|
||||||
|
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -269,11 +541,35 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.1"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -282,6 +578,30 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
|
sqlite3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3
|
||||||
|
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.9.4"
|
||||||
|
sqlite3_flutter_libs:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sqlite3_flutter_libs
|
||||||
|
sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.42"
|
||||||
|
sqlparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlparser
|
||||||
|
sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.43.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -298,6 +618,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -322,6 +650,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.4"
|
version: "0.7.4"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -338,6 +674,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.1"
|
version: "14.3.1"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -346,6 +690,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -354,6 +714,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.2 <4.0.0"
|
dart: ">=3.7.2 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.29.0"
|
||||||
|
|||||||
@@ -38,10 +38,16 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
intl: any
|
intl: any
|
||||||
shared_preferences: ^2.5.3
|
shared_preferences: ^2.5.3
|
||||||
|
drift: ^2.31.0
|
||||||
|
sqlite3_flutter_libs: ^0.5.42
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
path: ^1.9.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
build_runner: ^2.10.4
|
||||||
|
drift_dev: ^2.31.0
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/native.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:focus_seed/src/data/focus_seed_store.dart';
|
||||||
|
import 'package:focus_seed/src/data/local/focus_seed_database.dart';
|
||||||
|
import 'package:focus_seed/src/models/app_state.dart';
|
||||||
|
import 'package:focus_seed/src/models/focus_session.dart';
|
||||||
|
import 'package:focus_seed/src/models/habit.dart';
|
||||||
|
import 'package:focus_seed/src/models/mood.dart';
|
||||||
|
import 'package:focus_seed/src/models/reflection_entry.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('new installs seed localized starter habits into sqlite', () async {
|
||||||
|
final database = FocusSeedDatabase(NativeDatabase.memory());
|
||||||
|
addTearDown(database.close);
|
||||||
|
final store = FocusSeedStore(database: database);
|
||||||
|
|
||||||
|
final state = await store.load(
|
||||||
|
defaultHabitTitles: const ['Plan', 'Focus', 'Reflect'],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(state.anchor, isEmpty);
|
||||||
|
expect(state.habits.map((habit) => habit.title), [
|
||||||
|
'Plan',
|
||||||
|
'Focus',
|
||||||
|
'Reflect',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'sqlite snapshot round-trips focus, habit, and reflection data',
|
||||||
|
() async {
|
||||||
|
final database = FocusSeedDatabase(NativeDatabase.memory());
|
||||||
|
addTearDown(database.close);
|
||||||
|
final store = FocusSeedStore(database: database);
|
||||||
|
final now = DateTime(2026, 6, 12, 10, 30);
|
||||||
|
final state = AppState(
|
||||||
|
anchor: 'Ship the local database',
|
||||||
|
anchorDateKey: '2026-06-12',
|
||||||
|
habits: [
|
||||||
|
Habit(
|
||||||
|
id: 'habit-1',
|
||||||
|
title: 'Review the plan',
|
||||||
|
createdAt: now.subtract(const Duration(days: 2)),
|
||||||
|
completedDates: const {'2026-06-11', '2026-06-12'},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
sessions: [
|
||||||
|
FocusSession(id: 'session-1', minutes: 25, completedAt: now),
|
||||||
|
],
|
||||||
|
reflections: [
|
||||||
|
ReflectionEntry(
|
||||||
|
id: 'reflection-1',
|
||||||
|
dateKey: '2026-06-12',
|
||||||
|
note: 'Small scope kept it clean.',
|
||||||
|
mood: Mood.clear,
|
||||||
|
createdAt: now.add(const Duration(minutes: 1)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await store.save(state);
|
||||||
|
final loaded = await store.load();
|
||||||
|
|
||||||
|
expect(loaded.anchor, 'Ship the local database');
|
||||||
|
expect(loaded.anchorDateKey, '2026-06-12');
|
||||||
|
expect(loaded.habits.single.completedDates, {'2026-06-11', '2026-06-12'});
|
||||||
|
expect(loaded.sessions.single.minutes, 25);
|
||||||
|
expect(loaded.reflections.single.note, 'Small scope kept it clean.');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('legacy shared preferences json migrates once and is removed', () async {
|
||||||
|
final legacyState = AppState(
|
||||||
|
anchor: 'Migrate safely',
|
||||||
|
anchorDateKey: '2026-06-12',
|
||||||
|
habits: [
|
||||||
|
Habit(
|
||||||
|
id: 'habit-legacy',
|
||||||
|
title: 'Keep existing data',
|
||||||
|
createdAt: DateTime(2026, 6, 10),
|
||||||
|
completedDates: const {'2026-06-12'},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
sessions: [
|
||||||
|
FocusSession(
|
||||||
|
id: 'session-legacy',
|
||||||
|
minutes: 15,
|
||||||
|
completedAt: DateTime(2026, 6, 12, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
reflections: const [],
|
||||||
|
);
|
||||||
|
SharedPreferences.setMockInitialValues({
|
||||||
|
'focus_seed_state_v1': jsonEncode(legacyState.toJson()),
|
||||||
|
});
|
||||||
|
final database = FocusSeedDatabase(NativeDatabase.memory());
|
||||||
|
addTearDown(database.close);
|
||||||
|
final store = FocusSeedStore(database: database);
|
||||||
|
|
||||||
|
final migrated = await store.load();
|
||||||
|
final preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
expect(migrated.anchor, 'Migrate safely');
|
||||||
|
expect(migrated.habits.single.title, 'Keep existing data');
|
||||||
|
expect(migrated.sessions.single.minutes, 15);
|
||||||
|
expect(preferences.getString('focus_seed_state_v1'), isNull);
|
||||||
|
});
|
||||||
|
}
|
||||||
+20
-10
@@ -1,6 +1,9 @@
|
|||||||
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:focus_seed/src/app/focus_seed_app.dart';
|
import 'package:focus_seed/src/app/focus_seed_app.dart';
|
||||||
|
import 'package:focus_seed/src/data/focus_seed_store.dart';
|
||||||
|
import 'package:focus_seed/src/data/local/focus_seed_database.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -14,8 +17,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Today guides the user from intention to focus', (tester) async {
|
testWidgets('Today guides the user from intention to focus', (tester) async {
|
||||||
await tester.pumpWidget(const FocusSeedApp(locale: Locale('en')));
|
await pumpFocusSeedApp(tester, locale: const Locale('en'));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text('Focus Seed'), findsOneWidget);
|
expect(find.text('Focus Seed'), findsOneWidget);
|
||||||
expect(find.text("TODAY'S INTENTION"), findsOneWidget);
|
expect(find.text("TODAY'S INTENTION"), findsOneWidget);
|
||||||
@@ -35,8 +37,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('bottom navigation exposes Journey and Me', (tester) async {
|
testWidgets('bottom navigation exposes Journey and Me', (tester) async {
|
||||||
await tester.pumpWidget(const FocusSeedApp(locale: Locale('en')));
|
await pumpFocusSeedApp(tester, locale: const Locale('en'));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.tap(find.text('Journey'));
|
await tester.tap(find.text('Journey'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@@ -60,8 +61,7 @@ void main() {
|
|||||||
tester.platformDispatcher.clearTextScaleFactorTestValue();
|
tester.platformDispatcher.clearTextScaleFactorTestValue();
|
||||||
});
|
});
|
||||||
|
|
||||||
await tester.pumpWidget(const FocusSeedApp(locale: Locale('en')));
|
await pumpFocusSeedApp(tester, locale: const Locale('en'));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text('Focus Seed'), findsOneWidget);
|
expect(find.text('Focus Seed'), findsOneWidget);
|
||||||
expect(find.byType(Scrollable), findsWidgets);
|
expect(find.byType(Scrollable), findsWidgets);
|
||||||
@@ -69,8 +69,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('development default uses concise Chinese copy', (tester) async {
|
testWidgets('development default uses concise Chinese copy', (tester) async {
|
||||||
await tester.pumpWidget(const FocusSeedApp());
|
await pumpFocusSeedApp(tester);
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text('Focus Seed'), findsOneWidget);
|
expect(find.text('Focus Seed'), findsOneWidget);
|
||||||
expect(find.text('\u4eca\u65e5\u76ee\u6807'), findsOneWidget);
|
expect(find.text('\u4eca\u65e5\u76ee\u6807'), findsOneWidget);
|
||||||
@@ -88,8 +87,7 @@ void main() {
|
|||||||
testWidgets('development language switch changes the active locale', (
|
testWidgets('development language switch changes the active locale', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
await tester.pumpWidget(const FocusSeedApp());
|
await pumpFocusSeedApp(tester);
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.tap(find.text('\u6211\u7684'));
|
await tester.tap(find.text('\u6211\u7684'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@@ -102,3 +100,15 @@ void main() {
|
|||||||
expect(find.text('Language'), findsOneWidget);
|
expect(find.text('Language'), findsOneWidget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> pumpFocusSeedApp(WidgetTester tester, {Locale? locale}) async {
|
||||||
|
final database = FocusSeedDatabase(NativeDatabase.memory());
|
||||||
|
addTearDown(database.close);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
FocusSeedApp(
|
||||||
|
locale: locale,
|
||||||
|
focusSeedStore: FocusSeedStore(database: database),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user