init: ynab_lite Flutter project

This commit is contained in:
alexis 2026-07-11 18:10:09 +02:00
commit 9df372ebb7
37 changed files with 6343 additions and 0 deletions

45
.gitignore vendored Normal file
View file

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

30
.metadata Normal file
View file

@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ee80f08bbf97172ec030b8751ceab557177a34a6"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
- platform: android
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

17
README.md Normal file
View file

@ -0,0 +1,17 @@
# ynab_lite
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

28
analysis_options.yaml Normal file
View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
android/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View file

@ -0,0 +1,45 @@
plugins {
id("com.android.application")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.ynab.ynab_lite"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.ynab.ynab_lite"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
flutter {
source = "../.."
}

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="ynab_lite"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View file

@ -0,0 +1,5 @@
package com.ynab.ynab_lite
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

24
android/build.gradle.kts Normal file
View file

@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View file

@ -0,0 +1,6 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
# This newDsl flag was added by the Flutter template
android.newDsl=false
# This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip

View file

@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.0.1" apply false
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
}
include(":app")

218
lib/app.dart Normal file
View file

@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'theme/app_theme.dart';
import 'screens/dashboard_screen.dart';
import 'screens/budget_screen.dart';
import 'screens/accounts_screen.dart';
import 'screens/transactions_screen.dart';
final _selectedIndexProvider = StateProvider<int>((ref) => 0);
class YnabApp extends ConsumerStatefulWidget {
const YnabApp({super.key});
@override
ConsumerState<YnabApp> createState() => _YnabAppState();
}
class _YnabAppState extends ConsumerState<YnabApp> {
late final List<Widget> _screens;
late final List<_NavItem> _navItems;
@override
void initState() {
super.initState();
_screens = const [
DashboardScreen(),
BudgetScreen(),
AccountsScreen(),
TransactionsScreen(),
];
_navItems = const [
_NavItem(icon: Icons.dashboard_outlined, selectedIcon: Icons.dashboard, label: 'Tableau de bord'),
_NavItem(icon: Icons.pie_chart_outline, selectedIcon: Icons.pie_chart, label: 'Budget'),
_NavItem(icon: Icons.account_balance_outlined, selectedIcon: Icons.account_balance, label: 'Comptes'),
_NavItem(icon: Icons.receipt_long_outlined, selectedIcon: Icons.receipt_long, label: 'Transactions'),
];
}
@override
Widget build(BuildContext context) {
final selectedIndex = ref.watch(_selectedIndexProvider);
return Scaffold(
backgroundColor: kSurfaceBg,
body: SafeArea(
child: Row(
children: [
// Side Navigation
Container(
width: 72,
decoration: BoxDecoration(
color: kSurfaceCard,
border: Border(
right: BorderSide(color: kDivider, width: 1),
),
),
child: Column(
children: [
const SizedBox(height: 20),
// App logo / icon
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: kGreenAccent.withAlpha(30),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Icon(Icons.currency_bitcoin, color: kGreenAccent, size: 22),
),
),
const SizedBox(height: 8),
Text(
'YNAB\nLite',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 9,
color: kGreenAccent,
fontWeight: FontWeight.w700,
height: 1.2,
),
),
const SizedBox(height: 28),
// Navigation items
...List.generate(_navItems.length, (i) {
final item = _navItems[i];
final active = i == selectedIndex;
return _NavRailItem(
icon: active ? item.selectedIcon : item.icon,
label: item.label,
active: active,
onTap: () => ref.read(_selectedIndexProvider.notifier).state = i,
);
}),
const Spacer(),
// Settings at bottom
_NavRailItem(
icon: Icons.settings_outlined,
selectedIcon: Icons.settings,
label: 'Paramètres',
active: false,
onTap: () {},
),
const SizedBox(height: 20),
],
),
),
// Main Content
Expanded(
child: Column(
children: [
// Header
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: kSurfaceBg,
border: Border(
bottom: BorderSide(color: kDivider, width: 0.5),
),
),
child: Row(
children: [
Text(
_navItems[selectedIndex].label,
style: const TextStyle(
color: kTextPrimary,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
IconButton(
icon: const Icon(Icons.add, color: kGreenAccent),
onPressed: () {},
tooltip: 'Ajouter une transaction',
),
IconButton(
icon: const Icon(Icons.search, color: kTextSecondary),
onPressed: () {},
tooltip: 'Rechercher',
),
],
),
),
// Content
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: _screens[selectedIndex],
),
),
],
),
),
],
),
),
);
}
}
class _NavItem {
final IconData icon;
final IconData selectedIcon;
final String label;
const _NavItem({required this.icon, required this.selectedIcon, required this.label});
}
class _NavRailItem extends StatelessWidget {
final IconData icon;
final IconData? selectedIcon;
final String label;
final bool active;
final VoidCallback onTap;
const _NavRailItem({
required this.icon,
this.selectedIcon,
required this.label,
required this.active,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Tooltip(
message: label,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
width: 56,
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: active ? kGreenAccent.withAlpha(20) : null,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 22,
color: active ? kGreenAccent : kTextSecondary,
),
],
),
),
),
),
),
);
}
}

