├── ios ├── 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 │ ├── Runner.entitlements │ └── Base.lproj │ │ └── Main.storyboard ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore └── Podfile ├── lib ├── app │ ├── auth │ │ ├── infrastructure │ │ │ ├── models │ │ │ │ └── auth_type.dart │ │ │ └── repositories │ │ │ │ └── auth_repository_impl.dart │ │ ├── domain │ │ │ ├── datasources │ │ │ │ └── auth_datasource.dart │ │ │ └── repositories │ │ │ │ └── auth_repository.dart │ │ └── presentation │ │ │ ├── widgets │ │ │ ├── auth_email │ │ │ │ ├── forgot_password.dart │ │ │ │ ├── auth_submit.dart │ │ │ │ ├── confirm_mail_header_widget.dart │ │ │ │ └── auth_header_widget.dart │ │ │ ├── recovery_password │ │ │ │ ├── recovery_submit.dart │ │ │ │ └── recovery_password_header.dart │ │ │ ├── forgot_password │ │ │ │ ├── forgot_password_submit.dart │ │ │ │ ├── recovery_email_header.dart │ │ │ │ └── forgot_password_header.dart │ │ │ └── auth │ │ │ │ ├── already_have_account.dart │ │ │ │ ├── no_account.dart │ │ │ │ └── auth_terms.dart │ │ │ └── screens │ │ │ ├── recovery_email_sent_screen.dart │ │ │ └── confirm_mail_sign_up_screen.dart │ ├── search │ │ ├── infrastructure │ │ │ └── models │ │ │ │ ├── search_type_enum.dart │ │ │ │ └── search_model.dart │ │ ├── domain │ │ │ └── entities │ │ │ │ └── search_entity.dart │ │ └── presentation │ │ │ ├── blocs │ │ │ └── search_event.dart │ │ │ └── widgets │ │ │ └── search_header_widget.dart │ ├── splash │ │ ├── utils │ │ │ └── splash_utils.dart │ │ └── presentation │ │ │ └── blocs │ │ │ ├── splash_event.dart │ │ │ └── splash_state.dart │ ├── profile │ │ ├── utils │ │ │ └── profile_utils.dart │ │ ├── domain │ │ │ ├── entities │ │ │ │ ├── profile_entity.dart │ │ │ │ └── insert_master_password_entity.dart │ │ │ ├── datasources │ │ │ │ └── profile_datasource.dart │ │ │ └── repositories │ │ │ │ └── profile_repository.dart │ │ ├── presentation │ │ │ └── blocs │ │ │ │ ├── new_app_version │ │ │ │ ├── new_app_version_event.dart │ │ │ │ ├── new_app_version_state.dart │ │ │ │ └── new_app_version_bloc.dart │ │ │ │ ├── experiencing_issues │ │ │ │ ├── experiencing_issues_event.dart │ │ │ │ └── experiencing_issues_state.dart │ │ │ │ └── profile │ │ │ │ ├── profile_event.dart │ │ │ │ ├── profile_state.dart │ │ │ │ └── profile_bloc.dart │ │ └── infraestructure │ │ │ ├── models │ │ │ ├── insert_master_password_model.dart │ │ │ └── profile_model.dart │ │ │ └── repositories_impl │ │ │ └── profile_repository_impl.dart │ ├── preferences │ │ ├── domain │ │ │ ├── entities │ │ │ │ ├── preferences_entity.dart │ │ │ │ ├── card_image_entity.dart │ │ │ │ └── pass_image_entity.dart │ │ │ ├── datasources │ │ │ │ ├── preferences_datasource.dart │ │ │ │ └── parameters_datasource.dart │ │ │ └── repositories │ │ │ │ └── preferences_repository.dart │ │ ├── infrastructure │ │ │ └── models │ │ │ │ ├── preferences_model.dart │ │ │ │ ├── card_image_model.dart │ │ │ │ └── pass_image_model.dart │ │ ├── presentation │ │ │ └── blocs │ │ │ │ ├── preferences_event.dart │ │ │ │ └── preferences_state.dart │ │ └── utils │ │ │ └── preferences_utils.dart │ ├── master_password │ │ ├── domain │ │ │ ├── entities │ │ │ │ ├── update_cards_entity.dart │ │ │ │ ├── update_passwords_entity.dart │ │ │ │ └── update_master_password_entity.dart │ │ │ ├── datasources │ │ │ │ └── master_password_datasource.dart │ │ │ └── repositories │ │ │ │ └── master_password_repository.dart │ │ ├── infrastructure │ │ │ ├── models │ │ │ │ ├── update_cards_model.dart │ │ │ │ ├── update_passwords_model.dart │ │ │ │ └── update_master_password_model.dart │ │ │ ├── repositories_impl │ │ │ │ └── master_password_repository_impl.dart │ │ │ └── datasources │ │ │ │ └── supabase_master_password_datasource.dart │ │ └── presentation │ │ │ ├── widget │ │ │ ├── submit_update_master_password_widget.dart │ │ │ └── master_password_header_widget.dart │ │ │ └── blocs │ │ │ └── master_password_event.dart │ ├── card │ │ ├── utils │ │ │ └── card_utils.dart │ │ ├── domain │ │ │ ├── entities │ │ │ │ └── card_entity.dart │ │ │ ├── datasources │ │ │ │ ├── card_datasource.dart │ │ │ │ └── supabase_biometric_datasource.dart │ │ │ └── repositories │ │ │ │ └── card_repository.dart │ │ ├── presentation │ │ │ ├── blocs │ │ │ │ └── card_report │ │ │ │ │ ├── card_report_event.dart │ │ │ │ │ └── card_report_state.dart │ │ │ └── widgets │ │ │ │ └── report │ │ │ │ └── card_report_header_widget.dart │ │ └── infrastructure │ │ │ ├── models │ │ │ ├── card_type.dart │ │ │ ├── card_cvv_form.dart │ │ │ ├── card_exp_form.dart │ │ │ ├── card_number_form.dart │ │ │ └── card_model.dart │ │ │ └── repositories_impl │ │ │ └── card_repository_impl.dart │ ├── enroll │ │ ├── domain │ │ │ ├── entities │ │ │ │ └── enroll_new_device_entity.dart │ │ │ ├── datasources │ │ │ │ └── enroll_datasource.dart │ │ │ └── repositories │ │ │ │ └── enroll_repository.dart │ │ ├── presentation │ │ │ ├── blocs │ │ │ │ ├── enroll_event.dart │ │ │ │ └── enroll_state.dart │ │ │ └── widgets │ │ │ │ └── enroll_submit_button.dart │ │ └── infrastructure │ │ │ ├── models │ │ │ └── enroll_new_device_model.dart │ │ │ ├── repositories │ │ │ └── enroll_repository_impl.dart │ │ │ └── datasources │ │ │ └── supabase_enroll_datasource.dart │ ├── biometric │ │ ├── domain │ │ │ ├── datasources │ │ │ │ └── biometric_datasource.dart │ │ │ └── repositories │ │ │ │ └── biometric_repository.dart │ │ ├── presentation │ │ │ ├── blocs │ │ │ │ ├── biometric_event.dart │ │ │ │ └── biometric_state.dart │ │ │ └── widgets │ │ │ │ ├── submit_biometric_widget.dart │ │ │ │ └── biometric_header_widget.dart │ │ └── infrastructure │ │ │ └── repositories_impl │ │ │ └── biometric_repository_impl.dart │ ├── password │ │ ├── domain │ │ │ ├── entities │ │ │ │ └── password_entity.dart │ │ │ ├── datasources │ │ │ │ └── password_datasource.dart │ │ │ └── repositories │ │ │ │ └── password_repository.dart │ │ ├── presentation │ │ │ ├── blocs │ │ │ │ └── password_report │ │ │ │ │ ├── password_report_event.dart │ │ │ │ │ └── password_report_state.dart │ │ │ └── widgets │ │ │ │ ├── report │ │ │ │ └── pass_report_header_widget.dart │ │ │ │ ├── generate_password │ │ │ │ ├── pass_generator_switch_widget.dart │ │ │ │ ├── pass_generator_numbers_switch_widget.dart │ │ │ │ └── pass_generator_symbols_switch_widget.dart │ │ │ │ └── pass_desc_widget.dart │ │ └── infrastructure │ │ │ └── repositories_impl │ │ │ └── password_repository_impl.dart │ ├── auth_init │ │ ├── domain │ │ │ ├── datasources │ │ │ │ └── auth_init_datasource.dart │ │ │ └── repositories │ │ │ │ └── auth_init_repository.dart │ │ ├── presentation │ │ │ ├── widgets │ │ │ │ └── submit_button_widget.dart │ │ │ └── blocs │ │ │ │ └── auth_init_event.dart │ │ └── infrastructure │ │ │ └── repositories │ │ │ └── auth_init_repository_impl.dart │ ├── dashboard │ │ ├── presentation │ │ │ └── widgets │ │ │ │ ├── settings │ │ │ │ ├── email_settings_widget.dart │ │ │ │ ├── log_out_settings_widget.dart │ │ │ │ ├── language_settings_widget.dart │ │ │ │ ├── update_master_password_widget.dart │ │ │ │ ├── terms_settings_widget.dart │ │ │ │ ├── policy_settings_widget.dart │ │ │ │ └── display_name_form_widget.dart │ │ │ │ ├── home │ │ │ │ └── home_search_widget.dart │ │ │ │ ├── tools │ │ │ │ ├── join_biometrics_widget.dart │ │ │ │ ├── add_password_widget.dart │ │ │ │ └── add_card_widget.dart │ │ │ │ └── bottom_navigation_bar_icon.dart │ │ └── infrastructure │ │ │ └── models │ │ │ └── display_name_form.dart │ ├── get_started │ │ └── presentation │ │ │ └── blocs │ │ │ ├── get_started_state.dart │ │ │ ├── get_started_event.dart │ │ │ └── get_started_bloc.dart │ └── sync_pass │ │ └── presentation │ │ ├── blocs │ │ ├── sync_event.dart │ │ └── sync_state.dart │ │ └── widgets │ │ └── submit_sync_pass_widget.dart ├── core │ ├── utils │ │ ├── form_utils.dart │ │ ├── auth_utils.dart │ │ ├── regex_utils.dart │ │ ├── device_info.dart │ │ ├── db_utils.dart │ │ └── biometric_utils.dart │ ├── file │ │ └── file_paths.dart │ ├── global │ │ └── utils │ │ │ └── secret_utils.dart │ ├── storage │ │ └── storage_preferences.dart │ ├── api │ │ ├── api_codes.dart │ │ ├── savepass_response_model.dart │ │ └── supabase_middleware.dart │ ├── lottie │ │ └── lottie_paths.dart │ ├── image │ │ └── image_paths.dart │ ├── form │ │ ├── text_form.dart │ │ ├── password_form.dart │ │ └── email_form.dart │ ├── env │ │ └── env.dart │ └── config │ │ └── routes.dart └── main.dart ├── assets ├── fonts │ ├── Geist-Black.ttf │ ├── Geist-Bold.ttf │ ├── Geist-Light.ttf │ ├── Geist-Thin.ttf │ ├── Geist-Medium.ttf │ ├── Geist-Regular.ttf │ ├── Geist-SemiBold.ttf │ ├── Geist-UltraBlack.ttf │ └── Geist-UltraLight.ttf └── images │ ├── card │ ├── chip.png │ └── american_express_face.png │ ├── logo │ └── logo_light.png │ ├── dashboard │ └── password.png │ └── auth │ └── apple.svg ├── l10n.yaml ├── android ├── gradle.properties ├── app │ └── src │ │ ├── main │ │ ├── ic_launcher-playstore.png │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── launcher_icon.png │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── launcher_icon.png │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── launcher_icon.png │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── launcher_icon.png │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── launcher_icon.png │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── launcher_icon.xml │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ └── values-night │ │ │ │ └── styles.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── example │ │ │ └── savepass │ │ │ └── MainActivity.kt │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle └── settings.gradle ├── analysis_options.yaml ├── devtools_options.yaml ├── .env.sample ├── .gitignore ├── LICENSE └── .metadata /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/app/auth/infrastructure/models/auth_type.dart: -------------------------------------------------------------------------------- 1 | enum AuthType { signIn, signUp } 2 | -------------------------------------------------------------------------------- /lib/app/search/infrastructure/models/search_type_enum.dart: -------------------------------------------------------------------------------- 1 | enum SearchType { password, card } 2 | -------------------------------------------------------------------------------- /lib/core/utils/form_utils.dart: -------------------------------------------------------------------------------- 1 | class FormUtils { 2 | static const int maxLength = 255; 3 | } 4 | -------------------------------------------------------------------------------- /assets/fonts/Geist-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/Geist-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Geist-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Geist-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-Thin.ttf -------------------------------------------------------------------------------- /assets/images/card/chip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/images/card/chip.png -------------------------------------------------------------------------------- /assets/fonts/Geist-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Geist-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-Regular.ttf -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /assets/fonts/Geist-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Geist-UltraBlack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-UltraBlack.ttf -------------------------------------------------------------------------------- /assets/fonts/Geist-UltraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/fonts/Geist-UltraLight.ttf -------------------------------------------------------------------------------- /assets/images/logo/logo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/images/logo/logo_light.png -------------------------------------------------------------------------------- /assets/images/dashboard/password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/images/dashboard/password.png -------------------------------------------------------------------------------- /assets/images/card/american_express_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/assets/images/card/american_express_face.png -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /lib/core/file/file_paths.dart: -------------------------------------------------------------------------------- 1 | class FilePaths { 2 | static const String privacyPolicyFile = 'assets/files/privacy_policy_en.pdf'; 3 | } 4 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | prefer_single_quotes: true 6 | require_trailing_commas: true 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/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/judatop/savepass/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /lib/core/global/utils/secret_utils.dart: -------------------------------------------------------------------------------- 1 | class SecretUtils { 2 | static const String masterPasswordKey = 'masterPassword'; 3 | static const String passwordKey = 'password'; 4 | } 5 | -------------------------------------------------------------------------------- /lib/core/storage/storage_preferences.dart: -------------------------------------------------------------------------------- 1 | class StoragePreferences { 2 | static const String brightnessKey = 'brightness'; 3 | static const String languageKey = 'language'; 4 | } 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judatop/savepass/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/judatop/savepass/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/app/splash/utils/splash_utils.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | class SplashUtils { 5 | 6 | static const int splashDuration = 3000; 7 | static const double imagePercentageWidth = 0.5; 8 | 9 | 10 | } -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/app/profile/utils/profile_utils.dart: -------------------------------------------------------------------------------- 1 | class ProfileUtils { 2 | static const String emailProvider = 'email'; 3 | static const String googleProvider = 'google'; 4 | static const String githubProvider = 'github'; 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/profile/domain/entities/profile_entity.dart: -------------------------------------------------------------------------------- 1 | class ProfileEntity { 2 | final String? displayName; 3 | final String? avatar; 4 | 5 | const ProfileEntity({ 6 | this.displayName, 7 | this.avatar, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /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.3-all.zip 6 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | SUPABASE_URL=<> 2 | SUPABASE_ANON_KEY=<> 3 | SUPABASE_BUCKET=<> 4 | SUPABASE_BUCKET_AVATARS_FOLDER=<> 5 | GOOGLE_WEB_CLIENT_ID=<> 6 | GOOGLE_IOS_CLIENT_ID=<> 7 | SUPABASE_REDIRECT_URL=<> 8 | BIOMETRIC_HASH_KEY=<> 9 | DERIVED_KEY=<> 10 | SENTRY_DSN=<> -------------------------------------------------------------------------------- /lib/app/preferences/domain/entities/preferences_entity.dart: -------------------------------------------------------------------------------- 1 | class PreferencesEntity { 2 | final BrightnessType brightness; 3 | 4 | const PreferencesEntity({ 5 | required this.brightness, 6 | }); 7 | } 8 | 9 | enum BrightnessType { system, light, dark } 10 | -------------------------------------------------------------------------------- /lib/app/preferences/domain/entities/card_image_entity.dart: -------------------------------------------------------------------------------- 1 | class CardImageEntity { 2 | final String id; 3 | final String type; 4 | final String imgUrl; 5 | 6 | const CardImageEntity({ 7 | required this.id, 8 | required this.type, 9 | required this.imgUrl, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/app/master_password/domain/entities/update_cards_entity.dart: -------------------------------------------------------------------------------- 1 | class UpdateCardsEntity { 2 | final String id; 3 | final String card; 4 | final String vaultId; 5 | 6 | const UpdateCardsEntity({ 7 | required this.id, 8 | required this.card, 9 | required this.vaultId, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/app/card/utils/card_utils.dart: -------------------------------------------------------------------------------- 1 | class CardUtils { 2 | static String formatCreditCardNumber(String cardNumber) { 3 | cardNumber = cardNumber.replaceAll(' ', ''); 4 | 5 | return cardNumber.replaceAllMapped( 6 | RegExp(r'.{1,4}'), 7 | (match) => '${match.group(0)} ', 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/app/enroll/domain/entities/enroll_new_device_entity.dart: -------------------------------------------------------------------------------- 1 | class EnrollNewDeviceEntity { 2 | final String deviceId; 3 | final String deviceName; 4 | final String type; 5 | 6 | const EnrollNewDeviceEntity({ 7 | required this.deviceId, 8 | required this.deviceName, 9 | required this.type, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/master_password/domain/entities/update_passwords_entity.dart: -------------------------------------------------------------------------------- 1 | class UpdatePasswordsEntity { 2 | final String id; 3 | final String password; 4 | final String vaultId; 5 | 6 | const UpdatePasswordsEntity({ 7 | required this.id, 8 | required this.password, 9 | required this.vaultId, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/app/preferences/domain/entities/pass_image_entity.dart: -------------------------------------------------------------------------------- 1 | class PassImageEntity { 2 | final String id; 3 | final String key; 4 | final String type; 5 | final String? domain; 6 | 7 | const PassImageEntity({ 8 | required this.id, 9 | required this.key, 10 | required this.type, 11 | this.domain, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.applesignin 6 | 7 | Default 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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/app/biometric/domain/datasources/biometric_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/core/api/savepass_response_model.dart'; 3 | 4 | abstract class BiometricDatasource { 5 | Future> enrollBiometric({ 6 | required String inputSecret, 7 | required String deviceId, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/biometric/domain/repositories/biometric_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/core/api/savepass_response_model.dart'; 3 | 4 | abstract class BiometricRepository { 5 | Future> enrollBiometric({ 6 | required String inputSecret, 7 | required String deviceId, 8 | }); 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/app/profile/presentation/blocs/new_app_version/new_app_version_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class NewAppVersionEvent extends Equatable { 4 | const NewAppVersionEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class DownloadNewVersionEvent extends NewAppVersionEvent { 11 | const DownloadNewVersionEvent() : super(); 12 | } 13 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/app/profile/presentation/blocs/experiencing_issues/experiencing_issues_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class ExperiencingIssuesEvent extends Equatable { 4 | const ExperiencingIssuesEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class ExperiencingIssuesInitialEvent extends ExperiencingIssuesEvent { 11 | const ExperiencingIssuesInitialEvent() : super(); 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/profile/presentation/blocs/experiencing_issues/experiencing_issues_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class ExperiencingIssuesState extends Equatable { 4 | const ExperiencingIssuesState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | 11 | class ExperiencingIssuesInitialState extends ExperiencingIssuesState { 12 | const ExperiencingIssuesInitialState() : super(); 13 | } -------------------------------------------------------------------------------- /lib/app/enroll/presentation/blocs/enroll_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class EnrollEvent extends Equatable { 4 | const EnrollEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class EnrollInitialEvent extends EnrollEvent { 11 | const EnrollInitialEvent() : super(); 12 | } 13 | 14 | class SubmitEnrollEvent extends EnrollEvent { 15 | const SubmitEnrollEvent() : super(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/search/domain/entities/search_entity.dart: -------------------------------------------------------------------------------- 1 | class SearchEntity { 2 | final String id; 3 | final String title; 4 | final String subtitle; 5 | final String? imgUrl; 6 | final String type; 7 | final String vaultId; 8 | 9 | const SearchEntity({ 10 | required this.id, 11 | required this.title, 12 | required this.subtitle, 13 | required this.imgUrl, 14 | required this.type, 15 | required this.vaultId, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/card/domain/entities/card_entity.dart: -------------------------------------------------------------------------------- 1 | class CardEntity { 2 | final String? id; 3 | final String? type; 4 | final String? typeId; 5 | final String? imgUrl; 6 | final String card; 7 | final String? vaultId; 8 | final String? description; 9 | 10 | const CardEntity({ 11 | this.id, 12 | this.type, 13 | this.typeId, 14 | this.imgUrl, 15 | required this.card, 16 | this.vaultId, 17 | this.description, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/app/master_password/domain/datasources/master_password_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/master_password/infrastructure/models/update_master_password_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class MasterPasswordDatasource { 6 | Future> updateMasterPassword({ 7 | required UpdateMasterPasswordModel model, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/master_password/domain/repositories/master_password_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/master_password/infrastructure/models/update_master_password_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class MasterPasswordRepository { 6 | Future> updateMasterPassword({ 7 | required UpdateMasterPasswordModel model, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/api/api_codes.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class ApiCodes{ 4 | 5 | static const String invalidMasterPassword = 'SPE004'; 6 | static const String alreadyHasDeviceEnrolled = 'SPE006'; 7 | static const String newPasswordsNeedsToBeDiferent = 'SPE011'; 8 | static const String reachedPasswordsLimit = 'SPE012'; 9 | static const String reachedCardsLimit = 'SPE013'; 10 | static const String userBlocked = 'SPE014'; 11 | static const String success = 'SP001'; 12 | 13 | 14 | } -------------------------------------------------------------------------------- /lib/app/enroll/domain/datasources/enroll_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/enroll/infrastructure/models/enroll_new_device_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class EnrollDatasource { 6 | Future> getDeviceName(); 7 | Future> enrollNewDevice({ 8 | required EnrollNewDeviceModel model, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/enroll/domain/repositories/enroll_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/enroll/infrastructure/models/enroll_new_device_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class EnrollRepository { 6 | Future> getDeviceName(); 7 | Future> enrollNewDevice({ 8 | required EnrollNewDeviceModel model, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/password/domain/entities/password_entity.dart: -------------------------------------------------------------------------------- 1 | class PasswordEntity { 2 | final String? id; 3 | final String? typeImg; 4 | final String? name; 5 | final String password; 6 | final String? vaultId; 7 | final String? description; 8 | final String? domain; 9 | 10 | const PasswordEntity({ 11 | this.id, 12 | this.typeImg, 13 | this.name, 14 | required this.password, 15 | this.vaultId, 16 | this.description, 17 | this.domain, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/app/master_password/infrastructure/models/update_cards_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:savepass/app/master_password/domain/entities/update_cards_entity.dart'; 2 | 3 | class UpdateCardsModel extends UpdateCardsEntity { 4 | UpdateCardsModel({ 5 | required super.id, 6 | required super.card, 7 | required super.vaultId, 8 | }); 9 | 10 | Map toJson() { 11 | return { 12 | 'id': id, 13 | 'card': card, 14 | 'vault_id': vaultId, 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/core/utils/auth_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AuthUtils { 4 | static const List googleButtonsColors = [ 5 | Color(0xFFFABC06), 6 | Color(0xFF38A953), 7 | Color(0xFF4286F5), 8 | ]; 9 | 10 | static const Color facebookButtonColor = Color(0xFF136AFF); 11 | 12 | static const imgWidth = 25.0; 13 | 14 | static const githubProvider = 'github'; 15 | static const googleProvider = 'google'; 16 | static const appleProvider = 'apple'; 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/profile/domain/entities/insert_master_password_entity.dart: -------------------------------------------------------------------------------- 1 | class InsertMasterPasswordEntity { 2 | final String secret; 3 | final String name; 4 | final String deviceId; 5 | final String deviceName; 6 | final String type; 7 | final String salt; 8 | 9 | const InsertMasterPasswordEntity({ 10 | required this.secret, 11 | required this.name, 12 | required this.deviceId, 13 | required this.deviceName, 14 | required this.type, 15 | required this.salt, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/profile/presentation/blocs/new_app_version/new_app_version_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class NewAppVersionState extends Equatable { 4 | const NewAppVersionState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class NewAppVersionInitialState extends NewAppVersionState { 11 | const NewAppVersionInitialState() : super(); 12 | } 13 | 14 | class GeneralErrorState extends NewAppVersionState { 15 | const GeneralErrorState() : super(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/lottie/lottie_paths.dart: -------------------------------------------------------------------------------- 1 | class LottiePaths { 2 | static const String getStarted = 'assets/lotties/get_started.json'; 3 | static const String noData = 'assets/lotties/no_data.json'; 4 | static const String device = 'assets/lotties/device.json'; 5 | static const String biometrics = 'assets/lotties/biometrics.json'; 6 | static const String mail = 'assets/lotties/mail.json'; 7 | static const String update = 'assets/lotties/update.json'; 8 | static const String error = 'assets/lotties/error.json'; 9 | } 10 | -------------------------------------------------------------------------------- /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/app/master_password/infrastructure/models/update_passwords_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:savepass/app/master_password/domain/entities/update_passwords_entity.dart'; 2 | 3 | class UpdatePasswordsModel extends UpdatePasswordsEntity { 4 | UpdatePasswordsModel({ 5 | required super.id, 6 | required super.password, 7 | required super.vaultId, 8 | }); 9 | 10 | Map toJson() { 11 | return { 12 | 'id': id, 13 | 'password': password, 14 | 'vault_id': vaultId, 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/enroll/infrastructure/models/enroll_new_device_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:savepass/app/enroll/domain/entities/enroll_new_device_entity.dart'; 2 | 3 | class EnrollNewDeviceModel extends EnrollNewDeviceEntity { 4 | const EnrollNewDeviceModel({ 5 | required super.deviceId, 6 | required super.deviceName, 7 | required super.type, 8 | }); 9 | 10 | Map toJson() { 11 | return { 12 | 'device_id': deviceId, 13 | 'device_name': deviceName, 14 | 'type': type, 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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/app/preferences/domain/datasources/preferences_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/preferences/domain/entities/preferences_entity.dart'; 3 | import 'package:savepass/app/preferences/infrastructure/models/preferences_model.dart'; 4 | 5 | abstract class PreferencesDatasource { 6 | Future> getTheme(); 7 | Future> setTheme(BrightnessType brightness); 8 | Future> getLanguage(); 9 | Future> setLanguage(String language); 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/auth_init/domain/datasources/auth_init_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/core/api/savepass_response_model.dart'; 3 | 4 | abstract class AuthInitDatasource { 5 | Future> checkMasterPassword({ 6 | required String inputSecret, 7 | required String deviceId, 8 | required String biometricHash, 9 | }); 10 | 11 | Future> getUserSalt(); 12 | 13 | Future> hasBiometrics({ 14 | required String deviceId, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/auth_init/domain/repositories/auth_init_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/core/api/savepass_response_model.dart'; 3 | 4 | abstract class AuthInitRepository { 5 | Future> checkMasterPassword({ 6 | required String inputSecret, 7 | required String deviceId, 8 | required String biometricHash, 9 | }); 10 | 11 | Future> getUserSalt(); 12 | 13 | Future> hasBiometrics({ 14 | required String deviceId, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/splash/presentation/blocs/splash_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class SplashEvent extends Equatable { 4 | const SplashEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class SplashInitialEvent extends SplashEvent { 11 | const SplashInitialEvent() : super(); 12 | } 13 | 14 | class ManageRouteChangeEvent extends SplashEvent { 15 | const ManageRouteChangeEvent() : super(); 16 | } 17 | 18 | class CheckAppVersionAndFeatureFlagEvent extends SplashEvent { 19 | const CheckAppVersionAndFeatureFlagEvent() : super(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/core/utils/regex_utils.dart: -------------------------------------------------------------------------------- 1 | class RegexUtils { 2 | static final numbersAndLettersWithSpace = RegExp(r'[0-9a-zA-Z\s]'); 3 | static final numbers = RegExp(r'[0-9]'); 4 | static final password = RegExp(r'[0-9a-zA-Z!@#\$%^&*(),.?":{}|<>\s]'); 5 | static final regexEmail = RegExp( 6 | r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$', 7 | ); 8 | static final regexDomain = RegExp( 9 | r'^[a-zA-Z0-9.-]+(?:\.[a-zA-Z0-9-]+)*$', 10 | ); 11 | static final lettersWithSpaceCapitalCase = RegExp(r'^[A-Z\s]+$'); 12 | static final lettersWithSpace = RegExp(r'^[a-zA-Z\s]+$'); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /lib/app/preferences/infrastructure/models/preferences_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:savepass/app/preferences/domain/entities/preferences_entity.dart'; 3 | 4 | class PreferencesModel extends PreferencesEntity with EquatableMixin { 5 | const PreferencesModel({ 6 | required super.brightness, 7 | }); 8 | 9 | PreferencesModel copyWith({ 10 | BrightnessType? brightness, 11 | }) { 12 | return PreferencesModel( 13 | brightness: brightness ?? this.brightness, 14 | ); 15 | } 16 | 17 | @override 18 | List get props => [ 19 | brightness, 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/settings/email_settings_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:savepass/main.dart'; 3 | 4 | class EmailSettingsWidget extends StatelessWidget { 5 | const EmailSettingsWidget({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Row( 10 | crossAxisAlignment: CrossAxisAlignment.center, 11 | mainAxisAlignment: MainAxisAlignment.center, 12 | children: [ 13 | Text( 14 | supabase.auth.currentUser?.email ?? '', 15 | style: const TextStyle(color: Colors.grey), 16 | ), 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/app/search/presentation/blocs/search_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class SearchEvent extends Equatable { 4 | const SearchEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class SearchInitialEvent extends SearchEvent { 11 | const SearchInitialEvent() : super(); 12 | } 13 | 14 | class ChangeSearchTxtEvent extends SearchEvent { 15 | final String searchText; 16 | 17 | const ChangeSearchTxtEvent({required this.searchText}) : super(); 18 | } 19 | 20 | class SubmitSearchEvent extends SearchEvent { 21 | final String? search; 22 | 23 | const SubmitSearchEvent({this.search}) : super(); 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/auth/domain/datasources/auth_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:supabase_flutter/supabase_flutter.dart'; 3 | 4 | abstract class AuthDatasource { 5 | Future> signUpWithEmailAndPassword({ 6 | required String email, 7 | required String password, 8 | }); 9 | 10 | Future> signInWithEmailAndPassword({ 11 | required String email, 12 | required String password, 13 | }); 14 | 15 | Future> recoveryPassword({ 16 | required String email, 17 | }); 18 | 19 | Future> updateNewPassword({ 20 | required String password, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/auth/domain/repositories/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:supabase_flutter/supabase_flutter.dart'; 2 | import 'package:dartz/dartz.dart'; 3 | 4 | abstract class AuthRepository { 5 | Future> signUpWithEmailAndPassword({ 6 | required String email, 7 | required String password, 8 | }); 9 | 10 | Future> signInWithEmailAndPassword({ 11 | required String email, 12 | required String password, 13 | }); 14 | 15 | Future> recoveryPassword({ 16 | required String email, 17 | }); 18 | 19 | Future> updateNewPassword({ 20 | required String password, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/get_started/presentation/blocs/get_started_state.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:equatable/equatable.dart'; 3 | 4 | abstract class GetStartedState extends Equatable { 5 | 6 | const GetStartedState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class GetStartedInitialState extends GetStartedState { 13 | const GetStartedInitialState() : super(); 14 | } 15 | 16 | class GetStartedLoadingState extends GetStartedState { 17 | const GetStartedLoadingState(); 18 | } 19 | 20 | class OpenSignInState extends GetStartedState { 21 | const OpenSignInState() : super(); 22 | } 23 | 24 | class OpenSignUpState extends GetStartedState { 25 | const OpenSignUpState() : super(); 26 | } -------------------------------------------------------------------------------- /lib/app/get_started/presentation/blocs/get_started_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class GetStartedEvent extends Equatable { 4 | const GetStartedEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class OpenSignInEvent extends GetStartedEvent { 11 | const OpenSignInEvent() : super(); 12 | } 13 | 14 | class OpenSignUpEvent extends GetStartedEvent { 15 | const OpenSignUpEvent() : super(); 16 | } 17 | 18 | class GetStartedInitialEvent extends GetStartedEvent { 19 | const GetStartedInitialEvent() : super(); 20 | } 21 | 22 | class GetStartedLoadingEvent extends GetStartedEvent { 23 | const GetStartedLoadingEvent() : super(); 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/card/presentation/blocs/card_report/card_report_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class CardReportEvent extends Equatable { 4 | const CardReportEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class CardReportInitialEvent extends CardReportEvent { 11 | const CardReportInitialEvent() : super(); 12 | } 13 | 14 | class ChangeSearchTxtEvent extends CardReportEvent { 15 | final String searchText; 16 | 17 | const ChangeSearchTxtEvent({required this.searchText}) : super(); 18 | } 19 | 20 | class SubmitSearchEvent extends CardReportEvent { 21 | final String? search; 22 | 23 | const SubmitSearchEvent({this.search}) : super(); 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/image/image_paths.dart: -------------------------------------------------------------------------------- 1 | class ImagePaths { 2 | static const String logoLightImage = 'assets/images/logo/logo_light.png'; 3 | static const String logoTextLightImage = 4 | 'assets/images/logo/logo_text_light.png'; 5 | static const String appleLogoImage = 'assets/images/auth/apple.svg'; 6 | static const String githubLogoImage = 'assets/images/auth/github.svg'; 7 | static const String googleLogoImage = 'assets/images/auth/google.svg'; 8 | static const String passwordImage = 'assets/images/dashboard/password.png'; 9 | static const String chipImage = 'assets/images/card/chip.png'; 10 | static const String americanExpressFaceImage = 11 | 'assets/images/card/american_express_face.png'; 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/card/infrastructure/models/card_type.dart: -------------------------------------------------------------------------------- 1 | enum CardType { 2 | visa, 3 | masterCard, 4 | americanExpress, 5 | discover, 6 | dinersClub, 7 | unknown 8 | } 9 | 10 | extension CardTypeExtension on CardType { 11 | String get stringValue { 12 | switch (this) { 13 | case CardType.visa: 14 | return 'Visa'; 15 | case CardType.masterCard: 16 | return 'Master Card'; 17 | case CardType.americanExpress: 18 | return 'American Express'; 19 | case CardType.discover: 20 | return 'Discover'; 21 | case CardType.dinersClub: 22 | return 'Diners Club'; 23 | case CardType.unknown: 24 | return 'Unknown Card'; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/app/dashboard/infrastructure/models/display_name_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | 4 | enum DisplayNameFormValidationError { 5 | empty, 6 | } 7 | 8 | class DisplayNameForm 9 | extends FormzInput { 10 | const DisplayNameForm.pure() : super.pure(''); 11 | 12 | const DisplayNameForm.dirty([super.value = '']) : super.dirty(); 13 | 14 | @override 15 | DisplayNameFormValidationError? validator(String? value) { 16 | return null; 17 | } 18 | 19 | String? getError( 20 | AppLocalizations intl10, 21 | DisplayNameFormValidationError? error, 22 | ) { 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/app/password/presentation/blocs/password_report/password_report_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class PassReportEvent extends Equatable { 4 | const PassReportEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class PassReportInitialEvent extends PassReportEvent { 11 | const PassReportInitialEvent() : super(); 12 | } 13 | 14 | class ChangeSearchTxtEvent extends PassReportEvent { 15 | final String searchText; 16 | 17 | const ChangeSearchTxtEvent({required this.searchText}) : super(); 18 | } 19 | 20 | class SubmitSearchEvent extends PassReportEvent { 21 | final String? search; 22 | 23 | const SubmitSearchEvent({this.search}) : super(); 24 | } 25 | -------------------------------------------------------------------------------- /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/app/profile/infraestructure/models/insert_master_password_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:savepass/app/profile/domain/entities/insert_master_password_entity.dart'; 2 | 3 | class InsertMasterPasswordModel extends InsertMasterPasswordEntity { 4 | const InsertMasterPasswordModel({ 5 | required super.secret, 6 | required super.name, 7 | required super.deviceId, 8 | required super.deviceName, 9 | required super.type, 10 | required super.salt, 11 | }); 12 | 13 | Map toJson() { 14 | return { 15 | 'secret': secret, 16 | 'name': name, 17 | 'device_id': deviceId, 18 | 'device_name': deviceName, 19 | 'type': type, 20 | 'salt': salt, 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/profile/presentation/blocs/profile/profile_event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:equatable/equatable.dart'; 4 | 5 | abstract class ProfileEvent extends Equatable { 6 | const ProfileEvent(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class SaveDerivedKeyEvent extends ProfileEvent { 13 | final Uint8List derivedKey; 14 | 15 | const SaveDerivedKeyEvent({ 16 | required this.derivedKey, 17 | }) : super(); 18 | } 19 | 20 | class SaveJwtEvent extends ProfileEvent { 21 | final String jwt; 22 | 23 | const SaveJwtEvent({ 24 | required this.jwt, 25 | }) : super(); 26 | } 27 | 28 | class ClearValuesEvent extends ProfileEvent { 29 | const ClearValuesEvent() : super(); 30 | } 31 | -------------------------------------------------------------------------------- /lib/app/sync_pass/presentation/blocs/sync_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class SyncEvent extends Equatable { 4 | const SyncEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class SyncInitialEvent extends SyncEvent { 11 | const SyncInitialEvent() : super(); 12 | } 13 | 14 | class SyncPasswordChangedEvent extends SyncEvent { 15 | final String password; 16 | 17 | const SyncPasswordChangedEvent({required this.password}) : super(); 18 | } 19 | 20 | class SubmitSyncPasswordEvent extends SyncEvent { 21 | const SubmitSyncPasswordEvent() : super(); 22 | } 23 | 24 | class ToggleMasterPasswordEvent extends SyncEvent { 25 | const ToggleMasterPasswordEvent() : super(); 26 | } 27 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/auth_email/forgot_password.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/core/config/routes.dart'; 5 | 6 | class ForgotPassword extends StatelessWidget { 7 | const ForgotPassword({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final intl = AppLocalizations.of(context)!; 12 | 13 | return InkWell( 14 | onTap: () => Modular.to.pushNamed(Routes.forgotPasswordRoute), 15 | child: Text( 16 | intl.forgotPassword, 17 | style: TextStyle(color: Colors.grey[600]), 18 | textAlign: TextAlign.start, 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/biometric/presentation/blocs/biometric_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class BiometricEvent extends Equatable { 4 | const BiometricEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class BiometricInitialEvent extends BiometricEvent { 11 | const BiometricInitialEvent() : super(); 12 | } 13 | 14 | class SubmitBiometricEvent extends BiometricEvent { 15 | const SubmitBiometricEvent() : super(); 16 | } 17 | 18 | class BiometricPasswordChangedEvent extends BiometricEvent { 19 | final String password; 20 | 21 | const BiometricPasswordChangedEvent({required this.password}) : super(); 22 | } 23 | 24 | class ToggleMasterPasswordEvent extends BiometricEvent { 25 | const ToggleMasterPasswordEvent() : super(); 26 | } -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/home/home_search_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | 5 | class HomeSearchWidget extends StatelessWidget { 6 | const HomeSearchWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | final intl = AppLocalizations.of(context)!; 11 | 12 | return AdsTextFormField( 13 | hintText: intl.homeSearch, 14 | key: const Key('home_search_textField'), 15 | keyboardType: TextInputType.text, 16 | errorText: null, 17 | enableSuggestions: false, 18 | textInputAction: TextInputAction.done, 19 | suffixIcon: Icons.search, 20 | enabled: false, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/preferences/presentation/blocs/preferences_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/app/preferences/domain/entities/preferences_entity.dart'; 4 | 5 | abstract class PreferencesEvent extends Equatable { 6 | const PreferencesEvent(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class ToggleBrightnessEvent extends PreferencesEvent { 13 | final BrightnessType brightness; 14 | 15 | const ToggleBrightnessEvent({required this.brightness}) : super(); 16 | } 17 | 18 | class ChangeLocaleEvent extends PreferencesEvent { 19 | final Locale locale; 20 | 21 | const ChangeLocaleEvent({required this.locale}) : super(); 22 | } 23 | 24 | class GetPreferencesEvent extends PreferencesEvent { 25 | const GetPreferencesEvent() : super(); 26 | } -------------------------------------------------------------------------------- /lib/app/master_password/domain/entities/update_master_password_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:savepass/app/master_password/domain/entities/update_cards_entity.dart'; 2 | import 'package:savepass/app/master_password/domain/entities/update_passwords_entity.dart'; 3 | 4 | class UpdateMasterPasswordEntity { 5 | final String oldPassword; 6 | final String newPassword; 7 | final String nameNewPassword; 8 | final String deviceIdParam; 9 | final String salt; 10 | final List passwords; 11 | final List cards; 12 | 13 | 14 | const UpdateMasterPasswordEntity({ 15 | required this.oldPassword, 16 | required this.newPassword, 17 | required this.nameNewPassword, 18 | required this.deviceIdParam, 19 | required this.salt, 20 | required this.passwords, 21 | required this.cards, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/biometric/infrastructure/repositories_impl/biometric_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/biometric/domain/datasources/biometric_datasource.dart'; 3 | import 'package:savepass/app/biometric/domain/repositories/biometric_repository.dart'; 4 | import 'package:savepass/core/api/savepass_response_model.dart'; 5 | 6 | class BiometricRepositoryImpl implements BiometricRepository { 7 | final BiometricDatasource datasource; 8 | 9 | BiometricRepositoryImpl({required this.datasource}); 10 | 11 | @override 12 | Future> enrollBiometric({ 13 | required String inputSecret, 14 | required String deviceId, 15 | }) async { 16 | return await datasource.enrollBiometric( 17 | inputSecret: inputSecret, 18 | deviceId: deviceId, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/preferences/domain/datasources/parameters_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/preferences/infrastructure/models/card_image_model.dart'; 3 | import 'package:savepass/app/preferences/infrastructure/models/pass_image_model.dart'; 4 | 5 | abstract class ParametersDatasource { 6 | Future> getPrivacyUrl(); 7 | Future> getTermsUrl(); 8 | Future> getFeatureFlag(); 9 | Future> getAppVersion(); 10 | Future> getAppStoreURL(); 11 | Future> getPlayStoreURL(); 12 | Future> getSavePassDocsURL(); 13 | Future> getSupportMail(); 14 | Future>> getPassImages(); 15 | Future>> getCardImages(); 16 | } 17 | -------------------------------------------------------------------------------- /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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/app/enroll/presentation/widgets/enroll_submit_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/molecules/button/ads_filled_button.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/enroll/presentation/blocs/enroll_bloc.dart'; 6 | import 'package:savepass/app/enroll/presentation/blocs/enroll_event.dart'; 7 | 8 | class EnrollSubmitButton extends StatelessWidget { 9 | const EnrollSubmitButton({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | 15 | return AdsFilledButton( 16 | onPressedCallback: () { 17 | final bloc = Modular.get(); 18 | bloc.add(const SubmitEnrollEvent()); 19 | }, 20 | text: intl.linkDevice, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/card/domain/datasources/card_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/card/infrastructure/models/card_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class CardDatasource { 6 | Future> insertCard({ 7 | required CardModel model, 8 | }); 9 | 10 | Future> editCard({ 11 | required CardModel model, 12 | }); 13 | 14 | Future> deleteCard({ 15 | required String cardId, 16 | required String vaultId, 17 | }); 18 | 19 | Future> getCards(); 20 | 21 | Future> getCardById({ 22 | required String cardId, 23 | }); 24 | 25 | Future> searchCards({ 26 | required String search, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/biometric/presentation/widgets/submit_biometric_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/biometric/presentation/blocs/biometric_bloc.dart'; 6 | import 'package:savepass/app/biometric/presentation/blocs/biometric_event.dart'; 7 | 8 | class SubmitBiometricWidget extends StatelessWidget { 9 | const SubmitBiometricWidget({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final bloc = Modular.get(); 15 | 16 | return AdsFilledButton( 17 | onPressedCallback: () { 18 | bloc.add(const SubmitBiometricEvent()); 19 | }, 20 | text: intl.acceptButton, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/card/domain/repositories/card_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/card/infrastructure/models/card_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class CardRepository { 6 | Future> insertCard({ 7 | required CardModel model, 8 | }); 9 | 10 | Future> editCard({ 11 | required CardModel model, 12 | }); 13 | 14 | Future> deleteCard({ 15 | required String cardId, 16 | required String vaultId, 17 | }); 18 | 19 | Future> getCards(); 20 | 21 | Future> getCardById({ 22 | required String cardId, 23 | }); 24 | 25 | Future> searchCards({ 26 | required String search, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/auth_email/auth_submit.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/auth/presentation/blocs/auth_bloc.dart'; 6 | import 'package:savepass/app/auth/presentation/blocs/auth_event.dart'; 7 | 8 | class AuthSubmit extends StatelessWidget { 9 | const AuthSubmit({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final appLocalizations = AppLocalizations.of(context)!; 14 | 15 | return AdsFilledIconButton( 16 | onPressedCallback: () { 17 | final bloc = Modular.get(); 18 | bloc.add(const AuthWithEmailEvent()); 19 | }, 20 | text: appLocalizations.signUpButtonText, 21 | icon: Icons.check, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/recovery_password/recovery_submit.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/auth/presentation/blocs/auth_bloc.dart'; 6 | import 'package:savepass/app/auth/presentation/blocs/auth_event.dart'; 7 | 8 | class RecoverySubmit extends StatelessWidget { 9 | const RecoverySubmit({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final appLocalizations = AppLocalizations.of(context)!; 14 | final bloc = Modular.get(); 15 | 16 | return AdsFilledIconButton( 17 | onPressedCallback: () => bloc.add(const RecoveryPasswordSubmitEvent()), 18 | text: appLocalizations.signUpButtonText, 19 | icon: Icons.check, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/auth_init/presentation/widgets/submit_button_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/auth_init/presentation/blocs/auth_init_bloc.dart'; 6 | import 'package:savepass/app/auth_init/presentation/blocs/auth_init_event.dart'; 7 | 8 | class SubmitButtonWidget extends StatelessWidget { 9 | const SubmitButtonWidget({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final appLocalizations = AppLocalizations.of(context)!; 14 | 15 | return AdsFilledButton( 16 | onPressedCallback: () { 17 | final bloc = Modular.get(); 18 | bloc.add(const SubmitEvent()); 19 | }, 20 | text: appLocalizations.signUpButtonText, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/master_password/infrastructure/repositories_impl/master_password_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/master_password/domain/datasources/master_password_datasource.dart'; 3 | import 'package:savepass/app/master_password/domain/repositories/master_password_repository.dart'; 4 | import 'package:savepass/app/master_password/infrastructure/models/update_master_password_model.dart'; 5 | import 'package:savepass/core/api/savepass_response_model.dart'; 6 | 7 | class MasterPasswordRepositoryImpl implements MasterPasswordRepository { 8 | final MasterPasswordDatasource datasource; 9 | 10 | MasterPasswordRepositoryImpl({required this.datasource}); 11 | 12 | @override 13 | Future> updateMasterPassword({ 14 | required UpdateMasterPasswordModel model, 15 | }) async { 16 | return await datasource.updateMasterPassword(model: model); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/app/search/infrastructure/models/search_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:savepass/app/search/domain/entities/search_entity.dart'; 3 | 4 | class SearchModel extends SearchEntity with EquatableMixin { 5 | SearchModel({ 6 | required super.id, 7 | required super.title, 8 | required super.subtitle, 9 | super.imgUrl, 10 | required super.type, 11 | required super.vaultId, 12 | }); 13 | 14 | factory SearchModel.fromJson(Map json) { 15 | return SearchModel( 16 | id: json['id'], 17 | title: json['title'], 18 | subtitle: json['subtitle'], 19 | imgUrl: json['imgurl'], 20 | type: json['type'], 21 | vaultId: json['vault_id'], 22 | ); 23 | } 24 | 25 | @override 26 | List get props => [ 27 | id, 28 | title, 29 | subtitle, 30 | imgUrl, 31 | type, 32 | vaultId, 33 | ]; 34 | } 35 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/forgot_password/forgot_password_submit.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/auth/presentation/blocs/auth_bloc.dart'; 6 | import 'package:savepass/app/auth/presentation/blocs/auth_event.dart'; 7 | 8 | class ForgotPasswordSubmit extends StatelessWidget { 9 | const ForgotPasswordSubmit({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final appLocalizations = AppLocalizations.of(context)!; 14 | final bloc = Modular.get(); 15 | 16 | return AdsFilledIconButton( 17 | onPressedCallback: () { 18 | bloc.add(const ForgotPasswordSubmitEvent()); 19 | }, 20 | text: appLocalizations.signUpButtonText, 21 | icon: Icons.check, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/sync_pass/presentation/widgets/submit_sync_pass_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/sync_pass/presentation/blocs/sync_bloc.dart'; 6 | import 'package:savepass/app/sync_pass/presentation/blocs/sync_event.dart'; 7 | 8 | class SubmitSyncPassWidget extends StatelessWidget { 9 | const SubmitSyncPassWidget({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final appLocalizations = AppLocalizations.of(context)!; 14 | 15 | return AdsFilledIconButton( 16 | onPressedCallback: () { 17 | final bloc = Modular.get(); 18 | bloc.add(const SubmitSyncPasswordEvent()); 19 | }, 20 | text: appLocalizations.signUpButtonText, 21 | icon: Icons.check, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/master_password/presentation/widget/submit_update_master_password_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/master_password/presentation/blocs/master_password_bloc.dart'; 6 | import 'package:savepass/app/master_password/presentation/blocs/master_password_event.dart'; 7 | 8 | class SubmitUpdateMasterPasswordWidget extends StatelessWidget { 9 | const SubmitUpdateMasterPasswordWidget({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final bloc = Modular.get(); 15 | 16 | return AdsFilledButton( 17 | onPressedCallback: () { 18 | bloc.add(const SubmitEvent()); 19 | }, 20 | text: intl.updateText, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/password/domain/datasources/password_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/password/infrastructure/models/password_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class PasswordDatasource { 6 | Future> insertPassword({ 7 | required PasswordModel model, 8 | }); 9 | 10 | Future> editPassword({ 11 | required PasswordModel model, 12 | }); 13 | 14 | Future> deletePassword({ 15 | required String passwordId, 16 | required String vaultId, 17 | }); 18 | 19 | Future> getPasswords(); 20 | 21 | Future> getPasswordById({ 22 | required String passwordId, 23 | }); 24 | 25 | Future> searchPasswords({ 26 | required String search, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/password/domain/repositories/password_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/password/infrastructure/models/password_model.dart'; 3 | import 'package:savepass/core/api/savepass_response_model.dart'; 4 | 5 | abstract class PasswordRepository { 6 | Future> insertPassword({ 7 | required PasswordModel model, 8 | }); 9 | 10 | Future> editPassword({ 11 | required PasswordModel model, 12 | }); 13 | 14 | Future> deletePassword({ 15 | required String passwordId, 16 | required String vaultId, 17 | }); 18 | 19 | Future> getPasswords(); 20 | 21 | Future> getPasswordById({ 22 | required String passwordId, 23 | }); 24 | 25 | Future> searchPasswords({ 26 | required String search, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/enroll/infrastructure/repositories/enroll_repository_impl.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:savepass/app/enroll/domain/datasources/enroll_datasource.dart'; 5 | import 'package:savepass/app/enroll/domain/repositories/enroll_repository.dart'; 6 | import 'package:savepass/app/enroll/infrastructure/models/enroll_new_device_model.dart'; 7 | import 'package:savepass/core/api/savepass_response_model.dart'; 8 | 9 | class EnrollRepositoryImpl implements EnrollRepository { 10 | 11 | final EnrollDatasource datasource; 12 | 13 | EnrollRepositoryImpl({required this.datasource}); 14 | 15 | @override 16 | Future> getDeviceName() async { 17 | return await datasource.getDeviceName(); 18 | } 19 | 20 | @override 21 | Future> enrollNewDevice({ 22 | required EnrollNewDeviceModel model, 23 | }) async { 24 | return await datasource.enrollNewDevice(model: model); 25 | } 26 | 27 | 28 | 29 | } -------------------------------------------------------------------------------- /lib/app/preferences/infrastructure/models/card_image_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:savepass/app/preferences/domain/entities/card_image_entity.dart'; 3 | 4 | class CardImageModel extends CardImageEntity with EquatableMixin { 5 | const CardImageModel({ 6 | required super.id, 7 | required super.type, 8 | required super.imgUrl, 9 | }); 10 | 11 | factory CardImageModel.fromJson(Map json) { 12 | return CardImageModel( 13 | id: json['id'], 14 | type: json['type'], 15 | imgUrl: json['imgurl'], 16 | ); 17 | } 18 | 19 | CardImageModel copyWith({ 20 | String? id, 21 | String? type, 22 | String? imgUrl, 23 | }) { 24 | return CardImageModel( 25 | id: id ?? this.id, 26 | type: type ?? this.type, 27 | imgUrl: imgUrl ?? this.imgUrl, 28 | ); 29 | } 30 | 31 | @override 32 | List get props => [ 33 | id, 34 | type, 35 | imgUrl, 36 | ]; 37 | } 38 | -------------------------------------------------------------------------------- /lib/app/preferences/utils/preferences_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:savepass/app/preferences/domain/entities/preferences_entity.dart'; 2 | import 'package:savepass/app/preferences/infrastructure/models/preferences_model.dart'; 3 | import 'package:savepass/app/preferences/presentation/blocs/preferences_state.dart'; 4 | 5 | class PreferencesUtils { 6 | static const defaultAccountPreferencesModel = ThemeStateModel( 7 | theme: PreferencesModel( 8 | brightness: BrightnessType.dark, 9 | ), 10 | ); 11 | 12 | static String getLanguageText(String language) { 13 | switch (language) { 14 | case 'en': 15 | return 'English'; 16 | case 'es': 17 | return 'Español'; 18 | default: 19 | return 'English'; 20 | } 21 | } 22 | 23 | static String getLanguageCode(String language) { 24 | switch (language) { 25 | case 'English': 26 | return 'en'; 27 | case 'Español': 28 | return 'es'; 29 | default: 30 | return 'en'; 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/forgot_password/recovery_email_header.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:atomic_design_system/molecules/button/ads_filled_round_icon_button.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | 7 | class RecoveryEmailHeader extends StatelessWidget { 8 | const RecoveryEmailHeader({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final screenHeight = MediaQuery.of(context).size.height; 13 | 14 | return Column( 15 | children: [ 16 | if (Platform.isAndroid) SizedBox(height: screenHeight * 0.015), 17 | Row( 18 | children: [ 19 | AdsFilledRoundIconButton( 20 | icon: const Icon( 21 | Icons.keyboard_arrow_left, 22 | ), 23 | onPressedCallback: () { 24 | Modular.to.pop(); 25 | }, 26 | ), 27 | ], 28 | ), 29 | ], 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/auth_email/confirm_mail_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:atomic_design_system/molecules/button/ads_filled_round_icon_button.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | 7 | class ConfirmMailHeaderWidget extends StatelessWidget { 8 | const ConfirmMailHeaderWidget({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final screenHeight = MediaQuery.of(context).size.height; 13 | 14 | return Column( 15 | children: [ 16 | if (Platform.isAndroid) SizedBox(height: screenHeight * 0.015), 17 | Row( 18 | children: [ 19 | AdsFilledRoundIconButton( 20 | icon: const Icon( 21 | Icons.keyboard_arrow_left, 22 | ), 23 | onPressedCallback: () { 24 | Modular.to.pop(); 25 | }, 26 | ), 27 | ], 28 | ), 29 | ], 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | .env 47 | firebase.json 48 | google-services.json 49 | firebase_options.dart 50 | env.g.dart 51 | db 52 | GoogleService-Info.plist 53 | 54 | .kotlin -------------------------------------------------------------------------------- /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 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "8.1.0" apply false 23 | // START: FlutterFire Configuration 24 | id "com.google.gms.google-services" version "4.3.15" apply false 25 | // END: FlutterFire Configuration 26 | id "org.jetbrains.kotlin.android" version "2.0.21" apply false 27 | } 28 | 29 | include ":app" 30 | -------------------------------------------------------------------------------- /lib/app/profile/infraestructure/models/profile_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:savepass/app/profile/domain/entities/profile_entity.dart'; 3 | 4 | class ProfileModel extends ProfileEntity with EquatableMixin { 5 | const ProfileModel({ 6 | super.displayName, 7 | super.avatar, 8 | }); 9 | 10 | factory ProfileModel.fromJson(Map json) { 11 | return ProfileModel( 12 | displayName: json['full_name'], 13 | avatar: json['custom_avatar'] ?? json['avatar_url'], 14 | ); 15 | } 16 | 17 | Map toJson() { 18 | return { 19 | 'displayName': displayName, 20 | 'avatar': avatar, 21 | }; 22 | } 23 | 24 | ProfileModel copyWith({ 25 | String? displayName, 26 | String? avatar, 27 | }) { 28 | return ProfileModel( 29 | displayName: displayName ?? this.displayName, 30 | avatar: avatar ?? this.avatar, 31 | ); 32 | } 33 | 34 | @override 35 | List get props => [ 36 | displayName, 37 | avatar, 38 | ]; 39 | } 40 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/app/profile/domain/datasources/profile_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/profile/domain/entities/profile_entity.dart'; 3 | import 'package:savepass/app/profile/infraestructure/models/insert_master_password_model.dart'; 4 | import 'package:savepass/core/api/savepass_response_model.dart'; 5 | 6 | abstract class ProfileDatasource { 7 | Future> uploadAvatar(String imgPath); 8 | 9 | Future> updateProfile({ 10 | String? displayName, 11 | String? avatarUuid, 12 | }); 13 | 14 | Future> insertMasterPassword({ 15 | required InsertMasterPasswordModel model, 16 | }); 17 | 18 | Future> checkIfHasMasterPassword(); 19 | 20 | Future> getProfile(); 21 | 22 | Future> isEmailExists({ 23 | required String email, 24 | }); 25 | 26 | Future> deleteAccount(); 27 | 28 | Future> deleteAvatar(); 29 | } 30 | -------------------------------------------------------------------------------- /lib/app/profile/domain/repositories/profile_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/profile/domain/entities/profile_entity.dart'; 3 | import 'package:savepass/app/profile/infraestructure/models/insert_master_password_model.dart'; 4 | import 'package:savepass/core/api/savepass_response_model.dart'; 5 | 6 | abstract class ProfileRepository { 7 | Future> uploadAvatar(String imgPath); 8 | 9 | Future> updateProfile({ 10 | String? displayName, 11 | String? avatarUuid, 12 | }); 13 | 14 | Future> insertMasterPassword({ 15 | required InsertMasterPasswordModel model, 16 | }); 17 | 18 | Future> checkIfHasMasterPassword(); 19 | 20 | Future> getProfile(); 21 | 22 | Future> isEmailExists({ 23 | required String email, 24 | }); 25 | 26 | Future> deleteAccount(); 27 | 28 | Future> deleteAvatar(); 29 | } 30 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/app/splash/presentation/blocs/splash_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class SplashState extends Equatable { 4 | final SplashStateModel model; 5 | 6 | const SplashState(this.model); 7 | 8 | @override 9 | List get props => [model]; 10 | } 11 | 12 | class SplashInitialState extends SplashState { 13 | const SplashInitialState() : super(const SplashStateModel()); 14 | } 15 | 16 | class SplashLoadingState extends SplashState { 17 | const SplashLoadingState(super.model); 18 | } 19 | 20 | class OpenGetStartedState extends SplashState { 21 | const OpenGetStartedState(super.model); 22 | } 23 | 24 | class OpenAuthInitState extends SplashState { 25 | const OpenAuthInitState(super.model); 26 | } 27 | 28 | class OpenSyncMasterPasswordState extends SplashState { 29 | const OpenSyncMasterPasswordState(super.model); 30 | } 31 | 32 | class FeatureFlagState extends SplashState { 33 | const FeatureFlagState(super.model); 34 | } 35 | 36 | class SplashStateModel extends Equatable { 37 | const SplashStateModel(); 38 | 39 | @override 40 | List get props => []; 41 | } 42 | -------------------------------------------------------------------------------- /lib/core/form/text_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | import 'package:savepass/core/utils/form_utils.dart'; 4 | 5 | enum TextFormValidationError { empty, maxLength } 6 | 7 | class TextForm extends FormzInput { 8 | const TextForm.pure() : super.pure(''); 9 | 10 | const TextForm.dirty([super.value = '']) : super.dirty(); 11 | 12 | @override 13 | TextFormValidationError? validator(String? value) { 14 | if (value == null || value.isEmpty) { 15 | return TextFormValidationError.empty; 16 | } 17 | 18 | if (value.length > FormUtils.maxLength) { 19 | return TextFormValidationError.maxLength; 20 | } 21 | 22 | return null; 23 | } 24 | 25 | String? getError(AppLocalizations intl10, TextFormValidationError? error) { 26 | switch (error) { 27 | case TextFormValidationError.empty: 28 | return intl10.mandatoryField; 29 | case TextFormValidationError.maxLength: 30 | return intl10.maxLengthField; 31 | default: 32 | return null; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Juan García] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/app/auth_init/presentation/blocs/auth_init_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class AuthInitEvent extends Equatable { 4 | const AuthInitEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class AuthInitInitialEvent extends AuthInitEvent { 11 | final bool refreshAuth; 12 | 13 | const AuthInitInitialEvent({required this.refreshAuth}) : super(); 14 | } 15 | 16 | class CheckSupabaseBiometricsEvent extends AuthInitEvent{ 17 | const CheckSupabaseBiometricsEvent() : super(); 18 | } 19 | 20 | class PasswordChangedEvent extends AuthInitEvent { 21 | final String password; 22 | 23 | const PasswordChangedEvent({required this.password}) : super(); 24 | } 25 | 26 | class ToggleMasterPasswordEvent extends AuthInitEvent { 27 | const ToggleMasterPasswordEvent() : super(); 28 | } 29 | 30 | class SubmitEvent extends AuthInitEvent { 31 | const SubmitEvent() : super(); 32 | } 33 | 34 | class SubmitWithBiometricsEvent extends AuthInitEvent{ 35 | const SubmitWithBiometricsEvent() : super(); 36 | } 37 | 38 | class GetProfileEvent extends AuthInitEvent{ 39 | const GetProfileEvent() : super(); 40 | } -------------------------------------------------------------------------------- /lib/app/profile/presentation/blocs/profile/profile_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:equatable/equatable.dart'; 4 | 5 | abstract class ProfileState extends Equatable { 6 | final ProfileStateModel model; 7 | 8 | const ProfileState(this.model); 9 | 10 | @override 11 | List get props => [model]; 12 | } 13 | 14 | class ProfileInitialState extends ProfileState { 15 | const ProfileInitialState() : super(const ProfileStateModel()); 16 | } 17 | 18 | class ChangeProfileState extends ProfileState { 19 | const ChangeProfileState(super.model); 20 | } 21 | 22 | class ProfileStateModel extends Equatable { 23 | final Uint8List? derivedKey; 24 | final String? jwt; 25 | 26 | const ProfileStateModel({ 27 | this.derivedKey, 28 | this.jwt, 29 | }); 30 | 31 | ProfileStateModel copyWith({ 32 | Uint8List? derivedKey, 33 | String? jwt, 34 | }) { 35 | return ProfileStateModel( 36 | derivedKey: derivedKey ?? this.derivedKey, 37 | jwt: jwt ?? this.jwt, 38 | ); 39 | } 40 | 41 | @override 42 | List get props => [ 43 | derivedKey, 44 | jwt, 45 | ]; 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/tools/join_biometrics_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/molecules/card/ads_card.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class JoinBiometricsWidget extends StatelessWidget { 5 | const JoinBiometricsWidget({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | final deviceWidth = MediaQuery.of(context).size.width; 10 | final textTheme = Theme.of(context).textTheme; 11 | 12 | return AdsCard( 13 | onTap: () {}, 14 | bgColor: Colors.black87, 15 | child: Row( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | crossAxisAlignment: CrossAxisAlignment.center, 18 | children: [ 19 | const Icon( 20 | Icons.face, 21 | color: Colors.white, 22 | ), 23 | SizedBox(width: deviceWidth * 0.02), 24 | Text( 25 | 'Join with biometrics', 26 | style: textTheme.titleMedium?.copyWith( 27 | color: Colors.white, 28 | fontSize: 19.5, 29 | ), 30 | ), 31 | ], 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/core/api/savepass_response_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class SavePassResponseModel with EquatableMixin { 4 | final String code; 5 | final String? message; 6 | final Map? data; 7 | 8 | const SavePassResponseModel({ 9 | required this.code, 10 | this.message, 11 | this.data, 12 | }); 13 | 14 | factory SavePassResponseModel.fromJson(Map json) { 15 | return SavePassResponseModel( 16 | code: json['code'], 17 | message: json['message'], 18 | data: json['data'], 19 | ); 20 | } 21 | 22 | Map toJson() { 23 | return { 24 | 'code': code, 25 | 'message': message, 26 | 'data': data, 27 | }; 28 | } 29 | 30 | SavePassResponseModel copyWith({ 31 | String? code, 32 | String? message, 33 | Map? data, 34 | }) { 35 | return SavePassResponseModel( 36 | code: code ?? this.code, 37 | message: message ?? this.message, 38 | data: data ?? this.data, 39 | ); 40 | } 41 | 42 | @override 43 | List get props => [ 44 | code, 45 | message, 46 | data, 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /lib/core/form/password_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | import 'package:savepass/core/utils/form_utils.dart'; 4 | 5 | enum PasswordFormValidationError { empty, maxLength } 6 | 7 | class PasswordForm extends FormzInput { 8 | const PasswordForm.pure() : super.pure(''); 9 | 10 | const PasswordForm.dirty([super.value = '']) : super.dirty(); 11 | 12 | @override 13 | PasswordFormValidationError? validator(String? value) { 14 | if (value == null || value.isEmpty) { 15 | return PasswordFormValidationError.empty; 16 | } 17 | 18 | if (value.length > FormUtils.maxLength) { 19 | return PasswordFormValidationError.maxLength; 20 | } 21 | 22 | return null; 23 | } 24 | 25 | String? getError( 26 | AppLocalizations intl10, 27 | PasswordFormValidationError? error, 28 | ) { 29 | switch (error) { 30 | case PasswordFormValidationError.empty: 31 | return intl10.mandatoryField; 32 | case PasswordFormValidationError.maxLength: 33 | return intl10.maxLengthField; 34 | default: 35 | return null; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/app/get_started/presentation/blocs/get_started_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:savepass/app/get_started/presentation/blocs/get_started_event.dart'; 5 | import 'package:savepass/app/get_started/presentation/blocs/get_started_state.dart'; 6 | 7 | class GetStartedBloc extends Bloc { 8 | GetStartedBloc() : super(const GetStartedInitialState()) { 9 | on(onGetStartedInitialEvent); 10 | on(onOpenSignInEvent); 11 | on(onOpenSignUpEvent); 12 | } 13 | 14 | FutureOr onGetStartedInitialEvent( 15 | GetStartedInitialEvent event, 16 | Emitter emit, 17 | ) {} 18 | 19 | FutureOr onOpenSignInEvent( 20 | OpenSignInEvent event, 21 | Emitter emit, 22 | ) { 23 | emit(const GetStartedLoadingState()); 24 | emit(const OpenSignInState()); 25 | } 26 | 27 | FutureOr onOpenSignUpEvent( 28 | OpenSignUpEvent event, 29 | Emitter emit, 30 | ) { 31 | emit(const GetStartedLoadingState()); 32 | emit(const OpenSignUpState()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/core/form/email_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | 4 | enum EmailFormValidationError { invalid, empty } 5 | 6 | class EmailForm extends FormzInput { 7 | const EmailForm.pure() : super.pure(''); 8 | 9 | const EmailForm.dirty([super.value = '']) : super.dirty(); 10 | 11 | static final RegExp _emailRegExp = RegExp( 12 | r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$', 13 | ); 14 | 15 | @override 16 | EmailFormValidationError? validator(String? value) { 17 | if (value == null || value.isEmpty) { 18 | return EmailFormValidationError.empty; 19 | } 20 | 21 | if (!_emailRegExp.hasMatch(value)) { 22 | return EmailFormValidationError.invalid; 23 | } 24 | 25 | return null; 26 | } 27 | 28 | String? getError(AppLocalizations intl10, EmailFormValidationError? error) { 29 | switch (error) { 30 | case EmailFormValidationError.invalid: 31 | return intl10.incorrectEmail; 32 | case EmailFormValidationError.empty: 33 | return intl10.mandatoryField; 34 | default: 35 | return null; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/settings/log_out_settings_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_bloc.dart'; 5 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_event.dart'; 6 | import 'package:savepass/l10n/app_localizations.dart'; 7 | 8 | class LogOutSettingsWidget extends StatelessWidget { 9 | const LogOutSettingsWidget({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final bloc = Modular.get(); 15 | 16 | return Padding( 17 | padding: const EdgeInsets.all(20), 18 | child: Center( 19 | child: AdsTextButton( 20 | text: intl.logOut, 21 | onPressedCallback: () => bloc.add(const LogOutEvent()), 22 | textStyle: const TextStyle( 23 | fontWeight: FontWeight.bold, 24 | decoration: TextDecoration.underline, 25 | ), 26 | textAlign: TextAlign.center, 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/settings/language_settings_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:savepass/app/dashboard/presentation/widgets/settings/language_dropdown_widget.dart'; 5 | 6 | class LanguageSettingsWidget extends StatelessWidget { 7 | const LanguageSettingsWidget({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final intl = AppLocalizations.of(context)!; 12 | 13 | return AdsCard( 14 | child: Padding( 15 | padding: const EdgeInsets.all(20), 16 | child: Column( 17 | crossAxisAlignment: CrossAxisAlignment.stretch, 18 | children: [ 19 | AdsTitle( 20 | text: intl.appLanguage, 21 | textAlign: TextAlign.start, 22 | ), 23 | const SizedBox(height: 10), 24 | Text( 25 | intl.appLanguageDesc, 26 | textAlign: TextAlign.start, 27 | ), 28 | const SizedBox(height: 10), 29 | LanguageDropdownWidget(), 30 | ], 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/app/preferences/infrastructure/models/pass_image_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:savepass/app/preferences/domain/entities/pass_image_entity.dart'; 3 | 4 | class PassImageModel extends PassImageEntity with EquatableMixin { 5 | final bool selected; 6 | 7 | const PassImageModel({ 8 | required super.id, 9 | required super.key, 10 | required super.type, 11 | super.domain, 12 | this.selected = false, 13 | }); 14 | 15 | factory PassImageModel.fromJson(Map json) { 16 | return PassImageModel( 17 | id: json['id'], 18 | key: json['key'], 19 | type: json['type'], 20 | domain: json['domain'], 21 | ); 22 | } 23 | 24 | PassImageModel copyWith({ 25 | String? id, 26 | String? key, 27 | String? type, 28 | String? domain, 29 | bool? selected, 30 | }) { 31 | return PassImageModel( 32 | id: id ?? this.id, 33 | key: key ?? this.key, 34 | type: type ?? this.type, 35 | domain: domain ?? this.domain, 36 | selected: selected ?? this.selected, 37 | ); 38 | } 39 | 40 | @override 41 | List get props => [ 42 | id, 43 | key, 44 | type, 45 | domain, 46 | selected, 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /lib/app/auth_init/infrastructure/repositories/auth_init_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/auth_init/domain/datasources/auth_init_datasource.dart'; 3 | import 'package:savepass/app/auth_init/domain/repositories/auth_init_repository.dart'; 4 | import 'package:savepass/core/api/savepass_response_model.dart'; 5 | 6 | class AuthInitRepositoryImpl implements AuthInitRepository { 7 | final AuthInitDatasource datasource; 8 | 9 | AuthInitRepositoryImpl({required this.datasource}); 10 | 11 | @override 12 | Future> checkMasterPassword({ 13 | required String inputSecret, 14 | required String deviceId, 15 | required String biometricHash, 16 | }) async { 17 | return await datasource.checkMasterPassword( 18 | inputSecret: inputSecret, 19 | deviceId: deviceId, 20 | biometricHash: biometricHash, 21 | ); 22 | } 23 | 24 | @override 25 | Future> getUserSalt() async { 26 | return await datasource.getUserSalt(); 27 | } 28 | 29 | @override 30 | Future> hasBiometrics({ 31 | required String deviceId, 32 | }) async { 33 | return await datasource.hasBiometrics(deviceId: deviceId); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/app/preferences/domain/repositories/preferences_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/preferences/domain/entities/preferences_entity.dart'; 3 | import 'package:savepass/app/preferences/infrastructure/models/card_image_model.dart'; 4 | import 'package:savepass/app/preferences/infrastructure/models/pass_image_model.dart'; 5 | import 'package:savepass/app/preferences/infrastructure/models/preferences_model.dart'; 6 | 7 | abstract class PreferencesRepository { 8 | Future> getTheme(); 9 | Future> setTheme(BrightnessType brightness); 10 | Future> getLanguage(); 11 | Future> setLanguage(String language); 12 | Future> getPrivacyUrl(); 13 | Future> getTermsUrl(); 14 | Future> getFeatureFlag(); 15 | Future> getAppVersion(); 16 | Future> getAppStoreURL(); 17 | Future> getPlayStoreURL(); 18 | Future> getSavePassDocsURL(); 19 | Future> getSupportMail(); 20 | Future>> getPassImages(); 21 | Future>> getCardImages(); 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/recovery_password/recovery_password_header.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:atomic_design_system/atomic_design_system.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:savepass/l10n/app_localizations.dart'; 6 | import 'package:flutter_modular/flutter_modular.dart'; 7 | 8 | class RecoveryPasswordHeader extends StatelessWidget { 9 | const RecoveryPasswordHeader({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final screenWidth = MediaQuery.of(context).size.width; 15 | final screenHeight = MediaQuery.of(context).size.height; 16 | 17 | return Column( 18 | children: [ 19 | if (Platform.isAndroid) SizedBox(height: screenHeight * 0.015), 20 | Row( 21 | children: [ 22 | AdsFilledRoundIconButton( 23 | icon: const Icon( 24 | Icons.keyboard_arrow_left, 25 | ), 26 | onPressedCallback: () { 27 | Modular.to.pop(); 28 | }, 29 | ), 30 | SizedBox(width: screenWidth * 0.05), 31 | AdsHeadline( 32 | text: intl.newPasswordTitle, 33 | ), 34 | ], 35 | ), 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/app/master_password/infrastructure/models/update_master_password_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:savepass/app/master_password/domain/entities/update_master_password_entity.dart'; 2 | import 'package:savepass/app/master_password/infrastructure/models/update_cards_model.dart'; 3 | import 'package:savepass/app/master_password/infrastructure/models/update_passwords_model.dart'; 4 | 5 | class UpdateMasterPasswordModel extends UpdateMasterPasswordEntity { 6 | final List passwordsModel; 7 | final List cardsModel; 8 | 9 | const UpdateMasterPasswordModel({ 10 | required super.oldPassword, 11 | required super.newPassword, 12 | required super.nameNewPassword, 13 | required super.deviceIdParam, 14 | required super.salt, 15 | required this.passwordsModel, 16 | required this.cardsModel, 17 | }) : super( 18 | passwords: passwordsModel, 19 | cards: cardsModel, 20 | ); 21 | 22 | Map toJson() { 23 | return { 24 | 'old_password': oldPassword, 25 | 'new_password': newPassword, 26 | 'name_new_password': nameNewPassword, 27 | 'device_id_param': deviceIdParam, 28 | 'salt_param': salt, 29 | 'passwords': passwordsModel.map((e) => e.toJson()).toList(), 30 | 'cards': cardsModel.map((e) => e.toJson()).toList(), 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/app/card/domain/datasources/supabase_biometric_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:logging/logging.dart'; 3 | import 'package:savepass/app/biometric/domain/datasources/biometric_datasource.dart'; 4 | import 'package:savepass/core/api/savepass_response_model.dart'; 5 | import 'package:savepass/core/api/supabase_middleware.dart'; 6 | import 'package:savepass/core/utils/db_utils.dart'; 7 | import 'package:savepass/core/utils/snackbar_utils.dart'; 8 | 9 | class SupabaseBiometricDatasource implements BiometricDatasource { 10 | final Logger log; 11 | final SupabaseMiddleware middleware; 12 | 13 | SupabaseBiometricDatasource({ 14 | required this.log, 15 | required this.middleware, 16 | }); 17 | 18 | @override 19 | Future> enrollBiometric({ 20 | required String inputSecret, 21 | required String deviceId, 22 | }) async { 23 | try { 24 | final response = await middleware.doHttp( 25 | rpc: DbUtils.enrollBiometricFunction, 26 | params: { 27 | 'input_secret': inputSecret, 28 | 'device_id_param': deviceId, 29 | }, 30 | ); 31 | 32 | return Right(response); 33 | } catch (e, stackTrace) { 34 | log.severe('enrollBiometric: $e', e, stackTrace); 35 | return Left(Fail(SnackBarErrors.generalErrorCode)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/forgot_password/forgot_password_header.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:atomic_design_system/atomic_design_system.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:savepass/l10n/app_localizations.dart'; 6 | import 'package:flutter_modular/flutter_modular.dart'; 7 | 8 | class ForgotPasswordHeader extends StatelessWidget { 9 | const ForgotPasswordHeader({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final screenWidth = MediaQuery.of(context).size.width; 15 | final screenHeight = MediaQuery.of(context).size.height; 16 | 17 | return Column( 18 | children: [ 19 | if (Platform.isAndroid) SizedBox(height: screenHeight * 0.015), 20 | Row( 21 | children: [ 22 | AdsFilledRoundIconButton( 23 | icon: const Icon( 24 | Icons.keyboard_arrow_left, 25 | ), 26 | onPressedCallback: () { 27 | Modular.to.pop(); 28 | }, 29 | ), 30 | SizedBox(width: screenWidth * 0.05), 31 | Flexible( 32 | child: AdsHeadline( 33 | text: intl.forgotPasswordTitle, 34 | ), 35 | ), 36 | ], 37 | ), 38 | ], 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/card/infrastructure/models/card_cvv_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | import 'package:savepass/core/utils/form_utils.dart'; 4 | 5 | enum CardCvvFormValidationError { 6 | empty, 7 | minLength, 8 | maxLength, 9 | } 10 | 11 | class CardCvvForm extends FormzInput { 12 | const CardCvvForm.pure() : super.pure(''); 13 | 14 | const CardCvvForm.dirty([super.value = '']) : super.dirty(); 15 | 16 | @override 17 | CardCvvFormValidationError? validator(String? value) { 18 | if (value == null || value.isEmpty) { 19 | return CardCvvFormValidationError.empty; 20 | } 21 | 22 | if (value.length != 3) { 23 | return CardCvvFormValidationError.minLength; 24 | } 25 | 26 | if (value.length > FormUtils.maxLength) { 27 | return CardCvvFormValidationError.maxLength; 28 | } 29 | 30 | return null; 31 | } 32 | 33 | String? getError(AppLocalizations intl10, CardCvvFormValidationError? error) { 34 | switch (error) { 35 | case CardCvvFormValidationError.empty: 36 | return intl10.mandatoryField; 37 | case CardCvvFormValidationError.minLength: 38 | return intl10.cvvMinLength; 39 | case CardCvvFormValidationError.maxLength: 40 | return intl10.maxLengthField; 41 | default: 42 | return null; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/app/card/infrastructure/models/card_exp_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | import 'package:savepass/core/utils/form_utils.dart'; 4 | 5 | enum CardExpFormValidationError { 6 | empty, 7 | minLength, 8 | maxLength, 9 | } 10 | 11 | class CardExpForm extends FormzInput { 12 | const CardExpForm.pure() : super.pure(''); 13 | 14 | const CardExpForm.dirty([super.value = '']) : super.dirty(); 15 | 16 | @override 17 | CardExpFormValidationError? validator(String? value) { 18 | if (value == null || value.isEmpty) { 19 | return CardExpFormValidationError.empty; 20 | } 21 | 22 | if (value.length != 2) { 23 | return CardExpFormValidationError.minLength; 24 | } 25 | 26 | if (value.length > FormUtils.maxLength) { 27 | return CardExpFormValidationError.maxLength; 28 | } 29 | 30 | return null; 31 | } 32 | 33 | String? getError(AppLocalizations intl10, CardExpFormValidationError? error) { 34 | switch (error) { 35 | case CardExpFormValidationError.empty: 36 | return intl10.mandatoryField; 37 | case CardExpFormValidationError.minLength: 38 | return intl10.cardExpMinLength; 39 | case CardExpFormValidationError.maxLength: 40 | return intl10.maxLengthField; 41 | default: 42 | return null; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/savepass/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.juda.savepass 2 | 3 | import android.os.Bundle 4 | import android.os.PersistableBundle 5 | import io.flutter.embedding.android.FlutterFragmentActivity 6 | import android.provider.Settings 7 | import android.view.WindowManager 8 | import io.flutter.embedding.engine.FlutterEngine 9 | import io.flutter.plugin.common.MethodChannel 10 | 11 | class MainActivity: FlutterFragmentActivity(){ 12 | 13 | private val CHANNEL = "com.juda.savepass/device_info" 14 | 15 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) { 16 | super.configureFlutterEngine(flutterEngine) 17 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> 18 | if (call.method == "getAndroidId") { 19 | val androidId = getAndroidId() 20 | result.success(androidId) 21 | } else { 22 | result.notImplemented() 23 | } 24 | } 25 | } 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) 30 | } 31 | 32 | private fun getAndroidId(): String { 33 | return Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/app/biometric/presentation/widgets/biometric_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/foundations/ads_foundation_sizes.dart'; 2 | import 'package:atomic_design_system/molecules/button/ads_filled_round_icon_button.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | 6 | class BiometricHeaderWidget extends StatelessWidget { 7 | const BiometricHeaderWidget({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final screenWidth = MediaQuery.of(context).size.width; 12 | final screenHeight = MediaQuery.of(context).size.height; 13 | 14 | return Column( 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | children: [ 17 | SizedBox( 18 | height: 19 | (ADSFoundationSizes.defaultVerticalPadding / 2) * screenHeight, 20 | ), 21 | Row( 22 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 23 | children: [ 24 | AdsFilledRoundIconButton( 25 | icon: const Icon( 26 | Icons.keyboard_arrow_left, 27 | ), 28 | onPressedCallback: () { 29 | Modular.to.pop(); 30 | }, 31 | ), 32 | SizedBox( 33 | width: screenWidth * 0.02, 34 | ), 35 | ], 36 | ), 37 | ], 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/app/master_password/presentation/blocs/master_password_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class MasterPasswordEvent extends Equatable { 4 | const MasterPasswordEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class MasterPasswordInitialEvent extends MasterPasswordEvent { 11 | const MasterPasswordInitialEvent() : super(); 12 | } 13 | 14 | class OldPasswordChangedEvent extends MasterPasswordEvent { 15 | final String password; 16 | 17 | const OldPasswordChangedEvent({required this.password}) : super(); 18 | } 19 | 20 | class NewPasswordChangedEvent extends MasterPasswordEvent { 21 | final String password; 22 | 23 | const NewPasswordChangedEvent({required this.password}) : super(); 24 | } 25 | 26 | class RepeatPasswordChangedEvent extends MasterPasswordEvent { 27 | final String password; 28 | 29 | const RepeatPasswordChangedEvent({required this.password}) : super(); 30 | } 31 | 32 | class SubmitEvent extends MasterPasswordEvent { 33 | const SubmitEvent() : super(); 34 | } 35 | 36 | class ToggleOldPasswordEvent extends MasterPasswordEvent { 37 | const ToggleOldPasswordEvent() : super(); 38 | } 39 | 40 | class ToggleNewPasswordEvent extends MasterPasswordEvent { 41 | const ToggleNewPasswordEvent() : super(); 42 | } 43 | 44 | class ToggleRepeatPasswordEvent extends MasterPasswordEvent { 45 | const ToggleRepeatPasswordEvent() : super(); 46 | } -------------------------------------------------------------------------------- /lib/app/master_password/infrastructure/datasources/supabase_master_password_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:logging/logging.dart'; 3 | import 'package:savepass/app/master_password/domain/datasources/master_password_datasource.dart'; 4 | import 'package:savepass/app/master_password/infrastructure/models/update_master_password_model.dart'; 5 | import 'package:savepass/core/api/savepass_response_model.dart'; 6 | import 'package:savepass/core/api/supabase_middleware.dart'; 7 | import 'package:savepass/core/utils/db_utils.dart'; 8 | import 'package:savepass/core/utils/snackbar_utils.dart'; 9 | 10 | class SupabaseMasterPasswordDatasource implements MasterPasswordDatasource { 11 | final Logger log; 12 | final SupabaseMiddleware middleware; 13 | 14 | SupabaseMasterPasswordDatasource({ 15 | required this.log, 16 | required this.middleware, 17 | }); 18 | 19 | @override 20 | Future> updateMasterPassword({ 21 | required UpdateMasterPasswordModel model, 22 | }) async { 23 | try { 24 | final response = await middleware.doHttp( 25 | rpc: DbUtils.updateMasterPassword, 26 | params: model.toJson(), 27 | ); 28 | 29 | return Right(response); 30 | } catch (e, stackTrace) { 31 | log.severe('insertMasterPassword: $e', e, stackTrace); 32 | return Left(Fail(SnackBarErrors.generalErrorCode)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/app/card/infrastructure/models/card_number_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:savepass/l10n/app_localizations.dart'; 3 | import 'package:savepass/core/utils/form_utils.dart'; 4 | 5 | enum CardNumberFormValidationError { empty, minLength, maxLength } 6 | 7 | class CardNumberForm extends FormzInput { 8 | const CardNumberForm.pure() : super.pure(''); 9 | 10 | const CardNumberForm.dirty([super.value = '']) : super.dirty(); 11 | 12 | @override 13 | CardNumberFormValidationError? validator(String? value) { 14 | if (value == null || value.isEmpty) { 15 | return CardNumberFormValidationError.empty; 16 | } 17 | 18 | if (value.length != 16) { 19 | return CardNumberFormValidationError.minLength; 20 | } 21 | 22 | if (value.length > FormUtils.maxLength) { 23 | return CardNumberFormValidationError.maxLength; 24 | } 25 | 26 | return null; 27 | } 28 | 29 | String? getError( 30 | AppLocalizations intl10, CardNumberFormValidationError? error) { 31 | switch (error) { 32 | case CardNumberFormValidationError.empty: 33 | return intl10.mandatoryField; 34 | case CardNumberFormValidationError.minLength: 35 | return intl10.cardMinLength; 36 | case CardNumberFormValidationError.maxLength: 37 | return intl10.maxLengthField; 38 | default: 39 | return null; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/app/master_password/presentation/widget/master_password_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/foundations/ads_foundation_sizes.dart'; 2 | import 'package:atomic_design_system/molecules/button/ads_filled_round_icon_button.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | 6 | class MasterPasswordHeaderWidget extends StatelessWidget { 7 | const MasterPasswordHeaderWidget({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final screenWidth = MediaQuery.of(context).size.width; 12 | final screenHeight = MediaQuery.of(context).size.height; 13 | 14 | return Column( 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | children: [ 17 | SizedBox( 18 | height: 19 | (ADSFoundationSizes.defaultVerticalPadding / 2) * screenHeight, 20 | ), 21 | Row( 22 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 23 | children: [ 24 | AdsFilledRoundIconButton( 25 | icon: const Icon( 26 | Icons.keyboard_arrow_left, 27 | ), 28 | onPressedCallback: () { 29 | Modular.to.pop(); 30 | }, 31 | ), 32 | SizedBox( 33 | width: screenWidth * 0.02, 34 | ), 35 | ], 36 | ), 37 | ], 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/app/search/presentation/widgets/search_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/search/presentation/widgets/search_widget.dart'; 5 | 6 | class SearchHeaderWidget extends StatelessWidget { 7 | const SearchHeaderWidget({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final screenWidth = MediaQuery.of(context).size.width; 12 | final screenHeight = MediaQuery.of(context).size.height; 13 | 14 | return Column( 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | children: [ 17 | SizedBox( 18 | height: 19 | (ADSFoundationSizes.defaultVerticalPadding / 2) * screenHeight, 20 | ), 21 | Row( 22 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 23 | children: [ 24 | AdsFilledRoundIconButton( 25 | icon: const Icon( 26 | Icons.keyboard_arrow_left, 27 | ), 28 | onPressedCallback: () { 29 | Modular.to.pop(); 30 | }, 31 | ), 32 | SizedBox( 33 | width: screenWidth * 0.02, 34 | ), 35 | const Flexible(child: SearchWidget()), 36 | ], 37 | ), 38 | ], 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/enroll/presentation/blocs/enroll_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:formz/formz.dart'; 3 | 4 | abstract class EnrollState extends Equatable { 5 | final EnrollStateModel model; 6 | 7 | const EnrollState(this.model); 8 | 9 | @override 10 | List get props => [model]; 11 | } 12 | 13 | class EnrollInitialState extends EnrollState { 14 | const EnrollInitialState() : super(const EnrollStateModel()); 15 | } 16 | 17 | class ChangeEnrollState extends EnrollState { 18 | const ChangeEnrollState(super.model); 19 | } 20 | 21 | class GeneralErrorState extends EnrollState { 22 | const GeneralErrorState(super.model); 23 | } 24 | 25 | class SuccessEnrolledState extends EnrollState { 26 | const SuccessEnrolledState(super.model); 27 | } 28 | 29 | class EnrollStateModel extends Equatable { 30 | final String enrolledDevice; 31 | final FormzSubmissionStatus status; 32 | 33 | const EnrollStateModel({ 34 | this.enrolledDevice = '', 35 | this.status = FormzSubmissionStatus.initial, 36 | }); 37 | 38 | EnrollStateModel copyWith({ 39 | String? enrolledDevice, 40 | FormzSubmissionStatus? status, 41 | }) { 42 | return EnrollStateModel( 43 | enrolledDevice: enrolledDevice ?? this.enrolledDevice, 44 | status: status ?? this.status, 45 | ); 46 | } 47 | 48 | @override 49 | List get props => [ 50 | enrolledDevice, 51 | status, 52 | ]; 53 | } 54 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/settings/update_master_password_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/l10n/app_localizations.dart'; 5 | import 'package:savepass/core/config/routes.dart'; 6 | 7 | class UpdateMasterPasswordWidget extends StatelessWidget { 8 | const UpdateMasterPasswordWidget({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final intl = AppLocalizations.of(context)!; 13 | 14 | return AdsCard( 15 | child: Padding( 16 | padding: const EdgeInsets.all(20), 17 | child: Column( 18 | crossAxisAlignment: CrossAxisAlignment.stretch, 19 | children: [ 20 | AdsTitle( 21 | text: intl.updateMasterPasswordTitle, 22 | textAlign: TextAlign.start, 23 | ), 24 | const SizedBox(height: 10), 25 | Text( 26 | intl.updateMasterPasswordText, 27 | textAlign: TextAlign.start, 28 | ), 29 | const SizedBox(height: 10), 30 | AdsOutlinedButton( 31 | onPressedCallback: () => 32 | Modular.to.pushNamed(Routes.masterPasswordRoute), 33 | text: intl.updateText, 34 | ), 35 | ], 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/app/profile/presentation/blocs/profile/profile_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:logging/logging.dart'; 5 | import 'package:savepass/app/profile/presentation/blocs/profile/profile_event.dart'; 6 | import 'package:savepass/app/profile/presentation/blocs/profile/profile_state.dart'; 7 | 8 | class ProfileBloc extends Bloc { 9 | final Logger log; 10 | 11 | ProfileBloc({ 12 | required this.log, 13 | }) : super(const ProfileInitialState()) { 14 | on(_onSaveDerivedKeyEvent); 15 | on(_onSaveJwtEvent); 16 | on(_onClearValuesEvent); 17 | } 18 | 19 | FutureOr _onSaveDerivedKeyEvent( 20 | SaveDerivedKeyEvent event, 21 | Emitter emit, 22 | ) { 23 | emit( 24 | ChangeProfileState( 25 | state.model.copyWith( 26 | derivedKey: event.derivedKey, 27 | ), 28 | ), 29 | ); 30 | } 31 | 32 | FutureOr _onSaveJwtEvent( 33 | SaveJwtEvent event, 34 | Emitter emit, 35 | ) { 36 | emit( 37 | ChangeProfileState( 38 | state.model.copyWith( 39 | jwt: event.jwt, 40 | ), 41 | ), 42 | ); 43 | } 44 | 45 | FutureOr _onClearValuesEvent( 46 | ClearValuesEvent event, 47 | Emitter emit, 48 | ) { 49 | emit(const ProfileInitialState()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/bottom_navigation_bar_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_bloc.dart'; 5 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_event.dart'; 6 | 7 | class BottomNavigationBarIcon extends StatelessWidget { 8 | final IconData icon; 9 | final Color? color; 10 | final double? size; 11 | final int index; 12 | 13 | const BottomNavigationBarIcon({ 14 | super.key, 15 | required this.icon, 16 | this.color, 17 | this.size, 18 | required this.index, 19 | }); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | final deviceWidth = MediaQuery.of(context).size.width; 24 | final blockSizeHorizontal = deviceWidth / 100; 25 | 26 | return InkWell( 27 | onTap: () { 28 | final bloc = Modular.get(); 29 | bloc.add(ChangeIndexEvent(index: index)); 30 | }, 31 | child: Padding( 32 | padding: EdgeInsets.symmetric( 33 | horizontal: 30, 34 | vertical: blockSizeHorizontal * 4, 35 | ), 36 | child: Icon( 37 | icon, 38 | color: 39 | color ?? ADSFoundationsColors.disabledBackground.withOpacity(.4), 40 | size: ADSFoundationSizes.sizeIconMedium, 41 | ), 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/core/env/env.dart: -------------------------------------------------------------------------------- 1 | import 'package:envied/envied.dart'; 2 | 3 | part 'env.g.dart'; 4 | 5 | @Envied(path: '.env') 6 | final class Env { 7 | @EnviedField(varName: 'SUPABASE_URL', obfuscate: true) 8 | static final String supabaseURL = _Env.supabaseURL; 9 | 10 | @EnviedField(varName: 'SUPABASE_ANON_KEY', obfuscate: true) 11 | static final String supabaseAnonKey = _Env.supabaseAnonKey; 12 | 13 | @EnviedField(varName: 'SUPABASE_BUCKET', obfuscate: true) 14 | static final String supabaseBucket = _Env.supabaseBucket; 15 | 16 | @EnviedField(varName: 'SUPABASE_BUCKET_AVATARS_FOLDER', obfuscate: true) 17 | static final String supabaseBucketAvatarsFolder = 18 | _Env.supabaseBucketAvatarsFolder; 19 | 20 | @EnviedField(varName: 'GOOGLE_WEB_CLIENT_ID', obfuscate: true) 21 | static final String googleWebClientId = _Env.googleWebClientId; 22 | 23 | @EnviedField(varName: 'GOOGLE_IOS_CLIENT_ID', obfuscate: true) 24 | static final String googleIosClientId = _Env.googleIosClientId; 25 | 26 | @EnviedField(varName: 'SUPABASE_REDIRECT_URL', obfuscate: true) 27 | static final String supabaseRedirectUrl = _Env.supabaseRedirectUrl; 28 | 29 | @EnviedField(varName: 'BIOMETRIC_HASH_KEY', obfuscate: true) 30 | static final String biometricHashKey = _Env.biometricHashKey; 31 | 32 | @EnviedField(varName: 'DERIVED_KEY', obfuscate: true) 33 | static final String derivedKey = _Env.derivedKey; 34 | 35 | @EnviedField(varName: 'SENTRY_DSN', obfuscate: true) 36 | static final String sentryDsn = _Env.sentryDsn; 37 | } 38 | -------------------------------------------------------------------------------- /lib/app/auth/infrastructure/repositories/auth_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/auth/domain/datasources/auth_datasource.dart'; 3 | import 'package:savepass/app/auth/domain/repositories/auth_repository.dart'; 4 | import 'package:supabase_flutter/supabase_flutter.dart'; 5 | 6 | class AuthRepositoryImpl implements AuthRepository { 7 | final AuthDatasource datasource; 8 | 9 | AuthRepositoryImpl({required this.datasource}); 10 | 11 | @override 12 | Future> signInWithEmailAndPassword({ 13 | required String email, 14 | required String password, 15 | }) async { 16 | return await datasource.signInWithEmailAndPassword( 17 | email: email, 18 | password: password, 19 | ); 20 | } 21 | 22 | @override 23 | Future> signUpWithEmailAndPassword({ 24 | required String email, 25 | required String password, 26 | }) async { 27 | return await datasource.signUpWithEmailAndPassword( 28 | email: email, 29 | password: password, 30 | ); 31 | } 32 | 33 | @override 34 | Future> recoveryPassword({required String email}) async { 35 | return await datasource.recoveryPassword( 36 | email: email, 37 | ); 38 | } 39 | 40 | @override 41 | Future> updateNewPassword({ 42 | required String password, 43 | }) async { 44 | return await datasource.updateNewPassword( 45 | password: password, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/settings/terms_settings_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_bloc.dart'; 6 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_event.dart'; 7 | 8 | class TermsSettingsWidget extends StatelessWidget { 9 | const TermsSettingsWidget({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final bloc = Modular.get(); 14 | final intl = AppLocalizations.of(context)!; 15 | 16 | return AdsCard( 17 | child: Padding( 18 | padding: const EdgeInsets.all(20), 19 | child: Column( 20 | crossAxisAlignment: CrossAxisAlignment.stretch, 21 | children: [ 22 | AdsTitle( 23 | text: intl.termsTitle, 24 | textAlign: TextAlign.start, 25 | ), 26 | const SizedBox(height: 10), 27 | Text( 28 | intl.termsText, 29 | textAlign: TextAlign.start, 30 | ), 31 | const SizedBox(height: 10), 32 | AdsOutlinedButton( 33 | onPressedCallback: () => bloc.add(const OpenTermsEvent()), 34 | text: intl.openTerms, 35 | ), 36 | ], 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/settings/policy_settings_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_bloc.dart'; 6 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_event.dart'; 7 | 8 | class PolicySettingsWidget extends StatelessWidget { 9 | const PolicySettingsWidget({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final bloc = Modular.get(); 15 | 16 | return AdsCard( 17 | child: Padding( 18 | padding: const EdgeInsets.all(20), 19 | child: Column( 20 | crossAxisAlignment: CrossAxisAlignment.stretch, 21 | children: [ 22 | AdsTitle( 23 | text: intl.privacyPolicy, 24 | textAlign: TextAlign.start, 25 | ), 26 | const SizedBox(height: 10), 27 | Text( 28 | intl.privacyText, 29 | textAlign: TextAlign.start, 30 | ), 31 | const SizedBox(height: 10), 32 | AdsOutlinedButton( 33 | onPressedCallback: () => bloc.add(const OpenPrivacyPolicyEvent()), 34 | text: intl.openPrivacy, 35 | ), 36 | ], 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/card/presentation/widgets/report/card_report_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/foundations/ads_foundation_sizes.dart'; 2 | import 'package:atomic_design_system/molecules/button/ads_filled_round_icon_button.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/card/presentation/widgets/report/card_report_search_widget.dart'; 6 | 7 | class CardReportHeaderWidget extends StatelessWidget { 8 | const CardReportHeaderWidget({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final screenWidth = MediaQuery.of(context).size.width; 13 | final screenHeight = MediaQuery.of(context).size.height; 14 | 15 | return Column( 16 | crossAxisAlignment: CrossAxisAlignment.start, 17 | children: [ 18 | SizedBox( 19 | height: 20 | (ADSFoundationSizes.defaultVerticalPadding / 2) * screenHeight, 21 | ), 22 | Row( 23 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 24 | children: [ 25 | AdsFilledRoundIconButton( 26 | icon: const Icon( 27 | Icons.keyboard_arrow_left, 28 | ), 29 | onPressedCallback: () { 30 | Modular.to.pop(); 31 | }, 32 | ), 33 | SizedBox( 34 | width: screenWidth * 0.02, 35 | ), 36 | const Flexible(child: CardReportSearchWidget()), 37 | ], 38 | ), 39 | ], 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/app/password/presentation/widgets/report/pass_report_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/foundations/ads_foundation_sizes.dart'; 2 | import 'package:atomic_design_system/molecules/button/ads_filled_round_icon_button.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_modular/flutter_modular.dart'; 5 | import 'package:savepass/app/password/presentation/widgets/report/pass_report_search_widget.dart'; 6 | 7 | class PassReportHeaderWidget extends StatelessWidget { 8 | const PassReportHeaderWidget({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final screenWidth = MediaQuery.of(context).size.width; 13 | final screenHeight = MediaQuery.of(context).size.height; 14 | 15 | return Column( 16 | crossAxisAlignment: CrossAxisAlignment.start, 17 | children: [ 18 | SizedBox( 19 | height: 20 | (ADSFoundationSizes.defaultVerticalPadding / 2) * screenHeight, 21 | ), 22 | Row( 23 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 24 | children: [ 25 | AdsFilledRoundIconButton( 26 | icon: const Icon( 27 | Icons.keyboard_arrow_left, 28 | ), 29 | onPressedCallback: () { 30 | Modular.to.pop(); 31 | }, 32 | ), 33 | SizedBox( 34 | width: screenWidth * 0.02, 35 | ), 36 | const Flexible(child: PassReportSearchWidget()), 37 | ], 38 | ), 39 | ], 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/app/preferences/presentation/blocs/preferences_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/app/preferences/domain/entities/preferences_entity.dart'; 4 | import 'package:savepass/app/preferences/infrastructure/models/preferences_model.dart'; 5 | import 'package:savepass/app/preferences/utils/preferences_utils.dart'; 6 | 7 | abstract class PreferencesState extends Equatable { 8 | final ThemeStateModel model; 9 | 10 | const PreferencesState(this.model); 11 | 12 | @override 13 | List get props => [model]; 14 | } 15 | 16 | class ThemeInitialState extends PreferencesState { 17 | const ThemeInitialState() : super(PreferencesUtils.defaultAccountPreferencesModel); 18 | } 19 | 20 | class ChangePreferencesState extends PreferencesState { 21 | const ChangePreferencesState(super.model); 22 | } 23 | 24 | class ThemeStateModel extends Equatable { 25 | final PreferencesModel theme; 26 | final Locale locale; 27 | final String appVersion; 28 | 29 | const ThemeStateModel({ 30 | this.theme = const PreferencesModel(brightness: BrightnessType.dark), 31 | this.locale = const Locale('es'), 32 | this.appVersion = '', 33 | }); 34 | 35 | ThemeStateModel copyWith({ 36 | PreferencesModel? theme, 37 | Locale? locale, 38 | String? appVersion, 39 | }) { 40 | return ThemeStateModel( 41 | theme: theme ?? this.theme, 42 | locale: locale ?? this.locale, 43 | appVersion: appVersion ?? this.appVersion, 44 | ); 45 | } 46 | 47 | @override 48 | List get props => [ 49 | theme, 50 | locale, 51 | appVersion, 52 | ]; 53 | } 54 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:google_sign_in/google_sign_in.dart'; 5 | import 'package:intl/date_symbol_data_local.dart'; 6 | import 'package:savepass/app/app_widget.dart'; 7 | import 'package:savepass/core/config/app_module.dart'; 8 | import 'package:savepass/core/env/env.dart'; 9 | import 'package:sentry_flutter/sentry_flutter.dart'; 10 | import 'package:sentry_logging/sentry_logging.dart'; 11 | import 'package:supabase_flutter/supabase_flutter.dart'; 12 | 13 | final webClientId = Env.googleWebClientId; 14 | final iosClientId = Env.googleIosClientId; 15 | 16 | final GoogleSignIn googleSignIn = GoogleSignIn( 17 | clientId: iosClientId, 18 | serverClientId: webClientId, 19 | scopes: [ 20 | 'email', 21 | ], 22 | ); 23 | 24 | void main() async { 25 | WidgetsFlutterBinding.ensureInitialized(); 26 | 27 | await SystemChrome.setPreferredOrientations([ 28 | DeviceOrientation.portraitUp, 29 | DeviceOrientation.portraitDown, 30 | ]); 31 | 32 | await initializeDateFormatting('es', null); 33 | 34 | await Supabase.initialize( 35 | url: Env.supabaseURL, 36 | anonKey: Env.supabaseAnonKey, 37 | ); 38 | 39 | await SentryFlutter.init( 40 | (options) { 41 | options.dsn = Env.sentryDsn; 42 | options.sendDefaultPii = true; 43 | options.addIntegration(LoggingIntegration()); 44 | }, 45 | appRunner: () => runApp( 46 | ModularApp( 47 | module: AppModule(), 48 | child: const AppWidget(), 49 | ), 50 | ), 51 | ); 52 | } 53 | 54 | final supabase = Supabase.instance.client; 55 | -------------------------------------------------------------------------------- /lib/core/utils/device_info.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:device_info_plus/device_info_plus.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:logging/logging.dart'; 6 | 7 | class DeviceInfo { 8 | final Logger log; 9 | final MethodChannel _channel = 10 | const MethodChannel('com.juda.savepass/device_info'); 11 | 12 | const DeviceInfo({required this.log}); 13 | 14 | Future _getAndroidId() async { 15 | try { 16 | final String? androidId = await _channel.invokeMethod('getAndroidId'); 17 | return androidId; 18 | } on PlatformException catch (e, stackTrace) { 19 | log.severe('Error getting Android ID: ${e.message}', e, stackTrace); 20 | return null; 21 | } 22 | } 23 | 24 | Future _getIosId() async { 25 | try { 26 | final String? identifier = 27 | await _channel.invokeMethod('getIosIdentifierForVendor'); 28 | return identifier; 29 | } on PlatformException catch (e, stackTrace) { 30 | log.severe('Error getting iOS ID: ${e.message}', e, stackTrace); 31 | return null; 32 | } 33 | } 34 | 35 | Future getDeviceId() async { 36 | if (Platform.isAndroid) { 37 | return await _getAndroidId(); 38 | } else { 39 | return await _getIosId(); 40 | } 41 | } 42 | 43 | Future getDeviceName() async { 44 | final deviceInfo = DeviceInfoPlugin(); 45 | if (Platform.isAndroid) { 46 | return (await deviceInfo.androidInfo).model; 47 | } else { 48 | return (await deviceInfo.iosInfo).utsname.machine; 49 | } 50 | } 51 | 52 | String getDeviceType() { 53 | return Platform.isAndroid ? 'Android' : 'iOS'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /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/core/config/routes.dart: -------------------------------------------------------------------------------- 1 | class Routes { 2 | static const String splashRoute = '/'; 3 | static const String getStartedRoute = '/getStarted'; 4 | static const String introRoute = '/intro'; 5 | static const String dashboardRoute = '/dashboard'; 6 | static const String authRoute = '/auth'; 7 | static const String authEmailRoute = '/authEmail'; 8 | static const String forgotPasswordRoute = '/forgotPassword'; 9 | static const String accountRoute = '/account'; 10 | static const String settingsRoute = '/settings'; 11 | static const String themeRoute = '/theme'; 12 | static const String notificationRoute = '/notification'; 13 | static const String termsRoute = '/terms'; 14 | static const String permissionRoute = '/permission'; 15 | static const String authInitRoute = '/authInit'; 16 | static const String photoPermissionRoute = '/photoPermission'; 17 | static const String syncMasterPasswordRoute = '/syncMasterPassword'; 18 | static const String passwordRoute = '/password'; 19 | static const String cardRoute = '/card'; 20 | static const String searchRoute = '/search'; 21 | static const String passwordReport = '/passwordReport'; 22 | static const String cardReport = '/cardReport'; 23 | static const String enrollRoute = '/enroll'; 24 | static const String biometricRoute = '/biometric'; 25 | static const String masterPasswordRoute = '/masterPassword'; 26 | static const String emailSentRoute = '/emailSent'; 27 | static const String recoveryPasswordRoute = '/recoveryPassword'; 28 | static const String signUpConfirmMail = '/signUpConfirmMail'; 29 | static const String weAreExperiencingIssuesRoute = '/weAreExperiencingIssues'; 30 | static const String newAppVersionRoute = '/newAppVersion'; 31 | } 32 | -------------------------------------------------------------------------------- /lib/app/profile/presentation/blocs/new_app_version/new_app_version_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dartz/dartz.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:logging/logging.dart'; 7 | import 'package:savepass/app/preferences/domain/repositories/preferences_repository.dart'; 8 | import 'package:savepass/app/profile/presentation/blocs/new_app_version/new_app_version_event.dart'; 9 | import 'package:savepass/app/profile/presentation/blocs/new_app_version/new_app_version_state.dart'; 10 | import 'package:url_launcher/url_launcher.dart'; 11 | 12 | class NewAppVersionBloc 13 | extends Bloc { 14 | final Logger log; 15 | final PreferencesRepository preferencesRepository; 16 | 17 | NewAppVersionBloc({ 18 | required this.log, 19 | required this.preferencesRepository, 20 | }) : super(const NewAppVersionInitialState()) { 21 | on(_onDownloadNewVersionEvent); 22 | } 23 | 24 | FutureOr _onDownloadNewVersionEvent( 25 | DownloadNewVersionEvent event, 26 | Emitter emit, 27 | ) async { 28 | late Either, String> response; 29 | 30 | if (Platform.isAndroid) { 31 | response = await preferencesRepository.getPlayStoreURL(); 32 | } else { 33 | response = await preferencesRepository.getAppStoreURL(); 34 | } 35 | 36 | response.fold( 37 | (l) { 38 | emit(const GeneralErrorState()); 39 | }, 40 | (r) async { 41 | final Uri url = Uri.parse(r); 42 | 43 | if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { 44 | emit(const GeneralErrorState()); 45 | } 46 | }, 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/enroll/infrastructure/datasources/supabase_enroll_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:logging/logging.dart'; 3 | import 'package:savepass/app/enroll/domain/datasources/enroll_datasource.dart'; 4 | import 'package:savepass/app/enroll/infrastructure/models/enroll_new_device_model.dart'; 5 | import 'package:savepass/core/api/savepass_response_model.dart'; 6 | import 'package:savepass/core/api/supabase_middleware.dart'; 7 | import 'package:savepass/core/utils/db_utils.dart'; 8 | import 'package:savepass/core/utils/snackbar_utils.dart'; 9 | 10 | class SupabaseEnrollDatasource implements EnrollDatasource { 11 | final Logger log; 12 | final SupabaseMiddleware middleware; 13 | 14 | SupabaseEnrollDatasource({ 15 | required this.log, 16 | required this.middleware, 17 | }); 18 | 19 | @override 20 | Future> getDeviceName() async { 21 | try { 22 | final response = await middleware.doHttp( 23 | rpc: DbUtils.deviceNameFunction, 24 | ); 25 | 26 | return Right(response); 27 | } catch (e, stackTrace) { 28 | log.severe('getDeviceName: $e', e, stackTrace); 29 | return Left(Fail(SnackBarErrors.generalErrorCode)); 30 | } 31 | } 32 | 33 | @override 34 | Future> enrollNewDevice({ 35 | required EnrollNewDeviceModel model, 36 | }) async { 37 | try { 38 | final response = await middleware.doHttp( 39 | rpc: DbUtils.enrollNewDeviceFunction, 40 | params: model.toJson(), 41 | ); 42 | 43 | return Right(response); 44 | } catch (e, stackTrace) { 45 | log.severe('enrollNewDevice: $e', e, stackTrace); 46 | return Left(Fail(SnackBarErrors.generalErrorCode)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/card/presentation/blocs/card_report/card_report_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:formz/formz.dart'; 3 | import 'package:savepass/app/card/infrastructure/models/card_model.dart'; 4 | import 'package:savepass/core/form/text_form.dart'; 5 | 6 | abstract class CardReportState extends Equatable { 7 | final CardReportStateModel model; 8 | 9 | const CardReportState(this.model); 10 | 11 | @override 12 | List get props => [model]; 13 | } 14 | 15 | class CardReportInitialState extends CardReportState { 16 | const CardReportInitialState() : super(const CardReportStateModel()); 17 | } 18 | 19 | class ChangeCardReportState extends CardReportState { 20 | const ChangeCardReportState(super.model); 21 | } 22 | 23 | class GeneralErrorState extends CardReportState { 24 | const GeneralErrorState(super.model); 25 | } 26 | 27 | class LoadingCardState extends CardReportState { 28 | const LoadingCardState(super.model); 29 | } 30 | 31 | class CardReportStateModel extends Equatable { 32 | final TextForm searchForm; 33 | final FormzSubmissionStatus status; 34 | final List cards; 35 | 36 | const CardReportStateModel({ 37 | this.searchForm = const TextForm.pure(), 38 | this.status = FormzSubmissionStatus.initial, 39 | this.cards = const [], 40 | }); 41 | 42 | CardReportStateModel copyWith({ 43 | TextForm? searchForm, 44 | FormzSubmissionStatus? status, 45 | List? cards, 46 | }) { 47 | return CardReportStateModel( 48 | searchForm: searchForm ?? this.searchForm, 49 | status: status ?? this.status, 50 | cards: cards ?? this.cards, 51 | ); 52 | } 53 | 54 | @override 55 | List get props => [ 56 | searchForm, 57 | status, 58 | cards, 59 | ]; 60 | } 61 | -------------------------------------------------------------------------------- /lib/core/utils/db_utils.dart: -------------------------------------------------------------------------------- 1 | class DbUtils { 2 | static const profilesTable = 'profiles'; 3 | static const publicParametersTable = 'public_parameters'; 4 | static const passwordsParametersTable = 'passwords_parameters'; 5 | static const cardParametersTable = 'card_parameters'; 6 | static const passwordsTable = 'passwords'; 7 | static const cardsTable = 'cards'; 8 | 9 | static const hasMasterPasswordFunction = 'has_master_password'; 10 | static const checkMasterPasswordFunction = 'check_master_password'; 11 | static const deviceNameFunction = 'get_device_name'; 12 | static const enrollNewDeviceFunction = 'enroll_new_device'; 13 | static const getUserSaltFunction = 'get_user_salt'; 14 | static const isEmailExistsFunction = 'is_email_exist'; 15 | static const insertMasterPassword = 'insert_master_password'; 16 | static const updateMasterPassword = 'update_master_password'; 17 | static const deleteAccountFunction = 'delete_account'; 18 | static const enrollBiometricFunction = 'enroll_biometric'; 19 | static const hasBiometricsFunction = 'has_biometrics'; 20 | 21 | static const insertPasswordFunction = 'insert_password'; 22 | static const editPasswordFunction = 'edit_password'; 23 | static const deletepasswordFunction = 'delete_password'; 24 | static const getPasswordsFunction = 'get_passwords'; 25 | static const getPasswordByIdFunction = 'get_password_by_id'; 26 | static const searchPasswordFunction = 'search_password'; 27 | 28 | static const insertCardFunction = 'insert_card'; 29 | static const editCardFunction = 'edit_card'; 30 | static const deleteCardFunction = 'delete_card'; 31 | static const getCardsFunction = 'get_cards'; 32 | static const getCardByIdFunction = 'get_card_by_id'; 33 | static const searchCardFunction = 'search_card'; 34 | 35 | static const searchFunction = 'search'; 36 | } 37 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | 44 | 45 | target.build_configurations.each do |config| 46 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 47 | '$(inherited)', 48 | ## dart: PermissionGroup.photos 49 | 'PERMISSION_PHOTOS=1', 50 | ] 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/auth/already_have_account.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:atomic_design_system/atomic_design_system.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | import 'package:savepass/app/auth/presentation/blocs/auth_bloc.dart'; 7 | import 'package:savepass/l10n/app_localizations.dart'; 8 | import 'package:savepass/app/auth/presentation/blocs/auth_event.dart'; 9 | 10 | class AlreadyHaveAccount extends StatelessWidget { 11 | const AlreadyHaveAccount({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final colorScheme = Theme.of(context).colorScheme; 16 | final appLocalizations = AppLocalizations.of(context)!; 17 | final bloc = Modular.get(); 18 | final deviceHeight = MediaQuery.of(context).size.height; 19 | 20 | return Positioned( 21 | bottom: 0, 22 | left: 0, 23 | right: 0, 24 | child: Container( 25 | color: colorScheme.brightness == Brightness.light 26 | ? Colors.transparent 27 | : Colors.black, 28 | child: Padding( 29 | padding: EdgeInsets.only( 30 | top: deviceHeight * 0.01, 31 | bottom: deviceHeight * (Platform.isAndroid ? 0.01 : 0.03), 32 | ), 33 | child: Column( 34 | children: [ 35 | if (colorScheme.brightness == Brightness.light) const Divider(), 36 | AdsTextButton( 37 | text: appLocalizations.signUpAlreadyAccount, 38 | onPressedCallback: () => bloc.add(const OpenSignInEvent()), 39 | textStyle: const TextStyle( 40 | color: ADSFoundationsColors.linkColor, 41 | ), 42 | ), 43 | ], 44 | ), 45 | ), 46 | ), 47 | ); 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: "54e66469a933b60ddf175f858f82eaeb97e48c8d" 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: 54e66469a933b60ddf175f858f82eaeb97e48c8d 17 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 18 | - platform: android 19 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 20 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 21 | - platform: ios 22 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 23 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 24 | - platform: linux 25 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 26 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 27 | - platform: macos 28 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 29 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 30 | - platform: web 31 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 32 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 33 | - platform: windows 34 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 35 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 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 | -------------------------------------------------------------------------------- /lib/app/password/presentation/blocs/password_report/password_report_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:formz/formz.dart'; 3 | import 'package:savepass/app/password/infrastructure/models/password_model.dart'; 4 | import 'package:savepass/core/form/text_form.dart'; 5 | 6 | abstract class PassReportState extends Equatable { 7 | final PasswordReportStateModel model; 8 | 9 | const PassReportState(this.model); 10 | 11 | @override 12 | List get props => [model]; 13 | } 14 | 15 | class PassReportInitialState extends PassReportState { 16 | const PassReportInitialState() : super(const PasswordReportStateModel()); 17 | } 18 | 19 | class ChangePassReportState extends PassReportState { 20 | const ChangePassReportState(super.model); 21 | } 22 | 23 | class GeneralErrorState extends PassReportState { 24 | const GeneralErrorState(super.model); 25 | } 26 | 27 | class LoadingPasswordState extends PassReportState { 28 | const LoadingPasswordState(super.model); 29 | } 30 | 31 | class PasswordReportStateModel extends Equatable { 32 | final TextForm searchForm; 33 | final FormzSubmissionStatus status; 34 | final List passwords; 35 | 36 | const PasswordReportStateModel({ 37 | this.searchForm = const TextForm.pure(), 38 | this.status = FormzSubmissionStatus.initial, 39 | this.passwords = const [], 40 | }); 41 | 42 | PasswordReportStateModel copyWith({ 43 | TextForm? searchForm, 44 | FormzSubmissionStatus? status, 45 | List? passwords, 46 | }) { 47 | return PasswordReportStateModel( 48 | searchForm: searchForm ?? this.searchForm, 49 | status: status ?? this.status, 50 | passwords: passwords ?? this.passwords, 51 | ); 52 | } 53 | 54 | @override 55 | List get props => [ 56 | searchForm, 57 | status, 58 | passwords, 59 | ]; 60 | } 61 | -------------------------------------------------------------------------------- /lib/app/password/presentation/widgets/generate_password/pass_generator_switch_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/password/presentation/blocs/password/password_event.dart'; 5 | import 'package:savepass/l10n/app_localizations.dart'; 6 | import '../../blocs/password/password_bloc.dart'; 7 | import '../../blocs/password/password_state.dart'; 8 | 9 | class PassGeneratorSwitchWidget extends StatelessWidget { 10 | const PassGeneratorSwitchWidget({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final bloc = Modular.get(); 15 | final deviceWidth = MediaQuery.of(context).size.width; 16 | final colorScheme = Theme.of(context).colorScheme; 17 | final intl = AppLocalizations.of(context)!; 18 | 19 | return Row( 20 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 21 | children: [ 22 | Text( 23 | intl.easyToReadText, 24 | style: const TextStyle(fontWeight: FontWeight.w700), 25 | ), 26 | SizedBox( 27 | width: deviceWidth * 0.03, 28 | ), 29 | BlocBuilder( 30 | buildWhen: (previous, current) => 31 | previous.model.easyToRead != current.model.easyToRead, 32 | builder: (context, state) { 33 | final value = state.model.easyToRead; 34 | 35 | return Switch( 36 | value: value, 37 | activeColor: colorScheme.primary, 38 | onChanged: (bool value) { 39 | bloc.add(const ChangeEasyToReadEvent()); 40 | bloc.add(const GenerateRandomPasswordEvent()); 41 | }, 42 | ); 43 | }, 44 | ), 45 | ], 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /assets/images/auth/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | apple [#173] 6 | Created with Sketch. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/auth/no_account.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:atomic_design_system/atomic_design_system.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:savepass/l10n/app_localizations.dart'; 6 | import 'package:flutter_modular/flutter_modular.dart'; 7 | import 'package:savepass/app/auth/infrastructure/models/auth_type.dart'; 8 | import 'package:savepass/core/config/routes.dart'; 9 | 10 | class NoAccount extends StatelessWidget { 11 | const NoAccount({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final colorScheme = Theme.of(context).colorScheme; 16 | final appLocalizations = AppLocalizations.of(context)!; 17 | final screenHeight = MediaQuery.of(context).size.height; 18 | 19 | return Positioned( 20 | bottom: 0, 21 | left: 0, 22 | right: 0, 23 | child: Container( 24 | color: colorScheme.brightness == Brightness.light 25 | ? Colors.transparent 26 | : Colors.black, 27 | child: Padding( 28 | padding: EdgeInsets.only( 29 | top: screenHeight * 0.01, 30 | bottom: screenHeight * (Platform.isAndroid ? 0.01 : 0.03), 31 | ), 32 | child: Column( 33 | children: [ 34 | if (colorScheme.brightness == Brightness.light) const Divider(), 35 | AdsTextButton( 36 | text: appLocalizations.signInNoAccount, 37 | onPressedCallback: () { 38 | Modular.to.pop(); 39 | Modular.to 40 | .pushNamed(Routes.authRoute, arguments: AuthType.signUp); 41 | }, 42 | textStyle: const TextStyle( 43 | color: ADSFoundationsColors.linkColor, 44 | ), 45 | ), 46 | ], 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/app/sync_pass/presentation/blocs/sync_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:formz/formz.dart'; 3 | import 'package:savepass/app/sync_pass/infrastructure/models/master_password_form.dart'; 4 | 5 | abstract class SyncState extends Equatable { 6 | final SyncStateModel model; 7 | 8 | const SyncState(this.model); 9 | 10 | @override 11 | List get props => [model]; 12 | } 13 | 14 | class SyncInitialState extends SyncState { 15 | const SyncInitialState() : super(const SyncStateModel()); 16 | } 17 | 18 | class ChangeSyncState extends SyncState { 19 | const ChangeSyncState(super.model); 20 | } 21 | 22 | class OpenHomeState extends SyncState { 23 | const OpenHomeState(super.model); 24 | } 25 | 26 | class GeneralErrorState extends SyncState { 27 | const GeneralErrorState(super.model); 28 | } 29 | 30 | class SyncStateModel extends Equatable { 31 | final MasterPasswordForm masterPassword; 32 | final bool showPassword; 33 | final FormzSubmissionStatus status; 34 | final bool alreadySubmitted; 35 | 36 | const SyncStateModel({ 37 | this.masterPassword = const MasterPasswordForm.pure(), 38 | this.status = FormzSubmissionStatus.initial, 39 | this.alreadySubmitted = false, 40 | this.showPassword = false, 41 | }); 42 | 43 | SyncStateModel copyWith({ 44 | MasterPasswordForm? masterPassword, 45 | FormzSubmissionStatus? status, 46 | bool? alreadySubmitted, 47 | bool? showPassword, 48 | }) { 49 | return SyncStateModel( 50 | masterPassword: masterPassword ?? this.masterPassword, 51 | status: status ?? this.status, 52 | alreadySubmitted: alreadySubmitted ?? this.alreadySubmitted, 53 | showPassword: showPassword ?? this.showPassword, 54 | ); 55 | } 56 | 57 | @override 58 | List get props => [ 59 | masterPassword, 60 | status, 61 | alreadySubmitted, 62 | showPassword, 63 | ]; 64 | } 65 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/screens/recovery_email_sent_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:lottie/lottie.dart'; 5 | import 'package:savepass/app/auth/presentation/widgets/forgot_password/recovery_email_header.dart'; 6 | import 'package:savepass/core/lottie/lottie_paths.dart'; 7 | 8 | class RecoveryEmailSentScreen extends StatelessWidget { 9 | const RecoveryEmailSentScreen({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final deviceWidth = MediaQuery.of(context).size.width; 15 | final deviceHeight = MediaQuery.of(context).size.height; 16 | 17 | return AdsScreenTemplate( 18 | safeAreaBottom: false, 19 | safeAreaTop: true, 20 | wrapScroll: false, 21 | padding: EdgeInsets.zero, 22 | child: Padding( 23 | padding: EdgeInsets.only( 24 | left: deviceWidth * ADSFoundationSizes.defaultHorizontalPadding, 25 | right: deviceWidth * ADSFoundationSizes.defaultHorizontalPadding, 26 | bottom: deviceHeight * ADSFoundationSizes.defaultVerticalPadding, 27 | ), 28 | child: SingleChildScrollView( 29 | child: Column( 30 | children: [ 31 | const RecoveryEmailHeader(), 32 | SizedBox(height: deviceHeight * 0.06), 33 | AdsHeadline(text: intl.checkYourMailTitle), 34 | SizedBox(height: deviceHeight * 0.03), 35 | Lottie.asset( 36 | width: deviceWidth * 0.5, 37 | LottiePaths.mail, 38 | ), 39 | Text( 40 | intl.recoveryPasswordSent, 41 | textAlign: TextAlign.center, 42 | ), 43 | ], 44 | ), 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/screens/confirm_mail_sign_up_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:savepass/l10n/app_localizations.dart'; 4 | import 'package:lottie/lottie.dart'; 5 | import 'package:savepass/app/auth/presentation/widgets/auth_email/confirm_mail_header_widget.dart'; 6 | import 'package:savepass/core/lottie/lottie_paths.dart'; 7 | 8 | class ConfirmMailSignUpScreen extends StatelessWidget { 9 | const ConfirmMailSignUpScreen({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final intl = AppLocalizations.of(context)!; 14 | final deviceWidth = MediaQuery.of(context).size.width; 15 | final deviceHeight = MediaQuery.of(context).size.height; 16 | 17 | return AdsScreenTemplate( 18 | safeAreaBottom: false, 19 | safeAreaTop: true, 20 | wrapScroll: false, 21 | padding: EdgeInsets.zero, 22 | child: Padding( 23 | padding: EdgeInsets.only( 24 | left: deviceWidth * ADSFoundationSizes.defaultHorizontalPadding, 25 | right: deviceWidth * ADSFoundationSizes.defaultHorizontalPadding, 26 | bottom: deviceHeight * ADSFoundationSizes.defaultVerticalPadding, 27 | ), 28 | child: SingleChildScrollView( 29 | child: Column( 30 | children: [ 31 | const ConfirmMailHeaderWidget(), 32 | SizedBox(height: deviceHeight * 0.06), 33 | AdsHeadline(text: intl.checkYourMailTitle), 34 | SizedBox(height: deviceHeight * 0.03), 35 | Lottie.asset( 36 | width: deviceWidth * 0.5, 37 | LottiePaths.mail, 38 | ), 39 | Text( 40 | intl.signUpCheckMailText, 41 | textAlign: TextAlign.center, 42 | ), 43 | ], 44 | ), 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/password/infrastructure/repositories_impl/password_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/password/domain/datasources/password_datasource.dart'; 3 | import 'package:savepass/app/password/domain/repositories/password_repository.dart'; 4 | import 'package:savepass/app/password/infrastructure/models/password_model.dart'; 5 | import 'package:savepass/core/api/savepass_response_model.dart'; 6 | 7 | class PasswordRepositoryImpl implements PasswordRepository { 8 | final PasswordDatasource datasource; 9 | 10 | PasswordRepositoryImpl({required this.datasource}); 11 | 12 | @override 13 | Future> insertPassword({ 14 | required PasswordModel model, 15 | }) async { 16 | return await datasource.insertPassword(model: model); 17 | } 18 | 19 | @override 20 | Future> editPassword({ 21 | required PasswordModel model, 22 | }) async { 23 | return await datasource.editPassword(model: model); 24 | } 25 | 26 | @override 27 | Future> deletePassword({ 28 | required String passwordId, 29 | required String vaultId, 30 | }) async { 31 | return await datasource.deletePassword( 32 | passwordId: passwordId, 33 | vaultId: vaultId, 34 | ); 35 | } 36 | 37 | @override 38 | Future> getPasswords() async { 39 | return await datasource.getPasswords(); 40 | } 41 | 42 | @override 43 | Future> getPasswordById({ 44 | required String passwordId, 45 | }) async { 46 | return await datasource.getPasswordById( 47 | passwordId: passwordId, 48 | ); 49 | } 50 | 51 | @override 52 | Future> searchPasswords({ 53 | required String search, 54 | }) async { 55 | return await datasource.searchPasswords( 56 | search: search, 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/app/card/infrastructure/models/card_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:savepass/app/card/domain/entities/card_entity.dart'; 3 | 4 | class CardModel extends CardEntity with EquatableMixin { 5 | CardModel({ 6 | super.id, 7 | super.type, 8 | super.typeId, 9 | super.imgUrl, 10 | required super.card, 11 | super.vaultId, 12 | super.description, 13 | }); 14 | 15 | factory CardModel.fromJson(Map json) { 16 | return CardModel( 17 | id: json['id'], 18 | type: json['type'], 19 | typeId: json['type_id'], 20 | imgUrl: json['imgurl'], 21 | card: json['card'], 22 | vaultId: json['vault_id'], 23 | description: json['description'], 24 | ); 25 | } 26 | 27 | Map toInsertJson() { 28 | return { 29 | 'type_param': typeId, 30 | 'card_param': card, 31 | 'description_param': description, 32 | }; 33 | } 34 | 35 | Map toEditJson() { 36 | return { 37 | 'card_id_param': id, 38 | 'type_param': typeId, 39 | 'card_param': card, 40 | 'vault_id_param': vaultId, 41 | 'description_param': description, 42 | }; 43 | } 44 | 45 | CardModel copyWith({ 46 | String? id, 47 | String? type, 48 | String? typeId, 49 | String? imgUrl, 50 | String? card, 51 | String? vaultId, 52 | String? description, 53 | }) { 54 | return CardModel( 55 | id: id ?? this.id, 56 | type: type ?? this.type, 57 | typeId: typeId ?? this.typeId, 58 | imgUrl: imgUrl ?? this.imgUrl, 59 | card: card ?? this.card, 60 | vaultId: vaultId ?? this.vaultId, 61 | description: description ?? this.description, 62 | ); 63 | } 64 | 65 | @override 66 | List get props => [ 67 | id, 68 | type, 69 | typeId, 70 | imgUrl, 71 | card, 72 | vaultId, 73 | description, 74 | ]; 75 | } 76 | -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/auth_email/auth_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:atomic_design_system/molecules/button/ads_filled_round_icon_button.dart'; 4 | import 'package:atomic_design_system/molecules/text/ads_headline.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | import 'package:flutter_modular/flutter_modular.dart'; 8 | import 'package:savepass/app/auth/infrastructure/models/auth_type.dart'; 9 | import 'package:savepass/app/auth/presentation/blocs/auth_bloc.dart'; 10 | import 'package:savepass/app/auth/presentation/blocs/auth_state.dart'; 11 | import 'package:savepass/l10n/app_localizations.dart'; 12 | 13 | class AuthHeaderWidget extends StatelessWidget { 14 | const AuthHeaderWidget({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final intl = AppLocalizations.of(context)!; 19 | final screenWidth = MediaQuery.of(context).size.width; 20 | final screenHeight = MediaQuery.of(context).size.height; 21 | 22 | return Column( 23 | children: [ 24 | if (Platform.isAndroid) SizedBox(height: screenHeight * 0.015), 25 | Row( 26 | children: [ 27 | AdsFilledRoundIconButton( 28 | icon: const Icon( 29 | Icons.keyboard_arrow_left, 30 | ), 31 | onPressedCallback: () { 32 | Modular.to.pop(); 33 | }, 34 | ), 35 | SizedBox(width: screenWidth * 0.05), 36 | BlocBuilder( 37 | builder: (context, state) { 38 | final authType = state.model.authType; 39 | 40 | return AdsHeadline( 41 | text: authType == AuthType.signUp 42 | ? intl.getStartedSingUp 43 | : intl.getStartedSingIn, 44 | ); 45 | }, 46 | ), 47 | ], 48 | ), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/app/card/infrastructure/repositories_impl/card_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/card/domain/datasources/card_datasource.dart'; 3 | import 'package:savepass/app/card/domain/repositories/card_repository.dart'; 4 | import 'package:savepass/app/card/infrastructure/models/card_model.dart'; 5 | import 'package:savepass/core/api/savepass_response_model.dart'; 6 | import 'package:savepass/core/api/supabase_middleware.dart'; 7 | 8 | class CardRepositoryImpl implements CardRepository { 9 | final CardDatasource datasource; 10 | final SupabaseMiddleware middleware; 11 | 12 | CardRepositoryImpl({ 13 | required this.datasource, 14 | required this.middleware, 15 | }); 16 | 17 | @override 18 | Future> deleteCard({ 19 | required String cardId, 20 | required String vaultId, 21 | }) async { 22 | return await datasource.deleteCard( 23 | cardId: cardId, 24 | vaultId: vaultId, 25 | ); 26 | } 27 | 28 | @override 29 | Future> editCard({ 30 | required CardModel model, 31 | }) async { 32 | return await datasource.editCard( 33 | model: model, 34 | ); 35 | } 36 | 37 | @override 38 | Future> getCardById({ 39 | required String cardId, 40 | }) async { 41 | return await datasource.getCardById( 42 | cardId: cardId, 43 | ); 44 | } 45 | 46 | @override 47 | Future> getCards() async { 48 | return await datasource.getCards(); 49 | } 50 | 51 | @override 52 | Future> insertCard({ 53 | required CardModel model, 54 | }) async { 55 | return await datasource.insertCard( 56 | model: model, 57 | ); 58 | } 59 | 60 | @override 61 | Future> searchCards({ 62 | required String search, 63 | }) async { 64 | return await datasource.searchCards( 65 | search: search, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/settings/display_name_form_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_bloc.dart'; 7 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_event.dart'; 8 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_state.dart'; 9 | import 'package:savepass/l10n/app_localizations.dart'; 10 | import 'package:savepass/core/utils/regex_utils.dart'; 11 | 12 | class DisplayNameFormWidget extends StatelessWidget { 13 | final TextEditingController _controller = TextEditingController(); 14 | 15 | DisplayNameFormWidget({super.key}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final intl = AppLocalizations.of(context)!; 20 | final bloc = Modular.get(); 21 | 22 | return BlocBuilder( 23 | buildWhen: (previous, current) => 24 | (previous.model.displayName != current.model.displayName), 25 | builder: (context, state) { 26 | final displayName = state.model.displayName.value; 27 | _controller.text = displayName; 28 | 29 | return AdsTextField( 30 | controller: _controller, 31 | hintText: intl.optionalForm, 32 | key: const Key('settings_displayName_textField'), 33 | keyboardType: TextInputType.text, 34 | errorText: null, 35 | enableSuggestions: false, 36 | inputFormatters: [ 37 | FilteringTextInputFormatter.allow( 38 | RegexUtils.numbersAndLettersWithSpace, 39 | ), 40 | ], 41 | onChanged: (value) { 42 | bloc.add(ChangeDisplayNameEvent(displayName: value)); 43 | }, 44 | textInputAction: TextInputAction.done, 45 | ); 46 | }, 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} -------------------------------------------------------------------------------- /lib/app/auth/presentation/widgets/auth/auth_terms.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/auth/presentation/blocs/auth_bloc.dart'; 5 | import 'package:savepass/l10n/app_localizations.dart'; 6 | import 'package:savepass/app/auth/presentation/blocs/auth_event.dart'; 7 | 8 | class AuthTerms extends StatelessWidget { 9 | const AuthTerms({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final bloc = Modular.get(); 14 | final textTheme = Theme.of(context).textTheme; 15 | final deviceWidth = MediaQuery.of(context).size.width; 16 | final appLocalizations = AppLocalizations.of(context)!; 17 | 18 | return Padding( 19 | padding: EdgeInsets.symmetric(horizontal: deviceWidth * 0.03), 20 | child: RichText( 21 | textAlign: TextAlign.center, 22 | text: TextSpan( 23 | text: '${appLocalizations.byJoiningText} ', 24 | style: textTheme.bodyMedium, 25 | children: [ 26 | TextSpan( 27 | text: appLocalizations.privacyPolicy, 28 | style: const TextStyle( 29 | fontWeight: FontWeight.bold, 30 | decoration: TextDecoration.underline, 31 | ), 32 | recognizer: TapGestureRecognizer() 33 | ..onTap = () => bloc.add(const OpenPrivacyEvent()), 34 | ), 35 | TextSpan( 36 | text: ' ${appLocalizations.and} ', 37 | style: textTheme.bodyMedium, 38 | ), 39 | TextSpan( 40 | text: appLocalizations.termsTitle, 41 | style: const TextStyle( 42 | fontWeight: FontWeight.bold, 43 | decoration: TextDecoration.underline, 44 | ), 45 | recognizer: TapGestureRecognizer() 46 | ..onTap = () => bloc.add(const OpenTermsEvent()), 47 | ), 48 | ], 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/app/password/presentation/widgets/pass_desc_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/atomic_design_system.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:savepass/l10n/app_localizations.dart'; 5 | import 'package:flutter_modular/flutter_modular.dart'; 6 | import 'package:savepass/app/password/presentation/blocs/password/password_bloc.dart'; 7 | import 'package:savepass/app/password/presentation/blocs/password/password_event.dart'; 8 | import 'package:savepass/app/password/presentation/blocs/password/password_state.dart'; 9 | 10 | class PassDescWidget extends StatelessWidget { 11 | final TextEditingController _controller = TextEditingController(); 12 | 13 | PassDescWidget({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final intl = AppLocalizations.of(context)!; 18 | final bloc = Modular.get(); 19 | 20 | return BlocBuilder( 21 | buildWhen: (previous, current) => 22 | (previous.model.desc != current.model.desc), 23 | builder: (context, state) { 24 | final model = state.model; 25 | final desc = model.desc.value; 26 | 27 | if (_controller.text != desc) { 28 | final previousSelection = _controller.selection; 29 | _controller.value = TextEditingValue( 30 | text: desc, 31 | selection: previousSelection, 32 | ); 33 | } 34 | 35 | return AdsFormField( 36 | label: intl.passDesc, 37 | formField: AdsTextField( 38 | controller: _controller, 39 | key: const Key('password_desc_textField'), 40 | keyboardType: TextInputType.text, 41 | enableSuggestions: false, 42 | onChanged: (value) { 43 | bloc.add(ChangeDescEvent(desc: value)); 44 | }, 45 | textInputAction: TextInputAction.done, 46 | maxLines: 3, 47 | hintText: intl.optionalForm, 48 | ), 49 | ); 50 | }, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/app/biometric/presentation/blocs/biometric_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:formz/formz.dart'; 3 | import 'package:savepass/core/form/password_form.dart'; 4 | 5 | abstract class BiometricState extends Equatable { 6 | final BiometricStateModel model; 7 | 8 | const BiometricState(this.model); 9 | 10 | @override 11 | List get props => [model]; 12 | } 13 | 14 | class BiometricInitialState extends BiometricState { 15 | const BiometricInitialState() : super(const BiometricStateModel()); 16 | } 17 | 18 | class ChangeBiometricState extends BiometricState { 19 | const ChangeBiometricState(super.model); 20 | } 21 | 22 | class GeneralErrorState extends BiometricState { 23 | const GeneralErrorState(super.model); 24 | } 25 | 26 | class InvalidMasterPasswordState extends BiometricState { 27 | const InvalidMasterPasswordState(super.model); 28 | } 29 | 30 | class EnrolledSuccessfulState extends BiometricState{ 31 | const EnrolledSuccessfulState(super.model); 32 | } 33 | 34 | class BiometricStateModel extends Equatable { 35 | final PasswordForm masterPassword; 36 | final bool showPassword; 37 | final FormzSubmissionStatus status; 38 | final bool alreadySubmitted; 39 | 40 | const BiometricStateModel({ 41 | this.masterPassword = const PasswordForm.pure(), 42 | this.status = FormzSubmissionStatus.initial, 43 | this.alreadySubmitted = false, 44 | this.showPassword = false, 45 | }); 46 | 47 | BiometricStateModel copyWith({ 48 | PasswordForm? masterPassword, 49 | FormzSubmissionStatus? status, 50 | bool? alreadySubmitted, 51 | bool? showPassword, 52 | }) { 53 | return BiometricStateModel( 54 | masterPassword: masterPassword ?? this.masterPassword, 55 | status: status ?? this.status, 56 | alreadySubmitted: alreadySubmitted ?? this.alreadySubmitted, 57 | showPassword: showPassword ?? this.showPassword, 58 | ); 59 | } 60 | 61 | @override 62 | List get props => [ 63 | masterPassword, 64 | status, 65 | alreadySubmitted, 66 | showPassword, 67 | ]; 68 | } 69 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/tools/add_password_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/molecules/card/ads_card.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_bloc.dart'; 5 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_event.dart'; 6 | 7 | class AddPasswordWidget extends StatelessWidget { 8 | const AddPasswordWidget({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final deviceWidth = MediaQuery.of(context).size.width; 13 | final deviceHeight = MediaQuery.of(context).size.height; 14 | final textTheme = Theme.of(context).textTheme; 15 | final colorScheme = Theme.of(context).colorScheme; 16 | 17 | final isLight = colorScheme.brightness == Brightness.light; 18 | 19 | return AdsCard( 20 | elevation: 1, 21 | onTap: () async { 22 | final bloc = Modular.get(); 23 | bloc.add(const OnClickNewPassword()); 24 | }, 25 | bgColor: isLight ? const Color(0xFFE8E8E8) : Colors.black, 26 | child: Padding( 27 | padding: EdgeInsets.symmetric( 28 | vertical: deviceHeight * 0.02, 29 | ), 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.stretch, 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | const IconButton( 35 | onPressed: null, 36 | icon: Icon(Icons.lock), 37 | iconSize: 30, 38 | style: ButtonStyle( 39 | shape: WidgetStatePropertyAll(CircleBorder()), 40 | ), 41 | ), 42 | SizedBox(height: deviceWidth * 0.02), 43 | Text( 44 | 'Add password', 45 | style: textTheme.titleMedium?.copyWith( 46 | color: isLight ? Colors.black : Colors.white, 47 | fontSize: 19.5, 48 | ), 49 | textAlign: TextAlign.center, 50 | ), 51 | ], 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/app/profile/infraestructure/repositories_impl/profile_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:savepass/app/profile/domain/datasources/profile_datasource.dart'; 3 | import 'package:savepass/app/profile/domain/entities/profile_entity.dart'; 4 | import 'package:savepass/app/profile/domain/repositories/profile_repository.dart'; 5 | import 'package:savepass/app/profile/infraestructure/models/insert_master_password_model.dart'; 6 | import 'package:savepass/core/api/savepass_response_model.dart'; 7 | 8 | class ProfileRepositoryImpl implements ProfileRepository { 9 | final ProfileDatasource datasource; 10 | 11 | ProfileRepositoryImpl({required this.datasource}); 12 | 13 | @override 14 | Future> uploadAvatar(String imgPath) async { 15 | return await datasource.uploadAvatar(imgPath); 16 | } 17 | 18 | @override 19 | Future> updateProfile({ 20 | String? displayName, 21 | String? avatarUuid, 22 | }) async { 23 | return await datasource.updateProfile( 24 | displayName: displayName, 25 | avatarUuid: avatarUuid, 26 | ); 27 | } 28 | 29 | @override 30 | Future> insertMasterPassword({ 31 | required InsertMasterPasswordModel model, 32 | }) async { 33 | return await datasource.insertMasterPassword(model: model); 34 | } 35 | 36 | @override 37 | Future> checkIfHasMasterPassword() async { 38 | return await datasource.checkIfHasMasterPassword(); 39 | } 40 | 41 | @override 42 | Future> getProfile() async { 43 | return await datasource.getProfile(); 44 | } 45 | 46 | @override 47 | Future> isEmailExists({ 48 | required String email, 49 | }) async { 50 | return await datasource.isEmailExists(email: email); 51 | } 52 | 53 | @override 54 | Future> deleteAccount() async { 55 | return await datasource.deleteAccount(); 56 | } 57 | 58 | @override 59 | Future> deleteAvatar() async { 60 | return await datasource.deleteAvatar(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/core/api/supabase_middleware.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:jwt_decoder/jwt_decoder.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:savepass/app/profile/presentation/blocs/profile/profile_bloc.dart'; 7 | import 'package:savepass/core/api/savepass_response_model.dart'; 8 | import 'package:savepass/core/config/routes.dart'; 9 | import 'package:savepass/core/utils/db_utils.dart'; 10 | import 'package:savepass/main.dart'; 11 | 12 | class SupabaseMiddleware { 13 | final Logger log; 14 | final omitRpcs = [ 15 | DbUtils.hasMasterPasswordFunction, 16 | DbUtils.checkMasterPasswordFunction, 17 | DbUtils.getUserSaltFunction, 18 | DbUtils.isEmailExistsFunction, 19 | DbUtils.insertMasterPassword, 20 | DbUtils.hasBiometricsFunction, 21 | ]; 22 | 23 | SupabaseMiddleware({required this.log}); 24 | 25 | Future doHttp({ 26 | required String rpc, 27 | Map? params, 28 | }) async { 29 | try { 30 | if (!omitRpcs.contains(rpc)) { 31 | final bloc = Modular.get(); 32 | String? jwt = bloc.state.model.jwt; 33 | 34 | if (jwt == null) { 35 | log.warning('JWT is null'); 36 | } 37 | 38 | DateTime fechaExpiracion = JwtDecoder.getExpirationDate(jwt!); 39 | 40 | if (fechaExpiracion.isBefore(DateTime.now())) { 41 | await Modular.to.pushNamed( 42 | Routes.authInitRoute, 43 | arguments: true, 44 | ); 45 | 46 | jwt = bloc.state.model.jwt; 47 | } 48 | 49 | if (params == null) { 50 | params = { 51 | 'jwt': jwt, 52 | }; 53 | } else { 54 | params.addAll({ 55 | 'jwt': jwt, 56 | }); 57 | } 58 | } 59 | 60 | final response = await supabase.rpc( 61 | rpc, 62 | params: params, 63 | ); 64 | 65 | final json = jsonDecode(response); 66 | final model = SavePassResponseModel.fromJson(json); 67 | 68 | return model; 69 | } catch (e) { 70 | rethrow; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/app/password/presentation/widgets/generate_password/pass_generator_numbers_switch_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/password/presentation/blocs/password/password_bloc.dart'; 5 | import 'package:savepass/app/password/presentation/blocs/password/password_event.dart'; 6 | import 'package:savepass/app/password/presentation/blocs/password/password_state.dart'; 7 | import 'package:savepass/l10n/app_localizations.dart'; 8 | 9 | class PassGeneratorNumbersSwitchWidget extends StatelessWidget { 10 | const PassGeneratorNumbersSwitchWidget({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final bloc = Modular.get(); 15 | final deviceWidth = MediaQuery.of(context).size.width; 16 | final colorScheme = Theme.of(context).colorScheme; 17 | final intl = AppLocalizations.of(context)!; 18 | 19 | return Row( 20 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 21 | children: [ 22 | Text( 23 | intl.numbersText, 24 | style: const TextStyle(fontWeight: FontWeight.w700), 25 | ), 26 | SizedBox( 27 | width: deviceWidth * 0.03, 28 | ), 29 | BlocBuilder( 30 | buildWhen: (previous, current) => 31 | (previous.model.numbers != current.model.numbers) || 32 | (previous.model.easyToRead != current.model.easyToRead), 33 | builder: (context, state) { 34 | final easyToRead = state.model.easyToRead; 35 | final value = state.model.numbers; 36 | 37 | return Switch( 38 | value: value, 39 | activeColor: colorScheme.primary, 40 | onChanged: easyToRead 41 | ? null 42 | : (bool value) { 43 | bloc.add(const ChangeNumbersEvent()); 44 | bloc.add(const GenerateRandomPasswordEvent()); 45 | }, 46 | ); 47 | }, 48 | ), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/app/password/presentation/widgets/generate_password/pass_generator_symbols_switch_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/password/presentation/blocs/password/password_bloc.dart'; 5 | import 'package:savepass/app/password/presentation/blocs/password/password_event.dart'; 6 | import 'package:savepass/app/password/presentation/blocs/password/password_state.dart'; 7 | import 'package:savepass/l10n/app_localizations.dart'; 8 | 9 | class PassGeneratorSymbolsSwitchWidget extends StatelessWidget { 10 | const PassGeneratorSymbolsSwitchWidget({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final bloc = Modular.get(); 15 | final deviceWidth = MediaQuery.of(context).size.width; 16 | final colorScheme = Theme.of(context).colorScheme; 17 | final intl = AppLocalizations.of(context)!; 18 | 19 | return Row( 20 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 21 | children: [ 22 | Text( 23 | intl.symbolsText, 24 | style: const TextStyle(fontWeight: FontWeight.w700), 25 | ), 26 | SizedBox( 27 | width: deviceWidth * 0.03, 28 | ), 29 | BlocBuilder( 30 | buildWhen: (previous, current) => 31 | (previous.model.symbols != current.model.symbols) || 32 | (previous.model.easyToRead != current.model.easyToRead), 33 | builder: (context, state) { 34 | final easyToRead = state.model.easyToRead; 35 | final value = state.model.symbols; 36 | 37 | return Switch( 38 | value: value, 39 | activeColor: colorScheme.primary, 40 | onChanged: easyToRead 41 | ? null 42 | : (bool value) { 43 | bloc.add(const ChangeSymbolsEvent()); 44 | bloc.add(const GenerateRandomPasswordEvent()); 45 | }, 46 | ); 47 | }, 48 | ), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/app/dashboard/presentation/widgets/tools/add_card_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:atomic_design_system/molecules/card/ads_card.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_modular/flutter_modular.dart'; 4 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_bloc.dart'; 5 | import 'package:savepass/app/dashboard/presentation/blocs/dashboard_event.dart'; 6 | 7 | class AddCardWidget extends StatelessWidget { 8 | const AddCardWidget({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final deviceWidth = MediaQuery.of(context).size.width; 13 | final deviceHeight = MediaQuery.of(context).size.height; 14 | final textTheme = Theme.of(context).textTheme; 15 | final colorScheme = Theme.of(context).colorScheme; 16 | 17 | final isLight = colorScheme.brightness == Brightness.light; 18 | 19 | return AdsCard( 20 | elevation: 1, 21 | onTap: () { 22 | final bloc = Modular.get(); 23 | bloc.add(const OnClickNewCard()); 24 | }, 25 | bgColor: isLight ? const Color(0xFFE8E8E8) : Colors.black, 26 | child: Padding( 27 | padding: EdgeInsets.symmetric( 28 | vertical: deviceHeight * 0.02, 29 | ), 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.stretch, 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | const IconButton( 35 | onPressed: null, 36 | icon: Icon(Icons.credit_card), 37 | iconSize: 30, 38 | style: ButtonStyle( 39 | shape: WidgetStatePropertyAll( 40 | CircleBorder(), 41 | ), 42 | ), 43 | ), 44 | SizedBox(height: deviceWidth * 0.02), 45 | Text( 46 | 'Add card', 47 | style: textTheme.titleMedium?.copyWith( 48 | color: isLight ? Colors.black : Colors.white, 49 | fontSize: 19.5, 50 | ), 51 | textAlign: TextAlign.center, 52 | ), 53 | ], 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/core/utils/biometric_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 2 | import 'package:local_auth/local_auth.dart'; 3 | import 'package:logging/logging.dart'; 4 | import 'package:savepass/core/env/env.dart'; 5 | 6 | class BiometricUtils { 7 | final LocalAuthentication localAuth; 8 | final Logger log; 9 | 10 | const BiometricUtils({ 11 | required this.localAuth, 12 | required this.log, 13 | }); 14 | 15 | Future canAuthenticateWithBiometrics() async { 16 | final bool canAuthenticateWithBiometrics = 17 | await localAuth.canCheckBiometrics; 18 | final bool canAuthenticate = 19 | canAuthenticateWithBiometrics || await localAuth.isDeviceSupported(); 20 | return canAuthenticate; 21 | } 22 | 23 | Future hasBiometricsSaved() async { 24 | AndroidOptions androidOptions() => const AndroidOptions( 25 | encryptedSharedPreferences: true, 26 | ); 27 | final storage = FlutterSecureStorage(aOptions: androidOptions()); 28 | final val = await storage.read(key: Env.biometricHashKey); 29 | return val != null; 30 | } 31 | 32 | Future authenticate() async { 33 | bool isAuthenticated = false; 34 | 35 | try { 36 | final List availableBiometrics = 37 | await localAuth.getAvailableBiometrics(); 38 | 39 | if (availableBiometrics.isEmpty) { 40 | throw Exception('Register biometrics'); 41 | } 42 | 43 | isAuthenticated = await localAuth.authenticate( 44 | localizedReason: 'Please authenticate to show account balance', 45 | options: const AuthenticationOptions(biometricOnly: true), 46 | ); 47 | } catch (e, stackTrace) { 48 | log.severe('Biometric utils authenticate error: $e', e, stackTrace); 49 | } 50 | 51 | return isAuthenticated; 52 | } 53 | 54 | Future saveBiometrics() async { 55 | AndroidOptions androidOptions() => const AndroidOptions( 56 | encryptedSharedPreferences: true, 57 | ); 58 | final storage = FlutterSecureStorage(aOptions: androidOptions()); 59 | await storage.write(key: 'biometrics', value: true.toString()); 60 | return true; 61 | } 62 | } 63 | --------------------------------------------------------------------------------