Skip to content

Commit

Permalink
Merge pull request #69 from enrique-lozano/feat/transaction-tags
Browse files Browse the repository at this point in the history
Introduce transaction tags!
  • Loading branch information
enrique-lozano authored Oct 31, 2023
2 parents 3569b52 + 81e2c38 commit bab1906
Show file tree
Hide file tree
Showing 27 changed files with 1,596 additions and 93 deletions.
3 changes: 3 additions & 0 deletions assets/sql/migrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Here are the files that contains the SQL scripts to go from a version to another.

The name of this files will be v2.sql, v3.sql... and so on, where the number represent the target version. _DO NOT_ modify the `dbVersion` param of the `appData` table, this param is autoupdated when a migration occurs.
14 changes: 13 additions & 1 deletion assets/sql/migrations/v5.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
INSERT INTO userSettings VALUES ('accentColor', 'auto'),
('amoledMode', '0');
('amoledMode', '0');

CREATE TABLE IF NOT EXISTS tags (
id TEXT NOT NULL PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
color TEXT NOT NULL,
description TEXT
);

CREATE TABLE IF NOT EXISTS transactionTags (
transactionID TEXT NOT NULL REFERENCES transactions(id) ON DELETE CASCADE ON UPDATE CASCADE,
tagID TEXT NOT NULL REFERENCES tags(id) ON DELETE CASCADE ON UPDATE CASCADE
);
2 changes: 1 addition & 1 deletion lib/app/home/home.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import 'package:monekin/app/accounts/account_form.dart';
import 'package:monekin/app/home/widgets/home_drawer.dart';
import 'package:monekin/app/home/widgets/income_or_expense_card.dart';
import 'package:monekin/app/stats/widgets/balance_bar_chart_small.dart';
import 'package:monekin/app/stats/widgets/chart_by_categories.dart';
import 'package:monekin/app/stats/widgets/fund_evolution_line_chart.dart';
import 'package:monekin/app/stats/widgets/movements_distribution/chart_by_categories.dart';
import 'package:monekin/app/transactions/form/transaction_form.page.dart';
import 'package:monekin/app/transactions/transactions.page.dart';
import 'package:monekin/app/transactions/widgets/transaction_list.dart';
Expand Down
16 changes: 14 additions & 2 deletions lib/app/settings/settings.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:monekin/app/settings/appearance_settings_page.dart';
import 'package:monekin/app/settings/backup_settings_page.dart';
import 'package:monekin/app/settings/edit_profile_modal.dart';
import 'package:monekin/app/settings/help_us_page.dart';
import 'package:monekin/app/tags/tag_list.dart';
import 'package:monekin/core/database/services/user-setting/user_setting_service.dart';
import 'package:monekin/core/presentation/theme.dart';
import 'package:monekin/core/presentation/widgets/skeleton.dart';
Expand Down Expand Up @@ -101,8 +102,19 @@ class _SettingsPageState extends State<SettingsPage> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CategoriesList(
mode: CategoriesListMode.page)));
builder: (context) =>
const CategoriesList(mode: CategoriesListMode.page),
));
}),
createSettingItem(context,
title: t.tags.display(n: 10),
subtitle: t.settings.general.categories_descr,
icon: Icons.label_outline_rounded, onTap: () {
return Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TagList(),
));
}),
createSettingItem(context,
title: t.currencies.currency_manager,
Expand Down
34 changes: 24 additions & 10 deletions lib/app/stats/stats_page.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import 'package:monekin/app/stats/footer_segmented_calendar_button.dart';
import 'package:monekin/app/stats/widgets/balance_bar_chart.dart';
import 'package:monekin/app/stats/widgets/chart_by_categories.dart';
import 'package:monekin/app/stats/widgets/fund_evolution_line_chart.dart';
import 'package:monekin/app/stats/widgets/income_expense_comparason.dart';
import 'package:monekin/app/stats/widgets/movements_distribution/chart_by_categories.dart';
import 'package:monekin/app/stats/widgets/movements_distribution/tags_stats.dart';
import 'package:monekin/core/database/services/account/account_service.dart';
import 'package:monekin/core/models/transaction/transaction.dart';
import 'package:monekin/core/presentation/widgets/card_with_header.dart';
Expand Down Expand Up @@ -50,7 +51,7 @@ class _StatsPageState extends State<StatsPage> {
Widget buildContainerWithPadding(
List<Widget> children, {
EdgeInsetsGeometry padding =
const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
}) {
return SingleChildScrollView(
padding: padding,
Expand Down Expand Up @@ -91,7 +92,7 @@ class _StatsPageState extends State<StatsPage> {
icon: const Icon(Icons.filter_alt_outlined)),
],
bottom: TabBar(tabs: [
Tab(text: t.stats.by_categories),
Tab(text: t.stats.distribution),
Tab(text: t.stats.balance),
Tab(text: t.stats.cash_flow),
], isScrollable: true),
Expand Down Expand Up @@ -123,14 +124,27 @@ class _StatsPageState extends State<StatsPage> {
Expanded(
child: TabBarView(children: [
buildContainerWithPadding([
ChartByCategories(
startDate: currentStartDate,
endDate: currentEndDate,
showList: true,
initialSelectedType: TransactionType.expense,
filters: filters,
CardWithHeader(
title: t.stats.by_categories,
body: ChartByCategories(
startDate: currentStartDate,
endDate: currentEndDate,
showList: true,
initialSelectedType: TransactionType.expense,
filters: filters,
),
),
], padding: const EdgeInsets.all(0)),
const SizedBox(height: 16),
CardWithHeader(
title: t.stats.by_tags,
body: TagStats(
filters: filters.copyWith(
minDate: currentStartDate,
maxDate: currentEndDate,
),
),
),
]),
buildContainerWithPadding(
[
CardWithHeader(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:monekin/app/stats/widgets/chart_by_categories.dart';
import 'package:monekin/app/stats/widgets/movements_distribution/chart_by_categories.dart';
import 'package:monekin/core/database/services/exchange-rate/exchange_rate_service.dart';
import 'package:monekin/core/models/category/category.dart';
import 'package:monekin/core/models/supported-icon/supported_icon.dart';
import 'package:monekin/core/presentation/widgets/animated_progress_bar.dart';
import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
Expand All @@ -28,7 +29,7 @@ class CategoryStatsModal extends StatelessWidget {
required this.categoryData,
required this.dateRangeDisplayName});

final ChartByCategoriesDataItem categoryData;
final TrDistributionChartItem<Category> categoryData;
final String dateRangeDisplayName;

Future<List<SubcategoryModalItem>> getSubcategoriesData(
Expand All @@ -49,9 +50,6 @@ class CategoryStatsModal extends StatelessWidget {
amount: transaction.value.abs())
.first;

print(transaction);
print("-----------");

if (categoryToEdit != null) {
categoryToEdit.value += trValue;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,24 @@ import 'package:collection/collection.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:monekin/app/stats/widgets/category_stats_modal.dart';
import 'package:monekin/app/stats/widgets/movements_distribution/category_stats_modal.dart';
import 'package:monekin/core/database/services/category/category_service.dart';
import 'package:monekin/core/database/services/exchange-rate/exchange_rate_service.dart';
import 'package:monekin/core/database/services/transaction/transaction_service.dart';
import 'package:monekin/core/models/category/category.dart';
import 'package:monekin/core/models/transaction/transaction.dart';
import 'package:monekin/core/models/transaction/transaction_status.dart';
import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart';
import 'package:monekin/core/services/filters/date_range_service.dart';
import 'package:monekin/core/utils/color_utils.dart';
import 'package:monekin/i18n/translations.g.dart';

import '../../../core/services/filters/date_range_service.dart';

class ChartByCategoriesDataItem {
Category category;
class TrDistributionChartItem<T> {
T category;
List<MoneyTransaction> transactions;
double value;

ChartByCategoriesDataItem({
TrDistributionChartItem({
required this.category,
required this.transactions,
required this.value,
Expand Down Expand Up @@ -54,10 +52,10 @@ class _ChartByCategoriesState extends State<ChartByCategories> {
int touchedIndex = -1;
late TransactionType transactionsType;

Future<List<ChartByCategoriesDataItem>> getEvolutionData(
Future<List<TrDistributionChartItem<Category>>> getEvolutionData(
BuildContext context,
) async {
final data = <ChartByCategoriesDataItem>[];
final data = <TrDistributionChartItem<Category>>[];

final transactionService = TransactionService.instance;

Expand All @@ -79,11 +77,7 @@ class _ChartByCategoriesState extends State<ChartByCategories> {
.first;

for (final transaction in transactions) {
final trValue = await ExchangeRateService.instance
.calculateExchangeRateToPreferredCurrency(
fromCurrency: transaction.account.currencyId,
amount: transaction.value.abs())
.first;
final trValue = transaction.currentValueInPreferredCurrency.abs();

final categoryToEdit = data.firstWhereOrNull((cat) =>
cat.category.id == transaction.category?.id ||
Expand All @@ -94,7 +88,7 @@ class _ChartByCategoriesState extends State<ChartByCategories> {
categoryToEdit.transactions.add(transaction);
} else {
data.add(
ChartByCategoriesDataItem(
TrDistributionChartItem(
category: transaction.category!.parentCategoryID == null
? Category.fromDB(transaction.category!, null)
: (await CategoryService.instance
Expand All @@ -112,13 +106,12 @@ class _ChartByCategoriesState extends State<ChartByCategories> {

/// Returns a value between 0 and 100
double getElementPercentageInTotal(
double elementValue, List<ChartByCategoriesDataItem> items) {
return (elementValue /
items.map((e) => e.value).reduce((value, element) => value + element));
double elementValue, List<TrDistributionChartItem> items) {
return (elementValue / items.map((e) => e.value).sum);
}

List<ChartByCategoriesDataItem> deleteUnimportantItems(
List<ChartByCategoriesDataItem> data) {
List<TrDistributionChartItem> deleteUnimportantItems(
List<TrDistributionChartItem> data) {
const limit = 0.05;

final unimportantItems = data.where(
Expand All @@ -131,7 +124,7 @@ class _ChartByCategoriesState extends State<ChartByCategories> {
getElementPercentageInTotal(element.value, data) >= limit)
.toList();

final toAdd = ChartByCategoriesDataItem(
final toAdd = TrDistributionChartItem(
value: 0,
transactions: [],
category: Category(
Expand All @@ -152,7 +145,7 @@ class _ChartByCategoriesState extends State<ChartByCategories> {
}

List<PieChartSectionData> showingSections(
List<ChartByCategoriesDataItem> data) {
List<TrDistributionChartItem> data) {
if (data.isEmpty) {
return [
PieChartSectionData(
Expand Down
95 changes: 95 additions & 0 deletions lib/app/stats/widgets/movements_distribution/tags_stats.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:monekin/app/stats/widgets/movements_distribution/chart_by_categories.dart';
import 'package:monekin/core/database/services/tags/tags_service.dart';
import 'package:monekin/core/database/services/transaction/transaction_service.dart';
import 'package:monekin/core/models/tags/tag.dart';
import 'package:monekin/core/models/transaction/transaction.dart';
import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart';
import 'package:monekin/i18n/translations.g.dart';

class TagStats extends StatelessWidget {
const TagStats({super.key, required this.filters});

final TransactionFilters filters;

TrDistributionChartItem<Tag> getTagInfo(
Tag tag, List<MoneyTransaction> transactions) {
transactions = transactions
.where((element) => element.tags.any((elTag) => elTag.id == tag.id))
.toList();

return TrDistributionChartItem<Tag>(
category: tag,
transactions: transactions,
value: transactions.map((e) => e.currentValueInPreferredCurrency).sum,
);
}

@override
Widget build(BuildContext context) {
final t = Translations.of(context);

return StreamBuilder(
stream: TransactionService.instance.getTransactions(filters: filters),
builder: (context, trSnapshot) {
if (!trSnapshot.hasData) {
return const LinearProgressIndicator();
}

if (trSnapshot.data!.isEmpty) {
return Padding(
padding: const EdgeInsets.all(24),
child: Text(t.general.insufficient_data),
);
}

return StreamBuilder(
stream: TagService.instance.getTags(),
builder: (context, tagsSnapshot) {
if (!tagsSnapshot.hasData) {
return const LinearProgressIndicator();
}

final tags = tagsSnapshot.data!;

final tagsInfo = tags
.map((e) => getTagInfo(e, trSnapshot.data!))
.where((element) => element.transactions.isNotEmpty)
.toList();

tagsInfo.sort((a, b) => a.value.compareTo(b.value));

if (tags.isEmpty || tagsInfo.isEmpty) {
return Padding(
padding: const EdgeInsets.all(24),
child: Text(tags.isEmpty
? t.tags.empty_list
: t.general.insufficient_data),
);
}

return ListView.builder(
itemCount: tagsInfo.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final dataTag = tagsInfo[index];

return ListTile(
title: Text(dataTag.category.name),
subtitle: Text(
'${dataTag.transactions.length} ${t.transaction.display(n: dataTag.transactions.length)}'
.toLowerCase(),
),
trailing:
CurrencyDisplayer(amountToConvert: dataTag.value),
leading: dataTag.category.displayIcon(),
);
},
);
});
});
}
}
Loading

0 comments on commit bab1906

Please sign in to comment.