87
lib/data/db.dart Normal file
View file

@ -0,0 +1,87 @@
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'tables.dart';
part 'db.g.dart';
@DriftDatabase(
tables: [
Accounts,
CategoryGroups,
Categories,
Transactions,
Budgets,
],
)
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
static QueryExecutor _openConnection() {
return driftDatabase(name: 'ynab_lite');
}
@override
int get schemaVersion => 1;
// Seed
Future<void> seedIfEmpty() async {
final count = await select(accounts).get().then((r) => r.length);
if (count > 0) return;
// default category groups
final groups = [
CategoryGroupsCompanion.insert(name: 'Logement & Charges Fixes', sortOrder: const Value(1)),
CategoryGroupsCompanion.insert(name: 'Alimentation', sortOrder: const Value(2)),
CategoryGroupsCompanion.insert(name: 'Transport', sortOrder: const Value(3)),
CategoryGroupsCompanion.insert(name: 'Loisirs & Divertissements', sortOrder: const Value(4)),
CategoryGroupsCompanion.insert(name: 'Épargne & Objectifs', sortOrder: const Value(5)),
];
for (final g in groups) {
await into(categoryGroups).insert(g);
}
// categories
final cats = [
(group: 1, name: 'Loyer', icon: 'home'),
(group: 1, name: 'Électricité', icon: 'bolt'),
(group: 1, name: 'Eau', icon: 'water_drop'),
(group: 1, name: 'Assurance habitation', icon: 'shield'),
(group: 2, name: 'Courses', icon: 'shopping_cart'),
(group: 2, name: 'Restaurants', icon: 'restaurant'),
(group: 3, name: 'Essence', icon: 'local_gas_station'),
(group: 3, name: 'Transports en commun', icon: 'directions_bus'),
(group: 4, name: 'Streaming', icon: 'tv'),
(group: 4, name: 'Sorties', icon: 'celebration'),
(group: 5, name: 'Épargne d\'urgence', icon: 'savings'),
(group: 5, name: 'Vacances', icon: 'flight'),
];
for (int i = 0; i < cats.length; i++) {
await into(categories).insert(CategoriesCompanion.insert(
name: cats[i].name,
groupId: cats[i].group,
icon: Value(cats[i].icon),
sortOrder: Value(i + 1),
));
}
// default account
await into(accounts).insert(AccountsCompanion.insert(
name: 'Compte courant',
type: 'checking',
balance: const Value(2500.0),
sortOrder: const Value(1),
));
await into(accounts).insert(AccountsCompanion.insert(
name: 'Livret A',
type: 'savings',
balance: const Value(5000.0),
sortOrder: const Value(2),
));
await into(accounts).insert(AccountsCompanion.insert(
name: 'Espèces',
type: 'cash',
balance: const Value(120.0),
sortOrder: const Value(3),
));
}
}

3874
lib/data/db.g.dart Normal file

File diff suppressed because it is too large Load diff

47
lib/data/tables.dart Normal file
View file

