Added theme, reworked UI
This commit is contained in:
11
lib/app.dart
11
lib/app.dart
@@ -4,6 +4,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:tunas/clients/storage/storage_client.dart';
|
||||
import 'package:tunas/pages/home/home_page.dart';
|
||||
import 'package:tunas/repositories/account/account_repository.dart';
|
||||
import 'package:tunas/theme.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
const App({super.key});
|
||||
@@ -44,13 +45,9 @@ class _AppViewState extends State<AppView> {
|
||||
providers: [RepositoryProvider.value(value: _accountRepository)],
|
||||
child: MaterialApp(
|
||||
title: 'Tunas',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color.fromARGB(255, 55, 55, 55),
|
||||
brightness: Brightness.dark
|
||||
),
|
||||
useMaterial3: true
|
||||
),
|
||||
theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme),
|
||||
darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme),
|
||||
themeMode: ThemeMode.dark,
|
||||
initialRoute: '/home',
|
||||
routes: {
|
||||
'/home':(context) => const HomePage(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:tunas/domains/charts/models/month_totals.dart';
|
||||
import 'package:tunas/domains/transaction/models/transaction_line.dart';
|
||||
import 'package:tunas/domains/charts/models/chart_item.dart';
|
||||
import 'package:tunas/repositories/account/account_repository.dart';
|
||||
import 'package:tunas/repositories/account/models/category.dart';
|
||||
import 'package:tunas/repositories/account/models/transaction.dart';
|
||||
|
||||
part 'chart_event.dart';
|
||||
@@ -16,20 +17,30 @@ class ChartBloc extends Bloc<ChartEvent, ChartState> {
|
||||
ChartBloc({required AccountRepository accountRepository}) :
|
||||
_accountRepository = accountRepository, super(const ChartState()) {
|
||||
|
||||
on<ChartAccountLoad>(_onAccountLoad);
|
||||
on<ChartTransactionsLoad>(_onChartTransactionsLoad);
|
||||
on<ChartCategoriesLoad>(_onChartCategoriesLoad);
|
||||
on<ChartNextYear>(_onNextYear);
|
||||
on<ChartPreviousYear>(_onPreviousYear);
|
||||
|
||||
_accountRepository
|
||||
.getTransactionsStream()
|
||||
.listen((transactions) => add(ChartAccountLoad(transactions)));
|
||||
.listen((transactions) => add(ChartTransactionsLoad(transactions)));
|
||||
|
||||
_accountRepository
|
||||
.getCategoriesStream()
|
||||
.listen((categories) => add(ChartCategoriesLoad(categories)));
|
||||
}
|
||||
|
||||
_onAccountLoad(ChartAccountLoad event, Emitter<ChartState> emit) {
|
||||
_onChartTransactionsLoad(ChartTransactionsLoad event, Emitter<ChartState> emit) {
|
||||
ChartState localState = state.copyWith(transactions: event.transactions);
|
||||
emit(_computeStateStats(localState));
|
||||
}
|
||||
|
||||
_onChartCategoriesLoad(ChartCategoriesLoad event, Emitter<ChartState> emit) {
|
||||
ChartState localState = state.copyWith(categories: Map.fromEntries(event.categories.map((category) => MapEntry(category.label, category))));
|
||||
emit(_computeStateStats(localState));
|
||||
}
|
||||
|
||||
_onNextYear(ChartNextYear event, Emitter<ChartState> emit) {
|
||||
if (state.lastDate!.year >= state.currentYear + 1) {
|
||||
ChartState localState = state.copyWith(currentYear: state.currentYear + 1);
|
||||
@@ -123,7 +134,8 @@ class ChartBloc extends Bloc<ChartEvent, ChartState> {
|
||||
|
||||
scopedMonthlyTotals[transactionDateDay] = FlSpot(transactionDateDay.toDouble(), transactionLine.subTotal);
|
||||
|
||||
if (transaction.category.isEmpty) {
|
||||
final category = state.categories[transaction.category];
|
||||
if (category == null || category.saving) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -201,15 +213,20 @@ class ChartBloc extends Bloc<ChartEvent, ChartState> {
|
||||
List<ChartItem> scopedSimplifiedCategoriesPositiveTotals = [];
|
||||
List<ChartItem> scopedSimplifiedCategoriesNegativeTotals = [];
|
||||
|
||||
scopedCategoriesPositiveTotals.sort((a, b) => a.value.compareTo(b.value));
|
||||
scopedCategoriesPositiveTotalsPercents.sort((a, b) => a.value.compareTo(b.value));
|
||||
scopedCategoriesPositiveTotals.sort((a, b) => b.value.compareTo(a.value));
|
||||
scopedCategoriesPositiveTotalsPercents.sort((a, b) => b.value.compareTo(a.value));
|
||||
scopedCategoriesNegativeTotals.sort((a, b) => a.value.compareTo(b.value));
|
||||
scopedCategoriesNegativeTotalsPercents.sort((a, b) => a.value.compareTo(b.value));
|
||||
scopedSimplifiedCategoriesPositiveTotals.sort((a, b) => a.value.compareTo(b.value));
|
||||
scopedSimplifiedCategoriesPositiveTotalsPercents.sort((a, b) => a.value.compareTo(b.value));
|
||||
scopedSimplifiedCategoriesPositiveTotals.sort((a, b) => b.value.compareTo(a.value));
|
||||
scopedSimplifiedCategoriesPositiveTotalsPercents.sort((a, b) => b.value.compareTo(a.value));
|
||||
scopedSimplifiedCategoriesNegativeTotals.sort((a, b) => a.value.compareTo(b.value));
|
||||
scopedSimplifiedCategoriesNegativeTotalsPercents.sort((a, b) => a.value.compareTo(b.value));
|
||||
|
||||
for (var monthTotals in scopedCategoriesMonthlyTotals.values) {
|
||||
_sortMapByValues(monthTotals.positives);
|
||||
_sortMapByValues(monthTotals.negatives);
|
||||
}
|
||||
|
||||
return state.copyWith(
|
||||
scoppedProfit: scoppedTotal,
|
||||
scopedCategoriesPositiveTotals: scopedCategoriesPositiveTotals,
|
||||
@@ -226,4 +243,15 @@ class ChartBloc extends Bloc<ChartEvent, ChartState> {
|
||||
scopedMonthlyNegativeTotals: scopedMonthlyNegativeTotals,
|
||||
);
|
||||
}
|
||||
|
||||
Map<dynamic, dynamic> _sortMapByValues(Map<dynamic, dynamic> map) {
|
||||
final sortedEntries = map.entries.toList()..sort((a, b) {
|
||||
var diff = b.value.compareTo(a.value);
|
||||
return diff;
|
||||
});
|
||||
|
||||
return map
|
||||
..clear()
|
||||
..addEntries(sortedEntries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,21 @@ sealed class ChartEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ChartAccountLoad extends ChartEvent {
|
||||
final class ChartTransactionsLoad extends ChartEvent {
|
||||
final List<Transaction> transactions;
|
||||
const ChartAccountLoad(this.transactions);
|
||||
const ChartTransactionsLoad(this.transactions);
|
||||
|
||||
@override
|
||||
List<Object> get props => [transactions];
|
||||
}
|
||||
|
||||
final class ChartCategoriesLoad extends ChartEvent {
|
||||
final List<Category> categories;
|
||||
const ChartCategoriesLoad(this.categories);
|
||||
|
||||
@override
|
||||
List<Object> get props => [categories];
|
||||
}
|
||||
|
||||
final class ChartNextYear extends ChartEvent {}
|
||||
final class ChartPreviousYear extends ChartEvent {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
part of 'chart_bloc.dart';
|
||||
|
||||
final class ChartState extends Equatable {
|
||||
final Map<String, Category> categories;
|
||||
final List<Transaction> transactions;
|
||||
final List<TransactionLine> transactionsLines;
|
||||
|
||||
@@ -34,6 +35,7 @@ final class ChartState extends Equatable {
|
||||
final double scoppedProfit;
|
||||
|
||||
const ChartState({
|
||||
this.categories = const {},
|
||||
this.transactions = const [],
|
||||
this.transactionsLines = const [],
|
||||
this.globalTotal = 0,
|
||||
@@ -60,6 +62,7 @@ final class ChartState extends Equatable {
|
||||
});
|
||||
|
||||
ChartState copyWith({
|
||||
Map<String, Category>? categories,
|
||||
List<Transaction>? transactions,
|
||||
List<TransactionLine>? transactionsLines,
|
||||
double? globalTotal,
|
||||
@@ -85,6 +88,7 @@ final class ChartState extends Equatable {
|
||||
Map<int, double>? scopedMonthlyNegativeTotals,
|
||||
}) {
|
||||
return ChartState(
|
||||
categories: categories ?? this.categories,
|
||||
transactions: transactions ?? this.transactions,
|
||||
transactionsLines: transactionsLines ?? this.transactionsLines,
|
||||
globalTotal: globalTotal ?? this.globalTotal,
|
||||
@@ -113,6 +117,7 @@ final class ChartState extends Equatable {
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
categories,
|
||||
transactions,
|
||||
transactionsLines,
|
||||
globalTotal,
|
||||
|
||||
57
lib/pages/common/titled_container.dart
Normal file
57
lib/pages/common/titled_container.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TitledContainer extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget child;
|
||||
|
||||
const TitledContainer({super.key, required this.title, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.shadow,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(2, 2),
|
||||
spreadRadius: 0.1,
|
||||
blurStyle: BlurStyle.normal,
|
||||
)
|
||||
]
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w300,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,24 +15,25 @@ class DataPage extends StatelessWidget {
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 10),
|
||||
margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 10),
|
||||
child: const Column(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
const Text(
|
||||
'Settings',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 35,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ImportSettings(),
|
||||
CategoriesSettings(),
|
||||
AccountSettings(),
|
||||
],
|
||||
)
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: const [
|
||||
ImportSettings(),
|
||||
CategoriesSettings(),
|
||||
AccountSettings(),
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tunas/domains/account/account_bloc.dart';
|
||||
import 'package:tunas/pages/common/titled_container.dart';
|
||||
|
||||
class AccountSettings extends StatelessWidget {
|
||||
const AccountSettings({super.key});
|
||||
@@ -16,34 +17,15 @@ class AccountSettings extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AccountBloc, AccountState>(
|
||||
builder: (context, state) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
builder: (context, state) => TitledContainer(
|
||||
title: "Accounts",
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _computeCategoryList(state.subAccounts),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
|
||||
margin: const EdgeInsets.all(5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Accounts",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _computeCategoryList(state.subAccounts),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tunas/domains/category/category_bloc.dart';
|
||||
import 'package:tunas/pages/common/titled_container.dart';
|
||||
import 'package:tunas/repositories/account/models/category.dart';
|
||||
|
||||
class CategoriesSettings extends StatelessWidget {
|
||||
const CategoriesSettings({super.key});
|
||||
|
||||
List<Widget> _computeCategoryList(List<Category> categories) {
|
||||
List<Widget> _computeCategoryList(BuildContext context, List<Category> categories) {
|
||||
return categories.map((category) => Row(
|
||||
children: [
|
||||
Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.palette),
|
||||
color: category.rgbToColor(),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.savings),
|
||||
color: category.saving ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.foundation),
|
||||
color: category.essential ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error,
|
||||
),
|
||||
Container(width: 5),
|
||||
Text(category.label),
|
||||
Expanded(
|
||||
child: Text(category.label)
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.edit),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
)).toList();
|
||||
}
|
||||
@@ -23,35 +44,16 @@ class CategoriesSettings extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CategoryBloc, CategoryState>(
|
||||
builder: (context, state) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
builder: (context, state) => TitledContainer(
|
||||
title: "Categories",
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _computeCategoryList(context, state.categories),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
|
||||
margin: const EdgeInsets.all(5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Categories",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _computeCategoryList(state.categories),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tunas/domains/account/account_bloc.dart';
|
||||
import 'package:tunas/pages/common/titled_container.dart';
|
||||
|
||||
class ImportSettings extends StatelessWidget {
|
||||
const ImportSettings({super.key});
|
||||
@@ -8,25 +9,10 @@ class ImportSettings extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AccountBloc, AccountState>(
|
||||
builder: (context, state) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(5)
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
|
||||
margin: const EdgeInsets.all(5),
|
||||
builder: (context, state) => TitledContainer(
|
||||
title: "Import",
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Import",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
FilledButton(
|
||||
onPressed: () => context.read<AccountBloc>().add(const AccountImportCSV()),
|
||||
child: const Text('Import CSV')
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tunas/domains/charts/chart_bloc.dart';
|
||||
import 'package:tunas/pages/stats/widgets/account_counters.dart';
|
||||
import 'package:tunas/pages/stats/widgets/categories_totals_chart.dart';
|
||||
import 'package:tunas/pages/stats/widgets/main_counter.dart';
|
||||
import 'package:tunas/pages/stats/widgets/global_counter.dart';
|
||||
import 'package:tunas/pages/stats/widgets/monthly_categories_total_chart.dart';
|
||||
import 'package:tunas/pages/stats/widgets/monthly_total_chart.dart';
|
||||
import 'package:tunas/pages/stats/widgets/global_total_chart.dart';
|
||||
import 'package:tunas/pages/stats/widgets/profit_indicator.dart';
|
||||
import 'package:tunas/pages/stats/widgets/year_selector.dart';
|
||||
import 'package:tunas/repositories/account/account_repository.dart';
|
||||
@@ -31,7 +31,7 @@ class StatsPage extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: MainCounter(value: state.globalTotal)
|
||||
child: GlobalCounter(value: state.globalTotal)
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
|
||||
@@ -6,7 +6,7 @@ class AccountCounter extends StatelessWidget {
|
||||
|
||||
const AccountCounter({super.key, required this.accountsTotals});
|
||||
|
||||
List<Row> _renderAccountTotals() {
|
||||
List<Row> _renderAccountTotals(BuildContext context) {
|
||||
return accountsTotals.entries.toList().map((entry) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -21,7 +21,7 @@ class AccountCounter extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontFamily: 'NovaMono',
|
||||
fontSize: 15,
|
||||
color: entry.value > 0 ? const Color.fromARGB(255, 0, 255, 8) : Colors.red
|
||||
color: entry.value > 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error
|
||||
)),
|
||||
],
|
||||
)).toList();
|
||||
@@ -33,13 +33,22 @@ class AccountCounter extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(10),
|
||||
margin: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.blue
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.shadow,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(2, 2),
|
||||
spreadRadius: 0.1,
|
||||
blurStyle: BlurStyle.normal,
|
||||
)
|
||||
]
|
||||
),
|
||||
alignment: Alignment.centerRight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: _renderAccountTotals(),
|
||||
children: _renderAccountTotals(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@ class CategoriesTotalsChart extends StatelessWidget {
|
||||
Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
color: categoriesColors[item.label],
|
||||
decoration: BoxDecoration(
|
||||
color: categoriesColors[item.label],
|
||||
borderRadius: BorderRadius.circular(15)
|
||||
),
|
||||
),
|
||||
Container(width: 5),
|
||||
Text(item.label),
|
||||
@@ -64,8 +67,17 @@ class CategoriesTotalsChart extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(10),
|
||||
margin: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.blue
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.shadow,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(2, 2),
|
||||
spreadRadius: 0.1,
|
||||
blurStyle: BlurStyle.normal,
|
||||
)
|
||||
]
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -87,7 +99,7 @@ class CategoriesTotalsChart extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.blueGrey
|
||||
color: Theme.of(context).colorScheme.secondaryContainer
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
|
||||
39
lib/pages/stats/widgets/global_counter.dart
Normal file
39
lib/pages/stats/widgets/global_counter.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class GlobalCounter extends StatelessWidget {
|
||||
final double value;
|
||||
|
||||
const GlobalCounter({super.key, required this.value});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
margin: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.shadow,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(2, 2),
|
||||
spreadRadius: 0.1,
|
||||
blurStyle: BlurStyle.normal,
|
||||
)
|
||||
]
|
||||
),
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
NumberFormat('000.00 €', 'fr_FR').format(value),
|
||||
style: TextStyle(
|
||||
fontFamily: 'NovaMono',
|
||||
fontSize: 60,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: value > 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,7 @@ class GlobalTotalChart extends StatelessWidget {
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
bottom: 12,
|
||||
right: 20,
|
||||
top: 20,
|
||||
),
|
||||
child: AspectRatio(
|
||||
@@ -31,7 +29,7 @@ class GlobalTotalChart extends StatelessWidget {
|
||||
lineTouchData: LineTouchData(
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
maxContentWidth: 100,
|
||||
tooltipBgColor: Colors.black,
|
||||
tooltipBgColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
getTooltipItems: (touchedSpots) {
|
||||
return touchedSpots.map((LineBarSpot touchedSpot) {
|
||||
final textStyle = TextStyle(
|
||||
@@ -52,13 +50,18 @@ class GlobalTotalChart extends StatelessWidget {
|
||||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
color: Colors.pink,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
spots: monthlyTotals,
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
barWidth: 3,
|
||||
belowBarData: BarAreaData(
|
||||
show: false,
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [Theme.of(context).colorScheme.primary, Theme.of(context).colorScheme.secondary]
|
||||
.map((color) => color.withOpacity(0.2))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
dotData: const FlDotData(show: false),
|
||||
),
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class MainCounter extends StatelessWidget {
|
||||
final double value;
|
||||
|
||||
const MainCounter({super.key, required this.value});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
margin: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.blue
|
||||
),
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
NumberFormat('000.00 €', 'fr_FR').format(value),
|
||||
style: TextStyle(
|
||||
fontFamily: 'NovaMono',
|
||||
fontSize: 60,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: value > 0 ? const Color.fromARGB(255, 0, 255, 8) : Colors.red
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -100,10 +100,10 @@ class MonthlyCategoriesTotalChart extends StatelessWidget {
|
||||
Color color = Colors.black;
|
||||
if (rodIndex == 0) {
|
||||
value = "+$value";
|
||||
color = Colors.green;
|
||||
color = Theme.of(context).colorScheme.primary;
|
||||
} else {
|
||||
value = "-$value";
|
||||
color = Colors.red;
|
||||
color = Theme.of(context).colorScheme.error;
|
||||
}
|
||||
|
||||
return BarTooltipItem(
|
||||
|
||||
@@ -13,7 +13,7 @@ class ProfitIndicator extends StatelessWidget {
|
||||
margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
|
||||
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(5)
|
||||
),
|
||||
child: Text(
|
||||
@@ -22,7 +22,7 @@ class ProfitIndicator extends StatelessWidget {
|
||||
fontFamily: 'NovaMono',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: profit > 0 ? const Color.fromARGB(255, 0, 255, 8) : Colors.red
|
||||
color: profit > 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -12,8 +12,8 @@ class YearSelector extends StatelessWidget {
|
||||
margin: const EdgeInsets.fromLTRB(20, 0, 0, 0),
|
||||
padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(5)
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -48,13 +48,13 @@ class TransactionLine extends StatelessWidget {
|
||||
Text(
|
||||
NumberFormat(transaction.value > 0 ? '+#######.00 €' : '#######.00 €', 'fr_FR').format(transaction.value),
|
||||
style: TextStyle(
|
||||
color: transaction.value > 0 ? Colors.green : Colors.red
|
||||
color: transaction.value > 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error
|
||||
)
|
||||
),
|
||||
Text(
|
||||
NumberFormat('#######.00 €', 'fr_FR').format(subTotal),
|
||||
style: TextStyle(
|
||||
color: subTotal > 0 ? Colors.green : Colors.red
|
||||
color: subTotal > 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error
|
||||
)
|
||||
),
|
||||
],
|
||||
|
||||
@@ -3,22 +3,30 @@ import 'dart:ui';
|
||||
class Category {
|
||||
String label;
|
||||
String color;
|
||||
bool essential;
|
||||
bool saving;
|
||||
|
||||
Category({
|
||||
this.label = '',
|
||||
this.color = '',
|
||||
this.essential = false,
|
||||
this.saving = false,
|
||||
});
|
||||
|
||||
factory Category.fromJson(Map<String, dynamic> json) {
|
||||
return Category(
|
||||
label: json['label'],
|
||||
color: json['color']
|
||||
color: json['color'],
|
||||
essential: bool.parse(json['essential']),
|
||||
saving: bool.parse(json['saving']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, String> toJson() => {
|
||||
'label': label,
|
||||
'color': color,
|
||||
'essential': essential.toString(),
|
||||
'saving': saving.toString(),
|
||||
};
|
||||
|
||||
Color rgbToColor() {
|
||||
|
||||
69
lib/theme.dart
Normal file
69
lib/theme.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const lightColorScheme = ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: Color(0xFF006C45),
|
||||
onPrimary: Color(0xFFFFFFFF),
|
||||
primaryContainer: Color(0xFF51FFB1),
|
||||
onPrimaryContainer: Color(0xFF002112),
|
||||
secondary: Color(0xFF4E6355),
|
||||
onSecondary: Color(0xFFFFFFFF),
|
||||
secondaryContainer: Color(0xFFD0E8D7),
|
||||
onSecondaryContainer: Color(0xFF0B1F15),
|
||||
tertiary: Color(0xFF3C6472),
|
||||
onTertiary: Color(0xFFFFFFFF),
|
||||
tertiaryContainer: Color(0xFFBFE9F9),
|
||||
onTertiaryContainer: Color(0xFF001F28),
|
||||
error: Color(0xFFBA1A1A),
|
||||
errorContainer: Color(0xFFFFDAD6),
|
||||
onError: Color(0xFFFFFFFF),
|
||||
onErrorContainer: Color(0xFF410002),
|
||||
background: Color(0xFFFBFDF8),
|
||||
onBackground: Color(0xFF191C1A),
|
||||
surface: Color(0xFFFBFDF8),
|
||||
onSurface: Color(0xFF191C1A),
|
||||
surfaceVariant: Color(0xFFDCE5DC),
|
||||
onSurfaceVariant: Color(0xFF404943),
|
||||
outline: Color(0xFF707972),
|
||||
onInverseSurface: Color(0xFFEFF1ED),
|
||||
inverseSurface: Color(0xFF2E312E),
|
||||
inversePrimary: Color(0xFF21E297),
|
||||
shadow: Color(0xFF000000),
|
||||
surfaceTint: Color(0xFF006C45),
|
||||
outlineVariant: Color(0xFFC0C9C1),
|
||||
scrim: Color(0xFF000000),
|
||||
);
|
||||
|
||||
const darkColorScheme = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: Color(0xFF21E297),
|
||||
onPrimary: Color(0xFF003822),
|
||||
primaryContainer: Color(0xFF005233),
|
||||
onPrimaryContainer: Color(0xFF51FFB1),
|
||||
secondary: Color(0xFFB4CCBB),
|
||||
onSecondary: Color(0xFF203529),
|
||||
secondaryContainer: Color(0xFF364B3E),
|
||||
onSecondaryContainer: Color(0xFFD0E8D7),
|
||||
tertiary: Color(0xFFA4CDDD),
|
||||
onTertiary: Color(0xFF043542),
|
||||
tertiaryContainer: Color(0xFF234C59),
|
||||
onTertiaryContainer: Color(0xFFBFE9F9),
|
||||
error: Color(0xFFFFB4AB),
|
||||
errorContainer: Color(0xFF93000A),
|
||||
onError: Color(0xFF690005),
|
||||
onErrorContainer: Color(0xFFFFDAD6),
|
||||
background: Color(0xFF191C1A),
|
||||
onBackground: Color(0xFFE1E3DF),
|
||||
surface: Color(0xFF191C1A),
|
||||
onSurface: Color(0xFFE1E3DF),
|
||||
surfaceVariant: Color(0xFF404943),
|
||||
onSurfaceVariant: Color(0xFFC0C9C1),
|
||||
outline: Color(0xFF8A938B),
|
||||
onInverseSurface: Color(0xFF191C1A),
|
||||
inverseSurface: Color(0xFFE1E3DF),
|
||||
inversePrimary: Color(0xFF006C45),
|
||||
shadow: Color(0xFF000000),
|
||||
surfaceTint: Color(0xFF21E297),
|
||||
outlineVariant: Color(0xFF404943),
|
||||
scrim: Color(0xFF000000),
|
||||
);
|
||||
20
pubspec.lock
20
pubspec.lock
@@ -121,6 +121,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.65.0"
|
||||
flex_color_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flex_color_picker
|
||||
sha256: "0871edc170153cfc3de316d30625f40a85daecfa76ce541641f3cc0ec7757cbf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
flex_seed_scheme:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flex_seed_scheme
|
||||
sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -130,10 +146,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_bloc
|
||||
sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae
|
||||
sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.3"
|
||||
version: "8.1.4"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -15,7 +15,7 @@ dependencies:
|
||||
cupertino_icons: ^1.0.2
|
||||
fl_chart: ^0.65.0
|
||||
logging: ^1.2.0
|
||||
flutter_bloc: ^8.1.3
|
||||
flutter_bloc: ^8.1.4
|
||||
path_provider: ^2.1.1
|
||||
equatable: ^2.0.5
|
||||
rxdart: ^0.27.7
|
||||
@@ -27,6 +27,7 @@ dependencies:
|
||||
formz: ^0.6.1
|
||||
uuid: ^4.3.2
|
||||
flutter_typeahead: ^5.2.0
|
||||
flex_color_picker: ^3.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user