Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions assets/icons/ic_add_transaction_expense.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions assets/icons/ic_add_transaction_income.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion lib/core/l10n/app_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,11 @@
"add" : "اضافة",
"transaction_details": "تفاصيل المعاملة",
"income_details": "تفاصيل الدخل",
"expense_details": "تفاصيل المصروف"
"expense_details": "تفاصيل المصروف",
"add_transaction_sheet_subtitle": "اختر نوع المعاملة التي تريد إضافتها",
"make_expense": "تسجيل مصروف",
"continueButton": "متابعة",
"categoriesFilterTitle": "تصفية الفئات",
"categoriesFilterClear": "مسح",
"categoriesFilterApply": "تطبيق"
}
8 changes: 7 additions & 1 deletion lib/core/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,11 @@
"expense_details": "Expense details",
"whereDoYouUsuallySpendYourMoney": "Where do you usually spend your money?",
"suggestions": "Suggestions:",
"selectedCategories": "Selected Categories:"
"selectedCategories": "Selected Categories:",
"add_transaction_sheet_subtitle": "Select transaction type you want to add it",
"make_expense": "Make expense",
"continueButton": "Continue",
"categoriesFilterTitle": "Categories filter",
"categoriesFilterClear": "Clear",
"categoriesFilterApply": "Apply"
}
2 changes: 2 additions & 0 deletions lib/design_system/assets/app_assets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@ class AppAssets {
static const String icEmptyTransactionImage = '$_images/empty_transaction_image.png';
static const String icEmptyTransactionPattern = '$_icons/empty_transaction_pattern.svg';
static const String icFilter = "$_icons/ic_filter.svg";
static const String icAddAmount = "$_icons/ic_add_transaction_income.svg";
static const String icAddExpense = "$_icons/ic_add_transaction_expense.svg";
}
1 change: 1 addition & 0 deletions lib/design_system/constants/design_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class DesignConstants {
// Icon Sizes
static const double iconSizeSmall = 14.0;
static const double iconSizeMedium = 16.0;
static const double iconSizeMed = 24.0;
static const double iconSizeLarge = 28.0;

// Component Sizes
Expand Down
25 changes: 8 additions & 17 deletions lib/design_system/widgets/bottom_sheet.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:moneyplus/design_system/assets/app_assets.dart';
import 'package:moneyplus/design_system/theme/money_colors.dart';
import 'package:svg_flutter/svg.dart';
import 'package:moneyplus/design_system/theme/money_typography.dart';

import '../theme/money_extension_context.dart';

class MBottomSheet extends StatelessWidget {
final String title;
final Widget content;
Expand All @@ -19,9 +23,9 @@ class MBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
decoration: BoxDecoration(
color: context.colors.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
Expand All @@ -42,20 +46,7 @@ class MBottomSheet extends StatelessWidget {
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: const Color(0xFFE5E7EB),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: MoneyColors.light.body, width: 1),
),
child: Icon(
Icons.close,
size: 16,
color: MoneyColors.light.body,
),
),
child: SvgPicture.asset(AppAssets.iconCancel, width: 20, height: 20),
),
],
),
Expand Down
66 changes: 55 additions & 11 deletions lib/presentation/transactions/cubit/transaction_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/foundation.dart';
import 'package:moneyplus/domain/entity/transaction.dart';
import 'package:moneyplus/domain/entity/transaction_type.dart';
import 'package:moneyplus/domain/repository/transaction_repository.dart';
Expand All @@ -10,6 +11,17 @@ class TransactionCubit extends Cubit<TransactionState> {
TransactionCubit({required this.transactionRepository})
: super(TransactionState.initial());

static const List<String> _mockCategories = <String>[
'Food',
'Transport',
'Shopping',
'Bills',
'Health',
'Entertainment',
'Salary',
'Gifts',
];
Comment on lines +14 to +23
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have end point for current user categories?


void loadData() async {
emit(state.copyWith(status: TransactionStatus.loading));

Expand All @@ -18,7 +30,13 @@ class TransactionCubit extends Cubit<TransactionState> {
emit(
state.copyWith(
allTransactions: result,
filteredTransactions: _filterTransaction(now.month, now.year, result),
availableCategories: _mockCategories,
filteredTransactions: _filterTransaction(
month: now.month,
year: now.year,
transactions: result,
selectedCategories: state.selectedCategories,
),
selectedYear: now.year,
selectedMonth: now.month,
status: TransactionStatus.success,
Expand All @@ -38,9 +56,10 @@ class TransactionCubit extends Cubit<TransactionState> {
status: TransactionStatus.success,
allTransactions: result,
filteredTransactions: _filterTransaction(
state.selectedMonth,
state.selectedYear,
result,
month: state.selectedMonth,
year: state.selectedYear,
transactions: result,
selectedCategories: state.selectedCategories,
),
),
);
Expand All @@ -61,9 +80,26 @@ class TransactionCubit extends Cubit<TransactionState> {
state.copyWith(
status: TransactionStatus.success,
filteredTransactions: _filterTransaction(
month,
year,
state.allTransactions,
month: month,
year: year,
transactions: state.allTransactions,
selectedCategories: state.selectedCategories,
),
),
);
}

void setSelectedCategories(Set<String> categories) {
if (setEquals(state.selectedCategories, categories)) return;

emit(
state.copyWith(
selectedCategories: Set<String>.from(categories),
filteredTransactions: _filterTransaction(
month: state.selectedMonth,
year: state.selectedYear,
transactions: state.allTransactions,
selectedCategories: categories,
),
),
);
Expand All @@ -81,12 +117,20 @@ class TransactionCubit extends Cubit<TransactionState> {
}

List<Transaction> _filterTransaction(
int month,
int year,
List<Transaction> transactions,
{
required int month,
required int year,
required List<Transaction> transactions,
required Set<String> selectedCategories,
}
) {
return transactions.where((transaction) {
return transaction.date.year == year && transaction.date.month == month;
final matchesDate =
transaction.date.year == year && transaction.date.month == month;
if (!matchesDate) return false;

if (selectedCategories.isEmpty) return true;
return selectedCategories.contains(transaction.category.name);
}).toList();
}
}
8 changes: 8 additions & 0 deletions lib/presentation/transactions/cubit/transaction_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class TransactionState {
final TransactionTabs selectedTab;
final int selectedMonth;
final int selectedYear;
final List<String> availableCategories;
final Set<String> selectedCategories;

const TransactionState({
required this.status,
Expand All @@ -24,6 +26,8 @@ class TransactionState {
this.selectedTab = TransactionTabs.all,
this.selectedYear = 2026,
this.selectedMonth = 1,
this.availableCategories = const [],
this.selectedCategories = const <String>{},
});

factory TransactionState.initial() =>
Expand All @@ -37,6 +41,8 @@ class TransactionState {
TransactionTabs? selectedTab,
int? selectedYear,
int? selectedMonth,
List<String>? availableCategories,
Set<String>? selectedCategories,
}) {
return TransactionState(
status: status ?? this.status,
Expand All @@ -46,6 +52,8 @@ class TransactionState {
selectedTab: selectedTab ?? this.selectedTab,
selectedYear: selectedYear ?? this.selectedYear,
selectedMonth: selectedMonth ?? this.selectedMonth,
availableCategories: availableCategories ?? this.availableCategories,
selectedCategories: selectedCategories ?? this.selectedCategories,
);
}
}
13 changes: 12 additions & 1 deletion lib/presentation/transactions/screen/transactions_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:moneyplus/design_system/widgets/snack_bar.dart';
import 'package:moneyplus/presentation/transactions/cubit/transaction_cubit.dart';
import 'package:moneyplus/presentation/transactions/cubit/transaction_state.dart';
import 'package:moneyplus/presentation/transactions/widget/empty_transactions.dart';
import 'package:moneyplus/presentation/transactions/widget/categories_filter_bottom_sheet.dart';
import 'package:moneyplus/presentation/transactions/widget/loading_view.dart';
import 'package:moneyplus/presentation/transactions/widget/tabs_row.dart';
import 'package:moneyplus/presentation/transactions/widget/transaction_app_bar.dart';
Expand Down Expand Up @@ -40,7 +41,17 @@ class TransactionsScreen extends StatelessWidget {
year: state.selectedYear,
month: state.selectedMonth,
onDatePick: context.read<TransactionCubit>().setSelectedDate,
onFilterClicked: (){},
onFilterClicked: () async {
final result = await showCategoriesFilterBottomSheet(
context: context,
categories: state.availableCategories,
initialSelectedCategories: state.selectedCategories,
);
if (result == null) return;
context
.read<TransactionCubit>()
.setSelectedCategories(result.toSet());
},
),
),
SliverPadding(
Expand Down
102 changes: 102 additions & 0 deletions lib/presentation/transactions/widget/add_transaction_bottom_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:moneyplus/core/l10n/app_localizations.dart';
import 'package:moneyplus/design_system/assets/app_assets.dart';
import 'package:moneyplus/design_system/constants/design_constants.dart';
import 'package:moneyplus/design_system/theme/money_extension_context.dart';
import 'package:moneyplus/design_system/widgets/bottom_sheet.dart';
import 'package:moneyplus/design_system/widgets/buttons/button/default_button.dart';
import 'package:moneyplus/domain/entity/transaction_type.dart';
import 'package:moneyplus/presentation/navigation/routes.dart';
import 'package:moneyplus/presentation/transactions/widget/transaction_type_card.dart';
import 'package:moneyplus/utils/extenstions/show_bottom_sheet.dart';

class AddTransactionBottomSheet extends StatefulWidget {
final BuildContext parentContext;

const AddTransactionBottomSheet({
super.key,
required this.parentContext,
});

@override
State<AddTransactionBottomSheet> createState() =>
_AddTransactionBottomSheetState();
}

class _AddTransactionBottomSheetState extends State<AddTransactionBottomSheet> {
TransactionType? _selectedType;

void _selectType(TransactionType type) {
setState(() {
_selectedType = type;
});
}

void _onContinue() {
final selected = _selectedType;
if (selected == null) return;

Navigator.of(context).pop();
if (selected == TransactionType.income) {
AddIncomeRoute().push(widget.parentContext);
} else {
AddExpenseRoute().push(widget.parentContext);
}
}

@override
Widget build(BuildContext context) {
final typography = context.typography;
final colors = context.colors;
final l10n = AppLocalizations.of(context)!;

return MBottomSheet(
title: l10n.add_transaction,
content: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
l10n.add_transaction_sheet_subtitle,
style: typography.body.small.copyWith(color: colors.body),
),
const SizedBox(height: DesignConstants.spacingLarge),
Row(
children: [
Expanded(
child: TransactionTypeCard(
label: l10n.addIncome,
iconPath: AppAssets.icAddAmount,
selected: _selectedType == TransactionType.income,
onTap: () => _selectType(TransactionType.income),
),
),
const SizedBox(width: DesignConstants.spacingSmall),
Expanded(
child: TransactionTypeCard(
label: l10n.make_expense,
iconPath: AppAssets.icAddExpense,
selected: _selectedType == TransactionType.expense,
onTap: () => _selectType(TransactionType.expense),
),
),
],
),
],
),
actionButtons: [
DefaultButton(
text: l10n.continueButton,
isEnabled: _selectedType != null,
onPressed: _selectedType != null ? _onContinue : null,
),
],
);
}
}

void showAddTransactionBottomSheet(BuildContext context) {
context.showBlurBottomSheet(
AddTransactionBottomSheet(parentContext: context),
);
}

Loading