@ -0,0 +1,47 @@
import 'package:drift/drift.dart';
// Tables
class Accounts extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
TextColumn get type => text()(); // checking, savings, credit, cash
RealColumn get balance => real().withDefault(const Constant(0.0))();
BoolColumn get isClosed => boolean().withDefault(const Constant(false))();
IntColumn get sortOrder => integer().withDefault(const Constant(0))();
}
class CategoryGroups extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
IntColumn get sortOrder => integer().withDefault(const Constant(0))();
}
class Categories extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
IntColumn get groupId => integer().references(CategoryGroups, #id)();
RealColumn get budgeted => real().withDefault(const Constant(0.0))();
IntColumn get sortOrder => integer().withDefault(const Constant(0))();
TextColumn get icon => text().nullable()();
}
class Transactions extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get accountId => integer().references(Accounts, #id)();
IntColumn get categoryId => integer().references(Categories, #id).nullable()();
TextColumn get payee => text()();
TextColumn get notes => text().nullable()();
RealColumn get amount => real()(); // positive = income, negative = expense
DateTimeColumn get date => dateTime()();
BoolColumn get cleared => boolean().withDefault(const Constant(false))();
IntColumn get sortOrder => integer().withDefault(const Constant(0))();
}
class Budgets extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get categoryId => integer().references(Categories, #id)();
TextColumn get month => text()(); // "2026-07"
RealColumn get budgeted => real().withDefault(const Constant(0.0))();
IntColumn get sortOrder => integer().withDefault(const Constant(0))();
}

27
lib/main.dart Normal file
View file

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'app.dart';
import 'theme/app_theme.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
const ProviderScope(
child: YnabLiteApp(),
),
);
}
class YnabLiteApp extends StatelessWidget {
const YnabLiteApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'YNAB Lite',
debugShowCheckedModeBanner: false,
theme: ynabDarkTheme,
home: const YnabApp(),
);
}
}

View file

@ -0,0 +1,107 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:drift/drift.dart';
import '../data/db.dart';
export '../data/db.dart' show Account, CategoryGroup, Category, Transaction, Budget;
// Singleton DB provider
final dbProvider = Provider<AppDatabase>((ref) {
final db = AppDatabase();
ref.onDispose(() => db.close());
db.seedIfEmpty();
return db;
});
// Accounts
final accountsProvider = StreamProvider<List<Account>>((ref) {
final db = ref.watch(dbProvider);
return db.select(db.accounts).watch();
});
final openAccountsProvider = Provider<List<Account>>((ref) {
final accounts = ref.watch(accountsProvider).valueOrNull ?? [];
return accounts.where((a) => !a.isClosed).toList();
});
final totalBalanceProvider = Provider<double>((ref) {
final accounts = ref.watch(openAccountsProvider);
return accounts.fold(0.0, (sum, a) => sum + a.balance);
});
// Category Groups
final categoryGroupsProvider = StreamProvider<List<CategoryGroup>>((ref) {
final db = ref.watch(dbProvider);
return (db.select(db.categoryGroups)
..orderBy([(g) => OrderingTerm(expression: g.sortOrder)]))
.watch();
});
// Categories
final categoriesProvider = StreamProvider<List<Category>>((ref) {
final db = ref.watch(dbProvider);
return (db.select(db.categories)
..orderBy([(c) => OrderingTerm(expression: c.sortOrder)]))
.watch();
});
final categoriesByGroupProvider = Provider<Map<int, List<Category>>>((ref) {
final cats = ref.watch(categoriesProvider).valueOrNull ?? [];
final map = <int, List<Category>>{};
for (final c in cats) {
map.putIfAbsent(c.groupId, () => []).add(c);
}
return map;
});
// Transactions
final allTransactionsProvider = StreamProvider<List<Transaction>>((ref) {
final db = ref.watch(dbProvider);
return (db.select(db.transactions)
..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)]))
.watch();
});
final recentTransactionsProvider = Provider<List<Transaction>>((ref) {
final all = ref.watch(allTransactionsProvider).valueOrNull ?? [];
return all.take(10).toList();
});
final monthlyTransactionsProvider = Provider.family<List<Transaction>, String>((ref, month) {
final all = ref.watch(allTransactionsProvider).valueOrNull ?? [];
return all.where((t) {
final ym = '${t.date.year}-${t.date.month.toString().padLeft(2, '0')}';
return ym == month;
}).toList();
});
// Budget
final budgetsProvider = StreamProvider<List<Budget>>((ref) {
final db = ref.watch(dbProvider);
return db.select(db.budgets).watch();
});
final currentMonthProvider = Provider<String>((ref) {
final now = DateTime.now();
return '${now.year}-${now.month.toString().padLeft(2, '0')}';
});
final monthlyBudgetProvider = Provider.family<double?, int>((ref, categoryId) {
final month = ref.watch(currentMonthProvider);
final budgets = ref.watch(budgetsProvider).valueOrNull ?? [];
final match = budgets.where(
(b) => b.categoryId == categoryId && b.month == month,
);
return match.isNotEmpty ? match.first.budgeted : null;
});
final categorySpentProvider = Provider.family<double, int>((ref, categoryId) {
final month = ref.watch(currentMonthProvider);
final txs = ref.watch(monthlyTransactionsProvider(month));
final filtered = txs.where((t) => t.categoryId == categoryId);
return filtered.fold(0.0, (sum, t) => sum + t.amount);
});
final categoryRemainingProvider = Provider.family<double, int>((ref, categoryId) {
final budgeted = ref.watch(monthlyBudgetProvider(categoryId)) ?? 0.0;
final spent = ref.watch(categorySpentProvider(categoryId));
return budgeted + spent; // spent is negative, so budgeted + spent = remaining
});

