dev #1
Generated
-8
@@ -478,13 +478,6 @@
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="rxdart">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/rxdart-0.28.0/lib" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="shelf">
|
||||
<value>
|
||||
<list>
|
||||
@@ -725,7 +718,6 @@
|
||||
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/pub_semver-2.2.0/lib" />
|
||||
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/pubspec_parse-1.5.0/lib" />
|
||||
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/recase-4.1.0/lib" />
|
||||
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/rxdart-0.28.0/lib" />
|
||||
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/shelf-1.4.2/lib" />
|
||||
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/shelf_web_socket-3.0.0/lib" />
|
||||
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/source_gen-2.0.0/lib" />
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'dart:io';
|
||||
// ignore_for_file: unused_import // Часто генерируется при условном импорте
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
// import 'package:flutter/material.dart'; // Не требуется напрямую, т.к. Category импортируется
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
// import 'package:rxdart/rxdart.dart'; // Не используется
|
||||
// Условный импорт бэкенда базы данных
|
||||
// Выбирает реализацию connect() в зависимости от платформы
|
||||
import 'database_connection/connection.dart' // Базовый импорт
|
||||
if (dart.library.html) 'database_connection/connection_web.dart' // Для Веб
|
||||
if (dart.library.io) 'database_connection/connection_native.dart'; // Для Нативных платформ (Android, iOS, Desktop)
|
||||
|
||||
// Импортируем модель категории для возвращаемого типа и утилиты
|
||||
import '../models/category.dart';
|
||||
// Удален ненужный импорт: import '../models/transaction_record.dart';
|
||||
import '../utils/category_utils.dart'; // Helper for category details
|
||||
|
||||
// Эта строка указывает Drift сгенерировать файл database.g.dart
|
||||
@@ -30,7 +29,10 @@ class Transactions extends Table {
|
||||
// Аннотация @DriftDatabase указывает Drift сгенерировать код для этой базы данных
|
||||
@DriftDatabase(tables: [Transactions])
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase() : super(_openConnection());
|
||||
// Используем функцию connect() из условного импорта для создания соединения
|
||||
// Во время компиляции будет выбрана правильная реализация connect()
|
||||
// из connection_web.dart или connection_native.dart.
|
||||
AppDatabase() : super(connect());
|
||||
|
||||
// Версия схемы. Увеличивайте при изменении структуры таблиц.
|
||||
@override
|
||||
@@ -128,18 +130,3 @@ class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для открытия соединения с базой данных
|
||||
LazyDatabase _openConnection() {
|
||||
// Вычисление пути к файлу базы данных в папке документов приложения
|
||||
return LazyDatabase(() async {
|
||||
final dbFolder = await getApplicationDocumentsDirectory();
|
||||
// Создаем файл 'db.sqlite' в этой папке
|
||||
final file = File(p.join(dbFolder.path, 'db.sqlite'));
|
||||
print("Database file path: ${file.path}"); // Логируем путь для отладки
|
||||
|
||||
// Используем NativeDatabase для открытия соединения
|
||||
// logStatements: true полезен для отладки SQL-запросов
|
||||
return NativeDatabase(file, logStatements: false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// lib/database/database_connection/connection.dart
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
// Этот файл служит базой для условного импорта.
|
||||
// Реализации находятся в connection_web.dart и connection_native.dart.
|
||||
|
||||
// Определим функцию-заглушку, чтобы основной файл database.dart
|
||||
// мог ее импортировать без ошибок анализатора.
|
||||
// Во время выполнения будет вызвана реализация из соответствующего
|
||||
// файла (web или native) благодаря условному импорту.
|
||||
QueryExecutor connect() => throw UnsupportedError(
|
||||
'Stub connect function should not be called. Ensure conditional imports are set up correctly.');
|
||||
@@ -0,0 +1,33 @@
|
||||
// lib/database/database_connection/connection_native.dart
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
// sqlite3_flutter_libs и sqlite3 импортируются для возможной тонкой настройки,
|
||||
// но часто NativeDatabase справляется автоматически.
|
||||
// import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
// import 'package:sqlite3/sqlite3.dart';
|
||||
|
||||
QueryExecutor connect() {
|
||||
print("Connecting to database using NativeDatabase (Lazy)");
|
||||
// Используем LazyDatabase для отложенной инициализации на нативных платформах
|
||||
return LazyDatabase(() async {
|
||||
// Получаем папку для хранения документов приложения
|
||||
final dbFolder = await getApplicationDocumentsDirectory();
|
||||
// Создаем путь к файлу 'db.sqlite' в этой папке
|
||||
final file = File(p.join(dbFolder.path, 'db.sqlite'));
|
||||
print("Database file path (Native): ${file.path}"); // Логируем путь для отладки
|
||||
|
||||
// Настройка библиотеки sqlite3 (обычно не требуется для Android/iOS/macOS с sqlite3_flutter_libs)
|
||||
// if (Platform.isWindows || Platform.isLinux) {
|
||||
// // Может потребоваться дополнительная настройка для Desktop
|
||||
// // await applyWorkaroundToOpenSqlite3Library();
|
||||
// }
|
||||
|
||||
// Используем NativeDatabase для открытия соединения
|
||||
// logStatements: true полезен для отладки SQL-запросов
|
||||
return NativeDatabase(file, logStatements: false);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// lib/database/database_connection/connection_web.dart
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/web.dart';
|
||||
// import 'package:drift/wasm.dart'; // <-- Удалить этот импорт, он больше не нужен здесь
|
||||
|
||||
QueryExecutor connect() {
|
||||
print("Connecting to database using WebDatabase (default configuration)");
|
||||
|
||||
// Используем стандартный конструктор WebDatabase.
|
||||
// Drift попытается автоматически найти и загрузить sqlite3.wasm по пути /sqlite3.wasm
|
||||
return WebDatabase(
|
||||
'db', // Имя базы данных в IndexedDB
|
||||
logStatements: false,
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart'; // Для kIsWeb
|
||||
// import 'package:drift/wasm.dart'; // <-- Удалить этот импорт
|
||||
import 'app.dart'; // Import the new app root widget
|
||||
import 'database/database.dart'; // Import the database
|
||||
|
||||
@@ -6,11 +8,37 @@ Future<void> main() async {
|
||||
// Необходимо для асинхронных операций перед runApp, например, инициализации БД
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// --- Удалить весь этот блок ---
|
||||
// // Инициализация WASM-модуля SQLite для веб-платформы
|
||||
// if (kIsWeb) {
|
||||
// print("Running on Web, attempting to initialize sqlite3.wasm...");
|
||||
// // Указываем Drift, где найти файл sqlite3.wasm.
|
||||
// // WasmDatabase.resolveFile попытается найти его автоматически (обычно в корне /sqlite3.wasm).
|
||||
// final result = await WasmDatabase.resolveFile('sqlite3.wasm'); // <-- Ошибка здесь
|
||||
//
|
||||
// if (result.isSuccessful) {
|
||||
// print("sqlite3.wasm loaded successfully.");
|
||||
// } else {
|
||||
// // Если файл не найден или произошла ошибка загрузки
|
||||
// print("Error loading sqlite3.wasm: ${result.errorMessage}");
|
||||
// // Здесь можно предпринять действия, если загрузка не удалась,
|
||||
// // например, показать сообщение об ошибке пользователю или использовать
|
||||
// // альтернативное хранилище. Пока просто выводим ошибку в консоль.
|
||||
// // Приложение может не работать корректно без базы данных.
|
||||
// }
|
||||
// } else {
|
||||
// print("Running on Native platform, skipping WASM initialization.");
|
||||
// }
|
||||
// --- Конец удаляемого блока ---
|
||||
|
||||
|
||||
// Создаем единственный экземпляр базы данных для всего приложения
|
||||
// WebDatabase (вызываемый через connect() на вебе) должен сам справиться с WASM
|
||||
final database = AppDatabase();
|
||||
|
||||
// Опционально: Вставляем начальные данные, если база данных пуста
|
||||
// Это полезно для первого запуска или демонстрации
|
||||
// Делаем это после создания экземпляра БД
|
||||
await database.insertInitialDataIfNeeded();
|
||||
|
||||
// Запускаем приложение, передавая экземпляр базы данных
|
||||
|
||||
@@ -24,6 +24,7 @@ class SpendingPieChart extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final theme = Theme.of(context);
|
||||
final currencyFormatter = NumberFormat.currency(locale: 'en_US', symbol: '\$'); // Or your preferred locale/symbol
|
||||
|
||||
// Handle the case where there are no categories to display
|
||||
if (categories.isEmpty) {
|
||||
@@ -61,38 +62,48 @@ class SpendingPieChart extends StatelessWidget {
|
||||
// --- Pie Chart ---
|
||||
Expanded(
|
||||
flex: 5, // Give more space to the chart itself
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
// Handle touch events on the pie chart
|
||||
pieTouchData: PieTouchData(
|
||||
touchCallback: (FlTouchEvent event, pieTouchResponse) {
|
||||
// Ignore events not related to interaction
|
||||
if (!event.isInterestedForInteractions ||
|
||||
pieTouchResponse == null ||
|
||||
pieTouchResponse.touchedSection == null) {
|
||||
// If touch ends outside a section, deselect
|
||||
if (event is FlPanEndEvent || event is FlTapUpEvent) {
|
||||
if (selectedPieIndex != -1) {
|
||||
onSelectPieCategory(-1); // Deselect
|
||||
child: Stack( // Use Stack to overlay text on the chart center
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
PieChart(
|
||||
PieChartData(
|
||||
// Handle touch events on the pie chart
|
||||
pieTouchData: PieTouchData(
|
||||
touchCallback: (FlTouchEvent event, PieTouchResponse? pieTouchResponse) {
|
||||
// We are only interested in TapUp events to trigger selection changes
|
||||
if (event is FlTapUpEvent) {
|
||||
final section = pieTouchResponse?.touchedSection;
|
||||
if (section != null) {
|
||||
// Tap occurred ON a section
|
||||
final touchedIndex = section.touchedSectionIndex;
|
||||
// Toggle selection: if tapped section is already selected, deselect (-1), otherwise select it.
|
||||
onSelectPieCategory(touchedIndex == selectedPieIndex ? -1 : touchedIndex);
|
||||
} else {
|
||||
// Tap occurred OUTSIDE any section
|
||||
// Deselect if something was selected
|
||||
if (selectedPieIndex != -1) {
|
||||
onSelectPieCategory(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Get the index of the touched section
|
||||
final touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex;
|
||||
// Call the callback, toggling selection if the same slice is touched again
|
||||
onSelectPieCategory(touchedIndex == selectedPieIndex ? -1 : touchedIndex);
|
||||
},
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(show: false), // No border around the chart
|
||||
sectionsSpace: 2, // Space between slices
|
||||
centerSpaceRadius: 50, // Radius of the center hole
|
||||
sections: _generatePieSections(context), // Generate slices data
|
||||
startDegreeOffset: -90, // Start chart from the top (12 o'clock)
|
||||
),
|
||||
// Optional animation when data changes
|
||||
swapAnimationDuration: const Duration(milliseconds: 250),
|
||||
swapAnimationCurve: Curves.easeInOut,
|
||||
),
|
||||
borderData: FlBorderData(show: false), // No border around the chart
|
||||
sectionsSpace: 2, // Space between slices
|
||||
centerSpaceRadius: 50, // Radius of the center hole
|
||||
sections: _generatePieSections(context), // Generate slices data
|
||||
startDegreeOffset: -90, // Start chart from the top (12 o'clock)
|
||||
),
|
||||
// Optional animation when data changes
|
||||
swapAnimationDuration: const Duration(milliseconds: 250),
|
||||
swapAnimationCurve: Curves.easeInOut,
|
||||
// --- Center Text (Displayed when a slice is selected) ---
|
||||
if (selectedPieIndex != -1)
|
||||
_buildCenterText(context, currencyFormatter)
|
||||
else // Optional: Display total or default text when nothing is selected
|
||||
_buildDefaultCenterText(context, currencyFormatter),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8), // Spacing between chart and legend
|
||||
@@ -116,11 +127,77 @@ class SpendingPieChart extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// Builds the text displayed in the center when a slice is selected
|
||||
Widget _buildCenterText(BuildContext context, NumberFormat formatter) {
|
||||
final theme = Theme.of(context);
|
||||
// Check if selectedPieIndex is valid before accessing categories
|
||||
if (selectedPieIndex < 0 || selectedPieIndex >= categories.length) {
|
||||
// Return an empty container or default text if index is invalid
|
||||
return _buildDefaultCenterText(context, formatter);
|
||||
}
|
||||
final selectedCategory = categories[selectedPieIndex];
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
selectedCategory.name,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.8),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
formatter.format(selectedCategory.amount), // Format the amount as currency
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: selectedCategory.colorCode, // Use category color for amount
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Builds the default text displayed in the center when no slice is selected
|
||||
Widget _buildDefaultCenterText(BuildContext context, NumberFormat formatter) {
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Total', // Label for the total amount
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
formatter.format(totalExpenses), // Display total expenses
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textTheme.bodyLarge?.color // Use default text color
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Builds a single item for the legend
|
||||
Widget _buildPieLegendItem(BuildContext context, int index) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final theme = Theme.of(context);
|
||||
final isSelected = index == selectedPieIndex; // Check if this item is selected
|
||||
// Check if index is valid before accessing categories
|
||||
if (index < 0 || index >= categories.length) {
|
||||
return const SizedBox.shrink(); // Return empty if index is invalid
|
||||
}
|
||||
final category = categories[index];
|
||||
// Calculate percentage, handle totalExpenses being zero
|
||||
final percentage = totalExpenses > 0 ? (category.amount / totalExpenses * 100) : 0.0;
|
||||
@@ -188,6 +265,12 @@ class SpendingPieChart extends StatelessWidget {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return List.generate(categories.length, (i) {
|
||||
// Check if index is valid before accessing categories
|
||||
if (i < 0 || i >= categories.length) {
|
||||
// This should ideally not happen if List.generate is used correctly,
|
||||
// but adding a safeguard.
|
||||
return PieChartSectionData(); // Return an empty section
|
||||
}
|
||||
final isTouched = i == selectedPieIndex; // Check if this slice is selected
|
||||
// Make selected slice slightly larger
|
||||
final double radius = isTouched ? 65 : 55;
|
||||
|
||||
@@ -539,14 +539,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
rxdart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.7.0
|
||||
sdk: ^3.7.0 # Drift 2.18 requires Dart 3.4+, Drift 2.16 requires Dart 3.3+
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
@@ -32,11 +32,11 @@ dependencies:
|
||||
sdk: flutter
|
||||
fl_chart: ^0.71.0
|
||||
intl: ^0.19.0 # For date formatting
|
||||
drift: ^2.26.0 # Added Drift
|
||||
sqlite3_flutter_libs: ^0.5.22 # Recommended for Drift on Flutter
|
||||
path_provider: ^2.1.3 # To find database file location
|
||||
path: ^1.9.0 # To construct database file path
|
||||
rxdart: ^0.28.0 # Added rxdart for combining streams in database
|
||||
drift: ^2.18.0 # Updated Drift - основной пакет
|
||||
sqlite3_flutter_libs: ^0.5.24 # Needed for native platforms
|
||||
path_provider: ^2.1.3 # To find database file location on native
|
||||
path: ^1.9.0 # To construct database file path on native
|
||||
# rxdart: ^0.28.0 # Removed as it wasn't used in database.dart
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
@@ -44,8 +44,8 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
drift_dev: ^2.18.0 # Added Drift code generator
|
||||
build_runner: ^2.4.11 # Added build_runner
|
||||
drift_dev: ^2.18.0 # Updated Drift code generator
|
||||
build_runner: ^2.4.11 # Updated build_runner
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
|
||||
Reference in New Issue
Block a user