diff --git a/mobile/Makefile.toml b/mobile/Makefile.toml index b2a8f50..1437bdd 100644 --- a/mobile/Makefile.toml +++ b/mobile/Makefile.toml @@ -55,6 +55,13 @@ rm coverage/source_lcov.info # flutter pub global run dart_dot_reporter machine.log --show-message ''' +[tasks.test-logic] +script = ''' +fvm flutter test --exclude-tags golden --coverage --coverage-path="coverage/source_lcov.info" +./node_modules/.bin/lcov-result-merger ./coverage/source_lcov.info ./coverage/lcov.info +rm coverage/source_lcov.info +''' + [tasks.ci-test] script = ''' fvm flutter test diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index 931e1e8..e1ef466 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -7,10 +7,10 @@ import 'package:suito/src/localizations/japanese_cupertino_localizations.dart'; import 'package:suito/src/routing/app_router.dart'; import 'package:suito/src/utils/version_check.dart'; -class MyApp extends ConsumerWidget { - final GlobalKey navigatorKey = GlobalKey(); +final GlobalKey navigatorKey = GlobalKey(); - MyApp({super.key}); +class MyApp extends ConsumerWidget { + const MyApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/mobile/lib/src/app_bootstrap.dart b/mobile/lib/src/app_bootstrap.dart index c4510a1..7fc9bf7 100644 --- a/mobile/lib/src/app_bootstrap.dart +++ b/mobile/lib/src/app_bootstrap.dart @@ -40,7 +40,7 @@ class AppBootstrap { return TranslationProvider( child: UncontrolledProviderScope( container: container, - child: MyApp(), + child: const MyApp(), ), ); } diff --git a/mobile/lib/src/features/schedules/presentations/expense/expense_schedule_detail_screen.dart b/mobile/lib/src/features/schedules/presentations/expense/expense_schedule_detail_screen.dart new file mode 100644 index 0000000..53fbb81 --- /dev/null +++ b/mobile/lib/src/features/schedules/presentations/expense/expense_schedule_detail_screen.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:suito/i18n/translations.g.dart'; + +import 'expense_schedule_detail_view.dart'; + +class ExpenseScheduleDetailScreen extends ConsumerWidget { + const ExpenseScheduleDetailScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: Text(t.transactions.detail.title), + ), + body: const Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: ExpenseScheduleDetailView(), + ), + ); + } +} diff --git a/mobile/lib/src/features/schedules/presentations/expense/expense_schedule_detail_view.dart b/mobile/lib/src/features/schedules/presentations/expense/expense_schedule_detail_view.dart index 3fbf8b9..31bec87 100644 --- a/mobile/lib/src/features/schedules/presentations/expense/expense_schedule_detail_view.dart +++ b/mobile/lib/src/features/schedules/presentations/expense/expense_schedule_detail_view.dart @@ -2,50 +2,26 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/common_widgets/async_value_widget.dart'; import 'package:suito/src/common_widgets/currency_input_field.dart'; import 'package:suito/src/common_widgets/text_input_field.dart'; import 'package:suito/src/common_widgets/transition_text_field.dart'; import 'package:suito/src/constants/app_sizes.dart'; -import 'package:suito/src/features/schedules/services/expense/expense_schedule.dart'; -import 'package:suito/src/features/schedules/services/expense/expense_schedule_form_controller.dart'; import 'package:suito/src/features/schedules/services/expense/submit_expense_schedule_controller.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_service.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_controller.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart' as stitle; import 'package:suito/src/routing/app_router.dart'; import 'package:suito/src/utils/currency_formatter.dart'; class ExpenseScheduleDetailView extends ConsumerWidget { - final String? expenseScheduleID; - - const ExpenseScheduleDetailView({required this.expenseScheduleID, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final expenseScheduleValue = - ref.watch(expenseScheduleFutureProvider(id: expenseScheduleID)); - - return AsyncValueWidget( - value: expenseScheduleValue, - data: (expenseSchedule) => - _ExpenseScheduleDetailViewContents(expenseSchedule: expenseSchedule), - ); - } -} - -class _ExpenseScheduleDetailViewContents extends ConsumerWidget { - final ExpenseSchedule expenseSchedule; - - const _ExpenseScheduleDetailViewContents({required this.expenseSchedule}); + const ExpenseScheduleDetailView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final controller = ref - .watch(expenseScheduleFormControllerProvider(expenseSchedule).notifier); - final schedule = - ref.watch(expenseScheduleFormControllerProvider(expenseSchedule)); + final controller = ref.watch(expenseFormControllerProvider.notifier); + final schedule = ref.watch(expenseFormControllerProvider); return SingleChildScrollView( child: ListBody( @@ -68,18 +44,16 @@ class _ExpenseScheduleDetailViewContents extends ConsumerWidget { initialValue: schedule.category, labelText: t.transactions.detail.inputLabels.category, route: AppRoute.attribute, - onTap: () => ref - .read(transactionAttributeTypeProvider.notifier) - .state = TransactionAttributeType.category, + onTap: () => controller + .selectAttributeType(TransactionAttributeType.category), onChanged: controller.onChangeCategory), gapH12, TransitionTextField( initialValue: schedule.location, labelText: t.transactions.detail.inputLabels.location, route: AppRoute.attribute, - onTap: () => ref - .read(transactionAttributeTypeProvider.notifier) - .state = TransactionAttributeType.location, + onTap: () => controller + .selectAttributeType(TransactionAttributeType.location), onChanged: controller.onChangeLocation), gapH12, TransitionTextField( diff --git a/mobile/lib/src/features/schedules/presentations/income/income_schedule_detail_screen.dart b/mobile/lib/src/features/schedules/presentations/income/income_schedule_detail_screen.dart new file mode 100644 index 0000000..ad0f990 --- /dev/null +++ b/mobile/lib/src/features/schedules/presentations/income/income_schedule_detail_screen.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:suito/i18n/translations.g.dart'; + +import 'income_schedule_detail_view.dart'; + +class IncomeScheduleDetailScreen extends ConsumerWidget { + const IncomeScheduleDetailScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: Text(t.transactions.detail.title), + ), + body: const Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: IncomeScheduleDetailView(), + ), + ); + } +} diff --git a/mobile/lib/src/features/schedules/presentations/income/income_schedule_detail_view.dart b/mobile/lib/src/features/schedules/presentations/income/income_schedule_detail_view.dart index c92442a..8ab214b 100644 --- a/mobile/lib/src/features/schedules/presentations/income/income_schedule_detail_view.dart +++ b/mobile/lib/src/features/schedules/presentations/income/income_schedule_detail_view.dart @@ -2,48 +2,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/common_widgets/async_value_widget.dart'; import 'package:suito/src/common_widgets/currency_input_field.dart'; import 'package:suito/src/common_widgets/transition_text_field.dart'; import 'package:suito/src/constants/app_sizes.dart'; -import 'package:suito/src/features/schedules/services/income/income_schedule.dart'; -import 'package:suito/src/features/schedules/services/income/income_schedule_form_controller.dart'; import 'package:suito/src/features/schedules/services/income/submit_income_schedule_controller.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_service.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/routing/app_router.dart'; import 'package:suito/src/utils/currency_formatter.dart'; class IncomeScheduleDetailView extends ConsumerWidget { - final String? incomeScheduleID; - - const IncomeScheduleDetailView({required this.incomeScheduleID, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final incomeScheduleValue = - ref.watch(incomeScheduleFutureProvider(id: incomeScheduleID)); - - return AsyncValueWidget( - value: incomeScheduleValue, - data: (incomeSchedule) => - _IncomeScheduleDetailViewContents(incomeSchedule: incomeSchedule), - ); - } -} - -class _IncomeScheduleDetailViewContents extends ConsumerWidget { - final IncomeSchedule incomeSchedule; - - const _IncomeScheduleDetailViewContents({required this.incomeSchedule}); + const IncomeScheduleDetailView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final controller = ref - .watch(incomeScheduleFormControllerProvider(incomeSchedule).notifier); - final schedule = - ref.watch(incomeScheduleFormControllerProvider(incomeSchedule)); + final controller = ref.watch(incomeFormControllerProvider.notifier); + final schedule = ref.watch(incomeFormControllerProvider); return SingleChildScrollView( child: ListBody( diff --git a/mobile/lib/src/features/schedules/presentations/schedule/schedules_expense_list_header.dart b/mobile/lib/src/features/schedules/presentations/schedule/schedules_expense_list_header.dart new file mode 100644 index 0000000..98fd034 --- /dev/null +++ b/mobile/lib/src/features/schedules/presentations/schedule/schedules_expense_list_header.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:suito/i18n/translations.g.dart'; +import 'package:suito/src/constants/app_sizes.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_detail_navigator.dart'; + +class SchedulesExpenseListHeader extends ConsumerWidget { + const SchedulesExpenseListHeader({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final navigator = ref.watch(transactionDetailNavigatorProvider.notifier); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Container( + color: Colors.white, + child: Column( + children: [ + Row( + children: [ + gapW12, + Text( + t.schedules.expansionTile.expenseHeader, + ), + IconButton( + icon: const Icon( + Icons.add_circle, + color: Colors.red, + ), + onPressed: navigator.goNewExpenseSchedule), + ], + ), + const Divider( + height: 0, + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/src/features/schedules/presentations/schedule/schedules_expense_list_item.dart b/mobile/lib/src/features/schedules/presentations/schedule/schedules_expense_list_item.dart new file mode 100644 index 0000000..fe516c9 --- /dev/null +++ b/mobile/lib/src/features/schedules/presentations/schedule/schedules_expense_list_item.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:openapi/openapi.dart'; +import 'package:suito/i18n/translations.g.dart'; +import 'package:suito/src/common_widgets/custom_dismissible.dart'; +import 'package:suito/src/constants/app_sizes.dart'; +import 'package:suito/src/features/schedules/services/expense/delete_expense_schedule_controller.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_detail_navigator.dart'; +import 'package:suito/src/utils/currency_formatter.dart'; + +class SchedulesExpenseListItem extends ConsumerWidget { + final TransactionSchedule item; + + const SchedulesExpenseListItem({super.key, required this.item}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currencyFormatter = ref.watch(currencyFormatterProvider); + final navigator = ref.watch(transactionDetailNavigatorProvider.notifier); + + return CustomDismissible( + key: Key(item.id), + onDismissed: (direction) async { + final ScaffoldMessengerState state = ScaffoldMessenger.of(context); + + await ref + .read(deleteExpenseScheduleControllerProvider.notifier) + .deleteExpenseSchedule(item.id); + + // Show a snackbar. + state.showSnackBar( + SnackBar(content: Text(t.general.dismissible.snackBar))); + }, + child: GestureDetector( + onTap: () => navigator.goFetchExpenseSchedule(item.id), + child: Container( + color: Colors.white, + child: SizedBox( + height: 50, + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + Text(item.title), + gapW64, + gapW64, + SizedBox( + width: 64, + child: Align( + alignment: Alignment.centerRight, + child: Text(currencyFormatter.format(item.amount), + style: const TextStyle( + color: Colors.green, + )))), + gapW32 + ]), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/src/features/schedules/presentations/schedule/schedules_income_list_header.dart b/mobile/lib/src/features/schedules/presentations/schedule/schedules_income_list_header.dart new file mode 100644 index 0000000..5f8d386 --- /dev/null +++ b/mobile/lib/src/features/schedules/presentations/schedule/schedules_income_list_header.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:suito/i18n/translations.g.dart'; +import 'package:suito/src/constants/app_sizes.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_detail_navigator.dart'; + +class SchedulesIncomeListHeader extends ConsumerWidget { + const SchedulesIncomeListHeader({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final navigator = ref.watch(transactionDetailNavigatorProvider.notifier); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Container( + color: Colors.white, + child: Column( + children: [ + Row( + children: [ + gapW12, + Text( + t.schedules.expansionTile.incomeHeader, + ), + IconButton( + icon: const Icon( + Icons.add_circle, + color: Colors.green, + ), + onPressed: navigator.goNewIncomeSchedule), + ], + ), + const Divider( + height: 0, + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/src/features/schedules/presentations/schedule/schedules_income_list_item.dart b/mobile/lib/src/features/schedules/presentations/schedule/schedules_income_list_item.dart new file mode 100644 index 0000000..876d5a5 --- /dev/null +++ b/mobile/lib/src/features/schedules/presentations/schedule/schedules_income_list_item.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:openapi/openapi.dart'; +import 'package:suito/i18n/translations.g.dart'; +import 'package:suito/src/common_widgets/custom_dismissible.dart'; +import 'package:suito/src/constants/app_sizes.dart'; +import 'package:suito/src/features/schedules/services/income/delete_income_schedule_controller.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_detail_navigator.dart'; +import 'package:suito/src/utils/currency_formatter.dart'; + +class SchedulesIncomeListItem extends ConsumerWidget { + final TransactionSchedule item; + + const SchedulesIncomeListItem({super.key, required this.item}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currencyFormatter = ref.watch(currencyFormatterProvider); + final navigator = ref.watch(transactionDetailNavigatorProvider.notifier); + + return CustomDismissible( + key: Key(item.id), + onDismissed: (direction) async { + final ScaffoldMessengerState state = ScaffoldMessenger.of(context); + + await ref + .read(deleteIncomeScheduleControllerProvider.notifier) + .deleteIncomeSchedule(item.id); + + // Show a snackbar. + state.showSnackBar( + SnackBar(content: Text(t.general.dismissible.snackBar))); + }, + child: GestureDetector( + onTap: () => navigator.goFetchIncomeSchedule(item.id), + child: Container( + color: Colors.white, + child: SizedBox( + height: 50, + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + Text(item.title), + gapW64, + gapW64, + SizedBox( + width: 64, + child: Align( + alignment: Alignment.centerRight, + child: Text(currencyFormatter.format(item.amount), + style: const TextStyle( + color: Colors.red, + )))), + gapW32 + ]), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/src/features/schedules/presentations/schedule/schedules_list.dart b/mobile/lib/src/features/schedules/presentations/schedule/schedules_list.dart new file mode 100644 index 0000000..4a57748 --- /dev/null +++ b/mobile/lib/src/features/schedules/presentations/schedule/schedules_list.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:openapi/openapi.dart'; +import 'package:suito/src/common_widgets/async_value_widget.dart'; +import 'package:suito/src/features/schedules/services/schedule_service.dart'; + +import 'schedules_expense_list_header.dart'; +import 'schedules_expense_list_item.dart'; +import 'schedules_income_list_header.dart'; +import 'schedules_income_list_item.dart'; + +class SchedulesList extends ConsumerWidget { + const SchedulesList({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final schedulesValue = ref.watch(fetchSchedulesProvider); + + return AsyncValueWidget>>( + value: schedulesValue, + data: (schedules) => ListView.builder( + itemCount: schedules.length, + itemBuilder: (_, index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: Column( + children: [ + index == 0 + ? const SchedulesExpenseListHeader() + : const SchedulesIncomeListHeader(), + ...schedules[index] + .map( + (item) => index == 0 + ? SchedulesExpenseListItem(item: item) + : SchedulesIncomeListItem(item: item), + ) + .toList(), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/src/features/schedules/presentations/schedule_detail_screen.dart b/mobile/lib/src/features/schedules/presentations/schedule_detail_screen.dart deleted file mode 100644 index 5e6e791..0000000 --- a/mobile/lib/src/features/schedules/presentations/schedule_detail_screen.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; - -import 'expense/expense_schedule_detail_view.dart'; -import 'income/income_schedule_detail_view.dart'; - -class ScheduleDetailScreen extends ConsumerWidget { - final String? id; - final int type; - - const ScheduleDetailScreen({required this.id, required this.type, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final typeValue = ref.watch(typeProvider(type)); - - return Scaffold( - appBar: AppBar( - title: Text(t.transactions.detail.title), - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: TransactionType.isExpense(typeValue) - ? ExpenseScheduleDetailView(expenseScheduleID: id) - : IncomeScheduleDetailView(incomeScheduleID: id), - ), - ); - } -} - -final typeProvider = StateProvider.family - .autoDispose((ref, initialValue) => initialValue); diff --git a/mobile/lib/src/features/schedules/presentations/schedule_screen.dart b/mobile/lib/src/features/schedules/presentations/schedule_screen.dart index bcba671..272c261 100644 --- a/mobile/lib/src/features/schedules/presentations/schedule_screen.dart +++ b/mobile/lib/src/features/schedules/presentations/schedule_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/features/schedules/presentations/schedules_list.dart'; +import 'package:suito/src/features/schedules/presentations/schedule/schedules_list.dart'; class ScheduleScreen extends ConsumerWidget { const ScheduleScreen({super.key}); diff --git a/mobile/lib/src/features/schedules/presentations/schedules_list.dart b/mobile/lib/src/features/schedules/presentations/schedules_list.dart deleted file mode 100644 index 38f2538..0000000 --- a/mobile/lib/src/features/schedules/presentations/schedules_list.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/common_widgets/async_value_widget.dart'; -import 'package:suito/src/common_widgets/custom_dismissible.dart'; -import 'package:suito/src/constants/app_sizes.dart'; -import 'package:suito/src/features/schedules/services/schedule_service.dart'; -import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; -import 'package:suito/src/routing/app_router.dart'; -import 'package:suito/src/utils/currency_formatter.dart'; - -class SchedulesList extends ConsumerWidget { - const SchedulesList({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final schedulesValue = ref.watch(fetchSchedulesProvider); - final currencyFormatter = ref.watch(currencyFormatterProvider); - - return AsyncValueWidget>( - value: schedulesValue, - data: (schedules) => ListView.builder( - itemCount: schedules.length, - itemBuilder: (_, index) => // ExpansionTile( - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Container( - color: Colors.white, - child: Column( - children: [ - Row( - children: [ - gapW12, - Text(schedules[index].headerText), - IconButton( - icon: Icon( - Icons.add_circle, - color: schedules[index].mainColor, - ), - onPressed: () { - context.goNamed(AppRoute.scheduleDetail.name, - queryParameters: { - 'type': index == 0 - ? TransactionType.expense.value - .toString() - : TransactionType.income.value - .toString() - }); - }, - ), - ], - ), - const Divider( - height: 0, - ), - ], - ), - ), - ), - ...schedules[index] - .items - .map( - (item) => CustomDismissible( - key: Key(item.id), - onDismissed: (direction) async { - final ScaffoldMessengerState state = - ScaffoldMessenger.of(context); - - await schedules[index].onDismissed(item.id); - - // Show a snackbar. - state.showSnackBar(SnackBar( - content: Text(t.general.dismissible.snackBar))); - }, - child: GestureDetector( - onTap: () { - context.goNamed(AppRoute.scheduleDetail.name, - queryParameters: { - 'id': item.id, - 'type': index == 0 - ? TransactionType.expense.value.toString() - : TransactionType.income.value.toString() - }); - }, - child: Container( - color: Colors.white, - child: SizedBox( - height: 50, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text(item.title), - gapW64, - gapW64, - SizedBox( - width: 64, - child: Align( - alignment: Alignment.centerRight, - child: Text( - currencyFormatter - .format(item.amount), - style: TextStyle( - color: - schedules[index].mainColor, - )))), - gapW32 - ]), - ), - ), - ), - ), - ) - .toList(), - ], - ), - ), - ), - ); - } -} diff --git a/mobile/lib/src/features/schedules/services/expense/expense_schedule.dart b/mobile/lib/src/features/schedules/services/expense/expense_schedule.dart deleted file mode 100644 index 4f771e6..0000000 --- a/mobile/lib/src/features/schedules/services/expense/expense_schedule.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:openapi/openapi.dart'; -import 'package:suito/src/formz/amount.dart'; -import 'package:suito/src/formz/title.dart'; - -part 'expense_schedule.freezed.dart'; - -@freezed -class ExpenseSchedule with _$ExpenseSchedule { - const ExpenseSchedule._(); - - const factory ExpenseSchedule({ - required String id, - required Title title, - required Amount amount, - required String categoryID, - required String category, - required String locationID, - required String location, - required String memo, - required bool isValid, - }) = _ExpenseSchedule; - - bool get isNew => id == ''; - - static ExpenseSchedule init() => const ExpenseSchedule( - id: '', - title: Title.pure(), - amount: Amount.pure(), - categoryID: '', - category: '', - locationID: '', - location: '', - memo: '', - isValid: false, - ); - - static ExpenseSchedule fromModel( - ExpenseScheduleDetailRes res, categoriesMap, locationsMap) => - ExpenseSchedule( - id: res.expenseSchedule.id, - title: Title.dirty(res.expenseSchedule.title), - amount: Amount.dirty(res.expenseSchedule.amount), - categoryID: res.expenseSchedule.expenseCategoryID, - category: - categoriesMap[res.expenseSchedule.expenseCategoryID]?.name ?? '', - locationID: res.expenseSchedule.expenseLocationID, - location: - locationsMap[res.expenseSchedule.expenseLocationID]?.name ?? '', - memo: res.expenseSchedule.memo, - isValid: true, - ); -} diff --git a/mobile/lib/src/features/schedules/services/expense/expense_schedule.freezed.dart b/mobile/lib/src/features/schedules/services/expense/expense_schedule.freezed.dart deleted file mode 100644 index 0b882b7..0000000 --- a/mobile/lib/src/features/schedules/services/expense/expense_schedule.freezed.dart +++ /dev/null @@ -1,303 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'expense_schedule.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -/// @nodoc -mixin _$ExpenseSchedule { - String get id => throw _privateConstructorUsedError; - Title get title => throw _privateConstructorUsedError; - Amount get amount => throw _privateConstructorUsedError; - String get categoryID => throw _privateConstructorUsedError; - String get category => throw _privateConstructorUsedError; - String get locationID => throw _privateConstructorUsedError; - String get location => throw _privateConstructorUsedError; - String get memo => throw _privateConstructorUsedError; - bool get isValid => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $ExpenseScheduleCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $ExpenseScheduleCopyWith<$Res> { - factory $ExpenseScheduleCopyWith( - ExpenseSchedule value, $Res Function(ExpenseSchedule) then) = - _$ExpenseScheduleCopyWithImpl<$Res, ExpenseSchedule>; - @useResult - $Res call( - {String id, - Title title, - Amount amount, - String categoryID, - String category, - String locationID, - String location, - String memo, - bool isValid}); -} - -/// @nodoc -class _$ExpenseScheduleCopyWithImpl<$Res, $Val extends ExpenseSchedule> - implements $ExpenseScheduleCopyWith<$Res> { - _$ExpenseScheduleCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? amount = null, - Object? categoryID = null, - Object? category = null, - Object? locationID = null, - Object? location = null, - Object? memo = null, - Object? isValid = null, - }) { - return _then(_value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as Title, - amount: null == amount - ? _value.amount - : amount // ignore: cast_nullable_to_non_nullable - as Amount, - categoryID: null == categoryID - ? _value.categoryID - : categoryID // ignore: cast_nullable_to_non_nullable - as String, - category: null == category - ? _value.category - : category // ignore: cast_nullable_to_non_nullable - as String, - locationID: null == locationID - ? _value.locationID - : locationID // ignore: cast_nullable_to_non_nullable - as String, - location: null == location - ? _value.location - : location // ignore: cast_nullable_to_non_nullable - as String, - memo: null == memo - ? _value.memo - : memo // ignore: cast_nullable_to_non_nullable - as String, - isValid: null == isValid - ? _value.isValid - : isValid // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$_ExpenseScheduleCopyWith<$Res> - implements $ExpenseScheduleCopyWith<$Res> { - factory _$$_ExpenseScheduleCopyWith( - _$_ExpenseSchedule value, $Res Function(_$_ExpenseSchedule) then) = - __$$_ExpenseScheduleCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - Title title, - Amount amount, - String categoryID, - String category, - String locationID, - String location, - String memo, - bool isValid}); -} - -/// @nodoc -class __$$_ExpenseScheduleCopyWithImpl<$Res> - extends _$ExpenseScheduleCopyWithImpl<$Res, _$_ExpenseSchedule> - implements _$$_ExpenseScheduleCopyWith<$Res> { - __$$_ExpenseScheduleCopyWithImpl( - _$_ExpenseSchedule _value, $Res Function(_$_ExpenseSchedule) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? amount = null, - Object? categoryID = null, - Object? category = null, - Object? locationID = null, - Object? location = null, - Object? memo = null, - Object? isValid = null, - }) { - return _then(_$_ExpenseSchedule( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as Title, - amount: null == amount - ? _value.amount - : amount // ignore: cast_nullable_to_non_nullable - as Amount, - categoryID: null == categoryID - ? _value.categoryID - : categoryID // ignore: cast_nullable_to_non_nullable - as String, - category: null == category - ? _value.category - : category // ignore: cast_nullable_to_non_nullable - as String, - locationID: null == locationID - ? _value.locationID - : locationID // ignore: cast_nullable_to_non_nullable - as String, - location: null == location - ? _value.location - : location // ignore: cast_nullable_to_non_nullable - as String, - memo: null == memo - ? _value.memo - : memo // ignore: cast_nullable_to_non_nullable - as String, - isValid: null == isValid - ? _value.isValid - : isValid // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc - -class _$_ExpenseSchedule extends _ExpenseSchedule { - const _$_ExpenseSchedule( - {required this.id, - required this.title, - required this.amount, - required this.categoryID, - required this.category, - required this.locationID, - required this.location, - required this.memo, - required this.isValid}) - : super._(); - - @override - final String id; - @override - final Title title; - @override - final Amount amount; - @override - final String categoryID; - @override - final String category; - @override - final String locationID; - @override - final String location; - @override - final String memo; - @override - final bool isValid; - - @override - String toString() { - return 'ExpenseSchedule(id: $id, title: $title, amount: $amount, categoryID: $categoryID, category: $category, locationID: $locationID, location: $location, memo: $memo, isValid: $isValid)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_ExpenseSchedule && - (identical(other.id, id) || other.id == id) && - (identical(other.title, title) || other.title == title) && - (identical(other.amount, amount) || other.amount == amount) && - (identical(other.categoryID, categoryID) || - other.categoryID == categoryID) && - (identical(other.category, category) || - other.category == category) && - (identical(other.locationID, locationID) || - other.locationID == locationID) && - (identical(other.location, location) || - other.location == location) && - (identical(other.memo, memo) || other.memo == memo) && - (identical(other.isValid, isValid) || other.isValid == isValid)); - } - - @override - int get hashCode => Object.hash(runtimeType, id, title, amount, categoryID, - category, locationID, location, memo, isValid); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_ExpenseScheduleCopyWith<_$_ExpenseSchedule> get copyWith => - __$$_ExpenseScheduleCopyWithImpl<_$_ExpenseSchedule>(this, _$identity); -} - -abstract class _ExpenseSchedule extends ExpenseSchedule { - const factory _ExpenseSchedule( - {required final String id, - required final Title title, - required final Amount amount, - required final String categoryID, - required final String category, - required final String locationID, - required final String location, - required final String memo, - required final bool isValid}) = _$_ExpenseSchedule; - const _ExpenseSchedule._() : super._(); - - @override - String get id; - @override - Title get title; - @override - Amount get amount; - @override - String get categoryID; - @override - String get category; - @override - String get locationID; - @override - String get location; - @override - String get memo; - @override - bool get isValid; - @override - @JsonKey(ignore: true) - _$$_ExpenseScheduleCopyWith<_$_ExpenseSchedule> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/mobile/lib/src/features/schedules/services/expense/expense_schedule_form_controller.dart b/mobile/lib/src/features/schedules/services/expense/expense_schedule_form_controller.dart deleted file mode 100644 index 9052508..0000000 --- a/mobile/lib/src/features/schedules/services/expense/expense_schedule_form_controller.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:formz/formz.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:suito/src/features/schedules/repositories/expense/expense_schedule_detail_repository.dart'; -import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; -import 'package:suito/src/formz/amount.dart'; -import 'package:suito/src/formz/title.dart' as formz_title; - -import 'expense_schedule.dart'; - -part 'expense_schedule_form_controller.g.dart'; - -@riverpod -Future expenseScheduleFuture(ExpenseScheduleFutureRef ref, - {String? id}) async { - if (id == null) { - return ExpenseSchedule.init(); - } - - final categoriesMap = - await ref.read(expenseCategoriesMapFutureProvider.future); - final locationsMap = await ref.read(expenseLocationsMapFutureProvider.future); - final modelRes = await ref - .read(expenseScheduleDetailRepositoryProvider) - .fetchExpenseScheduleDetail(id); - return ExpenseSchedule.fromModel(modelRes, categoriesMap, locationsMap); -} - -@riverpod -class ExpenseScheduleFormController extends _$ExpenseScheduleFormController { - @override - ExpenseSchedule build(ExpenseSchedule arg) { - return arg; - } - - void onChangeTitle(String value) { - final title = formz_title.Title.dirty(value); - - state = state.copyWith( - title: title, - isValid: Formz.validate([ - title, - state.amount, - ]), - ); - } - - void onChangeAmount(int value) { - final amount = Amount.dirty(value); - - state = state.copyWith( - amount: amount, - isValid: Formz.validate([ - amount, - state.title, - ]), - ); - } - - void onChangeCategory(AttributeEntry? category) { - state = state.copyWith( - categoryID: category?.id ?? '', - category: category?.name ?? '', - ); - } - - void onChangeLocation(AttributeEntry? location) { - state = state.copyWith( - locationID: location?.id ?? '', - location: location?.name ?? '', - ); - } - - void onChangeMemo(String? value) { - state = state.copyWith( - memo: value ?? '', - ); - } -} diff --git a/mobile/lib/src/features/schedules/services/expense/expense_schedule_form_controller.g.dart b/mobile/lib/src/features/schedules/services/expense/expense_schedule_form_controller.g.dart deleted file mode 100644 index a34ad84..0000000 --- a/mobile/lib/src/features/schedules/services/expense/expense_schedule_form_controller.g.dart +++ /dev/null @@ -1,217 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ignore_for_file: non_constant_identifier_names - -part of 'expense_schedule_form_controller.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$expenseScheduleFutureHash() => - r'3c7974ba5e56e2918c5e38fce3b9cfb81b13d3d1'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -typedef ExpenseScheduleFutureRef - = AutoDisposeFutureProviderRef; - -/// See also [expenseScheduleFuture]. -@ProviderFor(expenseScheduleFuture) -const expenseScheduleFutureProvider = ExpenseScheduleFutureFamily(); - -/// See also [expenseScheduleFuture]. -class ExpenseScheduleFutureFamily extends Family> { - /// See also [expenseScheduleFuture]. - const ExpenseScheduleFutureFamily(); - - /// See also [expenseScheduleFuture]. - ExpenseScheduleFutureProvider call({ - String? id, - }) { - return ExpenseScheduleFutureProvider( - id: id, - ); - } - - @override - ExpenseScheduleFutureProvider getProviderOverride( - covariant ExpenseScheduleFutureProvider provider, - ) { - return call( - id: provider.id, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'expenseScheduleFutureProvider'; -} - -/// See also [expenseScheduleFuture]. -class ExpenseScheduleFutureProvider - extends AutoDisposeFutureProvider { - /// See also [expenseScheduleFuture]. - ExpenseScheduleFutureProvider({ - this.id, - }) : super.internal( - (ref) => expenseScheduleFuture( - ref, - id: id, - ), - from: expenseScheduleFutureProvider, - name: r'expenseScheduleFutureProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$expenseScheduleFutureHash, - dependencies: ExpenseScheduleFutureFamily._dependencies, - allTransitiveDependencies: - ExpenseScheduleFutureFamily._allTransitiveDependencies, - ); - - final String? id; - - @override - bool operator ==(Object other) { - return other is ExpenseScheduleFutureProvider && other.id == id; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); - } -} - -String _$expenseScheduleFormControllerHash() => - r'fb764a8d0dfe1af5e044ecb9baeb30a203119aa6'; - -abstract class _$ExpenseScheduleFormController - extends BuildlessAutoDisposeNotifier { - late final ExpenseSchedule arg; - - ExpenseSchedule build( - ExpenseSchedule arg, - ); -} - -/// See also [ExpenseScheduleFormController]. -@ProviderFor(ExpenseScheduleFormController) -const expenseScheduleFormControllerProvider = - ExpenseScheduleFormControllerFamily(); - -/// See also [ExpenseScheduleFormController]. -class ExpenseScheduleFormControllerFamily extends Family { - /// See also [ExpenseScheduleFormController]. - const ExpenseScheduleFormControllerFamily(); - - /// See also [ExpenseScheduleFormController]. - ExpenseScheduleFormControllerProvider call( - ExpenseSchedule arg, - ) { - return ExpenseScheduleFormControllerProvider( - arg, - ); - } - - @override - ExpenseScheduleFormControllerProvider getProviderOverride( - covariant ExpenseScheduleFormControllerProvider provider, - ) { - return call( - provider.arg, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'expenseScheduleFormControllerProvider'; -} - -/// See also [ExpenseScheduleFormController]. -class ExpenseScheduleFormControllerProvider - extends AutoDisposeNotifierProviderImpl { - /// See also [ExpenseScheduleFormController]. - ExpenseScheduleFormControllerProvider( - this.arg, - ) : super.internal( - () => ExpenseScheduleFormController()..arg = arg, - from: expenseScheduleFormControllerProvider, - name: r'expenseScheduleFormControllerProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$expenseScheduleFormControllerHash, - dependencies: ExpenseScheduleFormControllerFamily._dependencies, - allTransitiveDependencies: - ExpenseScheduleFormControllerFamily._allTransitiveDependencies, - ); - - final ExpenseSchedule arg; - - @override - bool operator ==(Object other) { - return other is ExpenseScheduleFormControllerProvider && other.arg == arg; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, arg.hashCode); - - return _SystemHash.finish(hash); - } - - @override - ExpenseSchedule runNotifierBuild( - covariant ExpenseScheduleFormController notifier, - ) { - return notifier.build( - arg, - ); - } -} -// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.dart b/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.dart index cb1d32f..4338ea1 100644 --- a/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.dart +++ b/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.dart @@ -3,10 +3,9 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:suito/src/features/schedules/repositories/expense/register_expense_schedule_repository.dart'; import 'package:suito/src/features/schedules/repositories/expense/update_expense_schedule_repository.dart'; import 'package:suito/src/features/schedules/services/schedule_service.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; import 'package:suito/src/utils/timezone_provider.dart'; -import 'expense_schedule.dart'; - part 'submit_expense_schedule_controller.g.dart'; @riverpod @@ -38,7 +37,7 @@ class SubmitExpenseScheduleController ..expenseCategoryID = schedule.categoryID ..expenseLocationID = schedule.locationID))); - Future submit(ExpenseSchedule expenseSchedule) async { + Future submit(ExpenseFormValue expenseSchedule) async { if (!expenseSchedule.isValid) return; state = const AsyncLoading(); diff --git a/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.g.dart b/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.g.dart index bfd6e44..a446949 100644 --- a/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.g.dart +++ b/mobile/lib/src/features/schedules/services/expense/submit_expense_schedule_controller.g.dart @@ -9,7 +9,7 @@ part of 'submit_expense_schedule_controller.dart'; // ************************************************************************** String _$submitExpenseScheduleControllerHash() => - r'91622c4576e5083cd35c5a43403735b94763a109'; + r'8ad8f18dc198f780a42734cf178ecb03ad26419f'; /// See also [SubmitExpenseScheduleController]. @ProviderFor(SubmitExpenseScheduleController) diff --git a/mobile/lib/src/features/schedules/services/income/income_schedule.dart b/mobile/lib/src/features/schedules/services/income/income_schedule.dart deleted file mode 100644 index 6dbe2c7..0000000 --- a/mobile/lib/src/features/schedules/services/income/income_schedule.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:openapi/openapi.dart'; -import 'package:suito/src/formz/amount.dart'; -import 'package:suito/src/formz/title.dart'; - -part 'income_schedule.freezed.dart'; - -@freezed -class IncomeSchedule with _$IncomeSchedule { - const IncomeSchedule._(); - - const factory IncomeSchedule({ - required String id, - required Title title, - required String incomeTypeID, - required Amount amount, - required String memo, - required bool isValid, - }) = _IncomeSchedule; - - bool get isNew => id == ''; - - static IncomeSchedule init() => const IncomeSchedule( - id: '', - incomeTypeID: '', - title: Title.pure(), - amount: Amount.pure(), - memo: '', - isValid: false, - ); - - static IncomeSchedule fromModel(IncomeScheduleDetailRes res, incomeTypeMap) => - IncomeSchedule( - id: res.incomeSchedule.id, - incomeTypeID: res.incomeSchedule.incomeTypeId, - title: Title.dirty( - incomeTypeMap[res.incomeSchedule.incomeTypeId].name ?? ''), - amount: Amount.dirty(res.incomeSchedule.amount), - memo: res.incomeSchedule.memo, - isValid: true, - ); -} diff --git a/mobile/lib/src/features/schedules/services/income/income_schedule.freezed.dart b/mobile/lib/src/features/schedules/services/income/income_schedule.freezed.dart deleted file mode 100644 index 45b836f..0000000 --- a/mobile/lib/src/features/schedules/services/income/income_schedule.freezed.dart +++ /dev/null @@ -1,240 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'income_schedule.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -/// @nodoc -mixin _$IncomeSchedule { - String get id => throw _privateConstructorUsedError; - Title get title => throw _privateConstructorUsedError; - String get incomeTypeID => throw _privateConstructorUsedError; - Amount get amount => throw _privateConstructorUsedError; - String get memo => throw _privateConstructorUsedError; - bool get isValid => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $IncomeScheduleCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $IncomeScheduleCopyWith<$Res> { - factory $IncomeScheduleCopyWith( - IncomeSchedule value, $Res Function(IncomeSchedule) then) = - _$IncomeScheduleCopyWithImpl<$Res, IncomeSchedule>; - @useResult - $Res call( - {String id, - Title title, - String incomeTypeID, - Amount amount, - String memo, - bool isValid}); -} - -/// @nodoc -class _$IncomeScheduleCopyWithImpl<$Res, $Val extends IncomeSchedule> - implements $IncomeScheduleCopyWith<$Res> { - _$IncomeScheduleCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? incomeTypeID = null, - Object? amount = null, - Object? memo = null, - Object? isValid = null, - }) { - return _then(_value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as Title, - incomeTypeID: null == incomeTypeID - ? _value.incomeTypeID - : incomeTypeID // ignore: cast_nullable_to_non_nullable - as String, - amount: null == amount - ? _value.amount - : amount // ignore: cast_nullable_to_non_nullable - as Amount, - memo: null == memo - ? _value.memo - : memo // ignore: cast_nullable_to_non_nullable - as String, - isValid: null == isValid - ? _value.isValid - : isValid // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$_IncomeScheduleCopyWith<$Res> - implements $IncomeScheduleCopyWith<$Res> { - factory _$$_IncomeScheduleCopyWith( - _$_IncomeSchedule value, $Res Function(_$_IncomeSchedule) then) = - __$$_IncomeScheduleCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - Title title, - String incomeTypeID, - Amount amount, - String memo, - bool isValid}); -} - -/// @nodoc -class __$$_IncomeScheduleCopyWithImpl<$Res> - extends _$IncomeScheduleCopyWithImpl<$Res, _$_IncomeSchedule> - implements _$$_IncomeScheduleCopyWith<$Res> { - __$$_IncomeScheduleCopyWithImpl( - _$_IncomeSchedule _value, $Res Function(_$_IncomeSchedule) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? incomeTypeID = null, - Object? amount = null, - Object? memo = null, - Object? isValid = null, - }) { - return _then(_$_IncomeSchedule( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as Title, - incomeTypeID: null == incomeTypeID - ? _value.incomeTypeID - : incomeTypeID // ignore: cast_nullable_to_non_nullable - as String, - amount: null == amount - ? _value.amount - : amount // ignore: cast_nullable_to_non_nullable - as Amount, - memo: null == memo - ? _value.memo - : memo // ignore: cast_nullable_to_non_nullable - as String, - isValid: null == isValid - ? _value.isValid - : isValid // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc - -class _$_IncomeSchedule extends _IncomeSchedule { - const _$_IncomeSchedule( - {required this.id, - required this.title, - required this.incomeTypeID, - required this.amount, - required this.memo, - required this.isValid}) - : super._(); - - @override - final String id; - @override - final Title title; - @override - final String incomeTypeID; - @override - final Amount amount; - @override - final String memo; - @override - final bool isValid; - - @override - String toString() { - return 'IncomeSchedule(id: $id, title: $title, incomeTypeID: $incomeTypeID, amount: $amount, memo: $memo, isValid: $isValid)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_IncomeSchedule && - (identical(other.id, id) || other.id == id) && - (identical(other.title, title) || other.title == title) && - (identical(other.incomeTypeID, incomeTypeID) || - other.incomeTypeID == incomeTypeID) && - (identical(other.amount, amount) || other.amount == amount) && - (identical(other.memo, memo) || other.memo == memo) && - (identical(other.isValid, isValid) || other.isValid == isValid)); - } - - @override - int get hashCode => - Object.hash(runtimeType, id, title, incomeTypeID, amount, memo, isValid); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_IncomeScheduleCopyWith<_$_IncomeSchedule> get copyWith => - __$$_IncomeScheduleCopyWithImpl<_$_IncomeSchedule>(this, _$identity); -} - -abstract class _IncomeSchedule extends IncomeSchedule { - const factory _IncomeSchedule( - {required final String id, - required final Title title, - required final String incomeTypeID, - required final Amount amount, - required final String memo, - required final bool isValid}) = _$_IncomeSchedule; - const _IncomeSchedule._() : super._(); - - @override - String get id; - @override - Title get title; - @override - String get incomeTypeID; - @override - Amount get amount; - @override - String get memo; - @override - bool get isValid; - @override - @JsonKey(ignore: true) - _$$_IncomeScheduleCopyWith<_$_IncomeSchedule> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/mobile/lib/src/features/schedules/services/income/income_schedule_form_controller.dart b/mobile/lib/src/features/schedules/services/income/income_schedule_form_controller.dart deleted file mode 100644 index c31d7f5..0000000 --- a/mobile/lib/src/features/schedules/services/income/income_schedule_form_controller.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:formz/formz.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:suito/src/features/schedules/repositories/income/income_schedule_detail_repository.dart'; -import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; -import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; -import 'package:suito/src/formz/amount.dart'; -import 'package:suito/src/formz/title.dart' as formz_title; - -import 'income_schedule.dart'; - -part 'income_schedule_form_controller.g.dart'; - -@riverpod -Future incomeScheduleFuture(IncomeScheduleFutureRef ref, - {String? id}) async { - if (id == null) { - return IncomeSchedule.init(); - } - - final incomeTypeMap = await ref.read(incomeTypeMapFutureProvider.future); - final modelRes = await ref - .read(incomeScheduleDetailRepositoryProvider) - .fetchIncomeScheduleDetail(id); - return IncomeSchedule.fromModel(modelRes, incomeTypeMap); -} - -@riverpod -class IncomeScheduleFormController extends _$IncomeScheduleFormController { - @override - IncomeSchedule build(IncomeSchedule arg) { - return arg; - } - - void onChangeTitle(AttributeEntry? entry) { - final title = formz_title.Title.dirty(entry?.name ?? ''); - - state = state.copyWith( - title: title, - incomeTypeID: entry?.id ?? '', - isValid: Formz.validate([ - title, - state.amount, - ]), - ); - } - - void onChangeAmount(int value) { - final amount = Amount.dirty(value); - - state = state.copyWith( - amount: amount, - isValid: Formz.validate([ - amount, - ]), - ); - } - - void onChangeMemo(String? value) { - state = state.copyWith( - memo: value ?? '', - ); - } -} diff --git a/mobile/lib/src/features/schedules/services/income/income_schedule_form_controller.g.dart b/mobile/lib/src/features/schedules/services/income/income_schedule_form_controller.g.dart deleted file mode 100644 index decff10..0000000 --- a/mobile/lib/src/features/schedules/services/income/income_schedule_form_controller.g.dart +++ /dev/null @@ -1,216 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ignore_for_file: non_constant_identifier_names - -part of 'income_schedule_form_controller.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$incomeScheduleFutureHash() => - r'25331787ca84f8bc582fc8779edb96cc54a936a8'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -typedef IncomeScheduleFutureRef = AutoDisposeFutureProviderRef; - -/// See also [incomeScheduleFuture]. -@ProviderFor(incomeScheduleFuture) -const incomeScheduleFutureProvider = IncomeScheduleFutureFamily(); - -/// See also [incomeScheduleFuture]. -class IncomeScheduleFutureFamily extends Family> { - /// See also [incomeScheduleFuture]. - const IncomeScheduleFutureFamily(); - - /// See also [incomeScheduleFuture]. - IncomeScheduleFutureProvider call({ - String? id, - }) { - return IncomeScheduleFutureProvider( - id: id, - ); - } - - @override - IncomeScheduleFutureProvider getProviderOverride( - covariant IncomeScheduleFutureProvider provider, - ) { - return call( - id: provider.id, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'incomeScheduleFutureProvider'; -} - -/// See also [incomeScheduleFuture]. -class IncomeScheduleFutureProvider - extends AutoDisposeFutureProvider { - /// See also [incomeScheduleFuture]. - IncomeScheduleFutureProvider({ - this.id, - }) : super.internal( - (ref) => incomeScheduleFuture( - ref, - id: id, - ), - from: incomeScheduleFutureProvider, - name: r'incomeScheduleFutureProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$incomeScheduleFutureHash, - dependencies: IncomeScheduleFutureFamily._dependencies, - allTransitiveDependencies: - IncomeScheduleFutureFamily._allTransitiveDependencies, - ); - - final String? id; - - @override - bool operator ==(Object other) { - return other is IncomeScheduleFutureProvider && other.id == id; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); - } -} - -String _$incomeScheduleFormControllerHash() => - r'85f7a50e78c086595aaa1aa5f69ceb646657e4f2'; - -abstract class _$IncomeScheduleFormController - extends BuildlessAutoDisposeNotifier { - late final IncomeSchedule arg; - - IncomeSchedule build( - IncomeSchedule arg, - ); -} - -/// See also [IncomeScheduleFormController]. -@ProviderFor(IncomeScheduleFormController) -const incomeScheduleFormControllerProvider = - IncomeScheduleFormControllerFamily(); - -/// See also [IncomeScheduleFormController]. -class IncomeScheduleFormControllerFamily extends Family { - /// See also [IncomeScheduleFormController]. - const IncomeScheduleFormControllerFamily(); - - /// See also [IncomeScheduleFormController]. - IncomeScheduleFormControllerProvider call( - IncomeSchedule arg, - ) { - return IncomeScheduleFormControllerProvider( - arg, - ); - } - - @override - IncomeScheduleFormControllerProvider getProviderOverride( - covariant IncomeScheduleFormControllerProvider provider, - ) { - return call( - provider.arg, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'incomeScheduleFormControllerProvider'; -} - -/// See also [IncomeScheduleFormController]. -class IncomeScheduleFormControllerProvider - extends AutoDisposeNotifierProviderImpl { - /// See also [IncomeScheduleFormController]. - IncomeScheduleFormControllerProvider( - this.arg, - ) : super.internal( - () => IncomeScheduleFormController()..arg = arg, - from: incomeScheduleFormControllerProvider, - name: r'incomeScheduleFormControllerProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$incomeScheduleFormControllerHash, - dependencies: IncomeScheduleFormControllerFamily._dependencies, - allTransitiveDependencies: - IncomeScheduleFormControllerFamily._allTransitiveDependencies, - ); - - final IncomeSchedule arg; - - @override - bool operator ==(Object other) { - return other is IncomeScheduleFormControllerProvider && other.arg == arg; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, arg.hashCode); - - return _SystemHash.finish(hash); - } - - @override - IncomeSchedule runNotifierBuild( - covariant IncomeScheduleFormController notifier, - ) { - return notifier.build( - arg, - ); - } -} -// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.dart b/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.dart index 6eb0d6e..1e05b38 100644 --- a/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.dart +++ b/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.dart @@ -3,10 +3,9 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:suito/src/features/schedules/repositories/income/register_income_schedule_repository.dart'; import 'package:suito/src/features/schedules/repositories/income/update_income_schedule_repository.dart'; import 'package:suito/src/features/schedules/services/schedule_service.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; import 'package:suito/src/utils/timezone_provider.dart'; -import 'income_schedule.dart'; - part 'submit_income_schedule_controller.g.dart'; @riverpod @@ -34,7 +33,7 @@ class SubmitIncomeScheduleController extends _$SubmitIncomeScheduleController { ..timezone = timezone ..incomeTypeId = inc.incomeTypeID))); - Future submit(IncomeSchedule incomeSchedule) async { + Future submit(IncomeFormValue incomeSchedule) async { if (!incomeSchedule.isValid) return; state = const AsyncLoading(); diff --git a/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.g.dart b/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.g.dart index fc98f81..c47a13b 100644 --- a/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.g.dart +++ b/mobile/lib/src/features/schedules/services/income/submit_income_schedule_controller.g.dart @@ -9,7 +9,7 @@ part of 'submit_income_schedule_controller.dart'; // ************************************************************************** String _$submitIncomeScheduleControllerHash() => - r'170da18e876ccda6cb2ac205cdeeb3aea41180b3'; + r'c2f54a725a253baad8947a96ed0ca3444d55110e'; /// See also [SubmitIncomeScheduleController]. @ProviderFor(SubmitIncomeScheduleController) diff --git a/mobile/lib/src/features/schedules/services/schedule_service.dart b/mobile/lib/src/features/schedules/services/schedule_service.dart index a4e14f6..40c6210 100644 --- a/mobile/lib/src/features/schedules/services/schedule_service.dart +++ b/mobile/lib/src/features/schedules/services/schedule_service.dart @@ -1,50 +1,16 @@ -import 'package:flutter/material.dart'; import 'package:openapi/openapi.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:suito/i18n/translations.g.dart'; import 'package:suito/src/features/schedules/repositories/schedules_repository.dart'; -import 'expense/delete_expense_schedule_controller.dart'; -import 'income/delete_income_schedule_controller.dart'; - part 'schedule_service.g.dart'; -class Schedules { - final String headerText; - final Color mainColor; - final List items; - final Future Function(String id) onDismissed; - - Schedules( - {required this.headerText, - required this.mainColor, - required this.items, - required this.onDismissed}); -} - @riverpod -Future> fetchSchedules(FetchSchedulesRef ref) async { +Future>> fetchSchedules( + FetchSchedulesRef ref) async { final schedulesRepository = ref.read(schedulesRepositoryProvider); final res = await schedulesRepository.fetchSchedulesList(); - // TODO theme return [ - Schedules( - headerText: t.schedules.expansionTile.expenseHeader, - mainColor: Colors.red, - items: res.expenseSchedules.toList(), - onDismissed: (id) async { - await ref - .read(deleteExpenseScheduleControllerProvider.notifier) - .deleteExpenseSchedule(id); - }), - Schedules( - headerText: t.schedules.expansionTile.incomeHeader, - mainColor: Colors.green, - items: res.incomeSchedules.toList(), - onDismissed: (id) async { - await ref - .read(deleteIncomeScheduleControllerProvider.notifier) - .deleteIncomeSchedule(id); - }), + res.expenseSchedules.toList(), + res.incomeSchedules.toList(), ]; } diff --git a/mobile/lib/src/features/schedules/services/schedule_service.g.dart b/mobile/lib/src/features/schedules/services/schedule_service.g.dart index 1a6318d..4e586c4 100644 --- a/mobile/lib/src/features/schedules/services/schedule_service.g.dart +++ b/mobile/lib/src/features/schedules/services/schedule_service.g.dart @@ -8,12 +8,12 @@ part of 'schedule_service.dart'; // RiverpodGenerator // ************************************************************************** -String _$fetchSchedulesHash() => r'd9672a8a5e9b9d67cc2d722ad8a42ff4c4fe0868'; +String _$fetchSchedulesHash() => r'5200acea33896af05863ae390e0431906f9bee65'; /// See also [fetchSchedules]. @ProviderFor(fetchSchedules) final fetchSchedulesProvider = - AutoDisposeFutureProvider>.internal( + AutoDisposeFutureProvider>>.internal( fetchSchedules, name: r'fetchSchedulesProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -23,5 +23,6 @@ final fetchSchedulesProvider = allTransitiveDependencies: null, ); -typedef FetchSchedulesRef = AutoDisposeFutureProviderRef>; +typedef FetchSchedulesRef + = AutoDisposeFutureProviderRef>>; // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/mobile/lib/src/features/transaction_attributes/presentations/transaction_attribute_select_screen.dart b/mobile/lib/src/features/transaction_attributes/presentations/transaction_attribute_select_screen.dart index 8481def..ec47691 100644 --- a/mobile/lib/src/features/transaction_attributes/presentations/transaction_attribute_select_screen.dart +++ b/mobile/lib/src/features/transaction_attributes/presentations/transaction_attribute_select_screen.dart @@ -29,11 +29,11 @@ class TransactionAttributeSelectScreen extends ConsumerWidget { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () async { - final record = ref.read(transactionAttributeIDProvider); + final id = ref.read(attributeIDProvider); final list = await ref.read(listAttributeEntriesProvider.future); if (context.mounted) { - final res = list.firstWhere((el) => el.id == record.id, + final res = list.firstWhere((el) => el.id == id, orElse: () => AttributeEntry('', '')); context.pop(res); } diff --git a/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.dart b/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.dart index 166151b..f67d105 100644 --- a/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.dart +++ b/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.dart @@ -3,6 +3,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_controller.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; import 'package:suito/src/formz/name.dart'; import 'transaction_attribute_entry.dart'; @@ -12,14 +14,18 @@ import 'transaction_attribute_words.dart'; part 'transaction_attribute_service.g.dart'; -enum TransactionAttributeType { category, location, incomeType } +enum TransactionAttributeType { + category, + location, + incomeType, +} final transactionAttributeTypeProvider = StateProvider( (ref) => TransactionAttributeType.category); -final transactionAttributeIDProvider = - StateProvider<({String? id, String name})>((ref) => (id: null, name: '')); +// final transactionAttributeIDProvider = +// StateProvider<({String? id, String name})>((ref) => (id: null, name: '')); @riverpod Future> listAttributeEntries( @@ -44,6 +50,22 @@ Future> listAttributeEntries( } } +@riverpod +String attributeID(AttributeIDRef ref) { + final type = ref.watch(transactionAttributeTypeProvider); + switch (type) { + case TransactionAttributeType.category: + final expense = ref.watch(expenseFormControllerProvider); + return expense.categoryID; + case TransactionAttributeType.location: + final expense = ref.watch(expenseFormControllerProvider); + return expense.locationID; + case TransactionAttributeType.incomeType: + final income = ref.watch(incomeFormControllerProvider); + return income.incomeTypeID; + } +} + @riverpod TransactionAttributeRepository transactionAttributeRepository( TransactionAttributeRepositoryRef ref) { @@ -153,7 +175,7 @@ class TransactionAttributeSearchController Future<({List filteredItems, AttributeEntry selected})> filteredCategories(FilteredCategoriesRef ref) async { final repo = ref.watch(transactionAttributeRepositoryProvider); - final selectedID = ref.watch(transactionAttributeIDProvider).id; + final selectedID = ref.watch(attributeIDProvider); final items = await ref.watch(listAttributeEntriesProvider.future); final searchInput = ref.watch(transactionAttributeSearchControllerProvider .select((value) => value.searchInput)); diff --git a/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.g.dart b/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.g.dart index 2059e6b..58c339d 100644 --- a/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.g.dart +++ b/mobile/lib/src/features/transaction_attributes/services/transaction_attribute_service.g.dart @@ -26,6 +26,20 @@ final listAttributeEntriesProvider = typedef ListAttributeEntriesRef = AutoDisposeFutureProviderRef>; +String _$attributeIDHash() => r'99bf1b367fbed6d6d3d6b7795e6e08073857c2b9'; + +/// See also [attributeID]. +@ProviderFor(attributeID) +final attributeIDProvider = AutoDisposeProvider.internal( + attributeID, + name: r'attributeIDProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$attributeIDHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AttributeIDRef = AutoDisposeProviderRef; String _$transactionAttributeRepositoryHash() => r'6b13c6d6b6be2e5b8234bb9826421771d1c46eba'; @@ -63,7 +77,7 @@ final transactionAttributeWordsProvider = typedef TransactionAttributeWordsRef = AutoDisposeProviderRef; String _$filteredCategoriesHash() => - r'3dd88c485c7cf8202dc64e924d753b4413ba5f91'; + r'594fc4eb01b8749a0feb7f84b1e55b084c55da2c'; /// See also [filteredCategories]. @ProviderFor(filteredCategories) diff --git a/mobile/lib/src/features/transactions/presentations/expense/expense_detail_screen.dart b/mobile/lib/src/features/transactions/presentations/expense/expense_detail_screen.dart new file mode 100644 index 0000000..6789968 --- /dev/null +++ b/mobile/lib/src/features/transactions/presentations/expense/expense_detail_screen.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:suito/i18n/translations.g.dart'; +import 'package:suito/src/features/transactions/presentations/expense/expense_detail_view.dart'; + +class ExpenseDetailScreen extends ConsumerWidget { + const ExpenseDetailScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: Text(t.transactions.detail.title), + ), + body: const Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: ExpenseDetailView(), + ), + ); + } +} diff --git a/mobile/lib/src/features/transactions/presentations/expense/expense_detail_view.dart b/mobile/lib/src/features/transactions/presentations/expense/expense_detail_view.dart index 322c39b..93917fb 100644 --- a/mobile/lib/src/features/transactions/presentations/expense/expense_detail_view.dart +++ b/mobile/lib/src/features/transactions/presentations/expense/expense_detail_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/common_widgets/async_value_widget.dart'; import 'package:suito/src/common_widgets/currency_input_field.dart'; import 'package:suito/src/common_widgets/text_input_field.dart'; import 'package:suito/src/common_widgets/transition_text_field.dart'; @@ -10,7 +9,6 @@ import 'package:suito/src/constants/app_sizes.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_service.dart'; import 'package:suito/src/features/transactions/presentations/widgets/transaction_date_picker.dart'; -import 'package:suito/src/features/transactions/services/expense/expense.dart'; import 'package:suito/src/features/transactions/services/expense/expense_form_controller.dart'; import 'package:suito/src/features/transactions/services/expense/submit_expense_controller.dart'; import 'package:suito/src/formz/amount.dart'; @@ -19,31 +17,12 @@ import 'package:suito/src/routing/app_router.dart'; import 'package:suito/src/utils/currency_formatter.dart'; class ExpenseDetailView extends ConsumerWidget { - final String? expenseID; - - const ExpenseDetailView({required this.expenseID, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final expenseValue = ref.watch(expenseFutureProvider(id: expenseID)); - - return AsyncValueWidget( - value: expenseValue, - data: (expense) => _ExpenseDetailViewContents(expense: expense), - ); - } -} - -class _ExpenseDetailViewContents extends ConsumerWidget { - final Expense expense; - - const _ExpenseDetailViewContents({required this.expense}); + const ExpenseDetailView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final exp = ref.watch(expenseFormControllerProvider(expense)); - final controller = - ref.watch(expenseFormControllerProvider(expense).notifier); + final exp = ref.watch(expenseFormControllerProvider); + final controller = ref.watch(expenseFormControllerProvider.notifier); return SingleChildScrollView( child: ListBody( @@ -71,24 +50,16 @@ class _ExpenseDetailViewContents extends ConsumerWidget { initialValue: exp.category, labelText: t.transactions.detail.inputLabels.category, route: AppRoute.attribute, - onTap: () { - ref.read(transactionAttributeTypeProvider.notifier).state = - TransactionAttributeType.category; - ref.read(transactionAttributeIDProvider.notifier).state = - (id: exp.categoryID, name: exp.category); - }, + onTap: () => controller + .selectAttributeType(TransactionAttributeType.category), onChanged: controller.onChangeCategory), gapH12, TransitionTextField( initialValue: exp.location, labelText: t.transactions.detail.inputLabels.location, route: AppRoute.attribute, - onTap: () { - ref.read(transactionAttributeTypeProvider.notifier).state = - TransactionAttributeType.location; - ref.read(transactionAttributeIDProvider.notifier).state = - (id: exp.locationID, name: exp.location); - }, + onTap: () => controller + .selectAttributeType(TransactionAttributeType.location), onChanged: controller.onChangeLocation), gapH12, TransitionTextField( @@ -105,10 +76,8 @@ class _ExpenseDetailViewContents extends ConsumerWidget { backgroundColor: const Color(0xff1D7094), ), onPressed: () async { - await ref - .read(submitExpenseControllerProvider.notifier) - .submit(exp); - if (context.mounted) context.pop(); + ref.read(submitExpenseControllerProvider.notifier).submit(exp); + context.pop(); }, child: Text( t.transactions.buttons.post, diff --git a/mobile/lib/src/features/transactions/presentations/incomes/income_detail_screen.dart b/mobile/lib/src/features/transactions/presentations/incomes/income_detail_screen.dart new file mode 100644 index 0000000..3a426d1 --- /dev/null +++ b/mobile/lib/src/features/transactions/presentations/incomes/income_detail_screen.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:suito/i18n/translations.g.dart'; +import 'package:suito/src/features/transactions/presentations/incomes/income_detail_view.dart'; + +class IncomeDetailScreen extends ConsumerWidget { + const IncomeDetailScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: Text(t.transactions.detail.title), + ), + body: const Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: IncomeDetailView(), + ), + ); + } +} diff --git a/mobile/lib/src/features/transactions/presentations/incomes/income_detail_view.dart b/mobile/lib/src/features/transactions/presentations/incomes/income_detail_view.dart index 7a861f4..4180c54 100644 --- a/mobile/lib/src/features/transactions/presentations/incomes/income_detail_view.dart +++ b/mobile/lib/src/features/transactions/presentations/incomes/income_detail_view.dart @@ -2,14 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/common_widgets/async_value_widget.dart'; import 'package:suito/src/common_widgets/currency_input_field.dart'; import 'package:suito/src/common_widgets/transition_text_field.dart'; import 'package:suito/src/constants/app_sizes.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_service.dart'; import 'package:suito/src/features/transactions/presentations/widgets/transaction_date_picker.dart'; -import 'package:suito/src/features/transactions/services/income/income.dart'; import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; import 'package:suito/src/features/transactions/services/income/submit_income_controller.dart'; import 'package:suito/src/formz/amount.dart'; @@ -17,30 +15,12 @@ import 'package:suito/src/routing/app_router.dart'; import 'package:suito/src/utils/currency_formatter.dart'; class IncomeDetailView extends ConsumerWidget { - final String? incomeID; - - const IncomeDetailView({required this.incomeID, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final incomeValue = ref.watch(incomeFutureProvider(id: incomeID)); - - return AsyncValueWidget( - value: incomeValue, - data: (income) => _IncomeDetailViewContents(income: income), - ); - } -} - -class _IncomeDetailViewContents extends ConsumerWidget { - final Income income; - - const _IncomeDetailViewContents({required this.income}); + const IncomeDetailView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final controller = ref.watch(incomeFormControllerProvider(income).notifier); - final inc = ref.watch(incomeFormControllerProvider(income)); + final controller = ref.watch(incomeFormControllerProvider.notifier); + final inc = ref.watch(incomeFormControllerProvider); return SingleChildScrollView( child: ListBody( @@ -54,12 +34,8 @@ class _IncomeDetailViewContents extends ConsumerWidget { initialValue: inc.title.value, labelText: t.transactions.detail.inputLabels.title, route: AppRoute.attribute, - onTap: () { - ref.read(transactionAttributeTypeProvider.notifier).state = - TransactionAttributeType.incomeType; - ref.read(transactionAttributeIDProvider.notifier).state = - (id: inc.incomeTypeID, name: inc.title.value); - }, + onTap: () => controller + .selectAttributeType(TransactionAttributeType.incomeType), onChanged: controller.onChangeTitle), gapH12, CurrencyInputField( diff --git a/mobile/lib/src/features/transactions/presentations/transaction/transactions_list_item.dart b/mobile/lib/src/features/transactions/presentations/transaction/transactions_list_item.dart index 670d683..c363e11 100644 --- a/mobile/lib/src/features/transactions/presentations/transaction/transactions_list_item.dart +++ b/mobile/lib/src/features/transactions/presentations/transaction/transactions_list_item.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:openapi/openapi.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_detail_navigator.dart'; import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; -import 'package:suito/src/routing/app_router.dart'; import 'package:suito/src/utils/currency_formatter.dart'; class TransactionsListItem extends ConsumerWidget { @@ -16,13 +15,12 @@ class TransactionsListItem extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currencyFormatter = ref.watch(currencyFormatterProvider); + final navigator = ref.watch(transactionDetailNavigatorProvider.notifier); return GestureDetector( - onTap: () { - context.goNamed(AppRoute.transactionDetail.name, queryParameters: { - 'id': transaction.id, - 'type': transaction.type.toString() - }); + onTap: () async { + // TODO エラー表示 + navigator.goFetch(transaction); }, child: Card( elevation: 0, diff --git a/mobile/lib/src/features/transactions/presentations/transaction_detail_screen.dart b/mobile/lib/src/features/transactions/presentations/transaction_detail_screen.dart index 2e2f422..3a4de42 100644 --- a/mobile/lib/src/features/transactions/presentations/transaction_detail_screen.dart +++ b/mobile/lib/src/features/transactions/presentations/transaction_detail_screen.dart @@ -1,34 +1,34 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/features/transactions/presentations/expense/expense_detail_view.dart'; -import 'package:suito/src/features/transactions/presentations/incomes/income_detail_view.dart'; -import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:suito/i18n/translations.g.dart'; +// import 'package:suito/src/features/transactions/presentations/expense/expense_detail_view.dart'; +// import 'package:suito/src/features/transactions/presentations/incomes/income_detail_view.dart'; +// import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; -class TransactionDetailScreen extends ConsumerWidget { - final String? id; - final int type; +// class TransactionDetailScreen extends ConsumerWidget { +// final String? id; +// final int type; - const TransactionDetailScreen( - {required this.id, required this.type, super.key}); +// const TransactionDetailScreen( +// {required this.id, required this.type, super.key}); - @override - Widget build(BuildContext context, WidgetRef ref) { - final typeValue = ref.watch(typeProvider(type)); +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// final typeValue = ref.watch(typeProvider(type)); - return Scaffold( - appBar: AppBar( - title: Text(t.transactions.detail.title), - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: TransactionType.isExpense(typeValue) - ? ExpenseDetailView(expenseID: id) - : IncomeDetailView(incomeID: id), - ), - ); - } -} +// return Scaffold( +// appBar: AppBar( +// title: Text(t.transactions.detail.title), +// ), +// body: Padding( +// padding: const EdgeInsets.symmetric(horizontal: 12), +// child: TransactionType.isExpense(typeValue) +// ? ExpenseDetailView(expenseID: id) +// : IncomeDetailView(incomeID: id), +// ), +// ); +// } +// } -final typeProvider = StateProvider.family - .autoDispose((ref, initialValue) => initialValue); +// final typeProvider = StateProvider.family +// .autoDispose((ref, initialValue) => initialValue); diff --git a/mobile/lib/src/features/transactions/presentations/transactions_screen.dart b/mobile/lib/src/features/transactions/presentations/transactions_screen.dart index 0df8f19..03e7112 100644 --- a/mobile/lib/src/features/transactions/presentations/transactions_screen.dart +++ b/mobile/lib/src/features/transactions/presentations/transactions_screen.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import 'package:suito/i18n/translations.g.dart'; -import 'package:suito/src/routing/app_router.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_detail_navigator.dart'; import 'transaction/transaction_months_dropdown.dart'; import 'transaction/transactions_list.dart'; @@ -13,6 +12,8 @@ class TransactionsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final navigator = ref.watch(transactionDetailNavigatorProvider.notifier); + return Scaffold( appBar: AppBar( title: Text(t.transactions.abbBar), @@ -25,9 +26,7 @@ class TransactionsScreen extends ConsumerWidget { ], ), floatingActionButton: FloatingActionButton( - onPressed: () { - context.goNamed(AppRoute.transactionDetail.name); - }, + onPressed: navigator.goNewExpense, backgroundColor: const Color(0xff2CAAE0), child: const Icon(Icons.add), ), diff --git a/mobile/lib/src/features/transactions/services/expense/expense_form_controller.dart b/mobile/lib/src/features/transactions/services/expense/expense_form_controller.dart index f534b40..307b4aa 100644 --- a/mobile/lib/src/features/transactions/services/expense/expense_form_controller.dart +++ b/mobile/lib/src/features/transactions/services/expense/expense_form_controller.dart @@ -1,36 +1,24 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:formz/formz.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_detail_repository.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; +import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_service.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart' as formz_title; -import 'package:suito/src/utils/datetime_utils.dart'; -import 'expense.dart'; +import 'expense_form_value.dart'; part 'expense_form_controller.g.dart'; -@riverpod -Future expenseFuture(ExpenseFutureRef ref, {String? id}) async { - if (id == null) { - final now = ref.watch(currentTimeProvider); - return Expense.init(now); - } - final categoriesMap = - await ref.read(expenseCategoriesMapFutureProvider.future); - final locationsMap = await ref.read(expenseLocationsMapFutureProvider.future); - final expense = - await ref.read(expenseDetailRepositoryProvider).fetchExpenseDetail(id); - return Expense.fromModel(expense, categoriesMap, locationsMap); -} +// bridge provider between fetching expense and building initial value for expense form +final expenseFormInitialValueProvider = StateProvider( + (ref) => ExpenseFormValue.newExpense(DateTime.now())); @riverpod class ExpenseFormController extends _$ExpenseFormController { @override - Expense build(Expense arg) { - return arg; + ExpenseFormValue build() { + return ref.read(expenseFormInitialValueProvider); } void onChangeTitle(String value) { @@ -76,4 +64,8 @@ class ExpenseFormController extends _$ExpenseFormController { memo: value ?? '', ); } + + void selectAttributeType(TransactionAttributeType type) { + ref.read(transactionAttributeTypeProvider.notifier).state = type; + } } diff --git a/mobile/lib/src/features/transactions/services/expense/expense_form_controller.g.dart b/mobile/lib/src/features/transactions/services/expense/expense_form_controller.g.dart index 9497968..f1ce615 100644 --- a/mobile/lib/src/features/transactions/services/expense/expense_form_controller.g.dart +++ b/mobile/lib/src/features/transactions/services/expense/expense_form_controller.g.dart @@ -8,205 +8,21 @@ part of 'expense_form_controller.dart'; // RiverpodGenerator // ************************************************************************** -String _$expenseFutureHash() => r'0e9847409e0ba7d52123cad6b6dccd90391f95ef'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -typedef ExpenseFutureRef = AutoDisposeFutureProviderRef; - -/// See also [expenseFuture]. -@ProviderFor(expenseFuture) -const expenseFutureProvider = ExpenseFutureFamily(); - -/// See also [expenseFuture]. -class ExpenseFutureFamily extends Family> { - /// See also [expenseFuture]. - const ExpenseFutureFamily(); - - /// See also [expenseFuture]. - ExpenseFutureProvider call({ - String? id, - }) { - return ExpenseFutureProvider( - id: id, - ); - } - - @override - ExpenseFutureProvider getProviderOverride( - covariant ExpenseFutureProvider provider, - ) { - return call( - id: provider.id, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'expenseFutureProvider'; -} - -/// See also [expenseFuture]. -class ExpenseFutureProvider extends AutoDisposeFutureProvider { - /// See also [expenseFuture]. - ExpenseFutureProvider({ - this.id, - }) : super.internal( - (ref) => expenseFuture( - ref, - id: id, - ), - from: expenseFutureProvider, - name: r'expenseFutureProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$expenseFutureHash, - dependencies: ExpenseFutureFamily._dependencies, - allTransitiveDependencies: - ExpenseFutureFamily._allTransitiveDependencies, - ); - - final String? id; - - @override - bool operator ==(Object other) { - return other is ExpenseFutureProvider && other.id == id; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); - } -} - String _$expenseFormControllerHash() => - r'9eb1b3099292ff77f57c6bf0587bfd7e1f6ec339'; - -abstract class _$ExpenseFormController - extends BuildlessAutoDisposeNotifier { - late final Expense arg; - - Expense build( - Expense arg, - ); -} + r'6f744ca293befc3e6eef6b746e932ad3308c2214'; /// See also [ExpenseFormController]. @ProviderFor(ExpenseFormController) -const expenseFormControllerProvider = ExpenseFormControllerFamily(); - -/// See also [ExpenseFormController]. -class ExpenseFormControllerFamily extends Family { - /// See also [ExpenseFormController]. - const ExpenseFormControllerFamily(); - - /// See also [ExpenseFormController]. - ExpenseFormControllerProvider call( - Expense arg, - ) { - return ExpenseFormControllerProvider( - arg, - ); - } - - @override - ExpenseFormControllerProvider getProviderOverride( - covariant ExpenseFormControllerProvider provider, - ) { - return call( - provider.arg, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'expenseFormControllerProvider'; -} - -/// See also [ExpenseFormController]. -class ExpenseFormControllerProvider - extends AutoDisposeNotifierProviderImpl { - /// See also [ExpenseFormController]. - ExpenseFormControllerProvider( - this.arg, - ) : super.internal( - () => ExpenseFormController()..arg = arg, - from: expenseFormControllerProvider, - name: r'expenseFormControllerProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$expenseFormControllerHash, - dependencies: ExpenseFormControllerFamily._dependencies, - allTransitiveDependencies: - ExpenseFormControllerFamily._allTransitiveDependencies, - ); - - final Expense arg; - - @override - bool operator ==(Object other) { - return other is ExpenseFormControllerProvider && other.arg == arg; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, arg.hashCode); - - return _SystemHash.finish(hash); - } - - @override - Expense runNotifierBuild( - covariant ExpenseFormController notifier, - ) { - return notifier.build( - arg, - ); - } -} +final expenseFormControllerProvider = AutoDisposeNotifierProvider< + ExpenseFormController, ExpenseFormValue>.internal( + ExpenseFormController.new, + name: r'expenseFormControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$expenseFormControllerHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ExpenseFormController = AutoDisposeNotifier; // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/mobile/lib/src/features/transactions/services/expense/expense.dart b/mobile/lib/src/features/transactions/services/expense/expense_form_value.dart similarity index 55% rename from mobile/lib/src/features/transactions/services/expense/expense.dart rename to mobile/lib/src/features/transactions/services/expense/expense_form_value.dart index 17fa9a9..bd77058 100644 --- a/mobile/lib/src/features/transactions/services/expense/expense.dart +++ b/mobile/lib/src/features/transactions/services/expense/expense_form_value.dart @@ -4,13 +4,13 @@ import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart'; import 'package:suito/src/utils/datetime_utils.dart'; -part 'expense.freezed.dart'; +part 'expense_form_value.freezed.dart'; @freezed -class Expense with _$Expense { - const Expense._(); +class ExpenseFormValue with _$ExpenseFormValue { + const ExpenseFormValue._(); - const factory Expense({ + const factory ExpenseFormValue({ required String id, required Title title, required Amount amount, @@ -21,11 +21,11 @@ class Expense with _$Expense { required String location, required String memo, required bool isValid, - }) = _Expense; + }) = _ExpenseFormValue; bool get isNew => id == ''; - static Expense init(DateTime now) => Expense( + static ExpenseFormValue newExpense(DateTime now) => ExpenseFormValue( id: '', title: const Title.pure(), amount: const Amount.pure(), @@ -38,8 +38,9 @@ class Expense with _$Expense { isValid: false, ); - static Expense fromModel(ExpenseDetailRes res, categoriesMap, locationsMap) => - Expense( + static ExpenseFormValue fromExpense( + ExpenseDetailRes res, categoriesMap, locationsMap) => + ExpenseFormValue( id: res.expense.id, title: Title.dirty(res.expense.title), amount: Amount.dirty(res.expense.amount), @@ -51,4 +52,21 @@ class Expense with _$Expense { memo: res.expense.memo, isValid: true, ); + + static ExpenseFormValue fromSchedule( + ExpenseScheduleDetailRes res, categoriesMap, locationsMap) => + ExpenseFormValue( + id: res.expenseSchedule.id, + title: Title.dirty(res.expenseSchedule.title), + amount: Amount.dirty(res.expenseSchedule.amount), + date: '', + categoryID: res.expenseSchedule.expenseCategoryID, + category: + categoriesMap[res.expenseSchedule.expenseCategoryID]?.name ?? '', + locationID: res.expenseSchedule.expenseLocationID, + location: + locationsMap[res.expenseSchedule.expenseLocationID]?.name ?? '', + memo: res.expenseSchedule.memo, + isValid: true, + ); } diff --git a/mobile/lib/src/features/transactions/services/expense/expense.freezed.dart b/mobile/lib/src/features/transactions/services/expense/expense_form_value.freezed.dart similarity index 81% rename from mobile/lib/src/features/transactions/services/expense/expense.freezed.dart rename to mobile/lib/src/features/transactions/services/expense/expense_form_value.freezed.dart index bb1135d..7b4b29e 100644 --- a/mobile/lib/src/features/transactions/services/expense/expense.freezed.dart +++ b/mobile/lib/src/features/transactions/services/expense/expense_form_value.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'expense.dart'; +part of 'expense_form_value.dart'; // ************************************************************************** // FreezedGenerator @@ -15,7 +15,7 @@ final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); /// @nodoc -mixin _$Expense { +mixin _$ExpenseFormValue { String get id => throw _privateConstructorUsedError; Title get title => throw _privateConstructorUsedError; Amount get amount => throw _privateConstructorUsedError; @@ -28,13 +28,15 @@ mixin _$Expense { bool get isValid => throw _privateConstructorUsedError; @JsonKey(ignore: true) - $ExpenseCopyWith get copyWith => throw _privateConstructorUsedError; + $ExpenseFormValueCopyWith get copyWith => + throw _privateConstructorUsedError; } /// @nodoc -abstract class $ExpenseCopyWith<$Res> { - factory $ExpenseCopyWith(Expense value, $Res Function(Expense) then) = - _$ExpenseCopyWithImpl<$Res, Expense>; +abstract class $ExpenseFormValueCopyWith<$Res> { + factory $ExpenseFormValueCopyWith( + ExpenseFormValue value, $Res Function(ExpenseFormValue) then) = + _$ExpenseFormValueCopyWithImpl<$Res, ExpenseFormValue>; @useResult $Res call( {String id, @@ -50,9 +52,9 @@ abstract class $ExpenseCopyWith<$Res> { } /// @nodoc -class _$ExpenseCopyWithImpl<$Res, $Val extends Expense> - implements $ExpenseCopyWith<$Res> { - _$ExpenseCopyWithImpl(this._value, this._then); +class _$ExpenseFormValueCopyWithImpl<$Res, $Val extends ExpenseFormValue> + implements $ExpenseFormValueCopyWith<$Res> { + _$ExpenseFormValueCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; @@ -119,10 +121,11 @@ class _$ExpenseCopyWithImpl<$Res, $Val extends Expense> } /// @nodoc -abstract class _$$_ExpenseCopyWith<$Res> implements $ExpenseCopyWith<$Res> { - factory _$$_ExpenseCopyWith( - _$_Expense value, $Res Function(_$_Expense) then) = - __$$_ExpenseCopyWithImpl<$Res>; +abstract class _$$_ExpenseFormValueCopyWith<$Res> + implements $ExpenseFormValueCopyWith<$Res> { + factory _$$_ExpenseFormValueCopyWith( + _$_ExpenseFormValue value, $Res Function(_$_ExpenseFormValue) then) = + __$$_ExpenseFormValueCopyWithImpl<$Res>; @override @useResult $Res call( @@ -139,10 +142,11 @@ abstract class _$$_ExpenseCopyWith<$Res> implements $ExpenseCopyWith<$Res> { } /// @nodoc -class __$$_ExpenseCopyWithImpl<$Res> - extends _$ExpenseCopyWithImpl<$Res, _$_Expense> - implements _$$_ExpenseCopyWith<$Res> { - __$$_ExpenseCopyWithImpl(_$_Expense _value, $Res Function(_$_Expense) _then) +class __$$_ExpenseFormValueCopyWithImpl<$Res> + extends _$ExpenseFormValueCopyWithImpl<$Res, _$_ExpenseFormValue> + implements _$$_ExpenseFormValueCopyWith<$Res> { + __$$_ExpenseFormValueCopyWithImpl( + _$_ExpenseFormValue _value, $Res Function(_$_ExpenseFormValue) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -159,7 +163,7 @@ class __$$_ExpenseCopyWithImpl<$Res> Object? memo = null, Object? isValid = null, }) { - return _then(_$_Expense( + return _then(_$_ExpenseFormValue( id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable @@ -206,8 +210,8 @@ class __$$_ExpenseCopyWithImpl<$Res> /// @nodoc -class _$_Expense extends _Expense { - const _$_Expense( +class _$_ExpenseFormValue extends _ExpenseFormValue { + const _$_ExpenseFormValue( {required this.id, required this.title, required this.amount, @@ -243,14 +247,14 @@ class _$_Expense extends _Expense { @override String toString() { - return 'Expense(id: $id, title: $title, amount: $amount, date: $date, categoryID: $categoryID, category: $category, locationID: $locationID, location: $location, memo: $memo, isValid: $isValid)'; + return 'ExpenseFormValue(id: $id, title: $title, amount: $amount, date: $date, categoryID: $categoryID, category: $category, locationID: $locationID, location: $location, memo: $memo, isValid: $isValid)'; } @override bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$_Expense && + other is _$_ExpenseFormValue && (identical(other.id, id) || other.id == id) && (identical(other.title, title) || other.title == title) && (identical(other.amount, amount) || other.amount == amount) && @@ -274,12 +278,12 @@ class _$_Expense extends _Expense { @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$_ExpenseCopyWith<_$_Expense> get copyWith => - __$$_ExpenseCopyWithImpl<_$_Expense>(this, _$identity); + _$$_ExpenseFormValueCopyWith<_$_ExpenseFormValue> get copyWith => + __$$_ExpenseFormValueCopyWithImpl<_$_ExpenseFormValue>(this, _$identity); } -abstract class _Expense extends Expense { - const factory _Expense( +abstract class _ExpenseFormValue extends ExpenseFormValue { + const factory _ExpenseFormValue( {required final String id, required final Title title, required final Amount amount, @@ -289,8 +293,8 @@ abstract class _Expense extends Expense { required final String locationID, required final String location, required final String memo, - required final bool isValid}) = _$_Expense; - const _Expense._() : super._(); + required final bool isValid}) = _$_ExpenseFormValue; + const _ExpenseFormValue._() : super._(); @override String get id; @@ -314,6 +318,6 @@ abstract class _Expense extends Expense { bool get isValid; @override @JsonKey(ignore: true) - _$$_ExpenseCopyWith<_$_Expense> get copyWith => + _$$_ExpenseFormValueCopyWith<_$_ExpenseFormValue> get copyWith => throw _privateConstructorUsedError; } diff --git a/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.dart b/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.dart index 3919809..f867dd6 100644 --- a/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.dart +++ b/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.dart @@ -2,11 +2,10 @@ import 'package:openapi/openapi.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:suito/src/features/transactions/repositories/expense/register_expense_repository.dart'; import 'package:suito/src/features/transactions/repositories/expense/update_expense_repository.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; import 'package:suito/src/utils/datetime_utils.dart'; -import 'expense.dart'; - part 'submit_expense_controller.g.dart'; @riverpod @@ -40,7 +39,7 @@ class SubmitExpenseController extends _$SubmitExpenseController { ..amount = ex.amount.value))); } - Future submit(Expense expense) async { + Future submit(ExpenseFormValue expense) async { if (!expense.isValid) return; state = const AsyncLoading(); diff --git a/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.g.dart b/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.g.dart index f93630c..ca4bd70 100644 --- a/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.g.dart +++ b/mobile/lib/src/features/transactions/services/expense/submit_expense_controller.g.dart @@ -9,7 +9,7 @@ part of 'submit_expense_controller.dart'; // ************************************************************************** String _$submitExpenseControllerHash() => - r'da3b7e511579aade841173834f26f90d123d65c9'; + r'959dc761fb81f8a4dd8db1ad02f43d24d7821aec'; /// See also [SubmitExpenseController]. @ProviderFor(SubmitExpenseController) diff --git a/mobile/lib/src/features/transactions/services/income/income_detail_navigator.dart b/mobile/lib/src/features/transactions/services/income/income_detail_navigator.dart new file mode 100644 index 0000000..07c76de --- /dev/null +++ b/mobile/lib/src/features/transactions/services/income/income_detail_navigator.dart @@ -0,0 +1,69 @@ +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:suito/src/app.dart'; +// import 'package:suito/src/features/schedules/repositories/income/income_schedule_detail_repository.dart'; +// import 'package:suito/src/features/transactions/repositories/income/income_detail_repository.dart'; +// import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; +// import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; +// import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; +// import 'package:suito/src/routing/app_router.dart'; +// import 'package:suito/src/utils/datetime_utils.dart'; + +// class IncomeDetailNavigator extends StateNotifier> { +// IncomeDetailNavigator(this._ref) : super(const AsyncData(null)); +// final Ref _ref; + +// _fetchIncomeTypes() => _ref.read(incomeTypeMapFutureProvider.future); + +// _goIncome() => _ref +// .read(goRouterProvider(navigatorKey)) +// .goNamed(AppRoute.incomeDetail.name); + +// _setInitialValue(value) { +// _ref.read(incomeFormInitialValueProvider.notifier).state = value; +// } + +// void goNewIncome() { +// final now = _ref.read(currentTimeProvider); +// _setInitialValue(IncomeFormValue.newIncome(now)); +// _goIncome; +// } + +// Future goFetchIncome(String id) async { +// state = const AsyncLoading(); +// final value = await AsyncValue.guard(() async { +// final incomeTypesMap = await _fetchIncomeTypes(); +// final income = +// await _ref.read(incomeDetailRepositoryProvider).fetchIncomeDetail(id); +// final exp = IncomeFormValue.fromIncome(income, incomeTypesMap); +// _setInitialValue(exp); +// }); + +// final success = value.hasError == false; +// if (!mounted) return; + +// state = value; +// if (success) _goIncome(); +// } + +// Future goFetchSchedule(String id) async { +// state = const AsyncLoading(); +// final value = await AsyncValue.guard(() async { +// final incomeTypesMap = await _fetchIncomeTypes(); +// final schedule = await _ref +// .read(incomeScheduleDetailRepositoryProvider) +// .fetchIncomeScheduleDetail(id); +// final exp = IncomeFormValue.fromSchedule(schedule, incomeTypesMap); +// _setInitialValue(exp); +// }); + +// final success = value.hasError == false; +// if (!mounted) return; + +// state = value; +// if (success) _goIncome(); +// } +// } + +// final incomeDetailNavigatorProvider = +// StateNotifierProvider.autoDispose>( +// (ref) => IncomeDetailNavigator(ref)); diff --git a/mobile/lib/src/features/transactions/services/income/income_form_controller.dart b/mobile/lib/src/features/transactions/services/income/income_form_controller.dart index d4cbd76..6053312 100644 --- a/mobile/lib/src/features/transactions/services/income/income_form_controller.dart +++ b/mobile/lib/src/features/transactions/services/income/income_form_controller.dart @@ -1,34 +1,24 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:formz/formz.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; -import 'package:suito/src/features/transactions/repositories/income/income_detail_repository.dart'; -import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; +import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_service.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart' as formz_title; -import 'package:suito/src/utils/datetime_utils.dart'; -import 'income.dart'; +import 'income_form_value.dart'; part 'income_form_controller.g.dart'; -@riverpod -Future incomeFuture(IncomeFutureRef ref, {String? id}) async { - if (id == null) { - final now = ref.watch(currentTimeProvider); - return Income.init(now); - } - - final incomeTypeMap = await ref.read(incomeTypeMapFutureProvider.future); - final modelRes = - await ref.read(incomeDetailRepositoryProvider).fetchIncomeDetail(id); - return Income.fromModel(modelRes, incomeTypeMap); -} +// bridge provider between fetching expense and building initial value for expense form +final incomeFormInitialValueProvider = StateProvider( + (ref) => IncomeFormValue.newIncome(DateTime.now())); @riverpod class IncomeFormController extends _$IncomeFormController { @override - Income build(Income arg) { - return arg; + IncomeFormValue build() { + return ref.read(incomeFormInitialValueProvider); } void onChangeTitle(AttributeEntry? entry) { @@ -67,4 +57,8 @@ class IncomeFormController extends _$IncomeFormController { memo: value ?? '', ); } + + void selectAttributeType(TransactionAttributeType type) { + ref.read(transactionAttributeTypeProvider.notifier).state = type; + } } diff --git a/mobile/lib/src/features/transactions/services/income/income_form_controller.g.dart b/mobile/lib/src/features/transactions/services/income/income_form_controller.g.dart index 3c6def4..3e5fe68 100644 --- a/mobile/lib/src/features/transactions/services/income/income_form_controller.g.dart +++ b/mobile/lib/src/features/transactions/services/income/income_form_controller.g.dart @@ -8,205 +8,21 @@ part of 'income_form_controller.dart'; // RiverpodGenerator // ************************************************************************** -String _$incomeFutureHash() => r'6ae77ee223c3d79c76f64b7a06429c49afaf0d7b'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -typedef IncomeFutureRef = AutoDisposeFutureProviderRef; - -/// See also [incomeFuture]. -@ProviderFor(incomeFuture) -const incomeFutureProvider = IncomeFutureFamily(); - -/// See also [incomeFuture]. -class IncomeFutureFamily extends Family> { - /// See also [incomeFuture]. - const IncomeFutureFamily(); - - /// See also [incomeFuture]. - IncomeFutureProvider call({ - String? id, - }) { - return IncomeFutureProvider( - id: id, - ); - } - - @override - IncomeFutureProvider getProviderOverride( - covariant IncomeFutureProvider provider, - ) { - return call( - id: provider.id, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'incomeFutureProvider'; -} - -/// See also [incomeFuture]. -class IncomeFutureProvider extends AutoDisposeFutureProvider { - /// See also [incomeFuture]. - IncomeFutureProvider({ - this.id, - }) : super.internal( - (ref) => incomeFuture( - ref, - id: id, - ), - from: incomeFutureProvider, - name: r'incomeFutureProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$incomeFutureHash, - dependencies: IncomeFutureFamily._dependencies, - allTransitiveDependencies: - IncomeFutureFamily._allTransitiveDependencies, - ); - - final String? id; - - @override - bool operator ==(Object other) { - return other is IncomeFutureProvider && other.id == id; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); - } -} - String _$incomeFormControllerHash() => - r'aa3320e71db9fee93d7ff8e6559100d2d1d29411'; - -abstract class _$IncomeFormController - extends BuildlessAutoDisposeNotifier { - late final Income arg; - - Income build( - Income arg, - ); -} + r'3880ff18dc8efb54608171067a75669ae2031d27'; /// See also [IncomeFormController]. @ProviderFor(IncomeFormController) -const incomeFormControllerProvider = IncomeFormControllerFamily(); - -/// See also [IncomeFormController]. -class IncomeFormControllerFamily extends Family { - /// See also [IncomeFormController]. - const IncomeFormControllerFamily(); - - /// See also [IncomeFormController]. - IncomeFormControllerProvider call( - Income arg, - ) { - return IncomeFormControllerProvider( - arg, - ); - } - - @override - IncomeFormControllerProvider getProviderOverride( - covariant IncomeFormControllerProvider provider, - ) { - return call( - provider.arg, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'incomeFormControllerProvider'; -} - -/// See also [IncomeFormController]. -class IncomeFormControllerProvider - extends AutoDisposeNotifierProviderImpl { - /// See also [IncomeFormController]. - IncomeFormControllerProvider( - this.arg, - ) : super.internal( - () => IncomeFormController()..arg = arg, - from: incomeFormControllerProvider, - name: r'incomeFormControllerProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$incomeFormControllerHash, - dependencies: IncomeFormControllerFamily._dependencies, - allTransitiveDependencies: - IncomeFormControllerFamily._allTransitiveDependencies, - ); - - final Income arg; - - @override - bool operator ==(Object other) { - return other is IncomeFormControllerProvider && other.arg == arg; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, arg.hashCode); - - return _SystemHash.finish(hash); - } - - @override - Income runNotifierBuild( - covariant IncomeFormController notifier, - ) { - return notifier.build( - arg, - ); - } -} +final incomeFormControllerProvider = + AutoDisposeNotifierProvider.internal( + IncomeFormController.new, + name: r'incomeFormControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$incomeFormControllerHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$IncomeFormController = AutoDisposeNotifier; // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/mobile/lib/src/features/transactions/services/income/income.dart b/mobile/lib/src/features/transactions/services/income/income_form_value.dart similarity index 55% rename from mobile/lib/src/features/transactions/services/income/income.dart rename to mobile/lib/src/features/transactions/services/income/income_form_value.dart index d345870..c2c4ece 100644 --- a/mobile/lib/src/features/transactions/services/income/income.dart +++ b/mobile/lib/src/features/transactions/services/income/income_form_value.dart @@ -4,13 +4,13 @@ import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart'; import 'package:suito/src/utils/datetime_utils.dart'; -part 'income.freezed.dart'; +part 'income_form_value.freezed.dart'; @freezed -class Income with _$Income { - const Income._(); +class IncomeFormValue with _$IncomeFormValue { + const IncomeFormValue._(); - const factory Income({ + const factory IncomeFormValue({ required String id, required Title title, required String incomeTypeID, @@ -18,11 +18,11 @@ class Income with _$Income { required String date, required String memo, required bool isValid, - }) = _Income; + }) = _IncomeFormValue; bool get isNew => id == ''; - static Income init(DateTime now) => Income( + static IncomeFormValue newIncome(DateTime now) => IncomeFormValue( id: '', incomeTypeID: '', title: const Title.pure(), @@ -32,7 +32,8 @@ class Income with _$Income { isValid: false, ); - static Income fromModel(IncomeDetailRes res, incomeTypeMap) => Income( + static IncomeFormValue fromIncome(IncomeDetailRes res, incomeTypeMap) => + IncomeFormValue( id: res.income.id, incomeTypeID: res.income.incomeTypeId, title: Title.dirty(incomeTypeMap[res.income.incomeTypeId].name ?? ''), @@ -41,4 +42,17 @@ class Income with _$Income { memo: res.income.memo, isValid: true, ); + + static IncomeFormValue fromSchedule( + IncomeScheduleDetailRes res, incomeTypeMap) => + IncomeFormValue( + id: res.incomeSchedule.id, + incomeTypeID: res.incomeSchedule.incomeTypeId, + title: Title.dirty( + incomeTypeMap[res.incomeSchedule.incomeTypeId].name ?? ''), + amount: Amount.dirty(res.incomeSchedule.amount), + date: '', + memo: res.incomeSchedule.memo, + isValid: true, + ); } diff --git a/mobile/lib/src/features/transactions/services/income/income.freezed.dart b/mobile/lib/src/features/transactions/services/income/income_form_value.freezed.dart similarity index 78% rename from mobile/lib/src/features/transactions/services/income/income.freezed.dart rename to mobile/lib/src/features/transactions/services/income/income_form_value.freezed.dart index 24dd6f1..5eb8a61 100644 --- a/mobile/lib/src/features/transactions/services/income/income.freezed.dart +++ b/mobile/lib/src/features/transactions/services/income/income_form_value.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'income.dart'; +part of 'income_form_value.dart'; // ************************************************************************** // FreezedGenerator @@ -15,7 +15,7 @@ final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); /// @nodoc -mixin _$Income { +mixin _$IncomeFormValue { String get id => throw _privateConstructorUsedError; Title get title => throw _privateConstructorUsedError; String get incomeTypeID => throw _privateConstructorUsedError; @@ -25,13 +25,15 @@ mixin _$Income { bool get isValid => throw _privateConstructorUsedError; @JsonKey(ignore: true) - $IncomeCopyWith get copyWith => throw _privateConstructorUsedError; + $IncomeFormValueCopyWith get copyWith => + throw _privateConstructorUsedError; } /// @nodoc -abstract class $IncomeCopyWith<$Res> { - factory $IncomeCopyWith(Income value, $Res Function(Income) then) = - _$IncomeCopyWithImpl<$Res, Income>; +abstract class $IncomeFormValueCopyWith<$Res> { + factory $IncomeFormValueCopyWith( + IncomeFormValue value, $Res Function(IncomeFormValue) then) = + _$IncomeFormValueCopyWithImpl<$Res, IncomeFormValue>; @useResult $Res call( {String id, @@ -44,9 +46,9 @@ abstract class $IncomeCopyWith<$Res> { } /// @nodoc -class _$IncomeCopyWithImpl<$Res, $Val extends Income> - implements $IncomeCopyWith<$Res> { - _$IncomeCopyWithImpl(this._value, this._then); +class _$IncomeFormValueCopyWithImpl<$Res, $Val extends IncomeFormValue> + implements $IncomeFormValueCopyWith<$Res> { + _$IncomeFormValueCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; @@ -98,9 +100,11 @@ class _$IncomeCopyWithImpl<$Res, $Val extends Income> } /// @nodoc -abstract class _$$_IncomeCopyWith<$Res> implements $IncomeCopyWith<$Res> { - factory _$$_IncomeCopyWith(_$_Income value, $Res Function(_$_Income) then) = - __$$_IncomeCopyWithImpl<$Res>; +abstract class _$$_IncomeFormValueCopyWith<$Res> + implements $IncomeFormValueCopyWith<$Res> { + factory _$$_IncomeFormValueCopyWith( + _$_IncomeFormValue value, $Res Function(_$_IncomeFormValue) then) = + __$$_IncomeFormValueCopyWithImpl<$Res>; @override @useResult $Res call( @@ -114,10 +118,11 @@ abstract class _$$_IncomeCopyWith<$Res> implements $IncomeCopyWith<$Res> { } /// @nodoc -class __$$_IncomeCopyWithImpl<$Res> - extends _$IncomeCopyWithImpl<$Res, _$_Income> - implements _$$_IncomeCopyWith<$Res> { - __$$_IncomeCopyWithImpl(_$_Income _value, $Res Function(_$_Income) _then) +class __$$_IncomeFormValueCopyWithImpl<$Res> + extends _$IncomeFormValueCopyWithImpl<$Res, _$_IncomeFormValue> + implements _$$_IncomeFormValueCopyWith<$Res> { + __$$_IncomeFormValueCopyWithImpl( + _$_IncomeFormValue _value, $Res Function(_$_IncomeFormValue) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -131,7 +136,7 @@ class __$$_IncomeCopyWithImpl<$Res> Object? memo = null, Object? isValid = null, }) { - return _then(_$_Income( + return _then(_$_IncomeFormValue( id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable @@ -166,8 +171,8 @@ class __$$_IncomeCopyWithImpl<$Res> /// @nodoc -class _$_Income extends _Income { - const _$_Income( +class _$_IncomeFormValue extends _IncomeFormValue { + const _$_IncomeFormValue( {required this.id, required this.title, required this.incomeTypeID, @@ -194,14 +199,14 @@ class _$_Income extends _Income { @override String toString() { - return 'Income(id: $id, title: $title, incomeTypeID: $incomeTypeID, amount: $amount, date: $date, memo: $memo, isValid: $isValid)'; + return 'IncomeFormValue(id: $id, title: $title, incomeTypeID: $incomeTypeID, amount: $amount, date: $date, memo: $memo, isValid: $isValid)'; } @override bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$_Income && + other is _$_IncomeFormValue && (identical(other.id, id) || other.id == id) && (identical(other.title, title) || other.title == title) && (identical(other.incomeTypeID, incomeTypeID) || @@ -219,20 +224,20 @@ class _$_Income extends _Income { @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$_IncomeCopyWith<_$_Income> get copyWith => - __$$_IncomeCopyWithImpl<_$_Income>(this, _$identity); + _$$_IncomeFormValueCopyWith<_$_IncomeFormValue> get copyWith => + __$$_IncomeFormValueCopyWithImpl<_$_IncomeFormValue>(this, _$identity); } -abstract class _Income extends Income { - const factory _Income( +abstract class _IncomeFormValue extends IncomeFormValue { + const factory _IncomeFormValue( {required final String id, required final Title title, required final String incomeTypeID, required final Amount amount, required final String date, required final String memo, - required final bool isValid}) = _$_Income; - const _Income._() : super._(); + required final bool isValid}) = _$_IncomeFormValue; + const _IncomeFormValue._() : super._(); @override String get id; @@ -250,6 +255,6 @@ abstract class _Income extends Income { bool get isValid; @override @JsonKey(ignore: true) - _$$_IncomeCopyWith<_$_Income> get copyWith => + _$$_IncomeFormValueCopyWith<_$_IncomeFormValue> get copyWith => throw _privateConstructorUsedError; } diff --git a/mobile/lib/src/features/transactions/services/income/submit_income_controller.dart b/mobile/lib/src/features/transactions/services/income/submit_income_controller.dart index c74f01b..2fc767f 100644 --- a/mobile/lib/src/features/transactions/services/income/submit_income_controller.dart +++ b/mobile/lib/src/features/transactions/services/income/submit_income_controller.dart @@ -2,11 +2,10 @@ import 'package:openapi/openapi.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:suito/src/features/transactions/repositories/income/register_income_repository.dart'; import 'package:suito/src/features/transactions/repositories/income/update_income_repository.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; import 'package:suito/src/utils/datetime_utils.dart'; -import 'income.dart'; - part 'submit_income_controller.g.dart'; @riverpod @@ -36,7 +35,7 @@ class SubmitIncomeController extends _$SubmitIncomeController { ..amount = inc.amount.value))); } - Future submit(Income income) async { + Future submit(IncomeFormValue income) async { if (!income.isValid) return; state = const AsyncLoading(); diff --git a/mobile/lib/src/features/transactions/services/income/submit_income_controller.g.dart b/mobile/lib/src/features/transactions/services/income/submit_income_controller.g.dart index cefd014..ee8e70a 100644 --- a/mobile/lib/src/features/transactions/services/income/submit_income_controller.g.dart +++ b/mobile/lib/src/features/transactions/services/income/submit_income_controller.g.dart @@ -9,7 +9,7 @@ part of 'submit_income_controller.dart'; // ************************************************************************** String _$submitIncomeControllerHash() => - r'870db52fbb8bc1ce47918db0905b8b8f3e13fc26'; + r'903693707f41e73165779b70d0f07181a78cfac7'; /// See also [SubmitIncomeController]. @ProviderFor(SubmitIncomeController) diff --git a/mobile/lib/src/features/transactions/services/transaction/transaction_detail_navigator.dart b/mobile/lib/src/features/transactions/services/transaction/transaction_detail_navigator.dart new file mode 100644 index 0000000..336082d --- /dev/null +++ b/mobile/lib/src/features/transactions/services/transaction/transaction_detail_navigator.dart @@ -0,0 +1,161 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:openapi/openapi.dart'; +import 'package:suito/src/app.dart'; +import 'package:suito/src/features/schedules/repositories/expense/expense_schedule_detail_repository.dart'; +import 'package:suito/src/features/schedules/repositories/income/income_schedule_detail_repository.dart'; +import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; +import 'package:suito/src/features/transactions/repositories/expense/expense_detail_repository.dart'; +import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; +import 'package:suito/src/features/transactions/repositories/income/income_detail_repository.dart'; +import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_controller.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; +import 'package:suito/src/routing/app_router.dart'; +import 'package:suito/src/utils/datetime_utils.dart'; + +import 'transaction_service.dart'; + +class TransactionDetailNavigator extends StateNotifier> { + TransactionDetailNavigator(this._ref) : super(const AsyncData(null)); + final Ref _ref; + + _fetchCategories() => _ref.read(expenseCategoriesMapFutureProvider.future); + _fetchLocations() => _ref.read(expenseLocationsMapFutureProvider.future); + _fetchIncomeTypes() => _ref.read(incomeTypeMapFutureProvider.future); + + _goExpense() { + final a = _ref.read(goRouterProvider(navigatorKey)); + a.goNamed(AppRoute.expenseDetail.name); + } + + _goExpenseSchedule() => _ref + .read(goRouterProvider(navigatorKey)) + .goNamed(AppRoute.scheduleExpenseDetail.name); + + _goIncome() => _ref + .read(goRouterProvider(navigatorKey)) + .goNamed(AppRoute.incomeDetail.name); + + _goIncomeSchedule() => _ref + .read(goRouterProvider(navigatorKey)) + .goNamed(AppRoute.scheduleIncomeDetail.name); + + _setInitialExpenseValue(ExpenseFormValue value) { + _ref.read(expenseFormInitialValueProvider.notifier).state = value; + } + + _setInitialIncomeValue(IncomeFormValue value) { + _ref.read(incomeFormInitialValueProvider.notifier).state = value; + } + + void goNewExpense() { + final now = _ref.read(currentTimeProvider); + _setInitialExpenseValue(ExpenseFormValue.newExpense(now)); + _goExpense(); + } + + void goNewExpenseSchedule() { + final now = _ref.read(currentTimeProvider); + _setInitialExpenseValue(ExpenseFormValue.newExpense(now)); + _goExpenseSchedule(); + } + + void goNewIncome() { + final now = _ref.read(currentTimeProvider); + _setInitialIncomeValue(IncomeFormValue.newIncome(now)); + _goIncome(); + } + + void goNewIncomeSchedule() { + final now = _ref.read(currentTimeProvider); + _setInitialIncomeValue(IncomeFormValue.newIncome(now)); + _goIncomeSchedule(); + } + + Future _goFetchExpense(String id) async { + state = const AsyncLoading(); + final value = await AsyncValue.guard(() async { + final categoriesMap = await _fetchCategories(); + final locationsMap = await _fetchLocations(); + final expense = await _ref + .read(expenseDetailRepositoryProvider) + .fetchExpenseDetail(id); + final exp = + ExpenseFormValue.fromExpense(expense, categoriesMap, locationsMap); + _setInitialExpenseValue(exp); + }); + + final success = value.hasError == false; + if (!mounted) return; + + state = value; + if (success) _goExpense(); + } + + Future _goFetchIncome(String id) async { + state = const AsyncLoading(); + final value = await AsyncValue.guard(() async { + final incomeTypesMap = await _fetchIncomeTypes(); + final income = + await _ref.read(incomeDetailRepositoryProvider).fetchIncomeDetail(id); + final inc = IncomeFormValue.fromIncome(income, incomeTypesMap); + _setInitialIncomeValue(inc); + }); + + final success = value.hasError == false; + if (!mounted) return; + + state = value; + if (success) _goIncome(); + } + + Future goFetch(Transaction transaction) async { + TransactionType.isExpense(transaction.type) + ? await _goFetchExpense(transaction.id) + : await _goFetchIncome(transaction.id); + } + + Future goFetchExpenseSchedule(String id) async { + state = const AsyncLoading(); + final value = await AsyncValue.guard(() async { + final categoriesMap = await _fetchCategories(); + final locationsMap = await _fetchLocations(); + final expense = await _ref + .read(expenseScheduleDetailRepositoryProvider) + .fetchExpenseScheduleDetail(id); + final exp = + ExpenseFormValue.fromSchedule(expense, categoriesMap, locationsMap); + _setInitialExpenseValue(exp); + }); + + final success = value.hasError == false; + if (!mounted) return; + + state = value; + if (success) _goExpenseSchedule(); + } + + Future goFetchIncomeSchedule(String id) async { + state = const AsyncLoading(); + final value = await AsyncValue.guard(() async { + final incomeTypesMap = await _fetchIncomeTypes(); + final income = await _ref + .read(incomeScheduleDetailRepositoryProvider) + .fetchIncomeScheduleDetail(id); + final inc = IncomeFormValue.fromSchedule(income, incomeTypesMap); + _setInitialIncomeValue(inc); + }); + + final success = value.hasError == false; + if (!mounted) return; + + state = value; + if (success) _goIncomeSchedule(); + } +} + +final transactionDetailNavigatorProvider = StateNotifierProvider.autoDispose< + TransactionDetailNavigator, + AsyncValue>((ref) => TransactionDetailNavigator(ref)); diff --git a/mobile/lib/src/routing/app_router.dart b/mobile/lib/src/routing/app_router.dart index baad151..4eda463 100644 --- a/mobile/lib/src/routing/app_router.dart +++ b/mobile/lib/src/routing/app_router.dart @@ -6,14 +6,15 @@ import 'package:suito/src/features/authentication/presentation/profile/custom_pr import 'package:suito/src/features/authentication/presentation/sign_in/custom_sign_in_screen.dart'; import 'package:suito/src/features/authentication/presentation/sign_out/custom_sign_out_screen.dart'; import 'package:suito/src/features/charts/presentations/charts_screen.dart'; -import 'package:suito/src/features/schedules/presentations/schedule_detail_screen.dart'; +import 'package:suito/src/features/schedules/presentations/expense/expense_schedule_detail_screen.dart'; +import 'package:suito/src/features/schedules/presentations/income/income_schedule_detail_screen.dart'; import 'package:suito/src/features/schedules/presentations/schedule_screen.dart'; import 'package:suito/src/features/transaction_attributes/presentations/settings/transaction_attribute_settings_screen.dart'; import 'package:suito/src/features/transaction_attributes/presentations/transaction_attribute_select_screen.dart'; +import 'package:suito/src/features/transactions/presentations/expense/expense_detail_screen.dart'; import 'package:suito/src/features/transactions/presentations/expense/expense_memo_screen.dart'; -import 'package:suito/src/features/transactions/presentations/transaction_detail_screen.dart'; +import 'package:suito/src/features/transactions/presentations/incomes/income_detail_screen.dart'; import 'package:suito/src/features/transactions/presentations/transactions_screen.dart'; -import 'package:suito/src/features/transactions/services/transaction//transaction_service.dart'; import 'package:suito/src/routing/go_router_refresh_stream.dart'; import 'package:suito/src/utils/app_lifecycle_state_provider.dart'; import 'package:suito/src/utils/version_check.dart'; @@ -25,11 +26,13 @@ enum AppRoute { signOut(path: '/sign-out'), home(path: '/home'), profile(path: '/profile'), - transactionDetail(path: 'transaction-detail'), + expenseDetail(path: 'expense'), + incomeDetail(path: 'income'), attribute(path: 'attribute'), attributeSettings(path: 'attributeSettings'), + scheduleExpenseDetail(path: 'expense'), + scheduleIncomeDetail(path: 'income'), memo(path: 'memo'), - scheduleDetail(path: 'schedule-detail'), scheduleTransactionAttribute(path: 'attribute'), scheduleMemo(path: 'memo'); @@ -100,16 +103,11 @@ final goRouterProvider = Provider.family>( const NoTransitionPage(child: TransactionsScreen()), routes: [ GoRoute( - name: AppRoute.transactionDetail.name, - path: AppRoute.transactionDetail.path, + name: AppRoute.expenseDetail.name, + path: AppRoute.expenseDetail.path, pageBuilder: (context, state) => CustomTransitionPage( key: state.pageKey, - child: TransactionDetailScreen( - id: state.queryParameters['id'], - type: int.tryParse( - state.queryParameters['type'] ?? '') ?? - TransactionType.expense.value, - ), + child: const ExpenseDetailScreen(), transitionsBuilder: _slideTransitionBuilder, ), routes: [ @@ -144,6 +142,15 @@ final goRouterProvider = Provider.family>( ), ), ]), + GoRoute( + name: AppRoute.incomeDetail.name, + path: AppRoute.incomeDetail.path, + pageBuilder: (context, state) => CustomTransitionPage( + key: state.pageKey, + child: const IncomeDetailScreen(), + transitionsBuilder: _slideTransitionBuilder, + ), + ), ]), GoRoute( name: NavigationBarRoute.charts.name, @@ -157,16 +164,11 @@ final goRouterProvider = Provider.family>( const NoTransitionPage(child: ScheduleScreen()), routes: [ GoRoute( - name: AppRoute.scheduleDetail.name, - path: AppRoute.scheduleDetail.path, + name: AppRoute.scheduleExpenseDetail.name, + path: AppRoute.scheduleExpenseDetail.path, pageBuilder: (context, state) => CustomTransitionPage( key: state.pageKey, - child: ScheduleDetailScreen( - id: state.queryParameters['id'], - type: int.tryParse( - state.queryParameters['type'] ?? '') ?? - TransactionType.expense.value, - ), + child: const ExpenseScheduleDetailScreen(), transitionsBuilder: _slideTransitionBuilder, ), routes: [ @@ -189,6 +191,15 @@ final goRouterProvider = Provider.family>( ), ), ]), + GoRoute( + name: AppRoute.scheduleIncomeDetail.name, + path: AppRoute.scheduleIncomeDetail.path, + pageBuilder: (context, state) => CustomTransitionPage( + key: state.pageKey, + child: const IncomeScheduleDetailScreen(), + transitionsBuilder: _slideTransitionBuilder, + ), + ), ]), ], ), diff --git a/mobile/test/src/features/schedules/services/expense/expense_schedule_form_controller_test.dart b/mobile/test/src/features/schedules/services/expense/expense_schedule_form_controller_test.dart deleted file mode 100644 index 71bb3d7..0000000 --- a/mobile/test/src/features/schedules/services/expense/expense_schedule_form_controller_test.dart +++ /dev/null @@ -1,214 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:openapi/openapi.dart'; -import 'package:suito/src/features/schedules/repositories/expense/expense_schedule_detail_repository.dart'; -import 'package:suito/src/features/schedules/services/expense/expense_schedule.dart'; -import 'package:suito/src/features/schedules/services/expense/expense_schedule_form_controller.dart'; -import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; - -import '../../../../mocks.dart'; - -void main() { - late MockExpenseScheduleDetailRepository expenseScheduleDetailRepository; - late MockExpenseCategoriesRepository expenseCategoryRepository; - late MockExpenseLocationsRepository expenseLocationRepository; - setUp(() { - expenseScheduleDetailRepository = MockExpenseScheduleDetailRepository(); - expenseCategoryRepository = MockExpenseCategoriesRepository(); - expenseLocationRepository = MockExpenseLocationsRepository(); - }); - - ProviderContainer makeProviderContainer() { - final container = ProviderContainer( - overrides: [ - expenseScheduleDetailRepositoryProvider - .overrideWithValue(expenseScheduleDetailRepository), - expenseCategoriesRepositoryProvider - .overrideWithValue(expenseCategoryRepository), - expenseLocationsRepositoryProvider - .overrideWithValue(expenseLocationRepository), - ], - ); - addTearDown(container.dispose); - return container; - } - - group('expenseScheduleFormController', () { - test('register new expense schedule - validation valid', () async { - final container = makeProviderContainer(); - final exp = ExpenseSchedule.init(); - final controller = - container.read(expenseScheduleFormControllerProvider(exp).notifier); - const title = 'New title value'; - const amount = 4000; - const memo = 'Memo'; - final category = AttributeEntry('category_id', 'category name'); - final location = AttributeEntry('location_id', 'location name'); - // run - controller.onChangeTitle(title); - controller.onChangeAmount(amount); - controller.onChangeCategory(category); - controller.onChangeLocation(location); - controller.onChangeMemo(memo); - // verify - final state = container.read(expenseScheduleFormControllerProvider(exp)); - expect(state.title.value, title); - expect(state.amount.value, amount); - expect(state.category, category.name); - expect(state.categoryID, category.id); - expect(state.location, location.name); - expect(state.locationID, location.id); - expect(state.memo, memo); - expect(state.isValid, true); - }); - - test('register new expense schedule - validation invalid', () async { - final container = makeProviderContainer(); - final exp = ExpenseSchedule.init(); - final controller = - container.read(expenseScheduleFormControllerProvider(exp).notifier); - const memo = 'Memo'; - final category = AttributeEntry('category_id', 'category name'); - final location = AttributeEntry('location_id', 'location name'); - // run - controller.onChangeCategory(category); - controller.onChangeLocation(location); - controller.onChangeMemo(memo); - // verify - final state = container.read(expenseScheduleFormControllerProvider(exp)); - expect(state.category, category.name); - expect(state.categoryID, category.id); - expect(state.location, location.name); - expect(state.locationID, location.id); - expect(state.memo, memo); - expect(state.isValid, false); - }); - - test('update expense schedule - validation invalid', () async { - final container = makeProviderContainer(); - final categoryMap = { - "category_id": ModelExpenseCategory((b) => b.name = 'A category') - }; - final locationMap = { - "location_id": ModelExpenseLocation((b) => b.name = 'A location') - }; - final res = ExpenseScheduleDetailRes( - (r) => r.expenseSchedule.replace(ModelExpenseSchedule((b) => b - ..id = 'expense_id' - ..title = 'registered title' - ..memo = '' - ..timezone = 'Asia/Tokyo' - ..amount = 400 - ..expenseCategoryID = 'category_id' - ..expenseLocationID = 'location_id'))); - final exp = ExpenseSchedule.fromModel(res, categoryMap, locationMap); - final controller = - container.read(expenseScheduleFormControllerProvider(exp).notifier); - const memo = 'Memo'; - final category = AttributeEntry('category_id', 'category name'); - final location = AttributeEntry('location_id', 'location name'); - // run - controller.onChangeTitle(''); - controller.onChangeCategory(category); - controller.onChangeLocation(location); - controller.onChangeMemo(memo); - // verify - final state = container.read(expenseScheduleFormControllerProvider(exp)); - expect(state.category, category.name); - expect(state.categoryID, category.id); - expect(state.location, location.name); - expect(state.locationID, location.id); - expect(state.memo, memo); - expect(state.isValid, false); - }); - - test('update expense schedule - validation valid', () async { - final container = makeProviderContainer(); - final categoryMap = { - "category_id": ModelExpenseCategory((b) => b.name = 'A category') - }; - final locationMap = { - "location_id": ModelExpenseLocation((b) => b.name = 'A location') - }; - final res = ExpenseScheduleDetailRes( - (r) => r.expenseSchedule.replace(ModelExpenseSchedule((b) => b - ..id = 'expense_id' - ..title = 'registered title' - ..memo = '' - ..amount = 400 - ..timezone = 'Asia/Tokyo' - ..expenseCategoryID = 'category_id' - ..expenseLocationID = 'location_id'))); - final exp = ExpenseSchedule.fromModel(res, categoryMap, locationMap); - final controller = - container.read(expenseScheduleFormControllerProvider(exp).notifier); - const memo = 'Memo'; - // run - controller.onChangeCategory(null); - controller.onChangeLocation(null); - controller.onChangeMemo(memo); - // verify - final state = container.read(expenseScheduleFormControllerProvider(exp)); - expect(state.category, ''); - expect(state.categoryID, ''); - expect(state.location, ''); - expect(state.locationID, ''); - expect(state.memo, memo); - expect(state.isValid, true); - }); - }); - - group('expenseScheduleFuture', () { - test('do not fetch expense schedule if id is null', () async { - // setup - final container = makeProviderContainer(); - // run - final schedule = - await container.read(expenseScheduleFutureProvider().future); - // check - expect(schedule.id, ''); - }); - - test('do fetch expense schedule if id is not null', () async { - // setup - final container = makeProviderContainer(); - final category = ModelExpenseCategory((e) => e - ..id = 'expense_category_id' - ..name = 'Test Category'); - final location = ModelExpenseLocation((e) => e - ..id = 'expense_location_id' - ..name = 'Test Location'); - final modelSchedule = ModelExpenseSchedule((e) => e - ..id = 'test_expense_id' - ..title = 'expense title' - ..amount = 400 - ..memo = '' - ..expenseCategoryID = category.id - ..expenseLocationID = location.id - ..timezone = 'Asia/Tokyo'); - when(() => expenseScheduleDetailRepository - .fetchExpenseScheduleDetail(modelSchedule.id)) - .thenAnswer((invocation) => Future.value(ExpenseScheduleDetailRes( - (b) => b.expenseSchedule.replace(modelSchedule)))); - when(() => expenseCategoryRepository.fetchExpenseCategoriesList()) - .thenAnswer((invocation) => Future.value([category])); - when(() => expenseLocationRepository.fetchExpenseLocationsList()) - .thenAnswer((invocation) => Future.value([location])); - // run - final expenseSchedule = await container - .read(expenseScheduleFutureProvider(id: modelSchedule.id).future); - // check - expect(expenseSchedule.id, modelSchedule.id); - expect(expenseSchedule.title.value, modelSchedule.title); - expect(expenseSchedule.amount.value, modelSchedule.amount); - expect(expenseSchedule.memo, modelSchedule.memo); - expect(expenseSchedule.category, category.name); - expect(expenseSchedule.categoryID, category.id); - expect(expenseSchedule.location, location.name); - expect(expenseSchedule.locationID, location.id); - }); - }); -} diff --git a/mobile/test/src/features/schedules/services/expense/submit_expense_schedule_controller_test.dart b/mobile/test/src/features/schedules/services/expense/submit_expense_schedule_controller_test.dart index 4611732..e6af7c3 100644 --- a/mobile/test/src/features/schedules/services/expense/submit_expense_schedule_controller_test.dart +++ b/mobile/test/src/features/schedules/services/expense/submit_expense_schedule_controller_test.dart @@ -4,8 +4,8 @@ import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; import 'package:suito/src/features/schedules/repositories/expense/register_expense_schedule_repository.dart'; import 'package:suito/src/features/schedules/repositories/expense/update_expense_schedule_repository.dart'; -import 'package:suito/src/features/schedules/services/expense/expense_schedule.dart'; import 'package:suito/src/features/schedules/services/expense/submit_expense_schedule_controller.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart'; import 'package:suito/src/utils/timezone_provider.dart'; @@ -38,8 +38,9 @@ void main() { test('register new expenseSchedule, success', () async { // setup final registerRepo = MockRegisterExpenseScheduleRepository(); - const expenseSchedule = ExpenseSchedule( + const expenseSchedule = ExpenseFormValue( id: '', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), categoryID: 'category_id', @@ -94,8 +95,9 @@ void main() { test('register new expenseSchedule, failure', () async { // setup final registerRepo = MockRegisterExpenseScheduleRepository(); - const expenseSchedule = ExpenseSchedule( + const expenseSchedule = ExpenseFormValue( id: '', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), categoryID: 'category_id', @@ -143,8 +145,9 @@ void main() { test('update expenseSchedule, success', () async { // setup final updateRepo = MockUpdateExpenseScheduleRepository(); - const expenseSchedule = ExpenseSchedule( + const expenseSchedule = ExpenseFormValue( id: 'expenseSchedule_id', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), categoryID: 'category_id', @@ -199,8 +202,9 @@ void main() { test('update expenseSchedule, failure', () async { // setup final updateRepo = MockUpdateExpenseScheduleRepository(); - const expenseSchedule = ExpenseSchedule( + const expenseSchedule = ExpenseFormValue( id: 'expenseSchedule_id', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), categoryID: 'category_id', @@ -250,8 +254,9 @@ void main() { // setup final registerRepo = MockRegisterExpenseScheduleRepository(); final updateRepo = MockUpdateExpenseScheduleRepository(); - const expenseSchedule = ExpenseSchedule( + const expenseSchedule = ExpenseFormValue( id: '', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), categoryID: 'category_id', diff --git a/mobile/test/src/features/schedules/services/income/income_schedule_form_controller_test.dart b/mobile/test/src/features/schedules/services/income/income_schedule_form_controller_test.dart deleted file mode 100644 index 278ec94..0000000 --- a/mobile/test/src/features/schedules/services/income/income_schedule_form_controller_test.dart +++ /dev/null @@ -1,161 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:openapi/openapi.dart'; -import 'package:suito/src/features/schedules/repositories/income/income_schedule_detail_repository.dart'; -import 'package:suito/src/features/schedules/services/income/income_schedule.dart'; -import 'package:suito/src/features/schedules/services/income/income_schedule_form_controller.dart'; -import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; -import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; - -import '../../../../mocks.dart'; - -void main() { - late MockIncomeScheduleDetailRepository incomeScheduleDetailRepository; - late MockIncomeTypesRepository incomeTypesRepository; - - setUp(() { - incomeScheduleDetailRepository = MockIncomeScheduleDetailRepository(); - incomeTypesRepository = MockIncomeTypesRepository(); - }); - ProviderContainer makeProviderContainer() { - final container = ProviderContainer( - overrides: [ - incomeScheduleDetailRepositoryProvider - .overrideWithValue(incomeScheduleDetailRepository), - incomeTypesRepositoryProvider.overrideWithValue(incomeTypesRepository), - ], - ); - addTearDown(container.dispose); - return container; - } - - group('incomeScheduleFormController', () { - test('register new income schedule - validation valid', () async { - final container = makeProviderContainer(); - final exp = IncomeSchedule.init(); - final controller = - container.read(incomeScheduleFormControllerProvider(exp).notifier); - final incomeType = AttributeEntry('income_type_id', 'Income Title'); - const amount = 4000; - const memo = 'Memo'; - // run - controller.onChangeTitle(incomeType); - controller.onChangeAmount(amount); - controller.onChangeMemo(memo); - // verify - final state = container.read(incomeScheduleFormControllerProvider(exp)); - expect(state.title.value, incomeType.name); - expect(state.amount.value, amount); - expect(state.incomeTypeID, incomeType.id); - expect(state.memo, memo); - expect(state.isValid, true); - }); - - test('register new income schedule - validation invalid', () async { - final container = makeProviderContainer(); - final exp = IncomeSchedule.init(); - final controller = - container.read(incomeScheduleFormControllerProvider(exp).notifier); - const memo = 'Memo'; - // run - controller.onChangeMemo(memo); - // verify - final state = container.read(incomeScheduleFormControllerProvider(exp)); - expect(state.memo, memo); - expect(state.isValid, false); - }); - - test('update income schedule - validation invalid', () async { - final container = makeProviderContainer(); - final incomeTypeMap = { - "income_type_id": ModelIncomeType((b) => b.name = 'A Income Type') - }; - final res = IncomeScheduleDetailRes( - (r) => r.incomeSchedule.replace(ModelIncomeSchedule((b) => b - ..id = 'income_id' - ..timezone = 'Asia/Tokyo' - ..memo = '' - ..amount = 400 - ..incomeTypeId = 'income_type_id'))); - final exp = IncomeSchedule.fromModel(res, incomeTypeMap); - final controller = - container.read(incomeScheduleFormControllerProvider(exp).notifier); - const memo = 'Memo'; - // run - controller.onChangeTitle(null); - controller.onChangeMemo(memo); - // verify - final state = container.read(incomeScheduleFormControllerProvider(exp)); - expect(state.title.value, ''); - expect(state.incomeTypeID, ''); - expect(state.memo, memo); - expect(state.isValid, false); - }); - - test('update income schedule - validation valid', () async { - final container = makeProviderContainer(); - final incomeTypeMap = { - "income_type_id": ModelIncomeType((b) => b.name = 'A Income Type') - }; - final res = IncomeScheduleDetailRes( - (r) => r.incomeSchedule.replace(ModelIncomeSchedule((b) => b - ..id = 'income_id' - ..timezone = 'Asia/Tokyo' - ..memo = '' - ..amount = 400 - ..incomeTypeId = 'income_type_id'))); - final exp = IncomeSchedule.fromModel(res, incomeTypeMap); - final controller = - container.read(incomeScheduleFormControllerProvider(exp).notifier); - const memo = 'Memo'; - // run - controller.onChangeMemo(memo); - // verify - final state = container.read(incomeScheduleFormControllerProvider(exp)); - expect(state.memo, memo); - expect(state.isValid, true); - }); - }); - - group('incomeScheduleFuture', () { - test('do not fetch income schedule if id is null', () async { - // setup - final container = makeProviderContainer(); - // run - final income = - await container.read(incomeScheduleFutureProvider().future); - // check - expect(income.id, ''); - }); - - test('do fetch income schedule if id is not null', () async { - // setup - final container = makeProviderContainer(); - final incomeType = ModelIncomeType((e) => e - ..id = 'income_location_id' - ..name = 'Test Location'); - final modelSchedule = ModelIncomeSchedule((e) => e - ..id = 'test_income_id' - ..incomeTypeId = incomeType.id - ..amount = 400 - ..memo = '' - ..timezone = 'Asia/Tokyo'); - when(() => incomeScheduleDetailRepository - .fetchIncomeScheduleDetail(modelSchedule.id)) - .thenAnswer((invocation) => Future.value(IncomeScheduleDetailRes( - (b) => b.incomeSchedule.replace(modelSchedule)))); - when(() => incomeTypesRepository.fetchIncomeTypesList()) - .thenAnswer((invocation) => Future.value([incomeType])); - // run - final income = await container - .read(incomeScheduleFutureProvider(id: modelSchedule.id).future); - // check - expect(income.id, modelSchedule.id); - expect(income.title.value, incomeType.name); - expect(income.amount.value, modelSchedule.amount); - expect(income.memo, modelSchedule.memo); - expect(income.incomeTypeID, incomeType.id); - }); - }); -} diff --git a/mobile/test/src/features/schedules/services/income/submit_income_schedule_controller_test.dart b/mobile/test/src/features/schedules/services/income/submit_income_schedule_controller_test.dart index 6c0ccef..4482c8c 100644 --- a/mobile/test/src/features/schedules/services/income/submit_income_schedule_controller_test.dart +++ b/mobile/test/src/features/schedules/services/income/submit_income_schedule_controller_test.dart @@ -4,8 +4,8 @@ import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; import 'package:suito/src/features/schedules/repositories/income/register_income_schedule_repository.dart'; import 'package:suito/src/features/schedules/repositories/income/update_income_schedule_repository.dart'; -import 'package:suito/src/features/schedules/services/income/income_schedule.dart'; import 'package:suito/src/features/schedules/services/income/submit_income_schedule_controller.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart'; import 'package:suito/src/utils/timezone_provider.dart'; @@ -38,8 +38,9 @@ void main() { test('register new incomeSchedule, success', () async { // setup final registerRepo = MockRegisterIncomeScheduleRepository(); - const incomeSchedule = IncomeSchedule( + const incomeSchedule = IncomeFormValue( id: '', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), incomeTypeID: 'income_type_id', @@ -87,8 +88,9 @@ void main() { test('register new incomeSchedule, failure', () async { // setup final registerRepo = MockRegisterIncomeScheduleRepository(); - const incomeSchedule = IncomeSchedule( + const incomeSchedule = IncomeFormValue( id: '', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), incomeTypeID: 'income_type_id', @@ -131,8 +133,9 @@ void main() { test('update incomeSchedule, success', () async { // setup final updateRepo = MockUpdateIncomeScheduleRepository(); - const incomeSchedule = IncomeSchedule( + const incomeSchedule = IncomeFormValue( id: 'incomeSchedule_id', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), incomeTypeID: 'income_type_id', @@ -180,8 +183,9 @@ void main() { test('update incomeSchedule, failure', () async { // setup final updateRepo = MockUpdateIncomeScheduleRepository(); - const incomeSchedule = IncomeSchedule( + const incomeSchedule = IncomeFormValue( id: 'incomeSchedule_id', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), incomeTypeID: 'income_type_id', @@ -226,8 +230,9 @@ void main() { // setup final registerRepo = MockRegisterIncomeScheduleRepository(); final updateRepo = MockUpdateIncomeScheduleRepository(); - const incomeSchedule = IncomeSchedule( + const incomeSchedule = IncomeFormValue( id: '', + date: '', title: Title.dirty('A title'), amount: Amount.dirty(400), incomeTypeID: 'income_type_id', diff --git a/mobile/test/src/features/schedules/services/schedule_service_test.dart b/mobile/test/src/features/schedules/services/schedule_service_test.dart index 5803ba8..bf66db0 100644 --- a/mobile/test/src/features/schedules/services/schedule_service_test.dart +++ b/mobile/test/src/features/schedules/services/schedule_service_test.dart @@ -34,8 +34,8 @@ void main() { final list = await container.read(fetchSchedulesProvider.future); // check expect(list.length, 2); - expect(list[0].items.length, 0); - expect(list[1].items.length, 0); + expect(list[0].length, 0); + expect(list[1].length, 0); }); test('fetchSchedules, success', () async { @@ -61,8 +61,8 @@ void main() { final list = await container.read(fetchSchedulesProvider.future); // check expect(list.length, 2); - expect(list[0].items.length, 1); - expect(list[1].items.length, 1); + expect(list[0].length, 1); + expect(list[1].length, 1); }); }); } diff --git a/mobile/test/src/features/transactions/presentations/transaction/expense/expense_detail_view_test.dart b/mobile/test/src/features/transactions/presentations/transaction/expense/expense_detail_view_test.dart index ebdc9d0..066b7d0 100644 --- a/mobile/test/src/features/transactions/presentations/transaction/expense/expense_detail_view_test.dart +++ b/mobile/test/src/features/transactions/presentations/transaction/expense/expense_detail_view_test.dart @@ -1,9 +1,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; -import '../../../../../mocks.dart'; import '../../../transactions_robot.dart'; void main() { @@ -15,7 +14,8 @@ void main() { await loadAppFonts(); final r = TransactionsRobot(tester); final now = DateTime(2023, 5, 1); - await r.pumpExpenseDetailScreen(id: null, now: now); + final expense = ExpenseFormValue.newExpense(now); + await r.pumpExpenseDetailScreen(expense); await screenMatchesGolden(tester, 'new_expense_detail'); }); }); @@ -24,17 +24,15 @@ void main() { (tester) async { await loadAppFonts(); final r = TransactionsRobot(tester); - final now = DateTime(2023, 5, 1); - final repository = MockExpenseDetailRepository(); - final categoryRepo = MockExpenseCategoriesRepository(); - final locationRepo = MockExpenseLocationsRepository(); const id = 'expense_id'; final category = ModelExpenseCategory((e) => e ..id = 'expense_category_id' ..name = 'Test Category'); + final categoryMap = {category.id: category}; final location = ModelExpenseLocation((e) => e ..id = 'expense_location_id' ..name = 'Test Location'); + final locationMap = {location.id: location}; final res = ExpenseDetailRes((r) => r.expense.replace(ModelExpense((b) => b ..id = id ..title = 'registered title' @@ -43,18 +41,9 @@ void main() { ..amount = 400 ..expenseCategoryID = category.id ..expenseLocationID = location.id))); - when(() => repository.fetchExpenseDetail(id)) - .thenAnswer((_) => Future.value(res)); - when(() => categoryRepo.fetchExpenseCategoriesList()) - .thenAnswer((invocation) => Future.value([category])); - when(() => locationRepo.fetchExpenseLocationsList()) - .thenAnswer((invocation) => Future.value([location])); - await r.pumpExpenseDetailScreen( - expenseRepo: repository, - categoryRepo: categoryRepo, - locationRepo: locationRepo, - id: id, - now: now); + + final expense = ExpenseFormValue.fromExpense(res, categoryMap, locationMap); + await r.pumpExpenseDetailScreen(expense); await screenMatchesGolden(tester, 'update_expense_detail'); }); } diff --git a/mobile/test/src/features/transactions/presentations/transaction/income/income_detail_view_test.dart b/mobile/test/src/features/transactions/presentations/transaction/income/income_detail_view_test.dart index f7062d6..8d1dda9 100644 --- a/mobile/test/src/features/transactions/presentations/transaction/income/income_detail_view_test.dart +++ b/mobile/test/src/features/transactions/presentations/transaction/income/income_detail_view_test.dart @@ -2,6 +2,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; import '../../../../../mocks.dart'; import '../../../transactions_robot.dart'; @@ -15,7 +16,8 @@ void main() { await loadAppFonts(); final r = TransactionsRobot(tester); final now = DateTime(2023, 5, 1); - await r.pumpIncomeDetailScreen(id: null, now: now); + final income = IncomeFormValue.newIncome(now); + await r.pumpIncomeDetailScreen(income); await screenMatchesGolden(tester, 'new_income_detail'); }); }); @@ -24,28 +26,27 @@ void main() { (tester) async { await loadAppFonts(); final r = TransactionsRobot(tester); - final now = DateTime(2023, 5, 1); final repository = MockIncomeDetailRepository(); final incomeTypeRepo = MockIncomeTypesRepository(); const id = 'income_id'; final incomeType = ModelIncomeType((e) => e ..id = 'income_type_id' ..name = 'Test Income Type'); + final incomeTypeMap = {incomeType.id: incomeType}; final res = IncomeDetailRes((r) => r.income.replace(ModelIncome((b) => b ..id = id ..localDate = '2023-05-03' ..memo = 'some memo' ..amount = 400 ..incomeTypeId = incomeType.id))); + final income = IncomeFormValue.fromIncome(res, incomeTypeMap); when(() => repository.fetchIncomeDetail(id)) .thenAnswer((invocation) => Future.value(res)); when(() => incomeTypeRepo.fetchIncomeTypesList()) .thenAnswer((invocation) => Future.value([incomeType])); - await r.pumpIncomeDetailScreen( - incomeRepo: repository, - incomeTypeRepo: incomeTypeRepo, - id: id, - now: now); + // run + await r.pumpIncomeDetailScreen(income); + // check await screenMatchesGolden(tester, 'update_income_detail'); }); } diff --git a/mobile/test/src/features/transactions/presentations/transaction/transactions_screen_test.dart b/mobile/test/src/features/transactions/presentations/transaction/transactions_screen_test.dart index 92c6f49..4b84741 100644 --- a/mobile/test/src/features/transactions/presentations/transaction/transactions_screen_test.dart +++ b/mobile/test/src/features/transactions/presentations/transaction/transactions_screen_test.dart @@ -62,7 +62,6 @@ void main() { final now = DateTime(2023, 7, 1); await r.pumpTransactionsScreen( now: now, transactionsRepo: repository, monthsRepo: monthsRepository); - await tester.pumpAndSettle(); await r.tapToggleButton(); r.expectDropdownMenuItemFound('2023-04'); @@ -83,7 +82,6 @@ void main() { final now = DateTime(2023, 7, 1); await r.pumpTransactionsScreen( now: now, transactionsRepo: repository, monthsRepo: monthsRepository); - await tester.pumpAndSettle(); r.expectEmptyLabelFound(); }); } diff --git a/mobile/test/src/features/transactions/services/expense/expense_form_controller_test.dart b/mobile/test/src/features/transactions/services/expense/expense_form_controller_test.dart index 05e5163..3cc2c14 100644 --- a/mobile/test/src/features/transactions/services/expense/expense_form_controller_test.dart +++ b/mobile/test/src/features/transactions/services/expense/expense_form_controller_test.dart @@ -1,13 +1,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; import 'package:suito/src/features/transactions/repositories/expense/expense_detail_repository.dart'; import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; -import 'package:suito/src/features/transactions/services/expense/expense.dart'; import 'package:suito/src/features/transactions/services/expense/expense_form_controller.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; import '../../../../mocks.dart'; @@ -39,9 +38,7 @@ void main() { group('expenseFormController', () { test('register new expense - validation valid', () async { final container = makeProviderContainer(); - final exp = Expense.init(DateTime.now()); - final controller = - container.read(expenseFormControllerProvider(exp).notifier); + final controller = container.read(expenseFormControllerProvider.notifier); const title = 'New title value'; const amount = 4000; const memo = 'Memo'; @@ -56,7 +53,7 @@ void main() { controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(expenseFormControllerProvider(exp)); + final state = container.read(expenseFormControllerProvider); expect(state.title.value, title); expect(state.amount.value, amount); expect(state.category, category.name); @@ -70,9 +67,7 @@ void main() { test('register new expense - validation invalid', () async { final container = makeProviderContainer(); - final exp = Expense.init(DateTime.now()); - final controller = - container.read(expenseFormControllerProvider(exp).notifier); + final controller = container.read(expenseFormControllerProvider.notifier); const memo = 'Memo'; const date = '2023-05-03'; final category = AttributeEntry('category_id', 'category name'); @@ -83,7 +78,7 @@ void main() { controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(expenseFormControllerProvider(exp)); + final state = container.read(expenseFormControllerProvider); expect(state.category, category.name); expect(state.categoryID, category.id); expect(state.location, location.name); @@ -110,9 +105,9 @@ void main() { ..amount = 400 ..expenseCategoryID = 'category_id' ..expenseLocationID = 'location_id'))); - final exp = Expense.fromModel(res, categoryMap, locationMap); - final controller = - container.read(expenseFormControllerProvider(exp).notifier); + final exp = ExpenseFormValue.fromExpense(res, categoryMap, locationMap); + container.read(expenseFormInitialValueProvider.notifier).state = exp; + final controller = container.read(expenseFormControllerProvider.notifier); const memo = 'Memo'; const date = '2023-05-03'; final category = AttributeEntry('category_id', 'category name'); @@ -124,7 +119,7 @@ void main() { controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(expenseFormControllerProvider(exp)); + final state = container.read(expenseFormControllerProvider); expect(state.category, category.name); expect(state.categoryID, category.id); expect(state.location, location.name); @@ -151,9 +146,9 @@ void main() { ..amount = 400 ..expenseCategoryID = 'category_id' ..expenseLocationID = 'location_id'))); - final exp = Expense.fromModel(res, categoryMap, locationMap); - final controller = - container.read(expenseFormControllerProvider(exp).notifier); + final exp = ExpenseFormValue.fromExpense(res, categoryMap, locationMap); + container.read(expenseFormInitialValueProvider.notifier).state = exp; + final controller = container.read(expenseFormControllerProvider.notifier); const memo = 'Memo'; const date = '2023-05-03'; // run @@ -162,7 +157,7 @@ void main() { controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(expenseFormControllerProvider(exp)); + final state = container.read(expenseFormControllerProvider); expect(state.category, ''); expect(state.categoryID, ''); expect(state.location, ''); @@ -173,52 +168,53 @@ void main() { }); }); - group('expenseFuture', () { - test('do not fetch expense if id is null', () async { - // setup - final container = makeProviderContainer(); - // run - final expense = await container.read(expenseFutureProvider().future); - // check - expect(expense.id, ''); - }); + // group('expenseFuture', () { + // test('do not fetch expense if id is null', () async { + // // setup + // final container = makeProviderContainer(); + // // run + // final expense = await container.read(expenseFutureProvider().future); + // // check + // expect(expense.id, ''); + // }); - test('do fetch expense if id is not null', () async { - // setup - final container = makeProviderContainer(); - final category = ModelExpenseCategory((e) => e - ..id = 'expense_category_id' - ..name = 'Test Category'); - final location = ModelExpenseLocation((e) => e - ..id = 'expense_location_id' - ..name = 'Test Location'); - final modelExpense = ModelExpense((e) => e - ..id = 'test_expense_id' - ..title = 'expense title' - ..amount = 400 - ..memo = '' - ..expenseCategoryID = category.id - ..expenseLocationID = location.id - ..localDate = '2023-05-03'); - when(() => expenseDetailRepository.fetchExpenseDetail(modelExpense.id)) - .thenAnswer((invocation) => Future.value( - ExpenseDetailRes((b) => b.expense.replace(modelExpense)))); - when(() => expenseCategoryRepository.fetchExpenseCategoriesList()) - .thenAnswer((invocation) => Future.value([category])); - when(() => expenseLocationRepository.fetchExpenseLocationsList()) - .thenAnswer((invocation) => Future.value([location])); - // run - final expense = await container - .read(expenseFutureProvider(id: modelExpense.id).future); - // check - expect(expense.id, modelExpense.id); - expect(expense.title.value, modelExpense.title); - expect(expense.amount.value, modelExpense.amount); - expect(expense.memo, modelExpense.memo); - expect(expense.category, category.name); - expect(expense.categoryID, category.id); - expect(expense.location, location.name); - expect(expense.locationID, location.id); - }); - }); + // test('do fetch expense if id is not null', () async { + // // setup + // final container = makeProviderContainer(); + // final category = ModelExpenseCategory((e) => e + // ..id = 'expense_category_id' + // ..name = 'Test Category'); + // final location = ModelExpenseLocation((e) => e + // ..id = 'expense_location_id' + // ..name = 'Test Location'); + // final modelExpense = ModelExpense((e) => e + // ..id = 'test_expense_id' + // ..title = 'expense title' + // ..amount = 400 + // ..memo = '' + // ..expenseCategoryID = category.id + // ..expenseLocationID = location.id + // ..localDate = '2023-05-03'); + // when(() => expenseDetailRepository.fetchExpenseDetail(modelExpense.id)) + // .thenAnswer((invocation) => Future.value( + // ExpenseDetailRes((b) => b.expense.replace(modelExpense)))); + // when(() => expenseCategoryRepository.fetchExpenseCategoriesList()) + // .thenAnswer((invocation) => Future.value([category])); + // when(() => expenseLocationRepository.fetchExpenseLocationsList()) + // .thenAnswer((invocation) => Future.value([location])); + // // run + // final expense = await container + // .read(expenseFutureProvider(id: modelExpense.id).future); + // // check + // expect(expense.id, modelExpense.id); + // expect(expense.title.value, modelExpense.title); + // expect(expense.amount.value, modelExpense.amount); + // expect(expense.memo, modelExpense.memo); + // expect(expense.category, category.name); + // expect(expense.categoryID, category.id); + // expect(expense.location, location.name); + // expect(expense.locationID, location.id); + // }); + // } +// ); } diff --git a/mobile/test/src/features/transactions/services/expense/submit_expense_controller_test.dart b/mobile/test/src/features/transactions/services/expense/submit_expense_controller_test.dart index a08eb07..aacfb1b 100644 --- a/mobile/test/src/features/transactions/services/expense/submit_expense_controller_test.dart +++ b/mobile/test/src/features/transactions/services/expense/submit_expense_controller_test.dart @@ -4,7 +4,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; import 'package:suito/src/features/transactions/repositories/expense/register_expense_repository.dart'; import 'package:suito/src/features/transactions/repositories/expense/update_expense_repository.dart'; -import 'package:suito/src/features/transactions/services/expense/expense.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; import 'package:suito/src/features/transactions/services/expense/submit_expense_controller.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart'; @@ -35,7 +35,7 @@ void main() { test('register new expense, success', () async { // setup final registerRepo = MockRegisterExpenseRepository(); - const expense = Expense( + const expense = ExpenseFormValue( id: '', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -92,7 +92,7 @@ void main() { test('register new expense, failure', () async { // setup final registerRepo = MockRegisterExpenseRepository(); - const expense = Expense( + const expense = ExpenseFormValue( id: '', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -142,7 +142,7 @@ void main() { test('update expense, success', () async { // setup final updateRepo = MockUpdateExpenseRepository(); - const expense = Expense( + const expense = ExpenseFormValue( id: 'expense_id', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -199,7 +199,7 @@ void main() { test('update expense, failure', () async { // setup final updateRepo = MockUpdateExpenseRepository(); - const expense = Expense( + const expense = ExpenseFormValue( id: 'expense_id', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -250,7 +250,7 @@ void main() { // setup final registerRepo = MockRegisterExpenseRepository(); final updateRepo = MockUpdateExpenseRepository(); - const expense = Expense( + const expense = ExpenseFormValue( id: '', title: Title.dirty('A title'), amount: Amount.dirty(400), diff --git a/mobile/test/src/features/transactions/services/income/income_form_controller_test.dart b/mobile/test/src/features/transactions/services/income/income_form_controller_test.dart index 5546c5a..1d90f3c 100644 --- a/mobile/test/src/features/transactions/services/income/income_form_controller_test.dart +++ b/mobile/test/src/features/transactions/services/income/income_form_controller_test.dart @@ -1,12 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; import 'package:suito/src/features/transaction_attributes/services/transaction_attribute_entry.dart'; import 'package:suito/src/features/transactions/repositories/income/income_detail_repository.dart'; import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; -import 'package:suito/src/features/transactions/services/income/income.dart'; import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; import '../../../../mocks.dart'; @@ -34,9 +33,7 @@ void main() { group('incomeFormController', () { test('register new income - validation valid', () async { final container = makeProviderContainer(); - final exp = Income.init(DateTime.now()); - final controller = - container.read(incomeFormControllerProvider(exp).notifier); + final controller = container.read(incomeFormControllerProvider.notifier); final incomeType = AttributeEntry('income_type_id', 'Income Title'); const amount = 4000; const memo = 'Memo'; @@ -47,7 +44,7 @@ void main() { controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(incomeFormControllerProvider(exp)); + final state = container.read(incomeFormControllerProvider); expect(state.title.value, incomeType.name); expect(state.amount.value, amount); expect(state.incomeTypeID, incomeType.id); @@ -58,16 +55,14 @@ void main() { test('register new income - validation invalid', () async { final container = makeProviderContainer(); - final exp = Income.init(DateTime.now()); - final controller = - container.read(incomeFormControllerProvider(exp).notifier); + final controller = container.read(incomeFormControllerProvider.notifier); const memo = 'Memo'; const date = '2023-05-03'; // run controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(incomeFormControllerProvider(exp)); + final state = container.read(incomeFormControllerProvider); expect(state.memo, memo); expect(state.date, date); expect(state.isValid, false); @@ -84,9 +79,9 @@ void main() { ..memo = '' ..amount = 400 ..incomeTypeId = 'income_type_id'))); - final exp = Income.fromModel(res, incomeTypeMap); - final controller = - container.read(incomeFormControllerProvider(exp).notifier); + final inc = IncomeFormValue.fromIncome(res, incomeTypeMap); + container.read(incomeFormInitialValueProvider.notifier).state = inc; + final controller = container.read(incomeFormControllerProvider.notifier); const memo = 'Memo'; const date = '2023-05-03'; // run @@ -94,7 +89,7 @@ void main() { controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(incomeFormControllerProvider(exp)); + final state = container.read(incomeFormControllerProvider); expect(state.title.value, ''); expect(state.incomeTypeID, ''); expect(state.memo, memo); @@ -113,58 +108,19 @@ void main() { ..memo = '' ..amount = 400 ..incomeTypeId = 'income_type_id'))); - final exp = Income.fromModel(res, incomeTypeMap); - final controller = - container.read(incomeFormControllerProvider(exp).notifier); + final inc = IncomeFormValue.fromIncome(res, incomeTypeMap); + container.read(incomeFormInitialValueProvider.notifier).state = inc; + final controller = container.read(incomeFormControllerProvider.notifier); const memo = 'Memo'; const date = '2023-05-03'; // run controller.onChangeMemo(memo); controller.onChangeDate(date); // verify - final state = container.read(incomeFormControllerProvider(exp)); + final state = container.read(incomeFormControllerProvider); expect(state.memo, memo); expect(state.date, date); expect(state.isValid, true); }); }); - - group('incomeFuture', () { - test('do not fetch income if id is null', () async { - // setup - final container = makeProviderContainer(); - // run - final income = await container.read(incomeFutureProvider().future); - // check - expect(income.id, ''); - }); - - test('do fetch income if id is not null', () async { - // setup - final container = makeProviderContainer(); - final incomeType = ModelIncomeType((e) => e - ..id = 'income_location_id' - ..name = 'Test Location'); - final modelIncome = ModelIncome((e) => e - ..id = 'test_income_id' - ..incomeTypeId = incomeType.id - ..amount = 400 - ..memo = '' - ..localDate = '2023-05-03'); - when(() => incomeDetailRepository.fetchIncomeDetail(modelIncome.id)) - .thenAnswer((invocation) => Future.value( - IncomeDetailRes((b) => b.income.replace(modelIncome)))); - when(() => incomeTypesRepository.fetchIncomeTypesList()) - .thenAnswer((invocation) => Future.value([incomeType])); - // run - final income = - await container.read(incomeFutureProvider(id: modelIncome.id).future); - // check - expect(income.id, modelIncome.id); - expect(income.title.value, incomeType.name); - expect(income.amount.value, modelIncome.amount); - expect(income.memo, modelIncome.memo); - expect(income.incomeTypeID, incomeType.id); - }); - }); } diff --git a/mobile/test/src/features/transactions/services/income/submit_income_controller_test.dart b/mobile/test/src/features/transactions/services/income/submit_income_controller_test.dart index e9e2653..d0037a1 100644 --- a/mobile/test/src/features/transactions/services/income/submit_income_controller_test.dart +++ b/mobile/test/src/features/transactions/services/income/submit_income_controller_test.dart @@ -4,7 +4,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:openapi/openapi.dart'; import 'package:suito/src/features/transactions/repositories/income/register_income_repository.dart'; import 'package:suito/src/features/transactions/repositories/income/update_income_repository.dart'; -import 'package:suito/src/features/transactions/services/income/income.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; import 'package:suito/src/features/transactions/services/income/submit_income_controller.dart'; import 'package:suito/src/formz/amount.dart'; import 'package:suito/src/formz/title.dart'; @@ -35,7 +35,7 @@ void main() { test('register new income, success', () async { // setup final registerRepo = MockRegisterIncomeRepository(); - const income = Income( + const income = IncomeFormValue( id: '', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -84,7 +84,7 @@ void main() { test('register new income, failure', () async { // setup final registerRepo = MockRegisterIncomeRepository(); - const income = Income( + const income = IncomeFormValue( id: '', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -128,7 +128,7 @@ void main() { test('update income, success', () async { // setup final updateRepo = MockUpdateIncomeRepository(); - const income = Income( + const income = IncomeFormValue( id: 'income_id', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -177,7 +177,7 @@ void main() { test('update income, failure', () async { // setup final updateRepo = MockUpdateIncomeRepository(); - const income = Income( + const income = IncomeFormValue( id: 'income_id', title: Title.dirty('A title'), amount: Amount.dirty(400), @@ -222,7 +222,7 @@ void main() { // setup final registerRepo = MockRegisterIncomeRepository(); final updateRepo = MockUpdateIncomeRepository(); - const income = Income( + const income = IncomeFormValue( id: '', title: Title.dirty('A title'), amount: Amount.dirty(400), diff --git a/mobile/test/src/features/transactions/services/transaction/transaction_detail_navigator_test.dart b/mobile/test/src/features/transactions/services/transaction/transaction_detail_navigator_test.dart new file mode 100644 index 0000000..27bc404 --- /dev/null +++ b/mobile/test/src/features/transactions/services/transaction/transaction_detail_navigator_test.dart @@ -0,0 +1,296 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:openapi/openapi.dart'; +import 'package:suito/src/features/schedules/repositories/expense/expense_schedule_detail_repository.dart'; +import 'package:suito/src/features/schedules/repositories/income/income_schedule_detail_repository.dart'; +import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; +import 'package:suito/src/features/transactions/repositories/expense/expense_detail_repository.dart'; +import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; +import 'package:suito/src/features/transactions/repositories/income/income_detail_repository.dart'; +import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_controller.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_detail_navigator.dart'; +import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; +import 'package:suito/src/routing/app_router.dart'; + +import '../../../../mocks.dart'; + +void main() { + late MockGoRouter mockGoRouter; + late MockExpenseDetailRepository mockExpenseDetailRepository; + late MockExpenseScheduleDetailRepository mockExpenseScheduleDetailRepository; + late MockIncomeDetailRepository mockIncomeDetailRepository; + late MockIncomeScheduleDetailRepository mockIncomeScheduleDetailRepository; + late MockExpenseCategoriesRepository mockExpenseCategoriesRepository; + late MockExpenseLocationsRepository mockExpenseLocationsRepository; + late MockIncomeTypesRepository mockIncomeTypesRepository; + setUp(() { + mockGoRouter = MockGoRouter(); + mockExpenseDetailRepository = MockExpenseDetailRepository(); + mockExpenseScheduleDetailRepository = MockExpenseScheduleDetailRepository(); + mockIncomeDetailRepository = MockIncomeDetailRepository(); + mockIncomeScheduleDetailRepository = MockIncomeScheduleDetailRepository(); + mockExpenseCategoriesRepository = MockExpenseCategoriesRepository(); + mockExpenseLocationsRepository = MockExpenseLocationsRepository(); + mockIncomeTypesRepository = MockIncomeTypesRepository(); + }); + + ProviderContainer makeProviderContainer() { + final container = ProviderContainer( + overrides: [ + goRouterProvider.overrideWith((ref, arg) => mockGoRouter), + expenseDetailRepositoryProvider + .overrideWithValue(mockExpenseDetailRepository), + expenseCategoriesRepositoryProvider + .overrideWithValue(mockExpenseCategoriesRepository), + expenseLocationsRepositoryProvider + .overrideWithValue(mockExpenseLocationsRepository), + incomeTypesRepositoryProvider + .overrideWithValue(mockIncomeTypesRepository), + incomeDetailRepositoryProvider + .overrideWithValue(mockIncomeDetailRepository), + expenseScheduleDetailRepositoryProvider + .overrideWithValue(mockExpenseScheduleDetailRepository), + incomeScheduleDetailRepositoryProvider + .overrideWithValue(mockIncomeScheduleDetailRepository), + ], + ); + addTearDown(container.dispose); + return container; + } + + setUpAll(() { + registerFallbackValue(const AsyncLoading()); + }); + + group('TransactionDetailNavigator', () { + test('goNewExpense', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + // run + navigator.goNewExpense(); + // check + verify(() => mockGoRouter.goNamed(AppRoute.expenseDetail.name)).called(1); + expect(container.read(expenseFormInitialValueProvider).isValid, false); + }); + + test('goNewExpenseSchedule', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + // run + navigator.goNewExpenseSchedule(); + // check + verify(() => mockGoRouter.goNamed(AppRoute.scheduleExpenseDetail.name)) + .called(1); + expect(container.read(expenseFormInitialValueProvider).isValid, false); + }); + + test('goNewIncome', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + // run + navigator.goNewIncome(); + // check + + verify(() => mockGoRouter.goNamed(AppRoute.incomeDetail.name)).called(1); + expect(container.read(incomeFormInitialValueProvider).isValid, false); + }); + + test('goNewIncomeSchedule', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + // run + navigator.goNewIncomeSchedule(); + // check + + verify(() => mockGoRouter.goNamed(AppRoute.scheduleIncomeDetail.name)) + .called(1); + expect(container.read(incomeFormInitialValueProvider).isValid, false); + }); + + test('goFetchExpense', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + final tr = Transaction( + (t) => t + ..id = 'transaction_id' + ..title = 'Transaction Title' + ..localDate = '2023-05-01' + ..amount = 400 + ..type = TransactionType.expense.value, + ); + final category = ModelExpenseCategory((e) => e + ..id = 'expense_category_id' + ..name = 'Test Category'); + final location = ModelExpenseLocation((e) => e + ..id = 'expense_location_id' + ..name = 'Test Location'); + final res = + ExpenseDetailRes((r) => r.expense.replace(ModelExpense((b) => b + ..id = tr.id + ..title = 'registered title' + ..localDate = '2023-05-03' + ..memo = 'Some memo' + ..amount = 400 + ..expenseCategoryID = category.id + ..expenseLocationID = location.id))); + when(() => mockExpenseCategoriesRepository.fetchExpenseCategoriesList()) + .thenAnswer((_) => Future.value([category])); + when(() => mockExpenseLocationsRepository.fetchExpenseLocationsList()) + .thenAnswer((_) => Future.value([location])); + when(() => mockExpenseDetailRepository.fetchExpenseDetail(tr.id)) + .thenAnswer((_) => Future.value(res)); + // run + await navigator.goFetch(tr); + // check + verify(() => mockExpenseCategoriesRepository.fetchExpenseCategoriesList()) + .called(1); + verify(() => mockExpenseLocationsRepository.fetchExpenseLocationsList()) + .called(1); + verify(() => mockExpenseDetailRepository.fetchExpenseDetail(tr.id)) + .called(1); + verify(() => mockGoRouter.goNamed(AppRoute.expenseDetail.name)).called(1); + final formValue = container.read(expenseFormInitialValueProvider); + expect(formValue.isValid, true); + expect(formValue.id, tr.id); + expect(formValue.title.value, res.expense.title); + expect(formValue.amount.value, res.expense.amount); + expect(formValue.category, category.name); + expect(formValue.location, location.name); + }); + + test('goFetchIncome', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + final tr = Transaction( + (t) => t + ..id = 'transaction_id' + ..title = 'Transaction Title' + ..localDate = '2023-05-01' + ..amount = 400 + ..type = TransactionType.income.value, + ); + final incomeType = ModelIncomeType((e) => e + ..id = 'income_type_id' + ..name = 'Test Income Type'); + final res = IncomeDetailRes((r) => r.income.replace(ModelIncome((b) => b + ..id = tr.id + ..localDate = '2023-05-03' + ..memo = '' + ..amount = 400 + ..incomeTypeId = incomeType.id))); + when(() => mockIncomeTypesRepository.fetchIncomeTypesList()) + .thenAnswer((_) => Future.value([incomeType])); + when(() => mockIncomeDetailRepository.fetchIncomeDetail(tr.id)) + .thenAnswer((_) => Future.value(res)); + // run + await navigator.goFetch(tr); + // check + verify(() => mockIncomeTypesRepository.fetchIncomeTypesList()).called(1); + verify(() => mockIncomeDetailRepository.fetchIncomeDetail(tr.id)) + .called(1); + verify(() => mockGoRouter.goNamed(AppRoute.incomeDetail.name)).called(1); + final formValue = container.read(incomeFormInitialValueProvider); + expect(formValue.isValid, true); + expect(formValue.id, tr.id); + expect(formValue.title.value, incomeType.name); + expect(formValue.amount.value, res.income.amount); + }); + + test('goFetchExpenseSchedule', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + final category = ModelExpenseCategory((e) => e + ..id = 'expense_category_id' + ..name = 'Test Category'); + final location = ModelExpenseLocation((e) => e + ..id = 'expense_location_id' + ..name = 'Test Location'); + const scheduleID = 'schedule_id'; + final res = ExpenseScheduleDetailRes( + (r) => r.expenseSchedule.replace(ModelExpenseSchedule((b) => b + ..id = scheduleID + ..title = 'registered title' + ..timezone = 'Asia/Tokyo' + ..memo = 'Some memo' + ..amount = 400 + ..expenseCategoryID = category.id + ..expenseLocationID = location.id))); + when(() => mockExpenseCategoriesRepository.fetchExpenseCategoriesList()) + .thenAnswer((_) => Future.value([category])); + when(() => mockExpenseLocationsRepository.fetchExpenseLocationsList()) + .thenAnswer((_) => Future.value([location])); + when(() => mockExpenseScheduleDetailRepository.fetchExpenseScheduleDetail( + scheduleID)).thenAnswer((_) => Future.value(res)); + // run + await navigator.goFetchExpenseSchedule(scheduleID); + // check + verify(() => mockExpenseCategoriesRepository.fetchExpenseCategoriesList()) + .called(1); + verify(() => mockExpenseLocationsRepository.fetchExpenseLocationsList()) + .called(1); + verify(() => mockExpenseScheduleDetailRepository + .fetchExpenseScheduleDetail(scheduleID)).called(1); + verify(() => mockGoRouter.goNamed(AppRoute.scheduleExpenseDetail.name)) + .called(1); + final formValue = container.read(expenseFormInitialValueProvider); + expect(formValue.isValid, true); + expect(formValue.id, scheduleID); + expect(formValue.title.value, res.expenseSchedule.title); + expect(formValue.amount.value, res.expenseSchedule.amount); + expect(formValue.category, category.name); + expect(formValue.location, location.name); + }); + + test('goFetchIncomeSchedule', () async { + // setup + final container = makeProviderContainer(); + final navigator = + container.read(transactionDetailNavigatorProvider.notifier); + final incomeType = ModelIncomeType((e) => e + ..id = 'income_type_id' + ..name = 'Test Income Type'); + const scheduleID = 'schedule_id'; + final res = IncomeScheduleDetailRes( + (r) => r.incomeSchedule.replace(ModelIncomeSchedule((b) => b + ..id = scheduleID + ..timezone = 'Asia/Tokyo' + ..memo = '' + ..amount = 400 + ..incomeTypeId = incomeType.id))); + when(() => mockIncomeTypesRepository.fetchIncomeTypesList()) + .thenAnswer((_) => Future.value([incomeType])); + when(() => mockIncomeScheduleDetailRepository.fetchIncomeScheduleDetail( + scheduleID)).thenAnswer((_) => Future.value(res)); + // run + await navigator.goFetchIncomeSchedule(scheduleID); + // check + verify(() => mockIncomeTypesRepository.fetchIncomeTypesList()).called(1); + verify(() => mockIncomeScheduleDetailRepository + .fetchIncomeScheduleDetail(scheduleID)).called(1); + verify(() => mockGoRouter.goNamed(AppRoute.scheduleIncomeDetail.name)) + .called(1); + final formValue = container.read(incomeFormInitialValueProvider); + expect(formValue.isValid, true); + expect(formValue.id, scheduleID); + expect(formValue.title.value, incomeType.name); + expect(formValue.amount.value, res.incomeSchedule.amount); + }); + }); +} diff --git a/mobile/test/src/features/transactions/transactions_robot.dart b/mobile/test/src/features/transactions/transactions_robot.dart index e1f403f..7944e3b 100644 --- a/mobile/test/src/features/transactions/transactions_robot.dart +++ b/mobile/test/src/features/transactions/transactions_robot.dart @@ -3,17 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:suito/src/app_theme.dart'; +import 'package:suito/src/common_widgets/transition_text_field.dart'; +import 'package:suito/src/features/transactions/presentations/expense/expense_detail_screen.dart'; +import 'package:suito/src/features/transactions/presentations/incomes/income_detail_screen.dart'; import 'package:suito/src/features/transactions/presentations/transaction/transactions_list_empty_label.dart'; -import 'package:suito/src/features/transactions/presentations/transaction_detail_screen.dart'; import 'package:suito/src/features/transactions/presentations/transactions_screen.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_categories_repository.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_detail_repository.dart'; -import 'package:suito/src/features/transactions/repositories/expense/expense_locations_repository.dart'; -import 'package:suito/src/features/transactions/repositories/income/income_detail_repository.dart'; -import 'package:suito/src/features/transactions/repositories/income/income_types_repository.dart'; import 'package:suito/src/features/transactions/repositories/transaction/transaction_months_repository.dart'; import 'package:suito/src/features/transactions/repositories/transaction/transactions_repository.dart'; -import 'package:suito/src/features/transactions/services/transaction/transaction_service.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_controller.dart'; +import 'package:suito/src/features/transactions/services/expense/expense_form_value.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_controller.dart'; +import 'package:suito/src/features/transactions/services/income/income_form_value.dart'; import 'package:suito/src/utils/datetime_utils.dart'; class TransactionsRobot { @@ -37,63 +37,33 @@ class TransactionsRobot { home: const TransactionsScreen(), theme: AppTheme().create()), ), ); + await tester.pumpAndSettle(); } - Future pumpExpenseDetailScreen( - {ExpenseDetailRepository? expenseRepo, - ExpenseCategoriesRepository? categoryRepo, - ExpenseLocationsRepository? locationRepo, - required String? id, - required DateTime now}) async { + Future pumpExpenseDetailScreen(ExpenseFormValue value) async { await tester.pumpWidget( ProviderScope( overrides: [ - currentTimeProvider.overrideWithValue(now), - if (expenseRepo != null) - expenseDetailRepositoryProvider.overrideWithValue(expenseRepo), - if (categoryRepo != null) - expenseCategoriesRepositoryProvider.overrideWithValue( - categoryRepo, - ), - if (locationRepo != null) - expenseLocationsRepositoryProvider.overrideWithValue( - locationRepo, - ) + expenseFormInitialValueProvider.overrideWith((ref) => value), ], child: MaterialApp( - home: TransactionDetailScreen( - id: id, - type: TransactionType.expense.value, - ), - theme: AppTheme().create()), + home: const ExpenseDetailScreen(), theme: AppTheme().create()), ), ); + await tester.pumpAndSettle(); } - Future pumpIncomeDetailScreen( - {IncomeDetailRepository? incomeRepo, - IncomeTypesRepository? incomeTypeRepo, - required String? id, - required DateTime now}) async { + Future pumpIncomeDetailScreen(IncomeFormValue value) async { await tester.pumpWidget( ProviderScope( overrides: [ - currentTimeProvider.overrideWithValue(now), - if (incomeRepo != null) - incomeDetailRepositoryProvider.overrideWithValue(incomeRepo), - if (incomeTypeRepo != null) - incomeTypesRepositoryProvider.overrideWithValue( - incomeTypeRepo, - ) + incomeFormInitialValueProvider.overrideWith((ref) => value), ], child: MaterialApp( - home: TransactionDetailScreen( - id: id, - type: TransactionType.income.value, - ), - theme: AppTheme().create()), + home: const IncomeDetailScreen(), theme: AppTheme().create()), ), ); + await tester.pumpAndSettle(); } void expectEmptyLabelFound() { @@ -122,4 +92,10 @@ class TransactionsRobot { await tester.tap(item); await tester.pumpAndSettle(); } + + Future tapMemoTextField() async { + final finder = find.byType(TransitionTextField); + await tester.tap(finder); + await tester.pumpAndSettle(); + } } diff --git a/mobile/test/src/mocks.dart b/mobile/test/src/mocks.dart index 4bd3728..3d882c0 100644 --- a/mobile/test/src/mocks.dart +++ b/mobile/test/src/mocks.dart @@ -1,3 +1,4 @@ +import 'package:go_router/go_router.dart'; import 'package:mocktail/mocktail.dart'; import 'package:suito/src/features/schedules/repositories/expense/delete_expense_schedule_repository.dart'; import 'package:suito/src/features/schedules/repositories/expense/expense_schedule_detail_repository.dart'; @@ -21,6 +22,8 @@ import 'package:suito/src/features/transactions/repositories/income/update_incom import 'package:suito/src/features/transactions/repositories/transaction/transaction_months_repository.dart'; import 'package:suito/src/features/transactions/repositories/transaction/transactions_repository.dart'; +class MockGoRouter extends Mock implements GoRouter {} + class MockExpenseDetailRepository extends Mock implements ExpenseDetailRepository {}