View file

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/db_provider.dart';
import '../theme/app_theme.dart';
class AccountsScreen extends ConsumerWidget {
const AccountsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final accounts = ref.watch(openAccountsProvider);
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
children: [
...accounts.map((a) => _AccountCard(account: a)),
],
);
}
}
class _AccountCard extends StatelessWidget {
final Account account;
const _AccountCard({required this.account});
IconData _icon() {
switch (account.type) {
case 'savings': return Icons.savings_outlined;
case 'credit': return Icons.credit_card_outlined;
case 'cash': return Icons.money_outlined;
default: return Icons.account_balance_outlined;
}
}
String _label() {
switch (account.type) {
case 'checking': return 'Compte courant';
case 'savings': return 'Épargne';
case 'credit': return 'Carte de crédit';
case 'cash': return 'Espèces';
default: return account.type;
}
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: kSurfaceElevated,
borderRadius: BorderRadius.circular(12),
),
child: Icon(_icon(), color: kTextSecondary, size: 22),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(account.name,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w600)),
const SizedBox(height: 2),
Text(_label(), style: Theme.of(context).textTheme.bodySmall),
],
),
),
Text(
formatCurrency(account.balance),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: account.balance >= 0 ? kGreenAccent : kRedAccent,
),
),
],
),
),
);
}
}

View file

@ -0,0 +1,170 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/db_provider.dart';
import '../theme/app_theme.dart';
class BudgetScreen extends ConsumerWidget {
const BudgetScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final groups = ref.watch(categoryGroupsProvider).valueOrNull ?? [];
final catsByGroup = ref.watch(categoriesByGroupProvider);
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Budget du mois', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 4),
Text('Juillet 2026', style: Theme.of(context).textTheme.bodySmall),
],
),
),
),
const SizedBox(height: 16),
...groups.map((group) {
final cats = catsByGroup[group.id] ?? [];
if (cats.isEmpty) return const SizedBox.shrink();
return _CategoryGroupTile(group: group, categoryIds: cats.map((c) => c.id).toList());
}),
],
);
}
}
class _CategoryGroupTile extends ConsumerWidget {
final CategoryGroup group;
final List<int> categoryIds;
const _CategoryGroupTile({required this.group, required this.categoryIds});
@override
Widget build(BuildContext context, WidgetRef ref) {
final expanded = ref.watch(_expandedGroupProvider(group.id));
final cats = ref.watch(categoriesByGroupProvider)[group.id] ?? [];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () => ref.read(_expandedGroupProvider(group.id).notifier).toggle(),
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 4),
child: Row(
children: [
Icon(
expanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right,
size: 18,
color: kTextSecondary,
),
const SizedBox(width: 8),
Text(group.name,
style: Theme.of(context)
.textTheme
.titleSmall
?.copyWith(fontWeight: FontWeight.w600)),
const Spacer(),
Text(
formatCurrency(
cats.fold(0.0, (s, c) => s + ref.watch(categoryRemainingProvider(c.id))),
),
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
),
if (expanded)
...cats.map((c) => _CategoryRow(categoryId: c.id)),
const Divider(height: 1),
],
);
}
}
class _CategoryRow extends ConsumerWidget {
final int categoryId;
const _CategoryRow({required this.categoryId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final cats = ref.watch(categoriesByGroupProvider);
final category = cats.values
.expand((list) => list)
.firstWhere((c) => c.id == categoryId, orElse: () => throw StateError('missing'));
final budgeted = ref.watch(monthlyBudgetProvider(categoryId)) ?? 0.0;
final spent = ref.watch(categorySpentProvider(categoryId));
final remaining = ref.watch(categoryRemainingProvider(categoryId));
final pct = budgeted > 0 ? ((budgeted + spent) / budgeted).clamp(0.0, 1.0) : 0.0;
return Padding(
padding: const EdgeInsets.only(left: 8, bottom: 4),
child: Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(category.name, style: Theme.of(context).textTheme.bodyMedium),
),
Text(
formatCurrency(remaining),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: remaining >= 0 ? kGreenAccent : kRedAccent,
),
),
],
),
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: pct,
backgroundColor: kDivider,
valueColor: AlwaysStoppedAnimation<Color>(
remaining >= 0 ? kGreenAccent : kRedAccent,
),
minHeight: 4,
),
),
const SizedBox(height: 2),
Row(
children: [
Text('Budget: ${formatCurrency(budgeted)}',
style: Theme.of(context).textTheme.bodySmall),
const SizedBox(width: 12),
Text('Dépensé: ${formatCurrency(spent.abs())}',
style: Theme.of(context).textTheme.bodySmall),
],
),
],
),
),
),
);
}
}
class _ExpandedGroupNotifier extends StateNotifier<bool> {
_ExpandedGroupNotifier() : super(true);
void toggle() => state = !state;
}
final _expandedGroupProvider =
StateNotifierProvider.family<_ExpandedGroupNotifier, bool, int>(
(ref, id) => _ExpandedGroupNotifier());

