├── devtools_options.yaml ├── linux ├── .gitignore ├── main.cc ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugin_registrant.cc │ ├── generated_plugins.cmake │ └── CMakeLists.txt └── my_application.h ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── GoogleService-Info.plist │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── firebase_app_id_file.json ├── RunnerTests │ └── RunnerTests.swift └── .gitignore ├── lib ├── core │ ├── enum │ │ ├── type_show.dart │ │ ├── enum.dart │ │ └── transaction.dart │ ├── shared │ │ ├── shared.dart │ │ ├── custom_icon_bottom.dart │ │ ├── custom_material_button.dart │ │ ├── custom_app_bar.dart │ │ ├── custom_tabbar.dart │ │ ├── custom_text_form_field.dart │ │ └── custom_item_button.dart │ ├── utils │ │ └── models │ │ │ └── app_result.dart │ ├── styles │ │ ├── app_font_size.dart │ │ ├── app_colors.dart │ │ ├── app_theme.dart │ │ └── app_text_style.dart │ ├── service │ │ └── network_info.dart │ ├── router │ │ ├── router.dart │ │ └── app_route.dart │ ├── models │ │ ├── transaction_hive_model.dart │ │ ├── transaction_model.g.dart │ │ ├── totals_transaction_model.dart │ │ ├── transaction_hive_model.g.dart │ │ └── transaction_model.dart │ ├── helper │ │ └── helper.dart │ ├── config │ │ └── overlay_style_config.dart │ └── firebase_options.dart ├── features │ ├── transaction │ │ ├── view │ │ │ ├── widgets │ │ │ │ └── widgets.dart │ │ │ └── transaction_view.dart │ │ └── data │ │ │ └── repository │ │ │ ├── transaction_base_repository.dart │ │ │ └── transaction_repository.dart │ ├── profile │ │ ├── view │ │ │ ├── widgets │ │ │ │ ├── widgets.dart │ │ │ │ ├── profile_form.dart │ │ │ │ └── profile_image.dart │ │ │ └── profile_view.dart │ │ └── data │ │ │ └── profile_repository │ │ │ ├── profile_base_repository.dart │ │ │ └── profile_repository.dart │ ├── settings │ │ ├── view │ │ │ ├── widgets │ │ │ │ ├── widgets.dart │ │ │ │ ├── dark_mode_switch.dart │ │ │ │ ├── version_sheet.dart │ │ │ │ └── item_settings.dart │ │ │ └── settings_view.dart │ │ └── data │ │ │ ├── themes_repository │ │ │ ├── themes_base_repository.dart │ │ │ └── themes_repository.dart │ │ │ └── auth_repository │ │ │ ├── auth_base_repository.dart │ │ │ └── auth_repository.dart │ ├── blocs │ │ ├── themes_bloc │ │ │ ├── themes_state.dart │ │ │ └── themes_cubit.dart │ │ ├── profile_bloc │ │ │ ├── profile_state.dart │ │ │ └── profile_cubit.dart │ │ ├── auth_bloc │ │ │ ├── auth_state.dart │ │ │ └── auth_cubit.dart │ │ ├── state_bloc │ │ │ ├── state_state.dart │ │ │ └── state_cubit.dart │ │ ├── main_bloc │ │ │ ├── main_state.dart │ │ │ └── main_cubit.dart │ │ └── transaction_bloc │ │ │ └── transaction_state.dart │ └── home │ │ ├── data │ │ ├── state_repository │ │ │ └── state_base_repository.dart │ │ └── main_repository │ │ │ └── main_base_repository.dart │ │ └── view │ │ ├── widgets │ │ ├── widgets.dart │ │ ├── bloc_listener_auth.dart │ │ ├── app_floating_button.dart │ │ ├── header_app_bar_profile.dart │ │ ├── header_app_bar_filter.dart │ │ ├── app_bottom_nav_bar.dart │ │ ├── profile_icon.dart │ │ ├── summary_card.dart │ │ └── filter_form.dart │ │ ├── state_view.dart │ │ ├── home_view.dart │ │ ├── all_view_transaction.dart │ │ └── main_view.dart ├── main.dart ├── bloc_observer.dart └── app │ └── daily_tracker_app.dart ├── packages ├── auth_user │ ├── LICENSE │ ├── CHANGELOG.md │ ├── lib │ │ ├── src │ │ │ ├── core │ │ │ │ └── exception │ │ │ │ │ ├── exception.dart │ │ │ │ │ └── firebase_exception.dart │ │ │ ├── auth_user_base.dart │ │ │ └── auth_user.dart │ │ └── auth_user.dart │ ├── analysis_options.yaml │ ├── .metadata │ ├── pubspec.yaml │ ├── .gitignore │ └── README.md ├── user_service │ ├── LICENSE │ ├── lib │ │ ├── src │ │ │ ├── core │ │ │ │ └── models │ │ │ │ │ ├── models.dart │ │ │ │ │ └── user_model.dart │ │ │ ├── user_service_base.dart │ │ │ └── user_service.dart │ │ └── user_service.dart │ ├── CHANGELOG.md │ ├── analysis_options.yaml │ ├── .metadata │ ├── pubspec.yaml │ ├── .gitignore │ └── README.md ├── db_hive_client │ ├── LICENSE │ ├── CHANGELOG.md │ ├── lib │ │ ├── db_hive_client.dart │ │ └── src │ │ │ ├── db_hive_client_base.dart │ │ │ └── db_hive_client.dart │ ├── analysis_options.yaml │ ├── .metadata │ ├── pubspec.yaml │ ├── .gitignore │ └── README.md └── db_firestore_client │ ├── LICENSE │ ├── CHANGELOG.md │ ├── lib │ ├── src │ │ ├── common │ │ │ └── core_typedefs.dart │ │ └── db_firestore_client_base.dart │ └── db_firestore_client.dart │ ├── analysis_options.yaml │ ├── .metadata │ ├── pubspec.yaml │ ├── .gitignore │ └── README.md ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── app_icon.png │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── app_icon.png │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── app_icon.png │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── app_icon.png │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── app_icon.png │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── daily_expense_tracker_app │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle └── settings.gradle ├── assets ├── icons │ └── app_icon.png ├── images │ ├── guest.png │ └── profile.png └── fonts │ ├── Inter-Bold.ttf │ ├── Inter-Light.ttf │ ├── Inter-Medium.ttf │ ├── Inter-Regular.ttf │ └── Inter-SemiBold.ttf ├── .gitignore ├── analysis_options.yaml ├── .metadata ├── pubspec.yaml ├── README.md └── .vscode └── launch.json /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | extensions: 2 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /lib/core/enum/type_show.dart: -------------------------------------------------------------------------------- 1 | enum TypeShow { all, limit } 2 | -------------------------------------------------------------------------------- /packages/auth_user/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /packages/user_service/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /packages/db_hive_client/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /packages/db_firestore_client/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/features/transaction/view/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | export 'transaction_form.dart'; 2 | -------------------------------------------------------------------------------- /packages/auth_user/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /packages/user_service/lib/src/core/models/models.dart: -------------------------------------------------------------------------------- 1 | export './user_model.dart'; 2 | -------------------------------------------------------------------------------- /packages/db_hive_client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /packages/user_service/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /packages/auth_user/lib/src/core/exception/exception.dart: -------------------------------------------------------------------------------- 1 | export './firebase_exception.dart'; 2 | -------------------------------------------------------------------------------- /packages/db_firestore_client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /lib/core/enum/enum.dart: -------------------------------------------------------------------------------- 1 | export './type_show.dart'; 2 | export 'categorys.dart'; 3 | export 'transaction.dart'; 4 | -------------------------------------------------------------------------------- /lib/features/profile/view/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | export './profile_form.dart'; 2 | export './profile_image.dart'; 3 | -------------------------------------------------------------------------------- /assets/icons/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/icons/app_icon.png -------------------------------------------------------------------------------- /assets/images/guest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/images/guest.png -------------------------------------------------------------------------------- /assets/images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/images/profile.png -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/fonts/Inter-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/assets/fonts/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /packages/db_hive_client/lib/db_hive_client.dart: -------------------------------------------------------------------------------- 1 | library db_hive_client; 2 | 3 | export 'src/db_hive_client_base.dart'; 4 | export 'src/db_hive_client.dart'; 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-hdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-mdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-xhdpi/app_icon.png -------------------------------------------------------------------------------- /packages/db_firestore_client/lib/src/common/core_typedefs.dart: -------------------------------------------------------------------------------- 1 | typedef ObjectMapper = T Function( 2 | Map? data, 3 | String? documentId, 4 | ); 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-xxhdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/features/settings/view/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | export './dark_mode_switch.dart'; 2 | export './item_settings.dart'; 3 | export './version_sheet.dart'; 4 | export 'auth_profile.dart'; 5 | -------------------------------------------------------------------------------- /packages/auth_user/lib/auth_user.dart: -------------------------------------------------------------------------------- 1 | library auth_user; 2 | 3 | export 'src/auth_user_base.dart'; 4 | export './src/core/exception/exception.dart'; 5 | export 'src/auth_user.dart'; 6 | -------------------------------------------------------------------------------- /packages/db_firestore_client/lib/db_firestore_client.dart: -------------------------------------------------------------------------------- 1 | library db_firestore_client; 2 | 3 | export 'src/db_firestore_client.dart'; 4 | export 'src/db_firestore_client_base.dart'; 5 | -------------------------------------------------------------------------------- /packages/user_service/lib/user_service.dart: -------------------------------------------------------------------------------- 1 | library user_service; 2 | 3 | export '/src/core/models/models.dart'; 4 | export 'src/user_service_base.dart'; 5 | export 'src/user_service.dart'; 6 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /packages/auth_user/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /packages/user_service/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haithemnini/daily_expense_tracker_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /packages/db_hive_client/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /packages/db_firestore_client/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/daily_expense_tracker_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.daily_expense_tracker_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /lib/features/settings/data/themes_repository/themes_base_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class ThemesBaseRepository { 4 | Future getTheme(); 5 | Future setTheme(ThemeMode theme); 6 | } 7 | -------------------------------------------------------------------------------- /lib/core/shared/shared.dart: -------------------------------------------------------------------------------- 1 | export './custom_app_bar.dart'; 2 | export './custom_icon_bottom.dart'; 3 | export './custom_tabbar.dart'; 4 | export './custom_text_form_field.dart'; 5 | export './tansaction_list.dart'; 6 | export 'custom_item_button.dart'; 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip 6 | -------------------------------------------------------------------------------- /lib/features/blocs/themes_bloc/themes_state.dart: -------------------------------------------------------------------------------- 1 | part of 'themes_cubit.dart'; 2 | 3 | @freezed 4 | class ThemesState with _$ThemesState { 5 | const factory ThemesState.initial() = _Initial; 6 | const factory ThemesState.loadedThemeMode( 7 | ThemeMode themeMode, 8 | ) = LoadedThemeMode; 9 | } 10 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:800188863112:ios:0923cbc2198d87dc5f053d", 5 | "FIREBASE_PROJECT_ID": "daily-expense-tracker-123e1", 6 | "GCM_SENDER_ID": "800188863112" 7 | } -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/core/utils/models/app_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'app_result.freezed.dart'; 4 | 5 | @Freezed() 6 | abstract class AppResult with _$AppResult { 7 | const factory AppResult.success(T data) = Success; 8 | const factory AppResult.failure(String message) = Failure; 9 | } 10 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/profile/data/profile_repository/profile_base_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:user_service/user_service.dart'; 2 | 3 | abstract class ProfileBaseRepository { 4 | Future updateProfile({ 5 | required User user, 6 | }); 7 | 8 | Future updateUserPicture({ 9 | required User user, 10 | required String imagePath, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/user_service/lib/src/user_service_base.dart: -------------------------------------------------------------------------------- 1 | import 'core/models/models.dart'; 2 | 3 | abstract class UserServiceBase { 4 | Stream getUser(String userId); 5 | Future createUser(User user); 6 | Future updateUser(User user); 7 | Future updateUserPicture(User user, String imagePath); 8 | Future deleteUser(User user); 9 | } 10 | -------------------------------------------------------------------------------- /packages/auth_user/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/db_hive_client/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/user_service/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/db_firestore_client/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /lib/features/blocs/profile_bloc/profile_state.dart: -------------------------------------------------------------------------------- 1 | part of 'profile_cubit.dart'; 2 | 3 | @freezed 4 | class ProfileState with _$ProfileState { 5 | const factory ProfileState.initial() = _Initial; 6 | const factory ProfileState.loading() = Loading; 7 | const factory ProfileState.success(String message) = Success; 8 | const factory ProfileState.error(String message) = Error; 9 | } 10 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/features/home/data/state_repository/state_base_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../../../core/enum/enum.dart'; 2 | import '../../../../core/utils/models/app_result.dart'; 3 | 4 | abstract class StateBaseRepository { 5 | Future>> getState({ 6 | required Category category, 7 | required DateTime startDate, 8 | required DateTime endDate, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /lib/features/home/data/main_repository/main_base_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../../../core/models/totals_transaction_model.dart'; 2 | import '../../../../core/models/transaction_model.dart'; 3 | import '../../../../core/utils/models/app_result.dart'; 4 | 5 | abstract class MainBaseRepository { 6 | Future>> getAll({required int? limit}); 7 | Future> getTotals(); 8 | } 9 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/features/transaction/data/repository/transaction_base_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../../../core/models/transaction_model.dart'; 2 | import '../../../../core/utils/models/app_result.dart'; 3 | 4 | abstract class TransactionBaseRepository { 5 | Future> addTransaction(Transaction transaction); 6 | Future> updateTransaction(Transaction transaction); 7 | Future> deleteTransaction(String transactionId); 8 | } 9 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | export './app_bottom_nav_bar.dart'; 2 | export './bloc_listener_auth.dart'; 3 | export './expense_card.dart'; 4 | export './filter_form.dart'; 5 | export './header_app_bar_filter.dart'; 6 | export './header_app_bar_profile.dart'; 7 | export './profile_icon.dart'; 8 | export './summary_card.dart'; 9 | export './transaction_filter.dart'; 10 | export 'app_floating_button.dart'; 11 | export 'state_bar_chart.dart'; 12 | -------------------------------------------------------------------------------- /packages/auth_user/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: auth_user 2 | description: "A new Flutter package project." 3 | version: 0.0.1 4 | homepage: 5 | 6 | environment: 7 | sdk: '>=3.2.3 <4.0.0' 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | firebase_auth: ^4.10.0 14 | google_sign_in: ^6.2.1 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | flutter_lints: ^2.0.0 20 | 21 | 22 | flutter: 23 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /lib/core/styles/app_font_size.dart: -------------------------------------------------------------------------------- 1 | class AppFontSize { 2 | static const double small = 12; 3 | static const double medium = 14; 4 | static const double large = 16; 5 | static const double xLarge = 18; 6 | static const double xxLarge = 20; 7 | static const double xxxLarge = 22; 8 | static const double xxxxLarge = 24; 9 | static const double xxxxxLarge = 26; 10 | static const double xxxxxxLarge = 28; 11 | static const double xxxxxxxLarge = 30; 12 | static const double xxxxxxxxLarge = 32; 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import 'app/daily_tracker_app.dart'; 5 | import 'bloc_observer.dart'; 6 | import 'core/app_injections.dart'; 7 | import 'core/router/app_route.dart'; 8 | 9 | void main() async { 10 | WidgetsFlutterBinding.ensureInitialized(); 11 | await Future.wait([initAppConfig()]); 12 | Bloc.observer = AppBlocObserver(); 13 | 14 | runApp( 15 | DailyTrackerApp( 16 | appRoute: AppRouter(), 17 | ), 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 14 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /packages/db_hive_client/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: db_hive_client 2 | description: "a hive db plugin for flutter" 3 | version: 0.0.1 4 | homepage: null 5 | 6 | environment: 7 | sdk: '>=3.2.3 <4.0.0' 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | hive: ^2.2.3 14 | hive_flutter: ^1.1.0 15 | path_provider: ^2.1.2 16 | 17 | dev_dependencies: 18 | flutter_lints: ^2.0.0 19 | flutter_test: 20 | sdk: flutter 21 | hive_generator: ^2.0.1 22 | build_runner: ^2.4.8 23 | 24 | flutter: null 25 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/features/blocs/auth_bloc/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_cubit.dart'; 2 | 3 | enum AuthStatus { authenticated, unauthenticated, unknown } 4 | 5 | @freezed 6 | class AuthState with _$AuthState { 7 | const factory AuthState.initial() = _Initial; 8 | const factory AuthState.authChanged({ 9 | required AuthStatus authStatus, 10 | User? user, 11 | }) = AuthChanged; 12 | const factory AuthState.loading() = Loading; 13 | const factory AuthState.loaded(User user) = Loaded; 14 | const factory AuthState.error(String message) = Error; 15 | } 16 | -------------------------------------------------------------------------------- /packages/db_firestore_client/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: db_firestore_client 2 | description: "a package for firebase database operations." 3 | version: 0.0.1 4 | homepage: null 5 | 6 | environment: 7 | sdk: '>=3.2.3 <4.0.0' 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | # firebase_auth: ^4.16.0 12 | # firebase_core: ^2.24.2 13 | # firebase_storage: ^11.6.0 14 | cloud_firestore: ^4.14.0 15 | flutter: 16 | sdk: flutter 17 | 18 | dev_dependencies: 19 | flutter_lints: ^2.0.0 20 | flutter_test: 21 | sdk: flutter 22 | 23 | flutter: null 24 | -------------------------------------------------------------------------------- /lib/features/blocs/state_bloc/state_state.dart: -------------------------------------------------------------------------------- 1 | part of 'state_cubit.dart'; 2 | 3 | @freezed 4 | class StateState with _$StateState { 5 | const factory StateState.initial() = _Initial; 6 | const factory StateState.loading() = Loading; 7 | 8 | const factory StateState.loaded( 9 | List transactions, 10 | Map chartData, 11 | ) = Loaded; 12 | 13 | const factory StateState.dateChanged( 14 | DateTime startDate, 15 | DateTime endDate, 16 | ) = DateChanged; 17 | const factory StateState.error(String message) = Error; 18 | } 19 | -------------------------------------------------------------------------------- /packages/user_service/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: user_service 2 | description: "A new Flutter package project." 3 | version: 0.0.1 4 | homepage: 5 | 6 | environment: 7 | sdk: '>=3.2.3 <4.0.0' 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | build_runner: ^2.4.7 14 | freezed: ^2.4.6 15 | freezed_annotation: ^2.4.1 16 | cloud_firestore: ^4.9.2 17 | firebase_auth: ^4.17.4 18 | firebase_storage: ^11.6.6 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | flutter_lints: ^2.0.0 24 | 25 | flutter: 26 | 27 | -------------------------------------------------------------------------------- /packages/auth_user/lib/src/auth_user_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart' as auth; 2 | 3 | abstract class AuthUserBase { 4 | /// The [user] stream is used to get the user. 5 | Stream get userStream; 6 | 7 | /// The [currentUser] method is used to get the current user. 8 | /// It returns the current user. 9 | auth.User? get currentUser; 10 | 11 | /// The [signInWithGoogle] method is used to sign in with google. 12 | Future signInWithGoogle(); 13 | 14 | /// The [signOut] method is used to sign out. 15 | Future signOut(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/blocs/main_bloc/main_state.dart: -------------------------------------------------------------------------------- 1 | part of 'main_cubit.dart'; 2 | 3 | @freezed 4 | class MainState with _$MainState { 5 | const factory MainState.initial() = _Initial; 6 | const factory MainState.loading() = Loading; 7 | const factory MainState.loadedAll(List transactions) = Loaded; 8 | 9 | const factory MainState.loadedLimit( 10 | List transactions, 11 | ) = LoadedLimit; 12 | 13 | const factory MainState.loadedTotals( 14 | TotalsTransaction totalsTransactions, 15 | ) = LoadedTotals; 16 | const factory MainState.error(String message) = Error; 17 | } 18 | -------------------------------------------------------------------------------- /lib/features/blocs/transaction_bloc/transaction_state.dart: -------------------------------------------------------------------------------- 1 | part of 'transaction_cubit.dart'; 2 | 3 | @freezed 4 | class TransactionState with _$TransactionState { 5 | const factory TransactionState.initial() = _Initial; 6 | const factory TransactionState.loading() = Loading; 7 | const factory TransactionState.success(String message) = Success; 8 | const factory TransactionState.error(String message) = Error; 9 | const factory TransactionState.loadTransaction({ 10 | required Categorys categorys, 11 | required DateTime transactionDate, 12 | required Category transactionCategory, 13 | }) = LoadTransaction; 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/settings/data/auth_repository/auth_base_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart' as auth; 2 | import 'package:user_service/user_service.dart'; 3 | 4 | import '../../../../core/utils/models/app_result.dart'; 5 | 6 | abstract class AuthBaseRepository { 7 | /// The [currentUser] method is used to get the current user. 8 | /// It returns the current user. 9 | auth.User? get currentUser; 10 | 11 | /// The [signInWithGoogle] method is used to sign in with google. 12 | Future> signInWithGoogle(); 13 | 14 | /// The [signOut] method is used to sign out. 15 | Future> signOut(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/service/network_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:internet_connection_checker/internet_connection_checker.dart'; 2 | 3 | abstract class NetworkBaseInfo { 4 | Future get isConnected; 5 | Stream get internetConnectionStream; 6 | } 7 | 8 | class NetworkInfo implements NetworkBaseInfo { 9 | final InternetConnectionChecker connectionChecker; 10 | 11 | NetworkInfo(this.connectionChecker); 12 | 13 | @override 14 | Future get isConnected => connectionChecker.hasConnection; 15 | 16 | @override 17 | Stream get internetConnectionStream { 18 | return connectionChecker.onStatusChange; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/auth_user/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | tasks.register("clean", Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /packages/db_hive_client/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /packages/user_service/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /packages/db_firestore_client/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /lib/features/profile/data/profile_repository/profile_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:user_service/user_service.dart'; 2 | 3 | import 'profile_base_repository.dart'; 4 | 5 | class ProfileRepository implements ProfileBaseRepository { 6 | final UserServiceBase _userService; 7 | 8 | ProfileRepository({ 9 | required UserServiceBase userService, 10 | }) : _userService = userService; 11 | 12 | @override 13 | Future updateProfile({required User user}) { 14 | return _userService.updateUser(user); 15 | } 16 | 17 | @override 18 | Future updateUserPicture({ 19 | required User user, 20 | required String imagePath, 21 | }) { 22 | return _userService.updateUserPicture(user, imagePath); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/styles/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class AppColors { 6 | static const primary = Color(0xFF00B2E7); 7 | static const secondary = Color(0xFFE064F7); 8 | static const tertiary = Color(0xFFFF8D6C); 9 | static const outline = Colors.grey; 10 | 11 | // 12 | static const backgroundlight = Color(0xFFF5F5F5); 13 | static const onBackgroundlight = Color(0xFF000000); 14 | 15 | // 16 | static const backgroundDark = Color(0xFF212121); 17 | static const onBackgroundDark = Color(0xFFFFFFFF); 18 | 19 | static const primaryGradient = LinearGradient( 20 | begin: Alignment.topLeft, 21 | end: Alignment.bottomRight, 22 | colors: [primary, secondary, tertiary], 23 | transform: GradientRotation(pi / 4), 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "800188863112", 4 | "project_id": "daily-expense-tracker-123e1", 5 | "storage_bucket": "daily-expense-tracker-123e1.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:800188863112:android:fe4ed12bb40ba62b5f053d", 11 | "android_client_info": { 12 | "package_name": "com.example.daily_expense_tracker_app" 13 | } 14 | }, 15 | "oauth_client": [], 16 | "api_key": [ 17 | { 18 | "current_key": "AIzaSyDC7TRqG_rifjnlAh1UHPrcB3tmVt979WQ" 19 | } 20 | ], 21 | "services": { 22 | "appinvite_service": { 23 | "other_platform_oauth_client": [] 24 | } 25 | } 26 | } 27 | ], 28 | "configuration_version": "1" 29 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/core/styles/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'app_colors.dart'; 4 | 5 | ThemeData appTheme = ThemeData( 6 | fontFamily: 'Inter', 7 | useMaterial3: true, 8 | colorScheme: const ColorScheme.light( 9 | primary: AppColors.primary, 10 | secondary: AppColors.secondary, 11 | tertiary: AppColors.tertiary, 12 | outline: AppColors.outline, 13 | background: AppColors.backgroundlight, 14 | onBackground: AppColors.onBackgroundlight, 15 | ), 16 | ); 17 | 18 | ThemeData appThemeDark = ThemeData( 19 | fontFamily: 'Inter', 20 | useMaterial3: true, 21 | colorScheme: const ColorScheme.dark( 22 | primary: AppColors.primary, 23 | secondary: AppColors.secondary, 24 | tertiary: AppColors.tertiary, 25 | outline: AppColors.outline, 26 | background: AppColors.backgroundDark, 27 | onBackground: AppColors.onBackgroundDark, 28 | ), 29 | ); 30 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version "7.3.0" apply false 27 | } 28 | 29 | include ":app" 30 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/bloc_listener_auth.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../core/enum/enum.dart'; 5 | import '../../../blocs/auth_bloc/auth_cubit.dart'; 6 | import '../../../blocs/main_bloc/main_cubit.dart'; 7 | 8 | class BlocListenerAuth extends StatelessWidget { 9 | const BlocListenerAuth({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return BlocListener( 14 | listener: (context, state) { 15 | state.maybeWhen( 16 | authChanged: (_, __) { 17 | return context.read().getAll(TypeShow.limit).then( 18 | (_) { 19 | return context.read().getTotals(); 20 | }, 21 | ); 22 | }, 23 | orElse: () {}, 24 | ); 25 | }, 26 | child: const SizedBox.shrink(), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/core/enum/transaction.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | 4 | enum TransactionAction { 5 | add, 6 | edit, 7 | } 8 | 9 | enum Category { 10 | expense( 11 | name: 'Expense', 12 | backgroundIcon: Colors.redAccent, 13 | icon: FontAwesomeIcons.minus, 14 | ), 15 | income( 16 | name: 'Income', 17 | backgroundIcon: Colors.greenAccent, 18 | icon: FontAwesomeIcons.plus, 19 | ); 20 | 21 | final String name; 22 | final IconData icon; 23 | final Color backgroundIcon; 24 | 25 | const Category({ 26 | required this.name, 27 | required this.icon, 28 | required this.backgroundIcon, 29 | }); 30 | 31 | // static TransactionCategory fromIndex(int categoryIndex) { 32 | // return TransactionCategory.values.firstWhere( 33 | // (category) => category.index == categoryIndex, 34 | // orElse: () => TransactionCategory.expense, 35 | // ); 36 | // } 37 | } 38 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API_KEY 6 | AIzaSyA_8iWOFNHMo-36B866eEJFyW279aiZfOM 7 | GCM_SENDER_ID 8 | 800188863112 9 | PLIST_VERSION 10 | 1 11 | BUNDLE_ID 12 | com.example.dailyExpenseTrackerApp 13 | PROJECT_ID 14 | daily-expense-tracker-123e1 15 | STORAGE_BUCKET 16 | daily-expense-tracker-123e1.appspot.com 17 | IS_ADS_ENABLED 18 | 19 | IS_ANALYTICS_ENABLED 20 | 21 | IS_APPINVITE_ENABLED 22 | 23 | IS_GCM_ENABLED 24 | 25 | IS_SIGNIN_ENABLED 26 | 27 | GOOGLE_APP_ID 28 | 1:800188863112:ios:0923cbc2198d87dc5f053d 29 | 30 | -------------------------------------------------------------------------------- /packages/db_hive_client/lib/src/db_hive_client_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class DbHiveClientBase { 4 | Future initDb({ 5 | required String boxName, 6 | required VoidCallback onRegisterAdapter, 7 | }); 8 | 9 | Future> getAll({ 10 | required String boxName, 11 | }); 12 | 13 | Future getById({ 14 | required String modelId, 15 | required String boxName, 16 | }); 17 | 18 | Future add({ 19 | required String modelId, 20 | required T modelHive, 21 | required String boxName, 22 | }); 23 | 24 | Future update({ 25 | required String modelId, 26 | required String boxName, 27 | required T modelHive, 28 | }); 29 | 30 | Future delete({ 31 | required String modelId, 32 | required String boxName, 33 | }); 34 | 35 | Future clearAll({ 36 | required String boxName, 37 | }); 38 | 39 | Stream> watchAll({ 40 | required String boxName, 41 | }); 42 | 43 | Future closeDb(); 44 | } 45 | -------------------------------------------------------------------------------- /lib/core/router/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Extension on [BuildContext] build context to provide easy access Context Extension. 4 | extension RoutesContextExtension on BuildContext { 5 | /// Pops the current screen from the navigation stack. 6 | void pop() => Navigator.pop(this); 7 | 8 | // Pushes a named route onto the navigation stack. 9 | void pushNamed(String routeName, {Object? arguments}) => 10 | Navigator.pushNamed(this, routeName, arguments: arguments); 11 | 12 | // Replaces the current screen with a named route on the navigation stack. 13 | void pushReplacementNamed(String routeName, {Object? arguments}) => 14 | Navigator.pushReplacementNamed(this, routeName, arguments: arguments); 15 | 16 | // Pushes a named route onto the navigation stack and removes all previous routes. 17 | void pushNamedAndRemoveUntil(String routeName, {Object? arguments}) { 18 | Navigator.pushNamedAndRemoveUntil( 19 | this, routeName, (Route route) => false, 20 | arguments: arguments); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/core/shared/custom_icon_bottom.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | 4 | import '../extension/extension.dart'; 5 | 6 | class CustomIconBottom extends StatelessWidget { 7 | const CustomIconBottom({ 8 | super.key, 9 | required this.onPressed, 10 | required this.icon, 11 | }); 12 | 13 | final VoidCallback onPressed; 14 | final IconData icon; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Material( 19 | color: context.colorScheme.surface, 20 | borderRadius: BorderRadius.circular(14), 21 | clipBehavior: Clip.antiAlias, 22 | child: InkWell( 23 | onTap: () => onPressed(), 24 | child: SizedBox( 25 | height: 35, 26 | width: 35, 27 | child: Center( 28 | child: FaIcon( 29 | icon, 30 | size: 16, 31 | color: context.colorScheme.outline, 32 | ), 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/core/shared/custom_material_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../styles/app_colors.dart'; 4 | import '../styles/app_text_style.dart'; 5 | 6 | class CustomMaterialButton extends StatelessWidget { 7 | const CustomMaterialButton({ 8 | super.key, 9 | required this.onPressed, 10 | required this.text, 11 | }); 12 | 13 | final String text; 14 | final VoidCallback onPressed; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Container( 19 | width: double.maxFinite, 20 | margin: const EdgeInsets.symmetric(vertical: 10), 21 | height: 55, 22 | decoration: BoxDecoration( 23 | borderRadius: BorderRadius.circular(14), 24 | gradient: AppColors.primaryGradient, 25 | ), 26 | child: MaterialButton( 27 | onPressed: onPressed, 28 | shape: RoundedRectangleBorder( 29 | borderRadius: BorderRadius.circular(14), 30 | ), 31 | child: Text( 32 | text, 33 | style: AppTextStyle.button.copyWith(color: Colors.white), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/features/settings/data/themes_repository/themes_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | import 'themes_base_repository.dart'; 5 | 6 | class ThemesRepository implements ThemesBaseRepository { 7 | final SharedPreferences _sharedPreferences; 8 | 9 | ThemesRepository({ 10 | required SharedPreferences sharedPreferences, 11 | }) : _sharedPreferences = sharedPreferences; 12 | 13 | @override 14 | Future getTheme() => _getCachedThemeMode(); 15 | 16 | @override 17 | Future setTheme(ThemeMode theme) => _cacheThemeMode(theme); 18 | 19 | Future _getCachedThemeMode() async { 20 | final int? cachedThemeModeIndex = _sharedPreferences.getInt("Themes_"); 21 | 22 | if (cachedThemeModeIndex != null && 23 | cachedThemeModeIndex < ThemeMode.values.length) { 24 | return ThemeMode.values[cachedThemeModeIndex]; 25 | } else { 26 | return ThemeMode.dark; 27 | } 28 | } 29 | 30 | Future _cacheThemeMode(ThemeMode themeMode) async { 31 | await _sharedPreferences.setInt("Themes_", themeMode.index); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/app_floating_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | 5 | import '../../../../core/extension/extension.dart'; 6 | import '../../../../core/router/app_route.dart'; 7 | import '../../../../core/router/router.dart'; 8 | import '../../../../core/styles/app_colors.dart'; 9 | import '../../../blocs/transaction_bloc/transaction_cubit.dart'; 10 | 11 | class AppFloatingButton extends StatelessWidget { 12 | const AppFloatingButton({ 13 | super.key, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Container( 19 | height: 60, 20 | width: 60, 21 | decoration: const BoxDecoration( 22 | shape: BoxShape.circle, 23 | gradient: AppColors.primaryGradient, 24 | ), 25 | child: IconButton( 26 | icon: Icon(FontAwesomeIcons.plus, color: context.colorScheme.surface), 27 | onPressed: () { 28 | context.read().isEditing = false; 29 | context.pushNamed(RoutesName.transaction); 30 | }, 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/bloc_observer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | 5 | class AppBlocObserver extends BlocObserver { 6 | @override 7 | void onCreate(BlocBase bloc) { 8 | super.onCreate(bloc); 9 | log('onCreate -- bloc: ${bloc.runtimeType}'); 10 | } 11 | 12 | @override 13 | void onEvent(Bloc bloc, Object? event) { 14 | super.onEvent(bloc, event); 15 | log('onEvent -- bloc: ${bloc.runtimeType}, event: $event'); 16 | } 17 | 18 | @override 19 | void onChange(BlocBase bloc, Change change) { 20 | super.onChange(bloc, change); 21 | log('onChange -- bloc: ${bloc.runtimeType}, change: $change'); 22 | } 23 | 24 | @override 25 | void onTransition(Bloc bloc, Transition transition) { 26 | super.onTransition(bloc, transition); 27 | log('onTransition -- bloc: ${bloc.runtimeType}, transition: $transition'); 28 | } 29 | 30 | @override 31 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 32 | log('onError -- bloc: ${bloc.runtimeType}, error: $error'); 33 | super.onError(bloc, error, stackTrace); 34 | } 35 | 36 | @override 37 | void onClose(BlocBase bloc) { 38 | super.onClose(bloc); 39 | log('onClose -- bloc: ${bloc.runtimeType}'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/core/models/transaction_hive_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'transaction_hive_model.g.dart'; 4 | 5 | @HiveType(typeId: 0) 6 | class TransactionHive extends HiveObject { 7 | @HiveField(0) 8 | String uuid; 9 | 10 | @HiveField(1) 11 | String? userId; 12 | 13 | @HiveField(2) 14 | double amount; 15 | 16 | @HiveField(3) 17 | DateTime date; 18 | 19 | @HiveField(4) 20 | int categorysIndex; 21 | 22 | @HiveField(5) 23 | CategoryHive category; 24 | 25 | TransactionHive({ 26 | required this.uuid, 27 | required this.userId, 28 | required this.date, 29 | required this.amount, 30 | required this.categorysIndex, 31 | required this.category, 32 | }); 33 | } 34 | 35 | @HiveType(typeId: 1) 36 | enum CategoryHive { 37 | @HiveField(0) 38 | expense, 39 | 40 | @HiveField(1) 41 | income, 42 | } 43 | 44 | class NewCategoryHiveAdapter extends TypeAdapter { 45 | @override 46 | final int typeId = 2; 47 | 48 | @override 49 | CategoryHive read(BinaryReader reader) { 50 | return CategoryHive.values[reader.readByte()]; 51 | } 52 | 53 | @override 54 | void write(BinaryWriter writer, CategoryHive obj) { 55 | writer.writeByte(obj.index); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/core/shared/custom_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | 4 | import '../extension/extension.dart'; 5 | import '../helper/helper.dart'; 6 | import '../router/router.dart'; 7 | import '../styles/app_text_style.dart'; 8 | 9 | class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { 10 | const CustomAppBar({super.key, this.title, this.onPressed}); 11 | 12 | final String? title; 13 | final void Function()? onPressed; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return AppBar( 18 | elevation: 0, 19 | scrolledUnderElevation: 0, 20 | backgroundColor: context.colorScheme.background, 21 | systemOverlayStyle: Helper.overlayStyleAppBar(context), 22 | leading: IconButton( 23 | icon: const FaIcon(FontAwesomeIcons.chevronLeft, size: 18), 24 | onPressed: () { 25 | if (onPressed != null) { 26 | onPressed!(); 27 | } else { 28 | context.pop(); 29 | } 30 | }, 31 | ), 32 | title: Text(title ?? '', style: AppTextStyle.title), 33 | ); 34 | } 35 | 36 | @override 37 | Size get preferredSize => const Size.fromHeight(kToolbarHeight); 38 | } 39 | -------------------------------------------------------------------------------- /lib/core/models/transaction_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'transaction_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$TransactionImpl _$$TransactionImplFromJson(Map json) => 10 | _$TransactionImpl( 11 | uuid: json['uuid'] as String?, 12 | userId: json['userId'] as String?, 13 | amount: (json['amount'] as num).toDouble(), 14 | date: const TimestampConverter().fromJson(json['date'] as Timestamp), 15 | categorysIndex: json['categorysIndex'] as int, 16 | category: $enumDecode(_$CategoryEnumMap, json['category']), 17 | ); 18 | 19 | Map _$$TransactionImplToJson(_$TransactionImpl instance) => 20 | { 21 | 'uuid': instance.uuid, 22 | 'userId': instance.userId, 23 | 'amount': instance.amount, 24 | 'date': const TimestampConverter().toJson(instance.date), 25 | 'categorysIndex': instance.categorysIndex, 26 | 'category': _$CategoryEnumMap[instance.category]!, 27 | }; 28 | 29 | const _$CategoryEnumMap = { 30 | Category.expense: 'expense', 31 | Category.income: 'income', 32 | }; 33 | -------------------------------------------------------------------------------- /lib/features/blocs/themes_bloc/themes_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | import '../../settings/data/themes_repository/themes_base_repository.dart'; 6 | 7 | part 'themes_cubit.freezed.dart'; 8 | part 'themes_state.dart'; 9 | 10 | class ThemesCubit extends Cubit { 11 | final ThemesBaseRepository _themesRepository; 12 | 13 | ThemesCubit({required ThemesBaseRepository themesRepository}) 14 | : _themesRepository = themesRepository, 15 | super(const ThemesState.loadedThemeMode(ThemeMode.system)) { 16 | getTheme(); 17 | } 18 | 19 | void getTheme() async { 20 | final theme = await _themesRepository.getTheme(); 21 | emit(ThemesState.loadedThemeMode(theme)); 22 | } 23 | 24 | Future setTheme(ThemeMode theme) async { 25 | await _themesRepository.setTheme(theme); 26 | emit(ThemesState.loadedThemeMode(theme)); 27 | } 28 | 29 | void toggleTheme() async { 30 | final theme = state.maybeWhen( 31 | orElse: () => ThemeMode.system, 32 | loadedThemeMode: (theme) => theme, 33 | ); 34 | 35 | final newTheme = 36 | theme == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; 37 | await setTheme(newTheme); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/core/models/totals_transaction_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | import '../enum/transaction.dart'; 4 | import 'transaction_model.dart'; 5 | 6 | part 'totals_transaction_model.freezed.dart'; 7 | 8 | @freezed 9 | class TotalsTransaction with _$TotalsTransaction { 10 | const factory TotalsTransaction({ 11 | required double totalIncome, 12 | required double totalExpense, 13 | required double totalBalance, 14 | }) = _TotalsTransactionModel; 15 | 16 | factory TotalsTransaction.calcu(List transactions) { 17 | return TotalsTransaction( 18 | // totalExpense = totalExpense 19 | totalExpense: transactions 20 | .where((transaction) => transaction.category == Category.expense) 21 | .map((transaction) => transaction.amount) 22 | .fold(0.0, (prev, amount) => prev + amount), 23 | 24 | // totalIncome = totalIncome - totalExpense 25 | totalIncome: transactions 26 | .where((transaction) => transaction.category == Category.income) 27 | .map((transaction) => transaction.amount) 28 | .fold(0.0, (prev, amount) => prev + amount), 29 | 30 | // totalBalance = totalIncome - totalExpense 31 | totalBalance: transactions 32 | .map((transaction) => transaction.amount) 33 | .fold(0.0, (prev, amount) => prev + amount)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/features/home/view/state_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../core/enum/enum.dart'; 4 | import '../../../core/shared/shared.dart'; 5 | import 'widgets/widgets.dart'; 6 | 7 | class StateView extends StatelessWidget { 8 | const StateView({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Padding( 13 | padding: const EdgeInsets.symmetric(horizontal: 25), 14 | child: Scaffold( 15 | body: _buidBody(), 16 | ), 17 | ); 18 | } 19 | 20 | Widget _buidBody() { 21 | return const SafeArea( 22 | bottom: false, 23 | child: Center( 24 | child: Column( 25 | children: [ 26 | HeaderAppBarFilter(), 27 | SizedBox(height: 12), 28 | Expanded( 29 | child: CustomTabBar( 30 | tabControllerCount: 2, 31 | tabs: [ 32 | Tab(text: 'Income'), 33 | Tab(text: 'Expense'), 34 | ], 35 | children: [ 36 | TransactionFilter( 37 | category: Category.income, 38 | ), 39 | TransactionFilter( 40 | category: Category.expense, 41 | ), 42 | ], 43 | ), 44 | ), 45 | ], 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/auth_user/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /packages/db_hive_client/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /packages/user_service/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /packages/db_firestore_client/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/header_app_bar_profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | 5 | import '../../../../core/enum/enum.dart'; 6 | import '../../../../core/router/app_route.dart'; 7 | import '../../../../core/router/router.dart'; 8 | import '../../../../core/shared/shared.dart'; 9 | import '../../../blocs/main_bloc/main_cubit.dart'; 10 | import 'widgets.dart'; 11 | 12 | class HeaderAppBarProfile extends StatelessWidget { 13 | const HeaderAppBarProfile({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Row( 18 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 19 | children: [ 20 | const ProfileIcon(), 21 | Row( 22 | children: [ 23 | CustomIconBottom( 24 | icon: FontAwesomeIcons.rotateLeft, 25 | onPressed: () { 26 | context.read().getAll(TypeShow.limit).then((_) { 27 | context.read().getTotals(); 28 | }); 29 | }, 30 | ), 31 | const SizedBox(width: 10), 32 | CustomIconBottom( 33 | icon: FontAwesomeIcons.gear, 34 | onPressed: () => context.pushNamed(RoutesName.settings), 35 | ), 36 | ], 37 | ) 38 | ], 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /lib/features/blocs/main_bloc/main_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | import '../../../../core/enum/enum.dart'; 7 | import '../../../../core/models/totals_transaction_model.dart'; 8 | import '../../../../core/models/transaction_model.dart'; 9 | import '../../home/data/main_repository/main_base_repository.dart'; 10 | 11 | part 'main_cubit.freezed.dart'; 12 | part 'main_state.dart'; 13 | 14 | class MainCubit extends Cubit { 15 | final MainBaseRepository _mainRepository; 16 | 17 | MainCubit({ 18 | required MainBaseRepository mainRepository, 19 | }) : _mainRepository = mainRepository, 20 | super(const MainState.initial()); 21 | 22 | Future getAll(TypeShow typeShow) async { 23 | emit(const MainState.loading()); 24 | 25 | final result = await _mainRepository.getAll( 26 | limit: typeShow == TypeShow.limit ? 10 : null, 27 | ); 28 | result.when( 29 | success: (all) => emit( 30 | typeShow == TypeShow.limit 31 | ? MainState.loadedLimit(all) 32 | : MainState.loadedAll(all), 33 | ), 34 | failure: (message) => emit(MainState.error(message)), 35 | ); 36 | } 37 | 38 | Future getTotals() async { 39 | final result = await _mainRepository.getTotals(); 40 | result.when( 41 | success: (allTotals) => emit(MainState.loadedTotals(allTotals)), 42 | failure: (message) => emit(MainState.error(message)), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/features/home/view/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../core/extension/extension.dart'; 4 | import '../../../core/helper/helper.dart'; 5 | import 'main_view.dart'; 6 | import 'state_view.dart'; 7 | import 'widgets/widgets.dart'; 8 | 9 | class HomeView extends StatefulWidget { 10 | const HomeView({super.key}); 11 | 12 | @override 13 | State createState() => _HomeViewState(); 14 | } 15 | 16 | class _HomeViewState extends State { 17 | // The current index of the bottom navigation bar. 18 | int _currentIndex = 0; 19 | // The pages to be displayed. 20 | final List _pages = const [ 21 | MainView(), 22 | StateView(), 23 | ]; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | extendBody: true, 29 | appBar: _buildAppBar(context), 30 | floatingActionButton: const AppFloatingButton(), 31 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 32 | bottomNavigationBar: AppBottomNavBar( 33 | currentIndex: _currentIndex, 34 | onTabTapped: (index) => setState(() => _currentIndex = index), 35 | ), 36 | body: _pages.elementAt(_currentIndex), 37 | ); 38 | } 39 | 40 | _buildAppBar(BuildContext context) { 41 | return AppBar( 42 | elevation: 0, 43 | toolbarHeight: 0, 44 | scrolledUnderElevation: 0, 45 | systemOverlayStyle: Helper.overlayStyleAppBar(context), 46 | backgroundColor: context.colorScheme.background, 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/features/settings/view/widgets/dark_mode_switch.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | 5 | import '../../../blocs/themes_bloc/themes_cubit.dart'; 6 | import 'widgets.dart'; 7 | 8 | class DarkModeSwitch extends StatefulWidget { 9 | const DarkModeSwitch({super.key}); 10 | 11 | @override 12 | State createState() => _DarkModeSwitchState(); 13 | } 14 | 15 | class _DarkModeSwitchState extends State { 16 | late bool isSwitched; 17 | 18 | @override 19 | void initState() { 20 | isSwitched = context.read().state.maybeMap( 21 | orElse: () => false, 22 | loadedThemeMode: (state) => state.themeMode == ThemeMode.dark, 23 | ); 24 | super.initState(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ItemSettings( 30 | title: isSwitched ? 'Dark Mode' : 'Light Mode', 31 | iconData: isSwitched ? FontAwesomeIcons.moon : FontAwesomeIcons.solidSun, 32 | backgroundIcon: Colors.grey.shade800, 33 | trailing: BlocBuilder( 34 | builder: (context, state) { 35 | return Switch( 36 | value: isSwitched, 37 | onChanged: (themeMode) { 38 | setState(() { 39 | isSwitched = themeMode; 40 | context.read().toggleTheme(); 41 | }); 42 | }, 43 | ); 44 | }, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/features/settings/view/settings_view.dart: -------------------------------------------------------------------------------- 1 | import '../../../core/utils/alerts/alerts.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | 5 | import '../../../core/shared/shared.dart'; 6 | import 'widgets/widgets.dart'; 7 | 8 | class SettingsView extends StatelessWidget { 9 | const SettingsView({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: const CustomAppBar(title: 'Settings'), 15 | body: Padding( 16 | padding: const EdgeInsets.symmetric(horizontal: 25), 17 | child: _buildBody(context), 18 | ), 19 | ); 20 | } 21 | 22 | Widget _buildBody(BuildContext context) { 23 | return SafeArea( 24 | child: Center( 25 | child: Column( 26 | mainAxisAlignment: MainAxisAlignment.start, 27 | crossAxisAlignment: CrossAxisAlignment.center, 28 | children: [ 29 | const AuthProfile(), 30 | const SizedBox(height: 10), 31 | const DarkModeSwitch(), 32 | const SizedBox(height: 10), 33 | ItemSettings( 34 | title: 'Version', 35 | iconData: FontAwesomeIcons.circleInfo, 36 | backgroundIcon: Colors.blueAccent, 37 | trailing: const FaIcon(FontAwesomeIcons.chevronRight, size: 16), 38 | onTap: () => Alerts.showSheet( 39 | context: context, 40 | child: const VersionSheet(), 41 | ), 42 | ), 43 | ], 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/features/settings/view/widgets/version_sheet.dart: -------------------------------------------------------------------------------- 1 | import '../../../../core/extension/extension.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../../core/styles/app_text_style.dart'; 5 | 6 | class VersionSheet extends StatelessWidget { 7 | const VersionSheet({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return SizedBox( 12 | height: context.deviceSize.height * 0.3, 13 | child: Padding( 14 | padding: const EdgeInsets.symmetric(horizontal: 25), 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.start, 17 | crossAxisAlignment: CrossAxisAlignment.center, 18 | children: [ 19 | const SizedBox(height: 10), 20 | const Text('Version 1.0.0', style: AppTextStyle.title), 21 | const SizedBox(height: 25), 22 | Text( 23 | 'CopyRight © 2024 Daily Expense Tracker App. All rights reserved.', 24 | style: AppTextStyle.body.copyWith( 25 | fontSize: 16, 26 | fontWeight: FontWeight.w400, 27 | ), 28 | textAlign: TextAlign.center, 29 | ), 30 | const SizedBox(height: 25), 31 | const Text( 32 | 'Developed by: ', 33 | style: AppTextStyle.body, 34 | ), 35 | const SizedBox(height: 10), 36 | Text( 37 | 'Haithem Nini', 38 | style: AppTextStyle.title2.copyWith( 39 | fontSize: 18, 40 | fontWeight: FontWeight.w400, 41 | ), 42 | ), 43 | ], 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/header_app_bar_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | 5 | import '../../../../core/router/router.dart'; 6 | import '../../../../core/shared/custom_material_button.dart'; 7 | import '../../../../core/shared/shared.dart'; 8 | import '../../../../core/styles/app_text_style.dart'; 9 | import '../../../../core/utils/alerts/alerts.dart'; 10 | import '../../../blocs/state_bloc/state_cubit.dart'; 11 | import 'widgets.dart'; 12 | 13 | class HeaderAppBarFilter extends StatelessWidget { 14 | const HeaderAppBarFilter({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Row( 19 | children: [ 20 | Text( 21 | 'Transaction', 22 | style: AppTextStyle.title.copyWith( 23 | fontWeight: FontWeight.w600, 24 | ), 25 | ), 26 | const Spacer(), 27 | CustomIconBottom( 28 | icon: FontAwesomeIcons.filter, 29 | onPressed: () => _showModalSheet(context), 30 | ) 31 | ], 32 | ); 33 | } 34 | 35 | void _showModalSheet(BuildContext context) { 36 | return Alerts.showSheet( 37 | context: context, 38 | child: Container( 39 | margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 25), 40 | child: Column( 41 | children: [ 42 | const FilterForm(), 43 | CustomMaterialButton( 44 | text: 'Done', 45 | onPressed: () { 46 | context.pop(); 47 | context.read().applyFilter(); 48 | }, 49 | ), 50 | ], 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/core/helper/helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:uuid/uuid.dart'; 4 | 5 | import '../config/overlay_style_config.dart'; 6 | 7 | class Helper { 8 | /// Sets the system UI overlay style for navigation elements. 9 | /// 10 | /// This method applies the [SystemUiOverlayStyle] configuration for navigation elements 11 | /// to the current [BuildContext]. 12 | static void overlayNavigation(BuildContext context) { 13 | OverlayStyleConfig.overlayNavigation(context); 14 | } 15 | 16 | /// Retrieves the system UI overlay style for app bars. 17 | /// 18 | /// This method returns the [SystemUiOverlayStyle] configuration for app bars 19 | /// based on the current [BuildContext]. 20 | /// 21 | /// Returns: 22 | /// The [SystemUiOverlayStyle] configuration for app bars. 23 | static overlayStyleAppBar(BuildContext context) { 24 | return OverlayStyleConfig.overlayAppBar(context); 25 | } 26 | 27 | /// Determines if the current theme mode in the given [context] is dark mode. 28 | /// Returns `true` if the theme mode is dark, `false` otherwise. 29 | /// If no [context] is provided, the current [BuildContext] is used. 30 | /// If no [context] is available, `false` is returned. 31 | /// This method is a shortcut for `Theme.of(context).brightness == Brightness.dark`. 32 | static bool isDarkMode(BuildContext context) { 33 | return Theme.of(context).brightness == Brightness.dark; 34 | } 35 | 36 | //get asset image path from assets folder 37 | static String getAssetImage(String name) { 38 | return 'assets/images/$name'; 39 | } 40 | 41 | //get asset svg path from assets folder 42 | static String getAssetSvg(String name) { 43 | return 'assets/svgs/$name'; 44 | } 45 | 46 | static String generateUUID() { 47 | return const Uuid().v4(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 17 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 18 | - platform: android 19 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 20 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 21 | - platform: ios 22 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 23 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 24 | - platform: linux 25 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 26 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 27 | - platform: macos 28 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 29 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 30 | - platform: web 31 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 32 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 33 | - platform: windows 34 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 35 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Daily Expense Tracker App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Daily Expense Tracker 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /packages/user_service/lib/src/core/models/user_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | import 'package:firebase_auth/firebase_auth.dart' as auth; 5 | 6 | part 'user_model.freezed.dart'; 7 | 8 | @freezed 9 | class User with _$User { 10 | const factory User({ 11 | required String? uuid, 12 | required String email, 13 | required String fullName, 14 | required String? photoUrl, 15 | }) = _User; 16 | 17 | factory User.empty() { 18 | return const User( 19 | uuid: '', 20 | email: '', 21 | fullName: '', 22 | photoUrl: '', 23 | ); 24 | } 25 | 26 | factory User.guest() { 27 | return const User( 28 | uuid: '', 29 | email: '', 30 | fullName: 'Guest', 31 | photoUrl: '', 32 | ); 33 | } 34 | 35 | factory User.toUser(auth.User user) { 36 | return User( 37 | uuid: user.uid, 38 | email: user.email!, 39 | fullName: user.displayName!, 40 | photoUrl: user.photoURL, 41 | ); 42 | } 43 | 44 | factory User.fromSnapshot(DocumentSnapshot snapshot) { 45 | return User( 46 | uuid: snapshot.id, 47 | email: snapshot['email'], 48 | fullName: snapshot['fullName'], 49 | photoUrl: snapshot['photoUrl'], 50 | ); 51 | } 52 | } 53 | 54 | extension UserExtensions on User { 55 | Map toDocument() { 56 | return { 57 | 'email': email, 58 | 'fullName': fullName, 59 | 'photoUrl': photoUrl, 60 | }; 61 | } 62 | 63 | // String get initials { 64 | // final List nameParts = fullName.split(' '); 65 | // if (nameParts.length > 1) { 66 | // return '${nameParts[0][0]}${nameParts[1][0]}'; 67 | // } else { 68 | // return nameParts[0][0]; 69 | // } 70 | // } 71 | 72 | // String get initials => fullName.split(' ').map((e) => e[0]).join(); 73 | } 74 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/app_bottom_nav_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | 4 | import '../../../../core/extension/extension.dart'; 5 | 6 | class AppBottomNavBar extends StatelessWidget { 7 | const AppBottomNavBar({ 8 | super.key, 9 | required this.currentIndex, 10 | required this.onTabTapped, 11 | }); 12 | 13 | final int currentIndex; 14 | final Function(int) onTabTapped; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Container( 19 | clipBehavior: Clip.antiAlias, 20 | decoration: BoxDecoration( 21 | borderRadius: const BorderRadius.vertical(top: Radius.circular(32)), 22 | boxShadow: [ 23 | BoxShadow( 24 | color: Colors.black12.withOpacity(0.01), 25 | blurRadius: 5, 26 | offset: const Offset(0, -1), 27 | ), 28 | ], 29 | ), 30 | child: BottomAppBar( 31 | height: 60, 32 | elevation: 0, 33 | notchMargin: 10, 34 | shape: const CircularNotchedRectangle(), 35 | child: Row( 36 | mainAxisAlignment: MainAxisAlignment.spaceAround, 37 | children: [ 38 | IconButton( 39 | icon: const FaIcon(FontAwesomeIcons.house, size: 18), 40 | onPressed: () => onTabTapped(0), 41 | color: currentIndex == 0 42 | ? context.colorScheme.primary 43 | : context.colorScheme.outline, 44 | ), 45 | IconButton( 46 | icon: const FaIcon(FontAwesomeIcons.chartSimple, size: 18), 47 | onPressed: () => onTabTapped(1), 48 | color: currentIndex == 1 49 | ? context.colorScheme.primary 50 | : context.colorScheme.outline, 51 | ), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/features/settings/view/widgets/item_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../core/extension/extension.dart'; 4 | import '../../../../core/shared/shared.dart'; 5 | import '../../../../core/styles/app_text_style.dart'; 6 | 7 | class ItemSettings extends StatelessWidget { 8 | const ItemSettings({ 9 | super.key, 10 | required this.title, 11 | required this.iconData, 12 | required this.trailing, 13 | required this.backgroundIcon, 14 | this.onTap, 15 | }); 16 | 17 | final String title; 18 | final Widget trailing; 19 | final IconData iconData; 20 | final Color backgroundIcon; 21 | final VoidCallback? onTap; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Padding( 26 | padding: const EdgeInsets.symmetric(vertical: 5), 27 | child: Material( 28 | color: context.colorScheme.surface, 29 | borderRadius: BorderRadius.circular(16), 30 | clipBehavior: Clip.antiAlias, 31 | child: InkWell( 32 | onTap: onTap, 33 | child: Container( 34 | padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), 35 | child: Row( 36 | children: [ 37 | IconItem( 38 | iconSize: 18, 39 | icon: iconData, 40 | iconItemHeight: 45, 41 | iconItemWidth: 45, 42 | iconColor: context.colorScheme.surface, 43 | backgroundIcon: backgroundIcon, 44 | ), 45 | const SizedBox(width: 12), 46 | Expanded( 47 | child: Text( 48 | title, 49 | style: AppTextStyle.body.copyWith( 50 | fontWeight: FontWeight.w600, 51 | ), 52 | ), 53 | ), 54 | const Spacer(), 55 | trailing, 56 | const SizedBox(width: 5), 57 | ], 58 | ), 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/app/daily_tracker_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../core/app_injections.dart'; 5 | import '../core/helper/helper.dart'; 6 | import '../core/router/app_route.dart'; 7 | import '../core/styles/app_theme.dart'; 8 | import '../features/blocs/auth_bloc/auth_cubit.dart'; 9 | import '../features/blocs/main_bloc/main_cubit.dart'; 10 | import '../features/blocs/profile_bloc/profile_cubit.dart'; 11 | import '../features/blocs/state_bloc/state_cubit.dart'; 12 | import '../features/blocs/themes_bloc/themes_cubit.dart'; 13 | import '../features/blocs/transaction_bloc/transaction_cubit.dart'; 14 | 15 | class DailyTrackerApp extends StatelessWidget { 16 | const DailyTrackerApp({ 17 | super.key, 18 | required AppRouter appRoute, 19 | }) : _appRouter = appRoute; 20 | 21 | final AppRouter _appRouter; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | Helper.overlayNavigation(context); 26 | return MultiBlocProvider( 27 | providers: [ 28 | BlocProvider(create: (_) => getIt()), 29 | BlocProvider(create: (_) => getIt()), 30 | BlocProvider(create: (_) => getIt()), 31 | BlocProvider(create: (_) => getIt()), 32 | BlocProvider(create: (_) => getIt()), 33 | BlocProvider(create: (_) => getIt()) 34 | ], 35 | child: BlocBuilder( 36 | buildWhen: (previous, current) => current is LoadedThemeMode, 37 | builder: (context, state) { 38 | final themeMode = context.read().state.maybeMap( 39 | orElse: () => ThemeMode.system, 40 | loadedThemeMode: (state) => state.themeMode, 41 | ); 42 | return MaterialApp( 43 | debugShowCheckedModeBanner: false, 44 | title: 'Daily Tracker', 45 | theme: appTheme, 46 | darkTheme: appThemeDark, 47 | themeMode: themeMode, 48 | initialRoute: RoutesName.home, 49 | onGenerateRoute: _appRouter.generateRoute, 50 | ); 51 | }, 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/core/config/overlay_style_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | /// A helper class for managing the system UI overlay settings in the app. 5 | class OverlayStyleConfig { 6 | const OverlayStyleConfig._(); 7 | 8 | /// Sets the system UI overlay style to the default style. 9 | static SystemUiOverlayStyle overlayAppBar(BuildContext context) { 10 | return const SystemUiOverlayStyle().copyWith( 11 | statusBarColor: Colors.transparent, 12 | statusBarIconBrightness: Theme.of(context).brightness == Brightness.light 13 | ? Brightness.dark 14 | : Brightness.light, 15 | systemNavigationBarIconBrightness: Brightness.dark, 16 | systemNavigationBarColor: Colors.transparent, 17 | systemNavigationBarDividerColor: Colors.transparent, 18 | systemNavigationBarContrastEnforced: false, 19 | systemStatusBarContrastEnforced: false, 20 | ); 21 | } 22 | 23 | /// Sets the system UI overlay style to the default style. 24 | static overlayNavigation(BuildContext context) { 25 | Brightness statusBarIconBrightness = 26 | Theme.of(context).brightness == Brightness.light 27 | ? Brightness.dark 28 | : Brightness.light; 29 | 30 | SystemUiOverlayStyle systemUiOverlayStyle = 31 | const SystemUiOverlayStyle().copyWith( 32 | statusBarColor: Colors.transparent, 33 | statusBarIconBrightness: statusBarIconBrightness, 34 | systemNavigationBarIconBrightness: statusBarIconBrightness, 35 | systemNavigationBarColor: Colors.transparent, 36 | systemNavigationBarDividerColor: Colors.transparent, 37 | systemNavigationBarContrastEnforced: false, 38 | systemStatusBarContrastEnforced: false, 39 | ); 40 | 41 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 42 | SystemChrome.setEnabledSystemUIMode( 43 | SystemUiMode.manual, 44 | overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom], 45 | ); 46 | 47 | SystemChrome.setEnabledSystemUIMode( 48 | SystemUiMode.edgeToEdge, 49 | overlays: [SystemUiOverlay.bottom], 50 | ); 51 | 52 | SystemChrome.setEnabledSystemUIMode( 53 | SystemUiMode.edgeToEdge, 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/features/blocs/auth_bloc/auth_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:user_service/user_service.dart'; 6 | 7 | import '../../settings/data/auth_repository/auth_base_repository.dart'; 8 | 9 | part 'auth_cubit.freezed.dart'; 10 | part 'auth_state.dart'; 11 | 12 | class AuthCubit extends Cubit { 13 | final AuthBaseRepository _authRepository; 14 | final UserServiceBase _userService; 15 | 16 | AuthCubit({ 17 | required AuthBaseRepository authRepository, 18 | required UserServiceBase userService, 19 | }) : _authRepository = authRepository, 20 | _userService = userService, 21 | super(const AuthState.authChanged( 22 | authStatus: AuthStatus.unknown, 23 | )) { 24 | initAuth(); 25 | } 26 | 27 | void initAuth() { 28 | if (_authRepository.currentUser != null) { 29 | _userService.getUser(_authRepository.currentUser!.uid).listen( 30 | (user) => emit( 31 | AuthState.authChanged( 32 | authStatus: AuthStatus.authenticated, 33 | user: user, 34 | ), 35 | ), 36 | ); 37 | } else { 38 | emit(const AuthState.authChanged( 39 | authStatus: AuthStatus.unauthenticated, 40 | )); 41 | } 42 | } 43 | 44 | Future signInWithGoogle() async { 45 | emit(const AuthState.loading()); 46 | final result = await _authRepository.signInWithGoogle(); 47 | result.when( 48 | success: (user) => { 49 | _userService.getUser(user.uuid!).listen( 50 | (user) => emit( 51 | AuthState.authChanged( 52 | user: user, 53 | authStatus: AuthStatus.authenticated, 54 | ), 55 | ), 56 | ), 57 | }, 58 | failure: (message) => emit(AuthState.error(message)), 59 | ); 60 | } 61 | 62 | Future signOut() async { 63 | emit(const AuthState.loading()); 64 | final result = await _authRepository.signOut(); 65 | result.when( 66 | success: (_) => emit(const AuthState.authChanged( 67 | authStatus: AuthStatus.unauthenticated, 68 | )), 69 | failure: (message) => emit(AuthState.error(message)), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/user_service/lib/src/user_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:firebase_storage/firebase_storage.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | 7 | import '../user_service.dart'; 8 | 9 | class UserService implements UserServiceBase { 10 | UserService({ 11 | FirebaseFirestore? firebaseFirestore, 12 | FirebaseStorage? firebaseStorage, 13 | }) : _firebaseFirestore = firebaseFirestore ?? FirebaseFirestore.instance, 14 | _firebaseStorage = firebaseStorage ?? FirebaseStorage.instance; 15 | 16 | final FirebaseFirestore _firebaseFirestore; 17 | final FirebaseStorage _firebaseStorage; 18 | 19 | @override 20 | Future createUser(User user) { 21 | debugPrint('Creating user with id: ${user.uuid}'); 22 | return _firebaseFirestore 23 | .collection('users') 24 | .doc(user.uuid) 25 | .set(user.toDocument()) 26 | .then((value) => user); 27 | } 28 | 29 | @override 30 | Stream getUser(String userId) { 31 | debugPrint('Getting user with id: $userId'); 32 | return _firebaseFirestore.collection('users').doc(userId).snapshots().map( 33 | (snapshot) { 34 | if (!snapshot.exists) { 35 | return null; 36 | } 37 | return User.fromSnapshot(snapshot); 38 | }, 39 | ); 40 | } 41 | 42 | @override 43 | Future updateUser(User user) { 44 | debugPrint('Updating user with id: ${user.uuid}'); 45 | return _firebaseFirestore 46 | .collection('users') 47 | .doc(user.uuid) 48 | .update(user.toDocument()) 49 | .then( 50 | (value) => debugPrint('User updated'), 51 | ); 52 | } 53 | 54 | @override 55 | Future deleteUser(User user) { 56 | debugPrint('Deleting user with id: ${user.uuid}'); 57 | return _firebaseFirestore.collection('users').doc(user.uuid).delete().then( 58 | (value) => debugPrint('User deleted'), 59 | ); 60 | } 61 | 62 | @override 63 | Future updateUserPicture(User user, String imagePath) { 64 | debugPrint('Updating user image with id: ${user.uuid}'); 65 | final ref = _firebaseStorage.ref().child('users/${user.uuid}/profile'); 66 | return ref 67 | .putFile(File(imagePath)) 68 | .then((value) => value.ref.getDownloadURL()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/features/blocs/state_bloc/state_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | import '../../../../core/enum/enum.dart'; 5 | import '../../../../core/models/transaction_model.dart'; 6 | import '../../home/data/state_repository/state_base_repository.dart'; 7 | 8 | part 'state_cubit.freezed.dart'; 9 | part 'state_state.dart'; 10 | 11 | class StateCubit extends Cubit { 12 | final StateBaseRepository _statBaseRepository; 13 | 14 | StateCubit({ 15 | required StateBaseRepository statBaseRepository, 16 | }) : _statBaseRepository = statBaseRepository, 17 | super(const StateState.initial()) { 18 | _startDate = DateTime.now(); 19 | _endDate = DateTime.now().subtract(const Duration(days: 7)); 20 | _transactionCategory = Category.income; 21 | } 22 | 23 | late DateTime _startDate; 24 | late DateTime _endDate; 25 | 26 | late DateTime newStartDate = _startDate; 27 | late DateTime newEndDate = _endDate; 28 | 29 | late Category _transactionCategory; 30 | 31 | DateTime get startDate => _startDate; 32 | DateTime get endDate => _endDate; 33 | 34 | Category get transactionCategory => _transactionCategory; 35 | 36 | void applyFilter() { 37 | // Check if the new start date and end date is different 38 | //from the old start date and end date 39 | if (newStartDate != _startDate || newEndDate != _endDate) { 40 | _startDate = newStartDate; 41 | _endDate = newEndDate; 42 | // Emit the new date 43 | emit(StateState.dateChanged(_startDate, _endDate)); 44 | //apply the filter and get the stat 45 | getStat(_transactionCategory); 46 | return; 47 | } 48 | } 49 | 50 | Future getStat(Category category) async { 51 | emit(const StateState.loading()); 52 | 53 | // Set the transaction category 54 | _transactionCategory = category; 55 | 56 | // Get the stat 57 | final result = await _statBaseRepository.getState( 58 | category: category, 59 | startDate: _startDate, 60 | endDate: _endDate, 61 | ); 62 | 63 | result.when( 64 | success: (all) => emit(StateState.loaded( 65 | all['transactions'] as List, 66 | all['chartData'] as Map, 67 | )), 68 | failure: (message) => emit(StateState.error(message)), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: daily_expense_tracker_app 2 | description: "A new Flutter project." 3 | publish_to: 'none' 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: '>=3.2.3 <4.0.0' 9 | 10 | dependencies: 11 | # UI/UX 12 | flutter: 13 | sdk: flutter 14 | cupertino_icons: ^1.0.2 15 | flutter_styled_toast: ^2.2.1 16 | font_awesome_flutter: ^10.6.0 17 | image_picker: ^1.0.7 18 | fl_chart: ^0.66.2 19 | 20 | # State Management 21 | bloc: ^8.1.2 22 | flutter_bloc: ^8.1.3 23 | 24 | # Data Handling 25 | cloud_firestore: ^4.14.0 26 | firebase_auth: ^4.16.0 27 | firebase_core: ^2.24.2 28 | hive: ^2.2.3 29 | hive_flutter: ^1.1.0 30 | shared_preferences: ^2.2.2 31 | 32 | # Networking 33 | cached_network_image: ^3.3.1 34 | internet_connection_checker: ^1.0.0+1 35 | 36 | #freezed 37 | freezed: ^2.4.6 38 | freezed_annotation: ^2.4.1 39 | 40 | # Utility 41 | get_it: ^7.6.4 42 | uuid: ^4.3.3 43 | 44 | # User Authentication 45 | auth_user: 46 | path: packages/auth_user 47 | user_service: 48 | path: packages/user_service 49 | 50 | # Database 51 | db_firestore_client: 52 | path: packages/db_firestore_client 53 | db_hive_client: 54 | path: packages/db_hive_client 55 | 56 | # Parsing and Serialization 57 | intl: ^0.18.0 58 | json_annotation: ^4.8.1 59 | json_serializable: ^6.7.1 60 | 61 | # Others 62 | pattern_formatter: ^3.0.0 63 | 64 | dev_dependencies: 65 | # Linting 66 | flutter_lints: ^2.0.0 67 | 68 | # Testing 69 | flutter_test: 70 | sdk: flutter 71 | 72 | # Code Generation 73 | build_runner: ^2.4.7 74 | hive_generator: ^2.0.1 75 | 76 | # Asset Generation 77 | flutter_launcher_icons: ^0.13.1 78 | 79 | flutter_launcher_icons: 80 | android: "app_icon" 81 | ios: true 82 | image_path: "assets/icons/app_icon.png" 83 | 84 | flutter: 85 | uses-material-design: true 86 | 87 | assets: 88 | - assets/svgs/ 89 | - assets/images/ 90 | - assets/icons/ 91 | - assets/fonts/ 92 | 93 | fonts: 94 | - family: Inter 95 | fonts: 96 | - asset: assets/fonts/Inter-Bold.ttf 97 | weight: 700 98 | - asset: assets/fonts/Inter-Light.ttf 99 | - asset: assets/fonts/Inter-Medium.ttf 100 | - asset: assets/fonts/Inter-Regular.ttf 101 | - asset: assets/fonts/Inter-SemiBold.ttf 102 | -------------------------------------------------------------------------------- /lib/features/settings/data/auth_repository/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth_user/auth_user.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart' as auth; 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:user_service/user_service.dart'; 5 | 6 | import '../../../../core/utils/models/app_result.dart'; 7 | import 'auth_base_repository.dart'; 8 | 9 | class AuthRepository implements AuthBaseRepository { 10 | final UserServiceBase _userService; 11 | final AuthUserBase _authUser; 12 | 13 | AuthRepository({ 14 | required UserServiceBase userService, 15 | required AuthUserBase authUser, 16 | }) : _userService = userService, 17 | _authUser = authUser; 18 | 19 | /// The [currentUser] method is used to get the current user. 20 | /// It returns the current user. 21 | @override 22 | auth.User? get currentUser => _authUser.currentUser; 23 | 24 | /// The [signInWithEmailAndPassword] method is used to sign in with email and password. 25 | @override 26 | Future> signInWithGoogle() async { 27 | try { 28 | // Sign in with Google 29 | final user = await _authUser.signInWithGoogle(); 30 | 31 | // Check if the user already exists in the database 32 | final existingUser = await _userService.getUser(user!.uid).first; 33 | 34 | if (existingUser == null) { 35 | // The user does not exist in the database, create a new user 36 | final createdUser = await _userService.createUser( 37 | User( 38 | uuid: user.uid, 39 | email: user.email!, 40 | fullName: user.displayName!, 41 | photoUrl: user.photoURL, 42 | ), 43 | ); 44 | return AppResult.success(createdUser); 45 | } else { 46 | // The user already exists in the database 47 | return AppResult.success(existingUser); 48 | } 49 | } on SignInWithGoogleFailure catch (err) { 50 | debugPrint('AuthProfileRepository: signUpWithGoogle: $err'); 51 | return AppResult.failure(err.message); 52 | } catch (err) { 53 | return AppResult.failure(err.toString()); 54 | } 55 | } 56 | 57 | /// The [signOut] method is used to sign out. 58 | @override 59 | Future> signOut() async { 60 | try { 61 | await _authUser.signOut(); 62 | return const AppResult.success(null); 63 | } catch (err) { 64 | return AppResult.failure(err.toString()); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/core/router/app_route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:user_service/user_service.dart'; 4 | 5 | import '../../features/home/view/all_view_transaction.dart'; 6 | import '../../features/home/view/home_view.dart'; 7 | import '../../features/profile/view/profile_view.dart'; 8 | import '../../features/settings/view/settings_view.dart'; 9 | import '../../features/transaction/view/transaction_view.dart'; 10 | 11 | @immutable 12 | class RoutesName { 13 | const RoutesName._(); 14 | static const String home = '/'; 15 | static const String settings = '/settings'; 16 | static const String profile = '/profile'; 17 | static const String transaction = '/transaction'; 18 | static const String allViewTransaction = '/allViewTransaction'; 19 | } 20 | 21 | @immutable 22 | class AppRouter { 23 | PageRoute generateRoute(RouteSettings settings) { 24 | // ignore: unused_local_variable 25 | final arguments = settings.arguments; 26 | 27 | switch (settings.name) { 28 | case RoutesName.home: 29 | return _getPageRoute( 30 | routeName: settings.name, 31 | viewToShow: const HomeView(), 32 | ); 33 | case RoutesName.transaction: 34 | return _getPageRoute( 35 | routeName: settings.name, 36 | viewToShow: const TransactionView(), 37 | ); 38 | case RoutesName.settings: 39 | return _getPageRoute( 40 | routeName: settings.name, 41 | viewToShow: const SettingsView(), 42 | ); 43 | 44 | case RoutesName.profile: 45 | return _getPageRoute( 46 | routeName: settings.name, 47 | viewToShow: ProfileView( 48 | user: arguments as User, 49 | ), 50 | ); 51 | 52 | case RoutesName.allViewTransaction: 53 | return _getPageRoute( 54 | routeName: settings.name, 55 | viewToShow: const AllViewTransaction(), 56 | ); 57 | 58 | default: 59 | return _getPageRoute( 60 | routeName: settings.name, 61 | viewToShow: Scaffold( 62 | body: Center( 63 | child: Text('No route defined for ${settings.name}'), 64 | ), 65 | ), 66 | ); 67 | } 68 | } 69 | 70 | PageRoute _getPageRoute({String? routeName, Widget? viewToShow}) { 71 | return CupertinoPageRoute( 72 | settings: RouteSettings(name: routeName), 73 | builder: (_) => viewToShow!, 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/core/shared/custom_tabbar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../extension/extension.dart'; 6 | 7 | class CustomTabBar extends StatefulWidget { 8 | final int tabControllerCount; 9 | final List tabs; 10 | final List children; 11 | 12 | const CustomTabBar({ 13 | Key? key, 14 | required this.tabControllerCount, 15 | required this.tabs, 16 | required this.children, 17 | }) : super(key: key); 18 | 19 | @override 20 | CustomTabBarState createState() => CustomTabBarState(); 21 | } 22 | 23 | class CustomTabBarState extends State 24 | with SingleTickerProviderStateMixin { 25 | late TabController _tabController; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _tabController = TabController( 31 | length: widget.tabControllerCount, 32 | vsync: this, 33 | ); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | _tabController.dispose(); 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Column( 45 | children: [ 46 | Container( 47 | height: 65, 48 | decoration: BoxDecoration( 49 | borderRadius: BorderRadius.circular(14), 50 | color: context.colorScheme.surface, 51 | ), 52 | child: TabBar( 53 | controller: _tabController, 54 | labelColor: Colors.white, 55 | dividerColor: Colors.transparent, 56 | indicatorSize: TabBarIndicatorSize.tab, 57 | unselectedLabelColor: context.colorScheme.outline, 58 | padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), 59 | indicator: BoxDecoration( 60 | gradient: LinearGradient( 61 | colors: [ 62 | context.colorScheme.primary, 63 | context.colorScheme.secondary, 64 | context.colorScheme.tertiary, 65 | ], 66 | transform: const GradientRotation(pi / 4), 67 | ), 68 | borderRadius: BorderRadius.circular(14), 69 | ), 70 | tabs: widget.tabs, 71 | ), 72 | ), 73 | Expanded( 74 | child: TabBarView( 75 | physics: const NeverScrollableScrollPhysics(), 76 | controller: _tabController, 77 | children: widget.children, 78 | ), 79 | ), 80 | ], 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/db_firestore_client/lib/src/db_firestore_client_base.dart: -------------------------------------------------------------------------------- 1 | import 'common/core_typedefs.dart'; 2 | 3 | abstract class DbFirestoreClientBase { 4 | Future addDocument({ 5 | required String collectionPath, 6 | required Map data, 7 | }); 8 | 9 | Future updateDocument({ 10 | required String collectionPath, 11 | required Map data, 12 | }); 13 | 14 | Future deleteDocument({ 15 | required String collectionPath, 16 | }); 17 | 18 | Future setDocument({ 19 | required String collectionPath, 20 | required String documentId, 21 | required Map data, 22 | required bool merge, 23 | }); 24 | 25 | Future getDocument({ 26 | required String documentId, 27 | required String collectionPath, 28 | required ObjectMapper objectMapper, 29 | }); 30 | 31 | Stream streamDocument({ 32 | required String collectionPath, 33 | required String documentId, 34 | required ObjectMapper objectMapper, 35 | }); 36 | 37 | Future> getCollection({ 38 | required String collectionPath, 39 | required ObjectMapper objectMapper, 40 | }); 41 | 42 | Stream> streamCollection({ 43 | required String collectionPath, 44 | required ObjectMapper objectMapper, 45 | }); 46 | 47 | Future> getQuery({ 48 | required String collectionPath, 49 | required ObjectMapper mapper, 50 | required String field, 51 | required dynamic isEqualTo, 52 | }); 53 | 54 | Stream> streamQuery({ 55 | required String collectionPath, 56 | required ObjectMapper mapper, 57 | required String field, 58 | required dynamic isEqualTo, 59 | }); 60 | 61 | Stream> streamCollectionOrderBy({ 62 | required String collectionPath, 63 | required ObjectMapper objectMapper, 64 | required String orderByField, 65 | bool descending = false, 66 | }); 67 | 68 | Future> getQueryOrderBy({ 69 | required String collectionPath, 70 | required ObjectMapper mapper, 71 | required String field, 72 | required dynamic isEqualTo, 73 | required String orderByField, 74 | bool descending = false, 75 | int? limit, 76 | }); 77 | 78 | Stream> streamQueryOrderBy({ 79 | required String collectionPath, 80 | required ObjectMapper mapper, 81 | required String field, 82 | required dynamic isEqualTo, 83 | required String orderByField, 84 | bool descending = false, 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /packages/auth_user/lib/src/auth_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth_user/src/auth_user_base.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:firebase_auth/firebase_auth.dart' as auth; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:google_sign_in/google_sign_in.dart'; 6 | 7 | import 'core/exception/exception.dart'; 8 | 9 | class AuthUser implements AuthUserBase { 10 | AuthUser({ 11 | auth.FirebaseAuth? firebaseAuth, 12 | GoogleSignIn? googleSignIn, 13 | }) : _firebaseAuth = firebaseAuth ?? auth.FirebaseAuth.instance, 14 | _googleSignIn = googleSignIn ?? GoogleSignIn.standard(); 15 | 16 | /// The [firebaseAuth] is used to create an instance of [FirebaseAuth]. 17 | final auth.FirebaseAuth _firebaseAuth; 18 | 19 | /// The [googleSignIn] is used to create an instance of [GoogleSignIn]. 20 | final GoogleSignIn _googleSignIn; 21 | 22 | /// The [user] method is used to get user. 23 | @override 24 | Stream get userStream => _firebaseAuth.authStateChanges(); 25 | 26 | /// The [currentUser] method is used to get the current user. 27 | /// It returns the current user. 28 | /// If the user is not logged in, it returns null. 29 | /// If the user is logged in, it returns the current user. 30 | /// If there is an error, it throws an exception. 31 | @override 32 | auth.User? get currentUser => _firebaseAuth.currentUser; 33 | 34 | /// The [signInWithGoogle] method is used to sign up with google. 35 | /// It returns the user. 36 | @override 37 | Future signInWithGoogle() async { 38 | try { 39 | final googleUser = await _googleSignIn.signIn(); 40 | final googleAuth = await googleUser!.authentication; 41 | 42 | final credential = auth.GoogleAuthProvider.credential( 43 | accessToken: googleAuth.accessToken, 44 | idToken: googleAuth.idToken, 45 | ); 46 | 47 | final userCredential = 48 | await _firebaseAuth.signInWithCredential(credential); 49 | 50 | debugPrint('userCredential: ${userCredential.user}'); 51 | 52 | return userCredential.user; 53 | } on auth.FirebaseAuthException catch (e) { 54 | debugPrint('FirebaseAuthException: ${e.code}'); 55 | throw SignInWithGoogleFailure.fromCode(e.code); 56 | } catch (e) { 57 | debugPrint('FirebaseAuthException: $e'); 58 | throw const SignInWithGoogleFailure(); 59 | } 60 | } 61 | 62 | /// The [signOut] method is used to sign out. 63 | @override 64 | Future signOut() async { 65 | await _firebaseAuth.signOut(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Daily Expense Tracker App 💰 2 | 3 | Manage your expenses effortlessly with this intuitive app.📱Track daily spending, set budgets, and stay on top of your finances with ease. 💳💡 4 | 5 | **Star ⭐ the repo if you like what you see 😉.** 6 | 7 | 8 | ## 📱 Screenshots 9 | ![App Mockups 45](https://github.com/haithemnini/daily_expense_tracker_app/assets/88515475/0e422cd7-cf38-48c5-bc8b-bf89fba69818) 10 | 11 | ## 📌 Features 12 | - Expense Tracking: 📊 Easily monitor daily spending habits. 13 | - Expense Analytics: 📊 Dive deep into expense analytics for detailed insights. 14 | - Expense Categories Icons: 🖼️ Customize categories with visually appealing icons. 15 | - Transaction History: 📜 Keep a record of all transactions. 16 | - Offline Mode: 📴 Access and update expenses even without an internet connection. 17 | - Cloud Sync: ☁️ Sync data across devices for seamless access. 18 | - Expense Insights: 💡 Gain valuable insights into your spending behavior to make informed financial decisions. 19 | - Goal Tracking: 🎯 Set and track financial goals to achieve better financial health. 20 | - Security: 🔒 Ensure the security of your financial data with robust encryption and privacy measures. 21 | 22 | ## 📦 Package Structure 23 | DAILY_EXPENSE_TRACKER_APP 24 | ├── assets/ 25 | │ ├── icons/ 26 | │ ├── images/ 27 | │ └── svgs/ 28 | ├── lib/ 29 | │ ├── app/ 30 | │ ├── core/ (Utility Functions and Classes) 31 | │ ├── features/ 32 | │ │ ├── bloc 33 | │ │ ├── auth_bloc/ (Authentication BLoC Code) 34 | │ │ ├── auth_bloc/ (Authentication BLoC Code) 35 | │ │ ├── main_bloc/ (Main BLoC Code) 36 | │ │ ├── profile_bloc/ (Profile BLoC Code) 37 | │ │ ├── state_bloc/ (State BLoC Code) 38 | │ │ ├── themes_bloc/ (Themes BLoC Code) 39 | │ │ └── transaction_bloc/ (Transaction BLoC Code) 40 | │ │ ├── home 41 | │ │ ├── data 42 | │ │ ├── view 43 | │ │ ├── profile 44 | │ │ ├── data 45 | │ │ ├── view 46 | │ │ ├── settings 47 | │ │ ├── data 48 | │ │ ├── view 49 | │ │ ├── transaction 50 | │ │ ├── data 51 | │ │ ├── view 52 | │ ├── bloc_observer.dart (BLoC Observer Code) 53 | │ └── main.dart (Application Entry Point) 54 | └── packages/ (Third-Party Dependencies) 55 | 56 | **Feel free to contribute and enhance the app! 🚀** 57 | 58 | 59 | -------------------------------------------------------------------------------- /lib/features/transaction/view/transaction_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../core/router/router.dart'; 5 | import '../../../core/shared/custom_material_button.dart'; 6 | import '../../../core/shared/shared.dart'; 7 | import '../../../core/utils/alerts/alerts.dart'; 8 | import '../../blocs/transaction_bloc/transaction_cubit.dart'; 9 | import 'widgets/widgets.dart'; 10 | 11 | class TransactionView extends StatelessWidget { 12 | const TransactionView({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | appBar: const CustomAppBar(), 18 | body: _buildBody(context), 19 | ); 20 | } 21 | 22 | Widget _buildBody(BuildContext context) { 23 | return Padding( 24 | padding: const EdgeInsets.symmetric(horizontal: 25), 25 | child: SafeArea( 26 | child: CustomScrollView( 27 | physics: const BouncingScrollPhysics(), 28 | slivers: [ 29 | SliverFillRemaining( 30 | hasScrollBody: false, 31 | child: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | crossAxisAlignment: CrossAxisAlignment.center, 34 | children: [ 35 | BlocListener( 36 | listener: (context, state) { 37 | state.maybeWhen( 38 | loading: () => Alerts.showLoaderDialog(context), 39 | success: (message) => _success(context, message), 40 | error: (message) { 41 | Alerts.showToastMsg(context, message); 42 | }, 43 | orElse: () => {}, 44 | ); 45 | }, 46 | child: const SizedBox.shrink(), 47 | ), 48 | const Spacer(), 49 | const SizedBox(height: 30), 50 | const TransactionForm(), 51 | const Spacer(), 52 | CustomMaterialButton( 53 | text: 'SAVE', 54 | onPressed: () => context 55 | .read() 56 | .addOrUpdateTransaction(), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ], 62 | ), 63 | ), 64 | ); 65 | } 66 | 67 | _success(BuildContext context, String message) { 68 | Alerts.hideLoaderDialog(context); 69 | Alerts.showToastMsg(context, message); 70 | context.pop(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/core/shared/custom_text_form_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pattern_formatter/pattern_formatter.dart'; 3 | 4 | import '../extension/extension.dart'; 5 | 6 | class CustomTextFormField extends StatelessWidget { 7 | const CustomTextFormField({ 8 | super.key, 9 | this.maxLines, 10 | required this.hintText, 11 | required this.fontSize, 12 | required this.textAlign, 13 | required this.fontWeight, 14 | required this.keyboardType, 15 | required this.controller, 16 | this.contentPadding, 17 | this.border, 18 | this.prefixIcon, 19 | this.enabledBorder, 20 | this.focusedBorder, 21 | this.errorBorder, 22 | this.prefixText, 23 | this.validator, 24 | }); 25 | 26 | final int? maxLines; 27 | final Widget? prefixIcon; 28 | final String hintText; 29 | final double fontSize; 30 | final TextAlign textAlign; 31 | final FontWeight fontWeight; 32 | final TextEditingController controller; 33 | final TextInputType keyboardType; 34 | final EdgeInsetsGeometry? contentPadding; 35 | final InputBorder? border; 36 | final InputBorder? enabledBorder; 37 | final InputBorder? focusedBorder; 38 | final InputBorder? errorBorder; 39 | final String? prefixText; 40 | final Function(String?)? validator; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return TextFormField( 45 | maxLines: maxLines, 46 | textAlign: textAlign, 47 | controller: controller, 48 | autovalidateMode: AutovalidateMode.onUserInteraction, 49 | onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), 50 | decoration: InputDecoration( 51 | prefixIcon: prefixIcon, 52 | prefixText: prefixText, 53 | isDense: true, 54 | hintText: hintText, 55 | hintStyle: TextStyle( 56 | fontSize: fontSize, 57 | color: context.colorScheme.outline, 58 | fontWeight: fontWeight, 59 | ), 60 | border: border ?? InputBorder.none, 61 | contentPadding: contentPadding ?? EdgeInsets.zero, 62 | enabledBorder: enabledBorder ?? InputBorder.none, 63 | focusedBorder: focusedBorder ?? InputBorder.none, 64 | errorBorder: errorBorder ?? InputBorder.none, 65 | ), 66 | style: TextStyle( 67 | fontSize: fontSize, 68 | fontWeight: fontWeight, 69 | color: context.colorScheme.onSurface, 70 | ), 71 | keyboardType: keyboardType, 72 | inputFormatters: [ 73 | if (keyboardType == TextInputType.number) 74 | ThousandsFormatter(allowFraction: true) 75 | ], 76 | validator: (value) => validator?.call(value), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | def keystoreProperties = new Properties() 25 | def keystorePropertiesFile = rootProject.file('key.properties') 26 | if (keystorePropertiesFile.exists()) { 27 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 28 | } 29 | 30 | android { 31 | namespace "com.example.daily_expense_tracker_app" 32 | compileSdkVersion flutter.compileSdkVersion 33 | ndkVersion flutter.ndkVersion 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | 40 | kotlinOptions { 41 | jvmTarget = '1.8' 42 | } 43 | 44 | sourceSets { 45 | main.java.srcDirs += 'src/main/kotlin' 46 | } 47 | 48 | defaultConfig { 49 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 50 | applicationId "com.example.daily_expense_tracker_app" 51 | // You can update the following values to match your application needs. 52 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 53 | minSdkVersion flutter.minSdkVersion 54 | targetSdkVersion flutter.targetSdkVersion 55 | versionCode flutterVersionCode.toInteger() 56 | versionName flutterVersionName 57 | multiDexEnabled true 58 | } 59 | 60 | signingConfigs { 61 | release { 62 | keyAlias keystoreProperties['keyAlias'] 63 | keyPassword keystoreProperties['keyPassword'] 64 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 65 | storePassword keystoreProperties['storePassword'] 66 | } 67 | } 68 | buildTypes { 69 | release { 70 | signingConfig signingConfigs.release 71 | } 72 | } 73 | 74 | } 75 | 76 | flutter { 77 | source '../..' 78 | } 79 | 80 | dependencies {} 81 | -------------------------------------------------------------------------------- /lib/features/blocs/profile_bloc/profile_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | import 'package:user_service/user_service.dart'; 7 | 8 | import '../../../../core/service/network_info.dart'; 9 | import '../../profile/data/profile_repository/profile_base_repository.dart'; 10 | 11 | part 'profile_cubit.freezed.dart'; 12 | part 'profile_state.dart'; 13 | 14 | class ProfileCubit extends Cubit { 15 | ProfileCubit({ 16 | required ProfileBaseRepository profileRepository, 17 | required NetworkBaseInfo networkInfo, 18 | }) : _profileRepository = profileRepository, 19 | _networkInfo = networkInfo, 20 | super(const ProfileState.initial()); 21 | 22 | final NetworkBaseInfo _networkInfo; 23 | final ProfileBaseRepository _profileRepository; 24 | 25 | /// The [GlobalKey] is used to identify the [Form] and 26 | final formKey = GlobalKey(); 27 | 28 | /// The [TextEditingController] is used to control the text being edited. 29 | final TextEditingController fullNameController = TextEditingController(); 30 | 31 | /// The [User] class is used to manage the user. 32 | User currentUser = User.empty(); 33 | 34 | User updatedUser = User.empty(); 35 | 36 | File? selectedImage; 37 | 38 | void init(User user) { 39 | fullNameController.text = user.fullName; 40 | currentUser = user; 41 | } 42 | 43 | void updateProfile() async { 44 | if (!formKey.currentState!.validate()) return; 45 | 46 | User updatedUser = currentUser.copyWith( 47 | photoUrl: selectedImage?.path ?? currentUser.photoUrl, 48 | fullName: fullNameController.text.trim(), 49 | ); 50 | 51 | if (updatedUser != currentUser) { 52 | emit(const ProfileState.loading()); 53 | 54 | if (!await _networkInfo.isConnected) { 55 | return emit( 56 | const ProfileState.error('No internet connection available'), 57 | ); 58 | } 59 | 60 | try { 61 | if (selectedImage != null) { 62 | final uploadPicture = await _profileRepository.updateUserPicture( 63 | user: currentUser, 64 | imagePath: selectedImage!.path, 65 | ); 66 | updatedUser = updatedUser.copyWith(photoUrl: uploadPicture); 67 | } 68 | 69 | await _profileRepository.updateProfile(user: updatedUser); 70 | 71 | selectedImage = null; 72 | return emit(const ProfileState.success('Profile updated successfully')); 73 | } catch (err) { 74 | return emit(ProfileState.error(err.toString())); 75 | } 76 | } else { 77 | debugPrint('No changes'); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/core/models/transaction_hive_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'transaction_hive_model.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class TransactionHiveAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 0; 12 | 13 | @override 14 | TransactionHive read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return TransactionHive( 20 | uuid: fields[0] as String, 21 | userId: fields[1] as String?, 22 | date: fields[3] as DateTime, 23 | amount: fields[2] as double, 24 | categorysIndex: fields[4] as int, 25 | category: fields[5] as CategoryHive, 26 | ); 27 | } 28 | 29 | @override 30 | void write(BinaryWriter writer, TransactionHive obj) { 31 | writer 32 | ..writeByte(6) 33 | ..writeByte(0) 34 | ..write(obj.uuid) 35 | ..writeByte(1) 36 | ..write(obj.userId) 37 | ..writeByte(2) 38 | ..write(obj.amount) 39 | ..writeByte(3) 40 | ..write(obj.date) 41 | ..writeByte(4) 42 | ..write(obj.categorysIndex) 43 | ..writeByte(5) 44 | ..write(obj.category); 45 | } 46 | 47 | @override 48 | int get hashCode => typeId.hashCode; 49 | 50 | @override 51 | bool operator ==(Object other) => 52 | identical(this, other) || 53 | other is TransactionHiveAdapter && 54 | runtimeType == other.runtimeType && 55 | typeId == other.typeId; 56 | } 57 | 58 | class CategoryHiveAdapter extends TypeAdapter { 59 | @override 60 | final int typeId = 1; 61 | 62 | @override 63 | CategoryHive read(BinaryReader reader) { 64 | switch (reader.readByte()) { 65 | case 0: 66 | return CategoryHive.expense; 67 | case 1: 68 | return CategoryHive.income; 69 | default: 70 | return CategoryHive.expense; 71 | } 72 | } 73 | 74 | @override 75 | void write(BinaryWriter writer, CategoryHive obj) { 76 | switch (obj) { 77 | case CategoryHive.expense: 78 | writer.writeByte(0); 79 | break; 80 | case CategoryHive.income: 81 | writer.writeByte(1); 82 | break; 83 | } 84 | } 85 | 86 | @override 87 | int get hashCode => typeId.hashCode; 88 | 89 | @override 90 | bool operator ==(Object other) => 91 | identical(this, other) || 92 | other is CategoryHiveAdapter && 93 | runtimeType == other.runtimeType && 94 | typeId == other.typeId; 95 | } 96 | -------------------------------------------------------------------------------- /lib/core/models/transaction_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | import '../enum/enum.dart'; 5 | import 'transaction_hive_model.dart'; 6 | 7 | part 'transaction_model.freezed.dart'; 8 | part 'transaction_model.g.dart'; 9 | 10 | @freezed 11 | class Transaction with _$Transaction { 12 | const factory Transaction({ 13 | required String? uuid, 14 | required String? userId, 15 | required double amount, 16 | @TimestampConverter() required DateTime date, 17 | required int categorysIndex, 18 | required Category category, 19 | }) = _Transaction; 20 | 21 | factory Transaction.fromJson(Map json) => 22 | _$TransactionFromJson(json); 23 | 24 | factory Transaction.empty() { 25 | return Transaction( 26 | uuid: '', 27 | userId: '', 28 | amount: 0.0, 29 | date: DateTime.now(), 30 | categorysIndex: 0, 31 | category: Category.expense, 32 | ); 33 | } 34 | 35 | factory Transaction.fromHiveModel(TransactionHive transactionHive) { 36 | return Transaction( 37 | uuid: transactionHive.uuid, 38 | userId: transactionHive.userId, 39 | amount: transactionHive.amount, 40 | date: transactionHive.date, 41 | categorysIndex: transactionHive.categorysIndex, 42 | category: transactionHive.category == CategoryHive.expense 43 | ? Category.expense 44 | : Category.income, 45 | ); 46 | } 47 | } 48 | 49 | class TimestampConverter implements JsonConverter { 50 | const TimestampConverter(); 51 | 52 | @override 53 | DateTime fromJson(Timestamp value) => value.toDate(); 54 | 55 | @override 56 | Timestamp toJson(DateTime value) => Timestamp.fromDate(value); 57 | } 58 | 59 | extension TransactionTotalsExtension on List { 60 | Transaction toCalcTotals() { 61 | return Transaction( 62 | uuid: '', 63 | userId: '', 64 | amount: fold( 65 | 0, 66 | (previousValue, element) { 67 | if (element.category == Category.expense) { 68 | return previousValue - element.amount; 69 | } else { 70 | return previousValue + element.amount; 71 | } 72 | }, 73 | ), 74 | date: DateTime.now(), 75 | categorysIndex: 0, 76 | category: Category.expense, 77 | ); 78 | } 79 | } 80 | 81 | extension TransactionExtension on Transaction { 82 | TransactionHive toHiveModel() { 83 | return TransactionHive( 84 | uuid: uuid!, 85 | userId: userId ?? '', 86 | amount: amount, 87 | date: date, 88 | categorysIndex: categorysIndex, 89 | category: category == Category.expense 90 | ? CategoryHive.expense 91 | : CategoryHive.income, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/auth_user/lib/src/core/exception/firebase_exception.dart: -------------------------------------------------------------------------------- 1 | /// Exception thrown when there is a failure during sign in with Google. 2 | class SignInWithGoogleFailure implements Exception { 3 | final String message; 4 | 5 | const SignInWithGoogleFailure([ 6 | this.message = 'Unknown error occurred.', 7 | ]); 8 | 9 | factory SignInWithGoogleFailure.fromCode(String code) { 10 | switch (code) { 11 | case 'account-exists-with-different-credential': 12 | return const SignInWithGoogleFailure( 13 | 'An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address.', 14 | ); 15 | case 'invalid-credential': 16 | return const SignInWithGoogleFailure( 17 | 'The credential data is malformed or has expired.', 18 | ); 19 | case 'operation-not-allowed': 20 | return const SignInWithGoogleFailure( 21 | 'Google sign-in is not enabled. Please enable Google sign in on the Firebase console.', 22 | ); 23 | case 'user-disabled': 24 | return const SignInWithGoogleFailure( 25 | 'This user has been disabled. Please contact support for help.', 26 | ); 27 | case 'user-not-found': 28 | return const SignInWithGoogleFailure( 29 | 'No user found for that email.', 30 | ); 31 | case 'wrong-password': 32 | return const SignInWithGoogleFailure( 33 | 'Wrong password. Please try again.', 34 | ); 35 | case 'invalid-verification-code': 36 | return const SignInWithGoogleFailure( 37 | 'The verification code is invalid. Please try again.', 38 | ); 39 | case 'invalid-verification-id': 40 | return const SignInWithGoogleFailure( 41 | 'The verification ID is invalid. Please try again.', 42 | ); 43 | 44 | case 'invalid-email': 45 | return const SignInWithGoogleFailure( 46 | 'Email is not valid or badly formatted.', 47 | ); 48 | case 'network-request-failed': 49 | return const SignInWithGoogleFailure( 50 | 'Please check your internet connection and try again.', 51 | ); 52 | 53 | case 'too-many-requests': 54 | return const SignInWithGoogleFailure( 55 | 'Too many requests. Please try again later.', 56 | ); 57 | 58 | case 'invalid-email-verified': 59 | return const SignInWithGoogleFailure( 60 | 'Email is not valid or badly formatted.', 61 | ); 62 | 63 | case 'sign_up_failed': 64 | return const SignInWithGoogleFailure( 65 | 'Sign in failed. Please try again.', 66 | ); 67 | 68 | default: 69 | return const SignInWithGoogleFailure(); 70 | } 71 | } 72 | @override 73 | String toString() => message; 74 | } 75 | -------------------------------------------------------------------------------- /lib/features/home/view/all_view_transaction.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../core/enum/enum.dart'; 5 | import '../../../core/router/router.dart'; 6 | import '../../../core/shared/shared.dart'; 7 | import '../../blocs/main_bloc/main_cubit.dart'; 8 | import '../../blocs/transaction_bloc/transaction_cubit.dart'; 9 | 10 | class AllViewTransaction extends StatefulWidget { 11 | const AllViewTransaction({super.key}); 12 | 13 | @override 14 | State createState() => _AllViewTransactionState(); 15 | } 16 | 17 | class _AllViewTransactionState extends State { 18 | @override 19 | void initState() { 20 | super.initState(); 21 | context.read().getAll(TypeShow.all); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | appBar: CustomAppBar( 28 | title: 'All Transaction', 29 | onPressed: () => _onRefresh(), 30 | ), 31 | body: _buidBody(), 32 | ); 33 | } 34 | 35 | Widget _buidBody() { 36 | return PopScope( 37 | canPop: true, 38 | onPopInvoked: (_) => { 39 | context.read().getAll(TypeShow.limit), 40 | context.read().getTotals(), 41 | }, 42 | child: Padding( 43 | padding: const EdgeInsets.symmetric(horizontal: 25), 44 | child: SafeArea( 45 | bottom: false, 46 | child: Column( 47 | mainAxisAlignment: MainAxisAlignment.center, 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | BlocListener( 51 | listenWhen: (previous, current) => current is Success, 52 | listener: (_, state) => state.maybeWhen( 53 | success: (_) => _success(context), 54 | orElse: () => {}, 55 | ), 56 | child: const SizedBox.shrink(), 57 | ), 58 | BlocBuilder( 59 | buildWhen: (previous, current) => current is Loaded, 60 | builder: (_, state) => state.maybeWhen( 61 | loadedAll: (transactions) => TransactionList( 62 | allTransactions: transactions, 63 | ), 64 | orElse: () => 65 | const Center(child: CircularProgressIndicator()), 66 | ), 67 | ), 68 | ], 69 | ), 70 | ), 71 | ), 72 | ); 73 | } 74 | 75 | void _onRefresh() { 76 | context.read().getAll(TypeShow.limit); 77 | context.read().getTotals(); 78 | 79 | return context.pop(); 80 | } 81 | 82 | void _success(BuildContext context) { 83 | context.read().getAll(TypeShow.all); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/profile_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../../../core/extension/extension.dart'; 7 | import '../../../../core/helper/helper.dart'; 8 | import '../../../../core/styles/app_text_style.dart'; 9 | import '../../../blocs/auth_bloc/auth_cubit.dart'; 10 | 11 | class ProfileIcon extends StatelessWidget { 12 | const ProfileIcon({ 13 | super.key, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return BlocBuilder( 19 | buildWhen: (previous, current) => current.maybeMap( 20 | authChanged: (state) => true, 21 | orElse: () => false, 22 | ), 23 | builder: (context, state) { 24 | final user = state.maybeMap( 25 | authChanged: (state) => state.user, 26 | orElse: () => null, 27 | ); 28 | 29 | return Row( 30 | mainAxisAlignment: MainAxisAlignment.start, 31 | crossAxisAlignment: CrossAxisAlignment.center, 32 | children: [ 33 | Column( 34 | mainAxisAlignment: MainAxisAlignment.center, 35 | crossAxisAlignment: CrossAxisAlignment.center, 36 | children: [ 37 | ClipOval( 38 | child: user?.photoUrl != null 39 | ? CachedNetworkImage( 40 | imageUrl: user!.photoUrl!, 41 | height: 40, 42 | width: 40, 43 | fit: BoxFit.cover, 44 | ) 45 | : Image.asset( 46 | Helper.getAssetImage('guest.png'), 47 | height: 40, 48 | width: 40, 49 | fit: BoxFit.cover, 50 | ), 51 | ), 52 | ], 53 | ), 54 | const SizedBox(width: 8), 55 | Column( 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | Text( 60 | 'Welcome !', 61 | style: AppTextStyle.caption.copyWith( 62 | color: context.colorScheme.outline, 63 | fontWeight: FontWeight.w400, 64 | ), 65 | ), 66 | Text( 67 | user?.fullName ?? 'Guest', 68 | style: AppTextStyle.subtitle.copyWith( 69 | color: context.colorScheme.onBackground, 70 | fontWeight: FontWeight.w600, 71 | ), 72 | ), 73 | ], 74 | ), 75 | ], 76 | ); 77 | }, 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/summary_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../core/extension/extension.dart'; 5 | import '../../../../core/models/transaction_model.dart'; 6 | import '../../../../core/styles/app_text_style.dart'; 7 | import '../../../blocs/state_bloc/state_cubit.dart'; 8 | import 'widgets.dart'; 9 | 10 | class SummaryCard extends StatelessWidget { 11 | const SummaryCard({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return BlocBuilder( 16 | builder: (context, state) { 17 | final startDate = state.maybeMap( 18 | initial: (state) => context.read().startDate, 19 | dateChanged: (state) => state.startDate, 20 | orElse: () => context.read().startDate, 21 | ); 22 | 23 | final endDate = state.maybeMap( 24 | initial: (state) => context.read().endDate, 25 | dateChanged: (state) => state.endDate, 26 | orElse: () => context.read().endDate, 27 | ); 28 | 29 | final List? allTransactions = state.mapOrNull( 30 | loaded: (state) => state.transactions, 31 | ); 32 | 33 | return Container( 34 | height: context.screenHeight(0.4), 35 | width: context.screenWidth(0.9), 36 | decoration: BoxDecoration( 37 | color: context.colorScheme.surface, 38 | borderRadius: BorderRadius.circular(16), 39 | ), 40 | child: Column( 41 | children: [ 42 | const SizedBox(height: 10.0), 43 | Text( 44 | { 45 | endDate.formattedDateOnly, 46 | startDate.formattedDateOnly, 47 | }.join(' - '), 48 | style: AppTextStyle.caption.copyWith( 49 | fontWeight: FontWeight.w600, 50 | ), 51 | ), 52 | const SizedBox(height: 10.0), 53 | Text( 54 | allTransactions?.toCalcTotals().amount.toCurrencyWithSymbol() ?? 55 | '0.00', 56 | style: AppTextStyle.title3, 57 | ), 58 | const SizedBox(height: 10.0), 59 | BlocBuilder( 60 | builder: (context, state) { 61 | return state.maybeMap( 62 | loaded: (loaded) => StateBarChart( 63 | chartData: loaded.chartData, 64 | ), 65 | loading: (_) => StateBarChart( 66 | chartData: {DateTime.now(): 0.0}, 67 | ), 68 | error: (error) => Center(child: Text(error.message)), 69 | orElse: () => const SizedBox.shrink(), 70 | ); 71 | }, 72 | ) 73 | ], 74 | ), 75 | ); 76 | }, 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/core/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // File generated by FlutterFire CLI. 2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members 3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 4 | import 'package:flutter/foundation.dart' 5 | show defaultTargetPlatform, kIsWeb, TargetPlatform; 6 | 7 | /// Default [FirebaseOptions] for use with your Firebase apps. 8 | /// 9 | /// Example: 10 | /// ```dart 11 | /// import 'firebase_options.dart'; 12 | /// // ... 13 | /// await Firebase.initializeApp( 14 | /// options: DefaultFirebaseOptions.currentPlatform, 15 | /// ); 16 | /// ``` 17 | class DefaultFirebaseOptions { 18 | static FirebaseOptions get currentPlatform { 19 | if (kIsWeb) { 20 | return web; 21 | } 22 | switch (defaultTargetPlatform) { 23 | case TargetPlatform.android: 24 | return android; 25 | case TargetPlatform.iOS: 26 | return ios; 27 | case TargetPlatform.macOS: 28 | return macos; 29 | case TargetPlatform.windows: 30 | throw UnsupportedError( 31 | 'DefaultFirebaseOptions have not been configured for windows - ' 32 | 'you can reconfigure this by running the FlutterFire CLI again.', 33 | ); 34 | case TargetPlatform.linux: 35 | throw UnsupportedError( 36 | 'DefaultFirebaseOptions have not been configured for linux - ' 37 | 'you can reconfigure this by running the FlutterFire CLI again.', 38 | ); 39 | default: 40 | throw UnsupportedError( 41 | 'DefaultFirebaseOptions are not supported for this platform.', 42 | ); 43 | } 44 | } 45 | 46 | static const FirebaseOptions web = FirebaseOptions( 47 | apiKey: 'AIzaSyDbc02PXK_mIy6zc7gxDIE-CBzgLJwcvLo', 48 | appId: '1:800188863112:web:e384020e1d1c58a85f053d', 49 | messagingSenderId: '800188863112', 50 | projectId: 'daily-expense-tracker-123e1', 51 | authDomain: 'daily-expense-tracker-123e1.firebaseapp.com', 52 | storageBucket: 'daily-expense-tracker-123e1.appspot.com', 53 | measurementId: 'G-M8LCZRBLQS', 54 | ); 55 | 56 | static const FirebaseOptions android = FirebaseOptions( 57 | apiKey: 'AIzaSyDC7TRqG_rifjnlAh1UHPrcB3tmVt979WQ', 58 | appId: '1:800188863112:android:fe4ed12bb40ba62b5f053d', 59 | messagingSenderId: '800188863112', 60 | projectId: 'daily-expense-tracker-123e1', 61 | storageBucket: 'daily-expense-tracker-123e1.appspot.com', 62 | ); 63 | 64 | static const FirebaseOptions ios = FirebaseOptions( 65 | apiKey: 'AIzaSyA_8iWOFNHMo-36B866eEJFyW279aiZfOM', 66 | appId: '1:800188863112:ios:0923cbc2198d87dc5f053d', 67 | messagingSenderId: '800188863112', 68 | projectId: 'daily-expense-tracker-123e1', 69 | storageBucket: 'daily-expense-tracker-123e1.appspot.com', 70 | iosBundleId: 'com.example.dailyExpenseTrackerApp', 71 | ); 72 | 73 | static const FirebaseOptions macos = FirebaseOptions( 74 | apiKey: 'AIzaSyA_8iWOFNHMo-36B866eEJFyW279aiZfOM', 75 | appId: '1:800188863112:ios:0b1eb9f3793afcf15f053d', 76 | messagingSenderId: '800188863112', 77 | projectId: 'daily-expense-tracker-123e1', 78 | storageBucket: 'daily-expense-tracker-123e1.appspot.com', 79 | iosBundleId: 'com.example.dailyExpenseTrackerApp.RunnerTests', 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /lib/features/profile/view/widgets/profile_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | import 'package:user_service/user_service.dart'; 5 | 6 | import '../../../../core/extension/extension.dart'; 7 | import '../../../../core/shared/shared.dart'; 8 | import '../../../../core/styles/app_text_style.dart'; 9 | import '../../../blocs/profile_bloc/profile_cubit.dart'; 10 | import 'widgets.dart'; 11 | 12 | class ProfileForm extends StatefulWidget { 13 | const ProfileForm({super.key}); 14 | 15 | @override 16 | State createState() => _ProfileFormState(); 17 | } 18 | 19 | class _ProfileFormState extends State { 20 | late User user; 21 | 22 | @override 23 | void initState() { 24 | user = context.read().currentUser; 25 | super.initState(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Form( 31 | key: context.read().formKey, 32 | child: Column( 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | crossAxisAlignment: CrossAxisAlignment.center, 35 | children: [ 36 | const SizedBox(height: 20), 37 | ProfileImage(user: user), 38 | const SizedBox(height: 20), 39 | Text( 40 | user.fullName, 41 | style: AppTextStyle.title.copyWith( 42 | fontWeight: FontWeight.w600, 43 | ), 44 | ), 45 | const SizedBox(height: 20), 46 | CustomTextFormField( 47 | maxLines: 1, 48 | fontSize: 16, 49 | hintText: 'exp: John Doe', 50 | controller: context.read().fullNameController, 51 | textAlign: TextAlign.start, 52 | fontWeight: FontWeight.w400, 53 | keyboardType: TextInputType.name, 54 | prefixIcon: const Icon(FontAwesomeIcons.userLarge, size: 12), 55 | border: OutlineInputBorder( 56 | borderRadius: BorderRadius.circular(18.0), 57 | borderSide: const BorderSide(width: 1.2), 58 | ), 59 | contentPadding: const EdgeInsets.symmetric( 60 | horizontal: 20, 61 | vertical: 15, 62 | ), 63 | enabledBorder: OutlineInputBorder( 64 | borderRadius: BorderRadius.circular(18.0), 65 | borderSide: BorderSide( 66 | color: context.colorScheme.outline, 67 | width: 1.2, 68 | ), 69 | ), 70 | focusedBorder: OutlineInputBorder( 71 | borderRadius: BorderRadius.circular(18.0), 72 | borderSide: BorderSide( 73 | color: context.colorScheme.primary, 74 | width: 1.2, 75 | ), 76 | ), 77 | errorBorder: OutlineInputBorder( 78 | borderRadius: BorderRadius.circular(18.0), 79 | borderSide: BorderSide( 80 | color: context.colorScheme.error, 81 | width: 1.2, 82 | ), 83 | ), 84 | validator: (value) { 85 | if (value!.isEmpty) { 86 | return 'Full name cannot be empty'; 87 | } 88 | return null; 89 | }, 90 | ), 91 | ], 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/db_hive_client/lib/src/db_hive_client.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hive_flutter/adapters.dart'; 3 | 4 | import '../db_hive_client.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | 7 | class DbHiveClient implements DbHiveClientBase { 8 | @override 9 | Future initDb({ 10 | required String boxName, 11 | required VoidCallback onRegisterAdapter, 12 | }) async { 13 | try { 14 | final appDocumentDir = await getApplicationDocumentsDirectory(); 15 | Hive.init(appDocumentDir.path); 16 | onRegisterAdapter(); 17 | await Hive.openBox(boxName); 18 | return true; 19 | } catch (err) { 20 | throw Exception('Failed to init db: $err'); 21 | } 22 | } 23 | 24 | @override 25 | Future> getAll({ 26 | required String boxName, 27 | }) async { 28 | try { 29 | final box = Hive.box(boxName); 30 | final result = box.values.toList(); 31 | return Future.value(result); 32 | } catch (err) { 33 | throw Exception('Failed to get all: $err'); 34 | } 35 | } 36 | 37 | @override 38 | Future getById({ 39 | required String modelId, 40 | required String boxName, 41 | }) { 42 | try { 43 | final box = Hive.box(boxName); 44 | return Future.value(box.get(modelId)); 45 | } catch (err) { 46 | throw Exception('Failed to get by id: $err'); 47 | } 48 | } 49 | 50 | @override 51 | Stream> watchAll({ 52 | required String boxName, 53 | }) { 54 | try { 55 | final box = Hive.box(boxName); 56 | return box.watch().map((event) => box.values.toList()); 57 | } catch (err) { 58 | throw Exception('Failed to watch all: $err'); 59 | } 60 | } 61 | 62 | @override 63 | Future add({ 64 | required String boxName, 65 | required String modelId, 66 | required T modelHive, 67 | }) { 68 | try { 69 | final box = Hive.box(boxName); 70 | box.put(modelId, modelHive); 71 | return Future.value(); 72 | } catch (err) { 73 | throw Exception('Failed to add: $err'); 74 | } 75 | } 76 | 77 | @override 78 | Future update({ 79 | required String modelId, 80 | required String boxName, 81 | required T modelHive, 82 | }) { 83 | try { 84 | final box = Hive.box(boxName); 85 | box.put(modelId, modelHive); 86 | return Future.value(); 87 | } catch (err) { 88 | throw Exception('Failed to update: $err'); 89 | } 90 | } 91 | 92 | @override 93 | Future delete({ 94 | required String modelId, 95 | required String boxName, 96 | }) { 97 | try { 98 | final box = Hive.box(boxName); 99 | box.delete(modelId); 100 | return Future.value(); 101 | } catch (err) { 102 | throw Exception('Failed to delete: $err'); 103 | } 104 | } 105 | 106 | @override 107 | Future clearAll({ 108 | required String boxName, 109 | }) { 110 | try { 111 | final box = Hive.box(boxName); 112 | box.clear(); 113 | 114 | return Future.value(); 115 | } catch (err) { 116 | throw Exception('Failed to clear all: $err'); 117 | } 118 | } 119 | 120 | @override 121 | Future closeDb() { 122 | try { 123 | Hive.close(); 124 | return Future.value(); 125 | } catch (err) { 126 | throw Exception('Failed to close db: $err'); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/features/home/view/widgets/filter_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | 5 | import '../../../../core/extension/extension.dart'; 6 | import '../../../../core/shared/shared.dart'; 7 | import '../../../../core/styles/app_text_style.dart'; 8 | import '../../../../core/utils/alerts/alerts.dart'; 9 | import '../../../blocs/state_bloc/state_cubit.dart'; 10 | 11 | class FilterForm extends StatefulWidget { 12 | const FilterForm({super.key}); 13 | 14 | @override 15 | State createState() => _FilterFormState(); 16 | } 17 | 18 | class _FilterFormState extends State { 19 | late DateTime startDate; 20 | late DateTime endDate; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | startDate = context.read().startDate; 26 | endDate = context.read().endDate; 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | const iconSize = 16.0; 32 | const iconItemHeight = 35.0; 33 | const iconItemWidth = 35.0; 34 | const padding = EdgeInsets.symmetric(horizontal: 12, vertical: 15); 35 | final backgroundItem = context.colorScheme.surface; 36 | 37 | return Column( 38 | children: [ 39 | _buidlHeaderText('Start Date'), 40 | const SizedBox(height: 10), 41 | CustomItemButton( 42 | text: startDate.formattedDateOnly, 43 | padding: padding, 44 | iconSize: iconSize, 45 | iconColor: Colors.white, 46 | iconItemWidth: iconItemWidth, 47 | iconItemHeight: iconItemHeight, 48 | backgroundIcon: context.colorScheme.outline, 49 | backgroundItem: backgroundItem, 50 | icon: FontAwesomeIcons.wallet, 51 | onPressed: () => _showPickeDate(context, startDate, true), 52 | ), 53 | const SizedBox(height: 15), 54 | _buidlHeaderText('End Date'), 55 | const SizedBox(height: 10), 56 | CustomItemButton( 57 | text: endDate.formattedDateOnly, 58 | padding: padding, 59 | iconSize: iconSize, 60 | iconColor: Colors.white, 61 | iconItemWidth: iconItemWidth, 62 | iconItemHeight: iconItemHeight, 63 | backgroundIcon: context.colorScheme.outline, 64 | backgroundItem: backgroundItem, 65 | icon: FontAwesomeIcons.wallet, 66 | onPressed: () => _showPickeDate(context, endDate, false), 67 | ), 68 | const SizedBox(height: 30), 69 | ], 70 | ); 71 | } 72 | 73 | Widget _buidlHeaderText(String title) { 74 | return Row(children: [ 75 | const SizedBox(width: 8), 76 | Text(title, style: AppTextStyle.body) 77 | ]); 78 | } 79 | 80 | Future _showPickeDate( 81 | BuildContext context, 82 | DateTime initialDate, 83 | bool isStartDate, 84 | ) { 85 | return Alerts.showPickeTransactionDate( 86 | context: context, 87 | firstDate: DateTime(2024), 88 | lastDate: DateTime(2035), 89 | initialDate: initialDate, 90 | onDateSelected: (newDate) { 91 | if (isStartDate) { 92 | context.read().newStartDate = newDate; 93 | setState(() => startDate = newDate); 94 | } else { 95 | context.read().newEndDate = newDate; 96 | setState(() => endDate = newDate); 97 | } 98 | }, 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/features/profile/view/widgets/profile_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 7 | import 'package:image_picker/image_picker.dart'; 8 | import 'package:user_service/user_service.dart'; 9 | 10 | import '../../../../core/extension/extension.dart'; 11 | import '../../../../core/styles/app_colors.dart'; 12 | import '../../../blocs/profile_bloc/profile_cubit.dart'; 13 | 14 | class ProfileImage extends StatefulWidget { 15 | const ProfileImage({super.key, required this.user}); 16 | 17 | final User user; 18 | 19 | @override 20 | State createState() => _ProfileImageState(); 21 | } 22 | 23 | class _ProfileImageState extends State { 24 | bool _isUpdateProfile = false; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Container( 29 | width: 150, 30 | height: 150, 31 | decoration: const BoxDecoration( 32 | shape: BoxShape.circle, 33 | color: Colors.red, 34 | gradient: AppColors.primaryGradient, 35 | ), 36 | child: Stack( 37 | children: [ 38 | _buildImage(), 39 | _buildCameraIcon(context), 40 | ], 41 | ), 42 | ); 43 | } 44 | 45 | Container _buildImage() { 46 | return Container( 47 | margin: const EdgeInsets.all(3), 48 | decoration: const BoxDecoration( 49 | shape: BoxShape.circle, 50 | color: Colors.white, 51 | ), 52 | child: ClipOval( 53 | child: _isUpdateProfile 54 | ? Image.file( 55 | context.read().selectedImage!, 56 | height: 150, 57 | width: 150, 58 | fit: BoxFit.cover, 59 | ) 60 | : CachedNetworkImage( 61 | imageUrl: widget.user.photoUrl!, 62 | height: 150, 63 | width: 150, 64 | fit: BoxFit.cover, 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | Align _buildCameraIcon(BuildContext context) { 71 | return Align( 72 | alignment: Alignment.bottomRight, 73 | child: Container( 74 | width: 35, 75 | height: 35, 76 | decoration: BoxDecoration( 77 | shape: BoxShape.circle, 78 | color: Colors.white, 79 | border: Border.all( 80 | width: 3, 81 | color: context.colorScheme.background, 82 | ), 83 | gradient: AppColors.primaryGradient, 84 | ), 85 | child: Material( 86 | color: Colors.transparent, 87 | borderRadius: BorderRadius.circular(16), 88 | clipBehavior: Clip.antiAlias, 89 | child: InkWell( 90 | onTap: () => _onChooseImage(), 91 | child: Icon( 92 | FontAwesomeIcons.cameraRetro, 93 | color: context.colorScheme.background, 94 | size: 15, 95 | ), 96 | ), 97 | ), 98 | ), 99 | ); 100 | } 101 | 102 | _onChooseImage() async { 103 | final picker = ImagePicker(); 104 | XFile? image = await picker.pickImage(source: ImageSource.gallery); 105 | if (image != null) { 106 | setState(() { 107 | context.read().selectedImage = File(image.path); 108 | _isUpdateProfile = true; 109 | }); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lib/features/profile/view/profile_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 | import 'package:user_service/user_service.dart'; 5 | 6 | import '../../../core/extension/extension.dart'; 7 | import '../../../core/helper/helper.dart'; 8 | import '../../../core/router/router.dart'; 9 | import '../../../core/shared/custom_material_button.dart'; 10 | import '../../../core/styles/app_text_style.dart'; 11 | import '../../../core/utils/alerts/alerts.dart'; 12 | import '../../blocs/profile_bloc/profile_cubit.dart'; 13 | import 'widgets/widgets.dart'; 14 | 15 | class ProfileView extends StatefulWidget { 16 | const ProfileView({super.key, required this.user}); 17 | 18 | final User user; 19 | 20 | @override 21 | State createState() => _ProfileViewState(); 22 | } 23 | 24 | class _ProfileViewState extends State { 25 | @override 26 | void initState() { 27 | context.read().init(widget.user); 28 | super.initState(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: _buildAppBar(context), 35 | body: BlocListener( 36 | listener: (context, state) { 37 | state.maybeMap( 38 | loading: (_) => Alerts.showLoaderDialog(context), 39 | success: (state) { 40 | context.pop(); 41 | context.pop(); 42 | Alerts.showToastMsg(context, state.message); 43 | }, 44 | error: (state) { 45 | context.pop(); 46 | context.pop(); 47 | Alerts.showToastMsg(context, state.message); 48 | }, 49 | orElse: () {}, 50 | ); 51 | }, 52 | child: Padding( 53 | padding: const EdgeInsets.symmetric(horizontal: 25), 54 | child: _buildBody(context), 55 | ), 56 | ), 57 | ); 58 | } 59 | 60 | AppBar _buildAppBar(BuildContext context) { 61 | return AppBar( 62 | elevation: 0, 63 | scrolledUnderElevation: 0, 64 | backgroundColor: context.colorScheme.background, 65 | systemOverlayStyle: Helper.overlayStyleAppBar(context), 66 | leading: IconButton( 67 | icon: const FaIcon(FontAwesomeIcons.chevronLeft, size: 18), 68 | onPressed: () => context.pop(), 69 | ), 70 | title: Text( 71 | 'Profile', 72 | style: AppTextStyle.title.copyWith( 73 | fontWeight: FontWeight.w600, 74 | ), 75 | ), 76 | ); 77 | } 78 | 79 | _buildBody(BuildContext context) { 80 | return SafeArea( 81 | child: CustomScrollView( 82 | physics: const BouncingScrollPhysics(), 83 | slivers: [ 84 | SliverFillRemaining( 85 | fillOverscroll: true, 86 | hasScrollBody: false, 87 | child: Center( 88 | child: Column( 89 | children: [ 90 | const ProfileForm(), 91 | const Spacer(), 92 | CustomMaterialButton( 93 | text: 'Save', 94 | onPressed: () { 95 | context.read().updateProfile(); 96 | }, 97 | ), 98 | const SizedBox(height: 20), 99 | ], 100 | ), 101 | ), 102 | ), 103 | ], 104 | ), 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "daily_expense_tracker_app", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "daily_expense_tracker_app (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "daily_expense_tracker_app (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | }, 24 | { 25 | "name": "auth_user", 26 | "cwd": "packages\\auth_user", 27 | "request": "launch", 28 | "type": "dart" 29 | }, 30 | { 31 | "name": "auth_user (profile mode)", 32 | "cwd": "packages\\auth_user", 33 | "request": "launch", 34 | "type": "dart", 35 | "flutterMode": "profile" 36 | }, 37 | { 38 | "name": "auth_user (release mode)", 39 | "cwd": "packages\\auth_user", 40 | "request": "launch", 41 | "type": "dart", 42 | "flutterMode": "release" 43 | }, 44 | { 45 | "name": "db_firestore_client", 46 | "cwd": "packages\\db_firestore_client", 47 | "request": "launch", 48 | "type": "dart" 49 | }, 50 | { 51 | "name": "db_firestore_client (profile mode)", 52 | "cwd": "packages\\db_firestore_client", 53 | "request": "launch", 54 | "type": "dart", 55 | "flutterMode": "profile" 56 | }, 57 | { 58 | "name": "db_firestore_client (release mode)", 59 | "cwd": "packages\\db_firestore_client", 60 | "request": "launch", 61 | "type": "dart", 62 | "flutterMode": "release" 63 | }, 64 | { 65 | "name": "db_hive_client", 66 | "cwd": "packages\\db_hive_client", 67 | "request": "launch", 68 | "type": "dart" 69 | }, 70 | { 71 | "name": "db_hive_client (profile mode)", 72 | "cwd": "packages\\db_hive_client", 73 | "request": "launch", 74 | "type": "dart", 75 | "flutterMode": "profile" 76 | }, 77 | { 78 | "name": "db_hive_client (release mode)", 79 | "cwd": "packages\\db_hive_client", 80 | "request": "launch", 81 | "type": "dart", 82 | "flutterMode": "release" 83 | }, 84 | { 85 | "name": "user_service", 86 | "cwd": "packages\\user_service", 87 | "request": "launch", 88 | "type": "dart" 89 | }, 90 | { 91 | "name": "user_service (profile mode)", 92 | "cwd": "packages\\user_service", 93 | "request": "launch", 94 | "type": "dart", 95 | "flutterMode": "profile" 96 | }, 97 | { 98 | "name": "user_service (release mode)", 99 | "cwd": "packages\\user_service", 100 | "request": "launch", 101 | "type": "dart", 102 | "flutterMode": "release" 103 | } 104 | ] 105 | } -------------------------------------------------------------------------------- /lib/features/transaction/data/repository/transaction_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:auth_user/auth_user.dart'; 4 | import 'package:db_firestore_client/db_firestore_client.dart'; 5 | import 'package:db_hive_client/db_hive_client.dart'; 6 | 7 | import '../../../../core/helper/helper.dart'; 8 | import '../../../../core/models/transaction_hive_model.dart'; 9 | import '../../../../core/models/transaction_model.dart'; 10 | import '../../../../core/utils/models/app_result.dart'; 11 | import 'transaction_base_repository.dart'; 12 | 13 | class TransactionRepository implements TransactionBaseRepository { 14 | final DbFirestoreClientBase _dbFirestoreClient; 15 | final DbHiveClientBase _dbHiveClient; 16 | final AuthUserBase _authUser; 17 | TransactionRepository({ 18 | required DbFirestoreClientBase dbFirestoreClient, 19 | required AuthUserBase authUser, 20 | required DbHiveClientBase dbHiveClient, 21 | }) : _dbFirestoreClient = dbFirestoreClient, 22 | _dbHiveClient = dbHiveClient, 23 | _authUser = authUser; 24 | 25 | /// The [isUserLoggedIn] method is used to check if the user is logged in. 26 | /// If the user is logged in, it returns true. 27 | bool get isUserLoggedIn => _authUser.currentUser != null; 28 | 29 | @override 30 | Future> addTransaction(Transaction transaction) async { 31 | try { 32 | // Generate UUID for the transaction document 33 | final generUUID = Helper.generateUUID(); 34 | 35 | if (!isUserLoggedIn) { 36 | await _dbHiveClient.add( 37 | boxName: 'transactions', 38 | modelId: generUUID, 39 | modelHive: transaction.copyWith(uuid: generUUID).toHiveModel(), 40 | ); 41 | } else { 42 | _dbFirestoreClient.setDocument( 43 | collectionPath: 'transactions', 44 | merge: false, 45 | documentId: generUUID, 46 | data: transaction 47 | .copyWith(uuid: generUUID, userId: _authUser.currentUser?.uid) 48 | .toJson(), 49 | ); 50 | // .then((value) => debugPrint('Transaction added successfully')); 51 | } 52 | 53 | return const AppResult.success(null); 54 | } catch (err) { 55 | return AppResult.failure(err.toString()); 56 | } 57 | } 58 | 59 | @override 60 | Future> updateTransaction(Transaction transaction) async { 61 | try { 62 | if (!isUserLoggedIn) { 63 | await _dbHiveClient.update( 64 | boxName: 'transactions', 65 | modelId: transaction.uuid!, 66 | modelHive: transaction.toHiveModel(), 67 | ); 68 | } else { 69 | await _dbFirestoreClient.updateDocument( 70 | collectionPath: 'transactions/${transaction.uuid}', 71 | data: transaction.toJson(), 72 | ); 73 | } 74 | return const AppResult.success(null); 75 | } catch (err) { 76 | return AppResult.failure(err.toString()); 77 | } 78 | } 79 | 80 | @override 81 | Future> deleteTransaction(String transactionId) async { 82 | try { 83 | if (!isUserLoggedIn) { 84 | await _dbHiveClient.delete( 85 | boxName: 'transactions', 86 | modelId: transactionId, 87 | ); 88 | return const AppResult.success(null); 89 | } 90 | 91 | await _dbFirestoreClient.deleteDocument( 92 | collectionPath: 'transactions/$transactionId', 93 | ); 94 | 95 | return const AppResult.success(null); 96 | } catch (err) { 97 | return AppResult.failure(err.toString()); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/core/styles/app_text_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'app_font_size.dart'; 4 | 5 | class AppTextStyle { 6 | static const TextStyle title = TextStyle( 7 | fontSize: AppFontSize.xLarge, 8 | fontWeight: FontWeight.bold, 9 | ); 10 | 11 | static const TextStyle title2 = TextStyle( 12 | fontSize: AppFontSize.xxxxxxxxLarge, 13 | fontWeight: FontWeight.bold, 14 | ); 15 | 16 | static const TextStyle title3 = TextStyle( 17 | fontSize: AppFontSize.xxxxxLarge, 18 | fontWeight: FontWeight.bold, 19 | ); 20 | static const TextStyle title4 = TextStyle( 21 | fontSize: AppFontSize.xxxxLarge, 22 | fontWeight: FontWeight.bold, 23 | ); 24 | 25 | static const TextStyle subtitle = TextStyle( 26 | fontSize: AppFontSize.large, 27 | fontWeight: FontWeight.bold, 28 | ); 29 | 30 | static const TextStyle body = TextStyle( 31 | fontSize: AppFontSize.medium, 32 | ); 33 | 34 | static TextStyle caption = const TextStyle( 35 | fontSize: AppFontSize.small, 36 | ); 37 | 38 | static const TextStyle button = TextStyle( 39 | fontSize: AppFontSize.medium, 40 | fontWeight: FontWeight.bold, 41 | ); 42 | 43 | static const TextStyle error = TextStyle( 44 | fontSize: AppFontSize.small, 45 | color: Colors.red, 46 | ); 47 | 48 | static const TextStyle success = TextStyle( 49 | fontSize: AppFontSize.small, 50 | color: Colors.greenAccent, 51 | ); 52 | 53 | static const TextStyle warning = TextStyle( 54 | fontSize: AppFontSize.small, 55 | color: Colors.orangeAccent, 56 | ); 57 | 58 | static const TextStyle info = TextStyle( 59 | fontSize: AppFontSize.small, 60 | color: Colors.blueAccent, 61 | ); 62 | 63 | static const TextStyle link = TextStyle( 64 | fontSize: AppFontSize.small, 65 | color: Colors.blueAccent, 66 | decoration: TextDecoration.underline, 67 | ); 68 | 69 | static const TextStyle linkBold = TextStyle( 70 | fontSize: AppFontSize.small, 71 | color: Colors.blue, 72 | fontWeight: FontWeight.bold, 73 | decoration: TextDecoration.underline, 74 | ); 75 | 76 | static const TextStyle linkBoldUnderline = TextStyle( 77 | fontSize: AppFontSize.small, 78 | color: Colors.blue, 79 | fontWeight: FontWeight.bold, 80 | decoration: TextDecoration.underline, 81 | ); 82 | 83 | static const TextStyle linkUnderline = TextStyle( 84 | fontSize: AppFontSize.small, 85 | color: Colors.blue, 86 | decoration: TextDecoration.underline, 87 | ); 88 | 89 | static const TextStyle linkBoldUnderlineWhite = TextStyle( 90 | fontSize: AppFontSize.small, 91 | color: Colors.white, 92 | fontWeight: FontWeight.bold, 93 | decoration: TextDecoration.underline, 94 | ); 95 | 96 | static const TextStyle linkUnderlineWhite = TextStyle( 97 | fontSize: AppFontSize.small, 98 | color: Colors.white, 99 | decoration: TextDecoration.underline, 100 | ); 101 | 102 | static const TextStyle linkBoldWhite = TextStyle( 103 | fontSize: AppFontSize.small, 104 | color: Colors.white, 105 | fontWeight: FontWeight.bold, 106 | ); 107 | 108 | static const TextStyle linkWhite = TextStyle( 109 | fontSize: AppFontSize.small, 110 | color: Colors.white, 111 | ); 112 | 113 | static const TextStyle linkBoldUnderlineBlack = TextStyle( 114 | fontSize: AppFontSize.small, 115 | color: Colors.black, 116 | fontWeight: FontWeight.bold, 117 | decoration: TextDecoration.underline, 118 | ); 119 | 120 | static const TextStyle linkUnderlineBlack = TextStyle( 121 | fontSize: AppFontSize.small, 122 | color: Colors.black, 123 | decoration: TextDecoration.underline, 124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /lib/core/shared/custom_item_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | 4 | import '../extension/extension.dart'; 5 | import '../styles/app_text_style.dart'; 6 | 7 | class CustomItemButton extends StatelessWidget { 8 | const CustomItemButton({ 9 | super.key, 10 | this.text, 11 | this.padding, 12 | this.leading, 13 | this.iconSize, 14 | this.trailing, 15 | this.onPressed, 16 | this.onLongPress, 17 | this.iconItemWidth, 18 | this.iconItemHeight, 19 | required this.icon, 20 | required this.iconColor, 21 | required this.backgroundItem, 22 | required this.backgroundIcon, 23 | }); 24 | 25 | final String? text; 26 | final IconData icon; 27 | final Color iconColor; 28 | final Widget? leading; 29 | final Widget? trailing; 30 | final double? iconSize; 31 | final double? iconItemWidth; 32 | final double? iconItemHeight; 33 | final Color backgroundItem; 34 | final Color backgroundIcon; 35 | final VoidCallback? onPressed; 36 | final VoidCallback? onLongPress; 37 | final EdgeInsetsGeometry? padding; 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Container( 42 | margin: const EdgeInsets.symmetric(vertical: 5.0), 43 | child: Material( 44 | color: backgroundItem, 45 | borderRadius: BorderRadius.circular(16), 46 | clipBehavior: Clip.antiAlias, 47 | child: InkWell( 48 | onTap: onPressed, 49 | onLongPress: onLongPress, 50 | child: Container( 51 | padding: padding ?? 52 | const EdgeInsets.symmetric( 53 | horizontal: 15, 54 | vertical: 15, 55 | ), 56 | child: Row( 57 | mainAxisAlignment: MainAxisAlignment.start, 58 | crossAxisAlignment: CrossAxisAlignment.center, 59 | children: [ 60 | IconItem( 61 | icon: icon, 62 | iconColor: iconColor, 63 | iconSize: iconSize ?? 18, 64 | backgroundIcon: backgroundIcon, 65 | iconItemHeight: iconItemHeight ?? 45, 66 | iconItemWidth: iconItemWidth ?? 45, 67 | ), 68 | const SizedBox(width: 12.0), 69 | leading ?? 70 | Text( 71 | text ?? '', 72 | style: AppTextStyle.body.copyWith( 73 | color: context.colorScheme.onSurface, 74 | ), 75 | ), 76 | trailing != null ? const Spacer() : const SizedBox(), 77 | trailing ?? const SizedBox(), 78 | ], 79 | ), 80 | ), 81 | ), 82 | ), 83 | ); 84 | } 85 | } 86 | 87 | class IconItem extends StatelessWidget { 88 | const IconItem({ 89 | super.key, 90 | required this.icon, 91 | required this.iconSize, 92 | required this.iconColor, 93 | required this.iconItemWidth, 94 | required this.backgroundIcon, 95 | required this.iconItemHeight, 96 | }); 97 | 98 | final IconData icon; 99 | final double iconSize; 100 | final Color iconColor; 101 | final Color backgroundIcon; 102 | final double iconItemWidth; 103 | final double iconItemHeight; 104 | 105 | @override 106 | Widget build(BuildContext context) { 107 | return Container( 108 | width: iconItemWidth, 109 | height: iconItemHeight, 110 | decoration: BoxDecoration( 111 | color: backgroundIcon, 112 | shape: BoxShape.circle, 113 | ), 114 | child: Center( 115 | child: FaIcon(icon, size: iconSize, color: iconColor), 116 | ), 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/features/home/view/main_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../core/enum/enum.dart'; 5 | import '../../../core/router/app_route.dart'; 6 | import '../../../core/router/router.dart'; 7 | import '../../../core/shared/shared.dart'; 8 | import '../../../core/styles/app_text_style.dart'; 9 | import '../../../core/utils/alerts/alerts.dart'; 10 | import '../../blocs/main_bloc/main_cubit.dart'; 11 | import '../../blocs/transaction_bloc/transaction_cubit.dart'; 12 | import 'widgets/widgets.dart'; 13 | 14 | class MainView extends StatefulWidget { 15 | const MainView({super.key}); 16 | 17 | @override 18 | State createState() => _MainViewState(); 19 | } 20 | 21 | class _MainViewState extends State { 22 | @override 23 | void initState() { 24 | context 25 | .read() 26 | .getAll(TypeShow.limit) 27 | .then((_) => context.read().getTotals()); 28 | 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Padding( 35 | padding: const EdgeInsets.symmetric(horizontal: 25), 36 | child: SafeArea( 37 | bottom: false, 38 | child: Center( 39 | child: Column( 40 | children: [ 41 | const BlocListenerAuth(), 42 | const SizedBox(height: 10.0), 43 | const HeaderAppBarProfile(), 44 | const SizedBox(height: 10.0), 45 | const ExpenseCard(), 46 | _buildHeaderTransaction(), 47 | BlocListener( 48 | listener: (_, state) => state.maybeWhen( 49 | success: (message) => _success(context, message), 50 | error: (message) => Alerts.showToastMsg(context, message), 51 | orElse: () => {}, 52 | ), 53 | child: const SizedBox.shrink(), 54 | ), 55 | BlocBuilder( 56 | buildWhen: (previous, current) => current.maybeWhen( 57 | loadedAll: (_) => true, 58 | loadedLimit: (_) => true, 59 | loading: () => true, 60 | orElse: () => false, 61 | ), 62 | builder: (_, state) => state.maybeWhen( 63 | loadedLimit: (transactions) => TransactionList( 64 | allTransactions: transactions, 65 | ), 66 | loading: () => const Center( 67 | child: CircularProgressIndicator(), 68 | ), 69 | orElse: () => const Center( 70 | child: CircularProgressIndicator(), 71 | ), 72 | ), 73 | ), 74 | ], 75 | ), 76 | ), 77 | ), 78 | ); 79 | } 80 | 81 | _buildHeaderTransaction() { 82 | return Row( 83 | children: [ 84 | const Text( 85 | 'Transaction', 86 | style: TextStyle( 87 | fontSize: 16, 88 | fontWeight: FontWeight.w600, 89 | ), 90 | ), 91 | const Spacer(), 92 | TextButton( 93 | child: Text( 94 | 'View All', 95 | style: AppTextStyle.caption.copyWith( 96 | fontWeight: FontWeight.w400, 97 | ), 98 | ), 99 | onPressed: () => context.pushNamed(RoutesName.allViewTransaction), 100 | ), 101 | ], 102 | ); 103 | } 104 | 105 | _success(BuildContext context, String message) { 106 | Alerts.showToastMsg(context, message); 107 | context 108 | .read() 109 | .getAll(TypeShow.limit) 110 | .then((_) => context.read().getTotals()); 111 | } 112 | } 113 | --------------------------------------------------------------------------------