View file

@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../providers/db_provider.dart';
import '../theme/app_theme.dart';
class DashboardScreen extends ConsumerWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final totalBalance = ref.watch(totalBalanceProvider);
final accounts = ref.watch(openAccountsProvider);
final recentTxs = ref.watch(recentTransactionsProvider);
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
children: [
// Total Balance
_BalanceCard(balance: totalBalance),
const SizedBox(height: 16),
// By accounts
Text('Comptes', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
...accounts.map((a) => _AccountMiniCard(account: a)),
const SizedBox(height: 24),
// Recent transactions
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Transactions récentes', style: Theme.of(context).textTheme.titleMedium),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.arrow_forward_ios, size: 14),
label: const Text('Voir tout'),
),
],
),
const SizedBox(height: 8),
if (recentTxs.isEmpty)
Card(
child: Padding(
padding: const EdgeInsets.all(32),
child: Center(child: Text('Aucune transaction', style: Theme.of(context).textTheme.bodyMedium)),
),
)
else
...recentTxs.map((t) => _TxItem(transaction: t)),
],
);
}
}
class _BalanceCard extends StatelessWidget {
final double balance;
const _BalanceCard({required this.balance});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Solde total', style: Theme.of(context).textTheme.bodyMedium),
const SizedBox(height: 8),
Text(
formatCurrency(balance),
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontSize: 36,
fontWeight: FontWeight.w700,
color: balance >= 0 ? kGreenAccent : kRedAccent,
),
),
],
),
),
);
}
}
class _AccountMiniCard extends StatelessWidget {
final Account account;
const _AccountMiniCard({required this.account});
IconData _icon() {
switch (account.type) {
case 'savings': return Icons.savings_outlined;
case 'credit': return Icons.credit_card_outlined;
case 'cash': return Icons.money_outlined;
default: return Icons.account_balance_outlined;
}
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 6),
child: ListTile(
dense: true,
leading: Icon(_icon(), color: kTextSecondary),
title: Text(account.name, style: Theme.of(context).textTheme.bodyMedium),
trailing: Text(
formatCurrency(account.balance),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: account.balance >= 0 ? kGreenAccent : kRedAccent,
),
),
),
);
}
}
class _TxItem extends StatelessWidget {
final Transaction transaction;
const _TxItem({required this.transaction});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 4),
child: ListTile(
dense: true,
leading: CircleAvatar(
radius: 18,
backgroundColor: kSurfaceElevated,
child: Icon(
transaction.amount >= 0 ? Icons.arrow_downward : Icons.arrow_upward,
size: 16,
color: transaction.amount >= 0 ? kGreenAccent : kRedAccent,
),
),
title: Text(transaction.payee, style: Theme.of(context).textTheme.bodyMedium),
subtitle: Text(
DateFormat('dd/MM/yy').format(transaction.date),
style: Theme.of(context).textTheme.bodySmall,
),
trailing: Text(
formatCurrency(transaction.amount),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: transaction.amount >= 0 ? kGreenAccent : kRedAccent,
),
),
),
);
}
}

View file

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../providers/db_provider.dart';
import '../theme/app_theme.dart';
class TransactionsScreen extends ConsumerWidget {
const TransactionsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final all = ref.watch(allTransactionsProvider).valueOrNull ?? [];
return ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
itemCount: all.length,
itemBuilder: (context, index) {
final t = all[index];
return Card(
margin: const EdgeInsets.only(bottom: 4),
child: ListTile(
dense: true,
leading: CircleAvatar(
radius: 18,
backgroundColor: kSurfaceElevated,
child: Icon(
t.amount >= 0 ? Icons.arrow_downward : Icons.arrow_upward,
size: 16,
color: t.amount >= 0 ? kGreenAccent : kRedAccent,
),
),
title: Text(t.payee, style: Theme.of(context).textTheme.bodyMedium),
subtitle: Text(
DateFormat('dd/MM/yy').format(t.date),
style: Theme.of(context).textTheme.bodySmall,
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
formatCurrency(t.amount),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: t.amount >= 0 ? kGreenAccent : kRedAccent,
),
),
],
),
),
);
},
);
}
}

144
lib/theme/app_theme.dart Normal file
View file

@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
// Couleurs custom
// Fond très sombre mais pas noir, vert fluo pour accent, elevation uniquement.
const kSurfaceBg = Color(0xFF12161C);
const kSurfaceCard = Color(0xFF1A1E26);
const kSurfaceElevated = Color(0xFF232730);
const kGreenAccent = Color(0xFF00E676);
const kGreenAccentDim = Color(0xFF00B85C);
const kRedAccent = Color(0xFFFF5252);
const kTextPrimary = Color(0xFFE8EAED);
const kTextSecondary = Color(0xFF9AA0A6);
const kDivider = Color(0xFF2C3038);
final ThemeData ynabDarkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed(
seedColor: kGreenAccent,
brightness: Brightness.dark,
surface: kSurfaceBg,
primary: kGreenAccent,
secondary: kGreenAccentDim,
error: kRedAccent,
onSurface: kTextPrimary,
onSurfaceVariant: kTextSecondary,
outline: kDivider,
),
scaffoldBackgroundColor: kSurfaceBg,
cardTheme: CardThemeData(
color: kSurfaceCard,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
clipBehavior: Clip.antiAlias,
),
navigationRailTheme: NavigationRailThemeData(
backgroundColor: kSurfaceCard,
indicatorColor: kGreenAccent.withAlpha(40),
unselectedIconTheme: const IconThemeData(color: kTextSecondary),
selectedIconTheme: const IconThemeData(color: kGreenAccent),
unselectedLabelTextStyle: GoogleFonts.inter(
color: kTextSecondary,
fontSize: 11,
),
selectedLabelTextStyle: GoogleFonts.inter(
color: kGreenAccent,
fontSize: 11,
fontWeight: FontWeight.w600,
),
labelType: NavigationRailLabelType.none,
),
appBarTheme: AppBarTheme(
backgroundColor: kSurfaceBg,
elevation: 0,
scrolledUnderElevation: 1,
centerTitle: false,
titleTextStyle: GoogleFonts.inter(
fontSize: 20,
fontWeight: FontWeight.w600,
color: kTextPrimary,
),
iconTheme: const IconThemeData(color: kTextSecondary),
),
textTheme: GoogleFonts.interTextTheme(
ThemeData.dark().textTheme.copyWith(
headlineLarge: const TextStyle(color: kTextPrimary),
headlineMedium: const TextStyle(color: kTextPrimary),
headlineSmall: const TextStyle(color: kTextPrimary),
titleLarge: const TextStyle(color: kTextPrimary),
titleMedium: const TextStyle(color: kTextPrimary),
titleSmall: const TextStyle(color: kTextPrimary),
bodyLarge: const TextStyle(color: kTextPrimary),
bodyMedium: const TextStyle(color: kTextPrimary, fontSize: 14),
bodySmall: const TextStyle(color: kTextSecondary, fontSize: 12),
labelLarge: const TextStyle(color: kTextPrimary),
labelMedium: const TextStyle(color: kTextSecondary),
labelSmall: const TextStyle(color: kTextSecondary),
),
),
dividerTheme: const DividerThemeData(color: kDivider, thickness: 1),
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: kGreenAccent,
foregroundColor: kSurfaceBg,
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: kSurfaceElevated,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: kGreenAccent, width: 1.5),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: kSurfaceCard,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
),
sliderTheme: SliderThemeData(
activeTrackColor: kGreenAccent,
thumbColor: kGreenAccent,
inactiveTrackColor: kDivider,
overlayColor: kGreenAccent.withAlpha(30),
),
dialogTheme: DialogThemeData(
backgroundColor: kSurfaceCard,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
);
// Utils
String formatCurrency(double amount) {
final sign = amount < 0 ? '-' : '';
final abs = amount.abs();
final parts = abs.toStringAsFixed(2).split('.');
final intPart = parts[0];
final decPart = parts[1];
final buf = StringBuffer();
int count = 0;
for (int i = intPart.length - 1; i >= 0; i--) {
if (count > 0 && count % 3 == 0) buf.write(' ');
buf.write(intPart[i]);
count++;
}
final formatted = '${buf.toString().split('').reversed.join()},$decPart';
return '$sign$formatted';
}
String formatCurrencyShort(double amount) {
final abs = amount.abs();
if (abs >= 1000000) return '${(abs / 1000000).toStringAsFixed(1)} M';
if (abs >= 1000) return '${(abs / 1000).toStringAsFixed(1)} k';
return '${abs.toStringAsFixed(2)}';
}

954
pubspec.lock Normal file
View file

@ -0,0 +1,954 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
url: "https://pub.dev"
source: hosted
version: "85.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c
url: "https://pub.dev"
source: hosted
version: "7.6.0"
analyzer_plugin:
dependency: transitive
description:
name: analyzer_plugin
sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce
url: "https://pub.dev"
source: hosted
version: "0.13.4"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
build:
dependency: transitive
description:
name: build
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78
url: "https://pub.dev"
source: hosted
version: "4.1.2"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
url: "https://pub.dev"
source: hosted
version: "2.5.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
url: "https://pub.dev"
source: hosted
version: "9.1.2"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
url: "https://pub.dev"
source: hosted
version: "8.12.6"
characters:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
url: "https://pub.dev"
source: hosted
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
ci:
dependency: transitive
description:
name: ci
sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
url: "https://pub.dev"
source: hosted
version: "1.2.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d"
url: "https://pub.dev"
source: hosted
version: "4.11.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.dev"
source: hosted
version: "1.0.9"
custom_lint:
dependency: transitive
description:
name: custom_lint
sha256: "9656925637516c5cf0f5da018b33df94025af2088fe09c8ae2ca54c53f2d9a84"
url: "https://pub.dev"
source: hosted
version: "0.7.6"
custom_lint_builder:
dependency: transitive
description:
name: custom_lint_builder
sha256: "6cdc8e87e51baaaba9c43e283ed8d28e59a0c4732279df62f66f7b5984655414"
url: "https://pub.dev"
source: hosted
version: "0.7.6"
custom_lint_core:
dependency: transitive
description:
name: custom_lint_core
sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be"
url: "https://pub.dev"
source: hosted
version: "0.7.5"
custom_lint_visitor:
dependency: transitive
description:
name: custom_lint_visitor
sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2"
url: "https://pub.dev"
source: hosted
version: "1.0.0+7.7.0"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
drift:
dependency: "direct main"
description:
name: drift
sha256: "540cf382a3bfa99b76e51514db5b0ebcd81ce3679b7c1c9cb9478ff3735e47a1"
url: "https://pub.dev"
source: hosted
version: "2.28.2"
drift_dev:
dependency: "direct dev"
description:
name: drift_dev
sha256: "68c138e884527d2bd61df2ade276c3a144df84d1adeb0ab8f3196b5afe021bd4"
url: "https://pub.dev"
source: hosted
version: "2.28.0"
drift_flutter:
dependency: "direct main"
description:
name: drift_flutter
sha256: b7534bf320aac5213259aac120670ba67b63a1fd010505babc436ff86083818f
url: "https://pub.dev"
source: hosted
version: "0.2.7"
equatable:
dependency: transitive
description:
name: equatable
sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d"
url: "https://pub.dev"
source: hosted
version: "0.66.2"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_riverpod:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
flutter_slidable:
dependency: "direct main"
description:
name: flutter_slidable
sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: b465e99ce64ba75e61c8c0ce3d87b66d8ac07f0b35d0a7e0263fcfc10f99e836
url: "https://pub.dev"
source: hosted
version: "13.2.5"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
url: "https://pub.dev"
source: hosted
version: "6.3.3"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hooks:
dependency: transitive
description:
name: hooks
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
hotreloader:
dependency: transitive
description:
name: hotreloader
sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.dev"
source: hosted
version: "3.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.dev"
source: hosted
version: "1.0.5"
jni:
dependency: transitive
description:
name: jni
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
url: "https://pub.dev"
source: hosted
version: "1.0.0"
jni_flutter:
dependency: transitive
description:
name: jni_flutter
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
url: "https://pub.dev"
source: hosted
version: "9.4.1"
package_config:
dependency: transitive
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
source: hosted
version: "2.2.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
url: "https://pub.dev"
source: hosted
version: "2.1.6"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pool:
dependency: transitive
description:
name: pool
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
recase:
dependency: transitive
description:
name: recase
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
url: "https://pub.dev"
source: hosted
version: "4.1.0"
record_use:
dependency: transitive
description:
name: record_use
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611"
url: "https://pub.dev"
source: hosted
version: "0.5.10"
riverpod_annotation:
dependency: "direct main"
description:
name: riverpod_annotation
sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
url: "https://pub.dev"
source: hosted
version: "2.6.1"
riverpod_generator:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: "44a0992d54473eb199ede00e2260bd3c262a86560e3c6f6374503d86d0580e36"
url: "https://pub.dev"
source: hosted
version: "2.6.5"
riverpod_lint:
dependency: "direct dev"
description:
name: riverpod_lint
sha256: "89a52b7334210dbff8605c3edf26cfe69b15062beed5cbfeff2c3812c33c9e35"
url: "https://pub.dev"
source: hosted
version: "2.6.5"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shelf:
dependency: transitive
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev"
source: hosted
version: "1.4.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
url: "https://pub.dev"
source: hosted
version: "2.9.4"
sqlite3_flutter_libs:
dependency: transitive
description:
name: sqlite3_flutter_libs
sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad
url: "https://pub.dev"
source: hosted
version: "0.5.42"
sqlparser:
dependency: transitive
description:
name: sqlparser
sha256: "57090342af1ce32bb499aa641f4ecdd2d6231b9403cea537ac059e803cc20d67"
url: "https://pub.dev"
source: hosted
version: "0.41.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
state_notifier:
dependency: transitive
description:
name: state_notifier
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
url: "https://pub.dev"
source: hosted
version: "1.0.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
url: "https://pub.dev"
source: hosted
version: "4.5.3"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
url: "https://pub.dev"
source: hosted
version: "15.2.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.12.2 <4.0.0"
flutter: ">=3.38.4"

34
pubspec.yaml Normal file
View file

@ -0,0 +1,34 @@
name: ynab_lite
description: "Budget manager — YNAB-like"
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.12.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.0
drift: ^2.20.0
drift_flutter: ^0.2.0
path_provider: ^2.1.3
go_router: ^13.0.1
intl: ^0.19.0
fl_chart: ^0.66.2
flutter_slidable: ^3.1.1
google_fonts: ^6.1.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.4.9
drift_dev: ^2.20.0
riverpod_generator: ^2.4.0
riverpod_lint: ^2.3.0
flutter:
uses-material-design: true

21
test/widget_test.dart Normal file
View file

@ -0,0 +1,21 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:ynab_lite/app.dart';
import 'package:ynab_lite/theme/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
testWidgets('App renders dashboard by default', (tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
theme: ynabDarkTheme,
home: YnabApp(),
),
),
);
expect(find.text('Tableau de bord'), findsOneWidget);
expect(find.text('Solde total'), findsOneWidget);
});
}